diff --git a/.github/check.sh b/.github/check.sh
new file mode 100644
index 0000000..3e76a34
--- /dev/null
+++ b/.github/check.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env sh
+
+set -o errexit
+set -o nounset
+
+pyclean() {
+ # Cleaning cache:
+ find . |
+ grep -E '(__pycache__|\.hypothesis|\.perm|\.cache|\.static|\.py[cod]$)' |
+ xargs rm -rf
+}
+
+run_checks() {
+ echo '[Check Started]'
+ set -x # we want to print commands during the CI process.
+
+ # Running linting for all python files in the project:
+ python -m flake8
+
+ # Running type checking, see https://github.com/typeddjango/django-stubs
+ python -m mypy leeteasy tests
+
+ # Running tests:
+ python -m pytest --cov
+
+ # Checking dependencies status:
+ python -m pip check
+
+ set +x
+ echo '[checks completed]'
+}
+
+# Remove any cache before the script:
+pyclean
+
+# Clean everything up:
+trap pyclean EXIT INT TERM
+
+# Run the CI process:
+run_checks
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..257fd35
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,50 @@
+name: Test
+on:
+ pull_request:
+ types: [ready_for_review]
+
+jobs:
+ test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ ubuntu-latest, macOS-latest]
+ python-version: [ '3.8', '3.9', '3.10' ]
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up Python ${{ matrix.python-version }}
+ id: setup-python
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Python version
+ run: python -c "import sys; print(sys.version)"
+
+ # Install pipenv
+ - name: Install pipenv
+ run: python3 -m pip install --upgrade pipenv
+
+ # create .venv folder
+ - name: create .venv folder
+ run: mkdir -p .venv
+
+ # caching dependencies
+ - name: Caching Dependencies
+ uses: actions/cache@v2
+ id: cache-dependencies
+ with:
+ path: .venv
+ key: ${{ matrix.os }}-python-${{ steps.setup-python.outputs.python-version }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
+
+ # install dependencies
+ - name: Install dependencies
+ if: steps.cache-dependencies.outputs.cache-hit != 'true'
+ run: |
+ pipenv install --dev --verbose
+ pipenv install types-requests --dev
+ # Run bash script
+ - name: run Bash script
+ run: pipenv run bash ./.github/check.sh
\ No newline at end of file
diff --git a/Pipfile b/Pipfile
index b02d641..21c0555 100644
--- a/Pipfile
+++ b/Pipfile
@@ -13,6 +13,13 @@ notify-py = "*"
flake8 = "*"
pytest = "*"
pytest-mock = "*"
+mypy = "*"
+darglint = "*"
+flake8-docstrings = "*"
+wemake-python-styleguide = "*"
+flake8-pytest-style = "*"
+flake8-bandit = "*"
+pytest-cov = "*"
[requires]
python_version = "3.8"
diff --git a/Pipfile.lock b/Pipfile.lock
index cf15dca..1dfa79c 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "64e11253c57c51a0d6a0ae423b649c0b13449cc0acea3bd1a5c05b903942004e"
+ "sha256": "50a0b9af3dae5b272919718d80645de15f074609242db3e8275ab39152f55ae6"
},
"pipfile-spec": 6,
"requires": {
@@ -93,7 +93,7 @@
"sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e",
"sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4.0'",
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'",
"version": "==1.26.12"
}
},
@@ -106,6 +106,81 @@
"markers": "python_version >= '3.5'",
"version": "==22.1.0"
},
+ "bandit": {
+ "hashes": [
+ "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2",
+ "sha256:412d3f259dab4077d0e7f0c11f50f650cc7d10db905d98f6520a95a18049658a"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==1.7.4"
+ },
+ "coverage": {
+ "extras": [
+ "toml"
+ ],
+ "hashes": [
+ "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79",
+ "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a",
+ "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f",
+ "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a",
+ "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa",
+ "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398",
+ "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba",
+ "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d",
+ "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf",
+ "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b",
+ "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518",
+ "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d",
+ "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795",
+ "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2",
+ "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e",
+ "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32",
+ "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745",
+ "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b",
+ "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e",
+ "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d",
+ "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f",
+ "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660",
+ "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62",
+ "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6",
+ "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04",
+ "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c",
+ "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5",
+ "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef",
+ "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc",
+ "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae",
+ "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578",
+ "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466",
+ "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4",
+ "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91",
+ "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0",
+ "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4",
+ "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b",
+ "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe",
+ "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b",
+ "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75",
+ "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b",
+ "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c",
+ "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72",
+ "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b",
+ "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f",
+ "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e",
+ "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53",
+ "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3",
+ "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84",
+ "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==6.5.0"
+ },
+ "darglint": {
+ "hashes": [
+ "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da",
+ "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"
+ ],
+ "index": "pypi",
+ "version": "==1.8.1"
+ },
"flake8": {
"hashes": [
"sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db",
@@ -114,6 +189,54 @@
"index": "pypi",
"version": "==5.0.4"
},
+ "flake8-bandit": {
+ "hashes": [
+ "sha256:068e09287189cbfd7f986e92605adea2067630b75380c6b5733dab7d87f9a84e",
+ "sha256:4c8a53eb48f23d4ef1e59293657181a3c989d0077c9952717e98a0eace43e06d"
+ ],
+ "index": "pypi",
+ "version": "==4.1.1"
+ },
+ "flake8-docstrings": {
+ "hashes": [
+ "sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde",
+ "sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b"
+ ],
+ "index": "pypi",
+ "version": "==1.6.0"
+ },
+ "flake8-plugin-utils": {
+ "hashes": [
+ "sha256:1fe43e3e9acf3a7c0f6b88f5338cad37044d2f156c43cb6b080b5f9da8a76f06",
+ "sha256:20fa2a8ca2decac50116edb42e6af0a1253ef639ad79941249b840531889c65a"
+ ],
+ "markers": "python_version >= '3.6' and python_full_version < '4.0.0'",
+ "version": "==1.3.2"
+ },
+ "flake8-pytest-style": {
+ "hashes": [
+ "sha256:5fedb371a950e9fe0e0e6bfc854be7d99151271208f34cd2cc517681ece27780",
+ "sha256:c1175713e9e11b78cd1a035ed0bca0d1e41d09c4af329a952750b61d4194ddac"
+ ],
+ "index": "pypi",
+ "version": "==1.6.0"
+ },
+ "gitdb": {
+ "hashes": [
+ "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd",
+ "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==4.0.9"
+ },
+ "gitpython": {
+ "hashes": [
+ "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704",
+ "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==3.1.27"
+ },
"iniconfig": {
"hashes": [
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
@@ -129,6 +252,43 @@
"markers": "python_version >= '3.6'",
"version": "==0.7.0"
},
+ "mypy": {
+ "hashes": [
+ "sha256:1021c241e8b6e1ca5a47e4d52601274ac078a89845cfde66c6d5f769819ffa1d",
+ "sha256:14d53cdd4cf93765aa747a7399f0961a365bcddf7855d9cef6306fa41de01c24",
+ "sha256:175f292f649a3af7082fe36620369ffc4661a71005aa9f8297ea473df5772046",
+ "sha256:26ae64555d480ad4b32a267d10cab7aec92ff44de35a7cd95b2b7cb8e64ebe3e",
+ "sha256:41fd1cf9bc0e1c19b9af13a6580ccb66c381a5ee2cf63ee5ebab747a4badeba3",
+ "sha256:5085e6f442003fa915aeb0a46d4da58128da69325d8213b4b35cc7054090aed5",
+ "sha256:58f27ebafe726a8e5ccb58d896451dd9a662a511a3188ff6a8a6a919142ecc20",
+ "sha256:6389af3e204975d6658de4fb8ac16f58c14e1bacc6142fee86d1b5b26aa52bda",
+ "sha256:724d36be56444f569c20a629d1d4ee0cb0ad666078d59bb84f8f887952511ca1",
+ "sha256:75838c649290d83a2b83a88288c1eb60fe7a05b36d46cbea9d22efc790002146",
+ "sha256:7b35ce03a289480d6544aac85fa3674f493f323d80ea7226410ed065cd46f206",
+ "sha256:85f7a343542dc8b1ed0a888cdd34dca56462654ef23aa673907305b260b3d746",
+ "sha256:86ebe67adf4d021b28c3f547da6aa2cce660b57f0432617af2cca932d4d378a6",
+ "sha256:8ee8c2472e96beb1045e9081de8e92f295b89ac10c4109afdf3a23ad6e644f3e",
+ "sha256:91781eff1f3f2607519c8b0e8518aad8498af1419e8442d5d0afb108059881fc",
+ "sha256:a692a8e7d07abe5f4b2dd32d731812a0175626a90a223d4b58f10f458747dd8a",
+ "sha256:a705a93670c8b74769496280d2fe6cd59961506c64f329bb179970ff1d24f9f8",
+ "sha256:c6e564f035d25c99fd2b863e13049744d96bd1947e3d3d2f16f5828864506763",
+ "sha256:cebca7fd333f90b61b3ef7f217ff75ce2e287482206ef4a8b18f32b49927b1a2",
+ "sha256:d6af646bd46f10d53834a8e8983e130e47d8ab2d4b7a97363e35b24e1d588947",
+ "sha256:e7aeaa763c7ab86d5b66ff27f68493d672e44c8099af636d433a7f3fa5596d40",
+ "sha256:eaa97b9ddd1dd9901a22a879491dbb951b5dec75c3b90032e2baa7336777363b",
+ "sha256:eb7a068e503be3543c4bd329c994103874fa543c1727ba5288393c21d912d795",
+ "sha256:f793e3dd95e166b66d50e7b63e69e58e88643d80a3dcc3bcd81368e0478b089c"
+ ],
+ "index": "pypi",
+ "version": "==0.982"
+ },
+ "mypy-extensions": {
+ "hashes": [
+ "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
+ "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
+ ],
+ "version": "==0.4.3"
+ },
"packaging": {
"hashes": [
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
@@ -137,6 +297,14 @@
"markers": "python_version >= '3.6'",
"version": "==21.3"
},
+ "pbr": {
+ "hashes": [
+ "sha256:cfcc4ff8e698256fc17ea3ff796478b050852585aa5bae79ecd05b2ab7b39b9a",
+ "sha256:da3e18aac0a3c003e9eea1a81bd23e5a3a75d745670dcf736317b7d966887fdf"
+ ],
+ "markers": "python_version >= '2.6'",
+ "version": "==5.10.0"
+ },
"pluggy": {
"hashes": [
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
@@ -161,6 +329,14 @@
"markers": "python_version >= '3.6'",
"version": "==2.9.1"
},
+ "pydocstyle": {
+ "hashes": [
+ "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc",
+ "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==6.1.1"
+ },
"pyflakes": {
"hashes": [
"sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2",
@@ -185,21 +361,114 @@
"index": "pypi",
"version": "==7.1.3"
},
+ "pytest-cov": {
+ "hashes": [
+ "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b",
+ "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"
+ ],
+ "index": "pypi",
+ "version": "==4.0.0"
+ },
"pytest-mock": {
"hashes": [
- "sha256:77f03f4554392558700295e05aed0b1096a20d4a60a4f3ddcde58b0c31c8fca2",
- "sha256:8a9e226d6c0ef09fcf20c94eb3405c388af438a90f3e39687f84166da82d5948"
+ "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b",
+ "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"
],
"index": "pypi",
- "version": "==3.8.2"
+ "version": "==3.10.0"
+ },
+ "pyyaml": {
+ "hashes": [
+ "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
+ "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
+ "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
+ "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
+ "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
+ "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
+ "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
+ "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
+ "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
+ "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
+ "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
+ "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
+ "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
+ "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
+ "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
+ "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
+ "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
+ "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
+ "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
+ "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
+ "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
+ "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
+ "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
+ "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
+ "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
+ "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
+ "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
+ "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
+ "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
+ "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
+ "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
+ "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
+ "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
+ "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
+ "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
+ "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
+ "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
+ "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
+ "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
+ "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==6.0"
+ },
+ "smmap": {
+ "hashes": [
+ "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94",
+ "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==5.0.0"
+ },
+ "snowballstemmer": {
+ "hashes": [
+ "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1",
+ "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"
+ ],
+ "version": "==2.2.0"
+ },
+ "stevedore": {
+ "hashes": [
+ "sha256:87e4d27fe96d0d7e4fc24f0cbe3463baae4ec51e81d95fbe60d2474636e0c7d8",
+ "sha256:f82cc99a1ff552310d19c379827c2c64dd9f85a38bcd5559db2470161867b786"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==4.0.0"
},
"tomli": {
"hashes": [
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
],
- "markers": "python_version >= '3.7'",
+ "markers": "python_version < '3.11'",
"version": "==2.0.1"
+ },
+ "typing-extensions": {
+ "hashes": [
+ "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02",
+ "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==4.3.0"
+ },
+ "wemake-python-styleguide": {
+ "hashes": [
+ "sha256:505a19d82f9c4f450c6f06bb8c74d86c99cabcc4d5e6d8ea70e90b13b049f34f",
+ "sha256:e1f47a2be6aa79ca8a1cfbbbffdd67bf4df32b76306f4c3dd2a620a2af78e671"
+ ],
+ "index": "pypi",
+ "version": "==0.0.1"
}
}
}
diff --git a/README.md b/README.md
index b8e60e5..f3640e5 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,8 @@ crontab -e
@reboot python -m leeteasy start 14:30 &
```
-## Contribution guideline
+## Contributing
-If you like this project and want to improve by adding features, fixing bugs or anything, please follow
-the [contributing guidelines](docs/CONTRIBUTING.md).
\ No newline at end of file
+We are very happy to see you here. Before sending your pull requests, make sure that you read the whole workflow and the naming conventions mentioned in the [contributing guidelines](docs/CONTRIBUTING.md).
+
+If you have any doubts regarding the contributing guidelines, please feel free to [state it clearly in an issue](https://github.com/sudiptob2/leet-easy/issues/new/choose). All the best!
\ No newline at end of file
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index fc297a9..d56ad5c 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -1,11 +1,55 @@
+Given below is the general workflow we expect you to follow while making contributions to this project.
+
+
+### Workflow
+1. Go to the issues tab and find an issue you would like to work on.
+
+ 1.1. Clarify any doubts in the comments section of the issue.
+
+2. Fork the project
+
+3. Create a branch and make small changes on it.
+
+4. Create a **draft PR**
+
+5. Then make other changes and push to the remote branch you created. In this way, the maintainers will be able to provide early reviews and comments for your commits which will save time later on.
+
+
+6. Once the above steps are done, you can change the PR status from **draft to active**
+
+
+7. Once the PR is approved, make sure to update and sync your branch
+
+8. Wait for the maintainers to merge your contribution
+
+9. Congratulations! You made your first contribution to Leet Easy
+
+
+
### Fixing a bug, or adding a new feature
This section generally defines how you can make code contribution. Please follow the below instructions to make code contributions.
-Code, PR, commit message format, etc convension I follow when I code. Please follow the links below to get details.
+Please follow the links given below to see the code, PR, commit message, etc. conventions which we follow.
-- [Git branch naming convension](BRANCH-NAMING.md)
+- [Git branch naming convention](BRANCH-NAMING.md)
- [Conventional Commits](https://www.conventionalcommits.org/) for commit messages, and [Commit message format](https://gist.github.com/digitaljhelms/3761873) for what to write
- [Linking a PR to an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
+
+
+
+### Examples
+
+#### Branch Names: `{branch_type}/{issue-tracker-id-}issue-one-liner`
+1. feature/2234-infinite-scroll
+2. documentation/3344-linux-installation
+3. test/2222-unit-tests
+
+#### Commits
+1. [#1234] feature: Submit button added.
+2. [#1232] fix: Infinite scroll fixed
+3. [#333] test: Add unit tests for xyz feature
+
+Keep contributing. We're eager to see your contributions!
\ No newline at end of file
diff --git a/leeteasy/__main__.py b/leeteasy/__main__.py
index eefdac0..3f7e7d3 100644
--- a/leeteasy/__main__.py
+++ b/leeteasy/__main__.py
@@ -5,6 +5,7 @@
import click
import schedule
+from leeteasy.constant import Constant
from leeteasy.services.notification_service import Notifier
from leeteasy.utils.validatiors import TimeValidator
@@ -14,13 +15,13 @@
'-d',
'--difficulty',
type=click.Choice(['Medium', 'Hard'], case_sensitive=False),
- help='Additional problem difficulty for notification.'
+ help='Additional problem difficulty for notification.',
)
@click.option(
- "--sleep_duration",
- default=3600,
- type=click.IntRange(1, 3600, clamp=True),
- help='Sleep duration in seconds.'
+ '--sleep_duration',
+ default=Constant.DEFAULT_SLEEP,
+ type=click.IntRange(1, Constant.DEFAULT_SLEEP, clamp=True),
+ help='Sleep duration in seconds.',
)
@click.argument('time')
def execute_start(time, difficulty, sleep_duration) -> None:
@@ -33,21 +34,20 @@ def execute_start(time, difficulty, sleep_duration) -> None:
Notifier.target_difficulty.append(difficulty)
schedule.every().day.at(time).do(Notifier.notify)
- while True:
+ while True: # NOQA: WPS457
schedule.run_pending()
clock.sleep(sleep_duration)
@click.command('stop')
def execute_stop() -> None:
- """Stops leeteasy process"""
+ """Stop leeteasy process."""
os.system('pkill -9 -f leeteasy')
@click.group('leeteasy')
def execute_root():
- """v0.4.0 | supported version strings: 0.7.2"""
- pass
+ """Group child command."""
execute_root.add_command(execute_start)
@@ -55,8 +55,11 @@ def execute_root():
if __name__ == '__main__':
if platform != 'win32':
- import pwd
+ import pwd # NOQA: WPS433
- os.environ[
- 'DBUS_SESSION_BUS_ADDRESS'] = f'unix:path=/run/user/{pwd.getpwuid(os.getuid()).pw_uid}/bus' # NOQA: E501
+ bus_addr = 'unix:path=/run/user/{0}/bus'.format(
+ pwd.getpwuid(os.getuid()).pw_uid,
+ )
+
+ os.environ['DBUS_SESSION_BUS_ADDRESS'] = bus_addr
execute_root()
diff --git a/leeteasy/constant.py b/leeteasy/constant.py
index c4931ce..7aa8d09 100644
--- a/leeteasy/constant.py
+++ b/leeteasy/constant.py
@@ -34,3 +34,6 @@ class Constant:
# http call retries
HTTP_CALL_RETRIES = 3
+
+ # default sleep duration
+ DEFAULT_SLEEP = 3600
diff --git a/leeteasy/models/challenge.py b/leeteasy/models/challenge.py
index 4546589..d83fb42 100644
--- a/leeteasy/models/challenge.py
+++ b/leeteasy/models/challenge.py
@@ -1,39 +1,44 @@
-from typing import List
+from typing import Dict, List, Optional
class Challenge:
"""Singleton Model class for daily challenge."""
title: str = ''
- raw_tags: List[dict] = None
+ raw_tags: List[Dict[str, str]] = []
ac_rate: float = 0
- difficulty: str = None
- question_id: int = None
+ difficulty: str = ''
+ question_id: int = 0
title_slug: str = ''
- date: str = None
+ date: str = ''
def __new__(cls):
- if not hasattr(cls, 'instance'):
- cls.instance = super(Challenge, cls).__new__(cls)
+ """Override default class creation logic."""
+ if not hasattr(cls, 'instance'): # NOQA : WPS421
+ cls.instance = super(Challenge, cls).__new__(cls) # NOQA: WPS608
return cls.instance
@property
def problem_link(self) -> str:
- """Returns the link of the problem."""
+ """Return the link of the problem."""
return 'https://leetcode.com/problems/{0}/'.format(
self.title_slug,
)
@property
- def tags(self) -> List[str]:
- """Returns the link of the problem."""
+ def tags(self) -> List[Optional[str]]:
+ """Return the link of the problem."""
tags = []
for tag in self.raw_tags:
tags.append(tag.get('name'))
return tags
def __str__(self):
- """Returns the string rep of the class."""
- return f"Title: {self.title}\nAcceptance Rate: {self.ac_rate}" \
- f"\nDifficulty: {self.difficulty}\n" + \
- f"id: {self.question_id}\nTags: {self.tags}"
+ """Return the string rep of the class."""
+ return 'Title: {0}\nAcceptance: {1}\nDifficulty: {2}\nID: {3}\nTags: {4}\n'.format(
+ self.title,
+ self.ac_rate,
+ self.difficulty,
+ self.question_id,
+ self.tags,
+ )
diff --git a/leeteasy/services/notification_service.py b/leeteasy/services/notification_service.py
index e6d99b3..ccdbe0a 100644
--- a/leeteasy/services/notification_service.py
+++ b/leeteasy/services/notification_service.py
@@ -2,6 +2,7 @@
from notifypy import Notify
+from leeteasy.models.challenge import Challenge
from leeteasy.services.request_handler import RequestHandler
from leeteasy.services.request_parser import RequestParser
@@ -11,11 +12,11 @@ class Notifier:
target_difficulty = ['Easy']
app_name = 'LeetEasy'
- challenge = None
+ challenge: Challenge
@classmethod
def prepare_notification(cls):
- """Prepares notification msg and triggers notification."""
+ """Prepare notification msg and triggers notification."""
challenge_info = RequestHandler.get_challenge_info()
cls.challenge = RequestParser.parse(challenge_info)
if cls.challenge.difficulty in cls.target_difficulty:
@@ -27,14 +28,16 @@ def prepare_notification(cls):
@classmethod
def notify(cls):
"""Send desktop notification."""
- app_name_with_subtitle = f'{cls.app_name} - Easy Problem Notification'
+ app_name_with_subtitle = '{0} - Easy Problem Notification'.format(cls.app_name)
icon_path = Path(__file__).parent.parent / 'assets/leetcoin.png'
notification = Notify(
default_notification_application_name=app_name_with_subtitle,
default_notification_icon=icon_path,
)
notification.message = cls.prepare_notification()
- notification.title = f'{cls.app_name} - {cls.challenge.difficulty} ' \
- f'Problem Alert \U0001F514'
+ notification.title = '{0} - {1} Problem Alert \U0001F514'.format(
+ cls.app_name,
+ cls.challenge.difficulty,
+ )
if notification.message:
notification.send()
diff --git a/leeteasy/services/request_handler.py b/leeteasy/services/request_handler.py
index 39991a1..d576fb1 100644
--- a/leeteasy/services/request_handler.py
+++ b/leeteasy/services/request_handler.py
@@ -1,5 +1,5 @@
import time
-from typing import Union
+from typing import Dict
import requests
@@ -9,23 +9,17 @@
class RequestHandler:
"""Provides services for requesting leetcode API."""
- @classmethod
- def get_challenge_info(cls) -> Union[dict, None]:
- """Gets daily challenge info from leetcode API."""
- url = Constant.LEETCODE_API_ENDPOINT
- query = Constant.DAILY_CODING_CHALLENGE_QUERY
- max_retries = Constant.HTTP_CALL_RETRIES # Change HTTP_CALL_RETRIES for more retries
+ url = Constant.LEETCODE_API_ENDPOINT
+ query = Constant.DAILY_CODING_CHALLENGE_QUERY
+ max_retries = Constant.HTTP_CALL_RETRIES
- for i in range(max_retries):
+ @classmethod
+ def get_challenge_info(cls) -> Dict:
+ """Get daily challenge info from leetcode API."""
+ for iteration in range(cls.max_retries):
try:
- response = requests.post(url, json={'query': query})
+ response = requests.post(cls.url, json={'query': cls.query})
return response.json().get('data').get('activeDailyCodingChallengeQuestion')
except Exception:
- """
- On first hit sleep 10 minutes.
- On second hit sleep 20 minutes.
- On third hit sleep 30 minutes.
- """
- time.sleep(((i+1)*10)*60)
- raise SystemExit(f"""Connection to leetcode failed and max retries have been exceeded.
- Total attempts made: {max_retries}. Try again""")
+ time.sleep(((iteration + 1) * 10) * 60)
+ raise SystemExit('Could not connect to the leetcode server.')
diff --git a/leeteasy/services/request_parser.py b/leeteasy/services/request_parser.py
index ff45217..eb2ba1c 100644
--- a/leeteasy/services/request_parser.py
+++ b/leeteasy/services/request_parser.py
@@ -1,3 +1,5 @@
+from typing import Dict
+
from leeteasy.models.challenge import Challenge
@@ -5,19 +7,20 @@ class RequestParser:
"""Parse responses of leetcode API."""
@classmethod
- def parse(cls, challenge_info: dict) -> Challenge:
+ def parse(cls, challenge_info: Dict) -> Challenge:
"""Parse API data ans update challenge model."""
- return RequestParser._parse_challenge_info(challenge_info)
+ return cls._parse_challenge_info(challenge_info)
@classmethod
def _parse_challenge_info(cls, challenge_info) -> Challenge:
"""Parse and update challenge model."""
+ question = challenge_info.get('question')
challenge = Challenge()
- challenge.title = challenge_info.get('question').get('title')
- challenge.ac_rate = challenge_info.get('question').get('acRate')
- challenge.difficulty = challenge_info.get('question').get('difficulty')
- challenge.question_id = challenge_info.get('question').get('frontendQuestionId')
+ challenge.title = question.get('title')
+ challenge.ac_rate = question.get('acRate')
+ challenge.difficulty = question.get('difficulty')
+ challenge.question_id = question.get('frontendQuestionId')
challenge.date = challenge_info.get('date')
- challenge.title_slug = challenge_info.get('question').get('titleSlug')
- challenge.raw_tags = challenge_info.get('question').get('topicTags')
+ challenge.title_slug = question.get('titleSlug')
+ challenge.raw_tags = question.get('topicTags')
return challenge
diff --git a/leeteasy/utils/validatiors.py b/leeteasy/utils/validatiors.py
index 0f4910f..f83ed9e 100644
--- a/leeteasy/utils/validatiors.py
+++ b/leeteasy/utils/validatiors.py
@@ -8,7 +8,7 @@ class TimeValidator:
@classmethod
def validate(cls, time: str):
- """Validates the given string is in valid time format."""
+ """Validate the given string is in valid time format."""
try:
return datetime.strptime(time, cls.time_format).time()
except ValueError:
diff --git a/setup.cfg b/setup.cfg
index 071d2ad..b23433f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -44,7 +44,61 @@ exclude =
docs.*
[flake8]
+# flake8 configuration:
+# https://flake8.pycqa.org/en/latest/user/configuration.html
max-line-length = 99
+max-complexity = 6
+format = wemake
+show-source = True
+statistics = True
+doctests = True
+enable-extensions = G
+# Excluding some directories:
+exclude = .git,__pycache__,.venv,.eggs,*.egg, tests
+
+# darglint configuration:
+# https://github.com/terrencepreilly/darglint
+strictness = long
+docstring-style = numpy
+
+# Disable some checks:
+ignore =
+ D100,
+ S605,
+ S607,
+ WPS115,
+ WPS306,
+ D104,
+ WPS201, # Found module with too many imports
+ WPS229, # Found too long try body length
+
+
+
+[mypy]
+# Mypy configuration:
+# https://mypy.readthedocs.io/en/latest/config_file.html
+allow_redefinition = False
+check_untyped_defs = True
+disallow_untyped_decorators = True
+disallow_any_explicit = True
+;disallow_any_generics = True
+;disallow_untyped_calls = True
+ignore_errors = False
+ignore_missing_imports = True
+implicit_reexport = False
+local_partial_types = True
+# strict_optional = True
+strict_equality = True
+no_implicit_optional = True
+warn_unused_ignores = True
+warn_redundant_casts = True
+warn_unused_configs = True
+warn_unreachable = True
+warn_no_return = True
+show_error_codes = True
+
+
+[options.entry_points]
console_scripts =
leeteasy = leeteasy.__main__:main
\ No newline at end of file
diff --git a/tests/test_services/test_request_handler.py b/tests/test_services/test_request_handler.py
deleted file mode 100644
index 4d6880e..0000000
--- a/tests/test_services/test_request_handler.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from leeteasy.services.request_handler import RequestHandler
-
-
-class TestRequestHandler:
- """Class for testing request handlers."""
-
- def test_get_challenge_info(self, mocker, fake_challenge_data):
- """Tests get_challenge_info method."""
-
- def fake_get_challenge_info():
- RequestHandler.challenge_info = fake_challenge_data
-
- mocker.patch.object(RequestHandler, 'get_challenge_info', fake_get_challenge_info)
- RequestHandler.get_challenge_info()
- assert type(RequestHandler.challenge_info) == dict
diff --git a/tests/test_services/test_request_parser.py b/tests/test_services/test_request_parser.py
index 537e581..8d15276 100644
--- a/tests/test_services/test_request_parser.py
+++ b/tests/test_services/test_request_parser.py
@@ -9,14 +9,14 @@ def test_parse(self, mocker, fake_challenge_data):
"""Tests parse method."""
def fake_get_challenge_info():
- RequestHandler.challenge_info = fake_challenge_data
+ return fake_challenge_data
mocker.patch.object(
RequestHandler,
'get_challenge_info',
fake_get_challenge_info,
)
- challenge = RequestParser.parse()
+ challenge = RequestParser.parse(RequestHandler.get_challenge_info())
assert challenge.title == fake_challenge_data.get('question').get('title')
assert challenge.difficulty == fake_challenge_data.get('question').get('difficulty')