diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..61c0fac --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,33 @@ +name: Tests + +on: + push: + branches: [master, develop] + pull_request: + branches: [master, develop] + +jobs: + unittest: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.7', '3.8', '3.9', '3.10'] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install pipenv + pipenv install + + - name: Run Unittest + run: | + pipenv run python -m unittest discover ./tests/ --verbose diff --git a/.gitignore b/.gitignore index 6bf4e1e..4989d93 100644 --- a/.gitignore +++ b/.gitignore @@ -128,3 +128,6 @@ dmypy.json # Pyre type checker .pyre/ + +# VSCode +.vscode diff --git a/Pipfile b/Pipfile index bd65ac8..79d0c86 100644 --- a/Pipfile +++ b/Pipfile @@ -5,9 +5,9 @@ name = "pypi" [packages] requests = ">=2.27.1" -pandas = ">=1.4.1" [dev-packages] +pylint = "*" [requires] python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock index 6599585..5d46c4d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c2bc3ad49aa1ec0cea5f148538c9965f7f5f5c5be86f2936e9d0807e7dba360d" + "sha256": "8d286595dd20848e21afca75948139b315fde9d4b313d3e58d574b33c020b1d7" }, "pipfile-spec": 6, "requires": { @@ -18,79 +18,27 @@ "default": { "certifi": { "hashes": [ - "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", - "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" + "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14", + "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382" ], - "version": "==2021.10.8" + "markers": "python_version >= '3.6'", + "version": "==2022.9.24" }, "charset-normalizer": { "hashes": [ - "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", - "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" + "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", + "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], - "markers": "python_version >= '3'", - "version": "==2.0.12" + "markers": "python_full_version >= '3.6.0'", + "version": "==2.1.1" }, "idna": { "hashes": [ - "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", - "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" - ], - "markers": "python_version >= '3'", - "version": "==3.3" - }, - "numpy": { - "hashes": [ - "sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676", - "sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4", - "sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce", - "sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123", - "sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1", - "sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e", - "sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5", - "sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d", - "sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a", - "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab", - "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75", - "sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168", - "sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4", - "sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f", - "sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18", - "sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62", - "sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe", - "sha256:f950f8845b480cffe522913d35567e29dd381b0dc7e4ce6a4a9f9156417d2430", - "sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802", - "sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa" - ], - "markers": "python_version < '3.10' and platform_machine != 'aarch64' and platform_machine != 'arm64'", - "version": "==1.22.3" - }, - "pandas": { - "hashes": [ - "sha256:0259cd11e7e6125aaea3af823b80444f3adad6149ff4c97fef760093598b3e34", - "sha256:04dd15d9db538470900c851498e532ef28d4e56bfe72c9523acb32042de43dfb", - "sha256:0b1a13f647e4209ed7dbb5da3497891d0045da9785327530ab696417ef478f84", - "sha256:19f7c632436b1b4f84615c3b127bbd7bc603db95e3d4332ed259dc815c9aaa26", - "sha256:1b384516dbb4e6aae30e3464c2e77c563da5980440fbdfbd0968e3942f8f9d70", - "sha256:1d85d5f6be66dfd6d1d8d13b9535e342a2214260f1852654b19fa4d7b8d1218b", - "sha256:2e5a7a1e0ecaac652326af627a3eca84886da9e667d68286866d4e33f6547caf", - "sha256:3129a35d9dad1d80c234dd78f8f03141b914395d23f97cf92a366dcd19f8f8bf", - "sha256:358b0bc98a5ff067132d23bf7a2242ee95db9ea5b7bbc401cf79205f11502fd3", - "sha256:3dfb32ed50122fe8c5e7f2b8d97387edd742cc78f9ec36f007ee126cd3720907", - "sha256:4e1176f45981c8ccc8161bc036916c004ca51037a7ed73f2d2a9857e6dbe654f", - "sha256:508c99debccd15790d526ce6b1624b97a5e1e4ca5b871319fb0ebfd46b8f4dad", - "sha256:6105af6533f8b63a43ea9f08a2ede04e8f43e49daef0209ab0d30352bcf08bee", - "sha256:6d6ad1da00c7cc7d8dd1559a6ba59ba3973be6b15722d49738b2be0977eb8a0c", - "sha256:7ea47ba1d6f359680130bd29af497333be6110de8f4c35b9211eec5a5a9630fa", - "sha256:8db93ec98ac7cb5f8ac1420c10f5e3c43533153f253fe7fb6d891cf5aa2b80d2", - "sha256:96e9ece5759f9b47ae43794b6359bbc54805d76e573b161ae770c1ea59393106", - "sha256:bbb15ad79050e8b8d39ec40dd96a30cd09b886a2ae8848d0df1abba4d5502a67", - "sha256:c614001129b2a5add5e3677c3a213a9e6fd376204cb8d17c04e84ff7dfc02a73", - "sha256:e6a7bbbb7950063bfc942f8794bc3e31697c020a14f1cd8905fc1d28ec674a01", - "sha256:f02e85e6d832be37d7f16cf6ac8bb26b519ace3e5f3235564a91c7f658ab2a43" + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" ], - "index": "pypi", - "version": "==1.4.1" + "markers": "python_version >= '3.5'", + "version": "==3.4" }, "python-dateutil": { "hashes": [ @@ -102,18 +50,18 @@ }, "pytz": { "hashes": [ - "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", - "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" + "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91", + "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174" ], - "version": "==2022.1" + "version": "==2022.4" }, "requests": { "hashes": [ - "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", - "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" + "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", + "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], "index": "pypi", - "version": "==2.27.1" + "version": "==2.28.1" }, "six": { "hashes": [ @@ -125,12 +73,206 @@ }, "urllib3": { "hashes": [ - "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", - "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" + "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", + "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.9" + "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" } }, - "develop": {} + "develop": { + "astroid": { + "hashes": [ + "sha256:2df4f9980c4511474687895cbfdb8558293c1a826d9118bb09233d7c2bff1c83", + "sha256:867a756bbf35b7bc07b35bfa6522acd01f91ad9919df675e8428072869792dce" + ], + "markers": "python_full_version >= '3.7.2'", + "version": "==2.12.11" + }, + "dill": { + "hashes": [ + "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302", + "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==0.3.5.1" + }, + "isort": { + "hashes": [ + "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", + "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" + ], + "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", + "version": "==5.10.1" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7", + "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a", + "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c", + "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc", + "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f", + "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09", + "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442", + "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e", + "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029", + "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61", + "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb", + "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0", + "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35", + "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42", + "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1", + "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad", + "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443", + "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd", + "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9", + "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148", + "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38", + "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55", + "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36", + "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a", + "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b", + "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44", + "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6", + "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69", + "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4", + "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84", + "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de", + "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28", + "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c", + "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1", + "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8", + "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b", + "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb" + ], + "markers": "python_version >= '3.6'", + "version": "==1.7.1" + }, + "mccabe": { + "hashes": [ + "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", + "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" + ], + "markers": "python_version >= '3.6'", + "version": "==0.7.0" + }, + "platformdirs": { + "hashes": [ + "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788", + "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19" + ], + "markers": "python_version >= '3.7'", + "version": "==2.5.2" + }, + "pylint": { + "hashes": [ + "sha256:5441e9294335d354b7bad57c1044e5bd7cce25c433475d76b440e53452fa5cb8", + "sha256:629cf1dbdfb6609d7e7a45815a8bb59300e34aa35783b5ac563acaca2c4022e9" + ], + "index": "pypi", + "version": "==2.15.4" + }, + "setuptools": { + "hashes": [ + "sha256:7999cbd87f1b6e1f33bf47efa368b224bed5e27b5ef2c4d46580186cbcb1a86a", + "sha256:a65e3802053e99fc64c6b3b29c11132943d5b8c8facbcc461157511546510967" + ], + "markers": "python_version >= '3.7'", + "version": "==62.0.0" + }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "markers": "python_version < '3.11'", + "version": "==2.0.1" + }, + "tomlkit": { + "hashes": [ + "sha256:571854ebbb5eac89abcb4a2e47d7ea27b89bf29e09c35395da6f03dd4ae23d1c", + "sha256:f2ef9da9cef846ee027947dc99a45d6b68a63b0ebc21944649505bf2e8bc5fe7" + ], + "markers": "python_version >= '3.6' and python_version < '4.0'", + "version": "==0.11.5" + }, + "typing-extensions": { + "hashes": [ + "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", + "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" + ], + "markers": "python_version < '3.10'", + "version": "==4.4.0" + }, + "wrapt": { + "hashes": [ + "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3", + "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b", + "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4", + "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2", + "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656", + "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3", + "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff", + "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310", + "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a", + "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57", + "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069", + "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383", + "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe", + "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87", + "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d", + "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b", + "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907", + "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f", + "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0", + "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28", + "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1", + "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853", + "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc", + "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3", + "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3", + "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164", + "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1", + "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c", + "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1", + "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7", + "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1", + "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320", + "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed", + "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1", + "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248", + "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c", + "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456", + "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77", + "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef", + "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1", + "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7", + "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86", + "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4", + "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d", + "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d", + "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8", + "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5", + "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471", + "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00", + "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68", + "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3", + "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d", + "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735", + "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d", + "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569", + "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7", + "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59", + "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5", + "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb", + "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b", + "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f", + "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462", + "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015", + "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af" + ], + "markers": "python_version < '3.11'", + "version": "==1.14.1" + } + } } diff --git a/README.md b/README.md index 9891742..11b4fc6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Logo filômetro -
Filômetro + Logo filometro +
Filometro

@@ -9,9 +9,6 @@ PyPI - - GitHub release (latest by date) - License MIT @@ -26,6 +23,9 @@ PyPI - Status + + Tests - Status +

@@ -36,28 +36,30 @@ - [Demo](#demo) - [Documentação](#documentação) - [Como utilizar?](#como-utilizar) + - [Objeto Posto](#objeto-posto) - [Métodos da classe Filometro](#métodos-da-classe-filometro) - - [Identificadores para filtragem (Enums)](#identificadores-para-filtragem-enumsidentificadores-para-filtragem-enums) + - [Identificadores para filtragem](#identificadores-para-filtragem) - [Contribuições](#contribuições) - [Licença](#licença) ## O que é? -Filômetro é um pacote que faz o papel de um wrapper (embrulho) do site [De Olho Na Fila](https://deolhonafila.prefeitura.sp.gov.br/), de modo a disponibilizar acesso a diversos dados sobre postos de vacinação em todo o Estado de São Paulo. +Filometro é um wrapper (embrulho) do site [De Olho Na Fila](https://deolhonafila.prefeitura.sp.gov.br/), de modo a disponibilizar acesso a diversos dados sobre os postos de vacinação em todo o Estado de São Paulo. Com esse pacote você tem acesso aos seguintes dados dos postos: - Equipamento - Endereço +- Contatos - Distrito - Zona -- Os imunizantes +- Os imunizantes disponíveis - Situação da fila - Modalidade - Data e hora da última atualização -As informações são exatamente as mesmas disponíveis no site oficial (De Olho na Fila), porém disponibilizados por meio de uma interface Python simples para facilitar o uso, manipulação e filtragem dos dados. Consulte a [documentação](#documentação) para saber como utiliza-lo. +As informações são exatamente as mesmas disponíveis no site oficial (De Olho na Fila), porém disponibilizados por meio de um pacote Python simples para facilitar a coleta, manipulação e filtragem dos dados. Consulte a [documentação](#documentação) para saber como utiliza-lo. ## Instalação @@ -71,7 +73,7 @@ $ pip install filometro ## Demo -Obtenha uma lista de postos que tem disponível em seu estoque o imunizante da PFizer e mostre a situação da fila, o endereço e a zona em que o posto está localizado: +Obtenha uma lista de postos que tem disponível em seu estoque o imunizante da PFizer: ```python >>> from filometro import Filometro @@ -86,24 +88,9 @@ Obtenha uma lista de postos que tem disponível em seu estoque o imunizante da P ... # comprimido Posto(equipment='UBS SANTA CRUZ', last_update='2021-12-22 12:46:35.190') ] - ->>> for posto in postos: -... print(f'Fila: {posto.situation}') -... print(f'Endereço: {posto.address}') -... print(f'Zona: {posto.zone}', end='\n\n') - -Fila: FILA PEQUENA -Endereço: R. HUMAITÁ, 520 - BELA VISTA - CEP: 01321-010 - Tel: 3241- 1632/ 3241-1163 -Zona: CENTRO - -... # comprimido - -Fila: FILA PEQUENA -Endereço: Rua Santa Cruz, 1.191 - Vila Mariana -Zona: SUL ``` -Verifique a documentação para obter informações sobre os métodos disponíveis no pacote. +Verifique a documentação para obter informações sobre os atributos e métodos disponíveis. ## Documentação @@ -147,64 +134,109 @@ Para atualizar os dados dos postos armazenados em memória é indicado utilizar Esse método recarrega todos os dados com as informações mais recentes disponíveis no site oficial. +Sempre que precisar, utilize a função `help()` em alguma classe, objeto ou método para obter ajuda: + +```python +>>> help(filometro) +``` + +### Objeto Posto + +Todos os métodos tem como retorno uma lista de dados, esses dados são representados no objeto Posto. Veja quais são seus atributos: + +```python +>>> posto.equipment +>>> posto.address +>>> posto.district +>>> posto.zone +>>> posto.maps +>>> posto.contacts +>>> posto.astrazeneca +>>> posto.coronavac +>>> posto.coronavac_pediatrica +>>> posto.pfizer +>>> posto.pfizer_pediatrica +>>> posto.janssen +>>> posto.influenza +>>> posto.intercambialidade +>>> posto.situation +>>> posto.modality +>>> posto.last_update +``` + ### Métodos da classe Filometro -- `Filometro.reload(...)` - Recarregar os dados com as informações mais recentes. -- `Filometro.all_postos(...)` - Retorna os dados de todos os postos. -- `Filometro.all_postos_open(...)` - Retorna os dados de todos os postos abertos no momento da busca. -- `Filometro.all_postos_closed(...)` - Retorna os dados de todos os postos fechados no momento da busca. -- `Filometro.by_zone(...)` - Retorna os dados dos postos por zona selecionada. -- `Filometro.by_modality(...)` - Retorna os dados dos postos por modalidade selecionada. -- `Filometro.by_district(...)` - Retorna os dados dos postos por distrito selecionado. -- `Filometro.by_situation(...)` - Retorna os dados dos postos por situação selecionada. -- `Filometro.by_immunizing(...)` - Retorna os dados dos postos por imunizante selecionado. -- `Filometro.to_dict(...)` - Retorna uma lista de objetos do tipo `dict` contendo todos os dados de postos. -- `Filometro.to_json(...)` - Retorna uma string `json` contendo todos os dados de postos. Também há suporte para a manipulação do retorno utilizando os mesmos argumentos do [método json integrado ao Python](https://docs.python.org/3/library/json.html). -- `Filometro.to_dataframe(...)` - Retorna um [DataFrame](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html) contendo os dados de todos os postos. +```python +>>> filometro.reload() # Atualizar dados em memória. +>>> filometro.all_postos() # Obter todos os postos. +>>> filometro.all_postos_open() # Obter todos os postos abertos. +>>> filometro.all_postos_closed() # Obter todos os postos fechados. +>>> filometro.by_zone(...) # Obter todos postos de uma zona. +>>> filometro.by_modality(...) # Obter todos postos de uma modalidade. +>>> filometro.by_district(...) # Obter todos postos de um distrito. +>>> filometro.by_situation(...) # Obter todos postos por situação. +>>> filometro.by_immunizing(...) # Obter todos postos que possuem um imunizante. +>>> filometro.to_dict() # Obter todos postos convertidos para `dict`. +``` -### Identificadores para filtragem (Enums) +### Identificadores para filtragem -Todos os Enums estão disponíveis para uso atráves da interface príncipal do pacote: +Todos os identificadores estão disponíveis para uso atráves da interface príncipal do pacote: ```python >>> from filometro import Zone, Modality, District, Situation, Immunizing ``` -> Para obter mais informações sobre cada um dos Enums, use as funções `dir()` ou `help()` passando um dos Enums como argumento. +`Zone` - Representa as zonas do Estado de São Paulo: + +```python +>>> Zone.SUL +>>> Zone.OESTE +>>> Zone.NORTE +>>> Zone.LESTE +>>> Zone.CENTRO +>>> Zone.MEGA_DRIVES +``` + +`Modality` - Representa as modalidades dos postos: + +```python +>>> Modality.PARQUES +>>> Modality.POSTO_FIXO +>>> Modality.POSTO_VOLANTE +>>> Modality.DRIVE_THRU +>>> Modality.MEGAPOSTO +``` + +`District` - Representa todos os distritos do Estado de São Paulo que disponíbilizam um imunizante em seus postos. Como há muitos distritos, use a função `dir` ou `help` para mais infomações: -- `Zone` - Representa as zonas do Estado de São Paulo. - - `Zone.SUL` - - `Zone.OESTE` - - `Zone.NORTE` - - `Zone.LESTE` - - `Zone.CENTRO` - - `Zone.MEGA_DRIVES` +```python +>>> help(District) +>>> dir(District) +``` -- `Modality` - Representa as modalidades dos postos de saúde. - - `Modality.PARQUES` - - `Modality.POSTO_FIXO` - - `Modality.POSTO_VOLANTE` - - `Modality.DRIVE_THRU` - - `Modality.MEGAPOSTO` +`Situation` - Representa as situações das filas nos postos: -- `District` - Representa todos os distritos do Estado de São Paulo que disponíbilizam um imunizante em seus postos de saúde. Use a função `dir(District)` ou `help(District)` para mais infomações. +```python +>>> Situation.NAO_FUNCIONANDO +>>> Situation.SEM_FILA +>>> Situation.FILA_PEQUENA +>>> Situation.FILA_MEDIA +>>> Situation.FILA_GRANDE +``` -- `Situation` - Representa as possíveis situações das filas nos postos de saúde. - - `Situation.NAO_FUNCIONANDO` - - `Situation.SEM_FILA` - - `Situation.FILA_PEQUENA` - - `Situation.FILA_MEDIA` - - `Situation.FILA_GRANDE` +`Immunizing` - Representa os imunizantes disponíveis nos postos no Estado de São Paulo: -- `Immunizing` - Representa os imunizantes disponíveis nos postos de saúde do Estado de São Paulo. - - `Immunizing.ASTRAZENECA` - - `Immunizing.INTERCAMBIALIDADE` - - `Immunizing.PFIZER` - - `Immunizing.PFIZER_PEDIATRICA` - - `Immunizing.CORONAVAC` - - `Immunizing.CORONAVAC_PEDIATRICA` - - `Immunizing.JANSSEN` - - `Immunizing.INFLUENZA` +```python +>>> Immunizing.ASTRAZENECA +>>> Immunizing.INTERCAMBIALIDADE +>>> Immunizing.PFIZER +>>> Immunizing.PFIZER_PEDIATRICA +>>> Immunizing.CORONAVAC +>>> Immunizing.CORONAVAC_PEDIATRICA +>>> Immunizing.JANSSEN +>>> Immunizing.INFLUENZA +``` ## Contribuições @@ -217,11 +249,11 @@ Abaixo mostro com o que você pode contribuir: - Existe uma issue aberta e você quer resolve-la, quer implementar uma nova funcionalidade ou melhorar a documentação? Faça suas adições e me envie um *Pull Request* -- Gostou do projeto, mas não quer ou ainda não consegue contribuir com ele? Considere deixar uma estrela ⭐ para o **Filômetro** +- Gostou do projeto, mas não quer ou ainda não consegue contribuir com ele? Considere deixar uma estrela ⭐ para o **Filometro** -Obrigado pelo interesse em colaborar de alguma forma com o projeto 😄 +Obrigado pelo interesse em colaborar de alguma forma com o projeto ❤ ## Licença -**Filômetro** utiliza a *licença MIT* em todo seu código, confira suas condições em [MIT License](https://github.com/matheusfelipeog/filometro/blob/master/LICENSE). +**Filometro** utiliza a *licença MIT* em todo seu código, confira suas condições em [MIT License](https://github.com/matheusfelipeog/filometro/blob/master/LICENSE). diff --git a/filometro/__init__.py b/filometro/__init__.py index aee0e9f..edfbc6f 100644 --- a/filometro/__init__.py +++ b/filometro/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ filometro --------- @@ -7,7 +6,6 @@ de todos os postos de saúde que são mostrados no site 'De Olho na Fila'. """ - __all__ = [ 'Zone', 'Modality', @@ -16,7 +14,7 @@ 'District', 'Filometro' ] -__version__ = '0.3.0' +__version__ = '1.0.0' __author__ = 'Matheus Felipe' from filometro.enums import Zone diff --git a/filometro/dataclasses.py b/filometro/dataclasses.py index 559956f..38bf478 100644 --- a/filometro/dataclasses.py +++ b/filometro/dataclasses.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ filometro.dataclasses --------------------- @@ -8,19 +7,24 @@ __all__ = ['Posto'] -from filometro import __version__ -from filometro import __author__ - from dataclasses import dataclass from dataclasses import field +from typing import List + +import re + @dataclass class Posto(): + """Modelo de dados do Posto.""" + equipment: str # equipamento address: str = field(repr=False) # endereço district: str = field(repr=False) # distrito zone: str = field(repr=False) # crs (Coordinate Reference Systems) + maps: str = field(init=False, repr=False) + contacts: List[str] = field(init=False, repr=False, default_factory=list) astrazeneca: str = field(repr=False) coronavac: str = field(repr=False) coronavac_pediatrica: str = field(repr=False) @@ -37,3 +41,45 @@ class Posto(): _id_zone: str = field(repr=False) # id_crs _id_tb_unidades: str = field(repr=False) _id_modality: str = field(repr=False) # id_tipo_posto + + def __post_init__(self) -> None: + self.contacts.extend(self._extract_contacts()) + self.address = self._extract_address() + self.maps = self._build_maps_link() + + @staticmethod + def _remove_substring_until_the_end(string: str, substring: str) -> str: + new_string = string + if substring in string: + idx_substring = string.find(substring) + new_string = string[:idx_substring] + return new_string + + def _extract_address(self) -> str: + address = self.address.upper() + address = address.replace('ENDEREÇO:', '') + + address = self._remove_substring_until_the_end(address, 'F:') + address = self._remove_substring_until_the_end(address, 'FONE:') + address = self._remove_substring_until_the_end(address, 'TEL:') + + address = address.strip().strip('-').strip(',') + + return address.strip() + + def _build_maps_link(self) -> str: + address = self.address.replace(' ', '+') + + return f'https://www.google.com.br/maps/place/{address}' + + def _extract_contacts(self) -> List[str]: + address = self.address.replace(' ', '').upper() + + concatenated_contacts = re.split(r'F:|TEL:|FONE:', address)[-1] + contacts = re.findall(r'\d{4}-?\d{4}', concatenated_contacts) + + for idx, contact in enumerate(contacts): + if '-' not in contact: + contacts[idx] = f'{contact[:4]}-{contact[4:]}' + + return contacts diff --git a/filometro/deolhonafila.py b/filometro/deolhonafila.py index 02f2c7a..24868fd 100644 --- a/filometro/deolhonafila.py +++ b/filometro/deolhonafila.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ filometro.deolhonafila ---------------------- @@ -8,13 +7,10 @@ __all__ = ['APIDeOlhoNaFila'] -from filometro import __version__ -from filometro import __author__ +from typing import List import requests -from typing import List - class APIDeOlhoNaFila(): """Um wrapper da API do site 'De Olho na Fila'.""" @@ -23,7 +19,10 @@ def __init__(self) -> None: self.endpoint = 'https://deolhonafila.prefeitura.sp.gov.br/processadores/dados.php' self.payload = {'dados': 'dados'} self.headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36', + 'User-Agent': ( + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)' + 'Chrome/95.0.4638.69 Safari/537.36' + ), 'Host': 'deolhonafila.prefeitura.sp.gov.br', 'Connection': 'keep-alive', 'Content-Length': '11', diff --git a/filometro/enums.py b/filometro/enums.py index 86fd68f..3db6d63 100644 --- a/filometro/enums.py +++ b/filometro/enums.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ filometro.enums --------------- @@ -14,13 +13,12 @@ 'District' ] -from filometro import __version__ -from filometro import __author__ - from enum import Enum class Zone(Enum): + """Fornece todas as zonas onde há um ou mais postos disponíveis.""" + SUL = 'SUL' OESTE = 'OESTE' NORTE = 'NORTE' @@ -30,6 +28,8 @@ class Zone(Enum): class Modality(Enum): + """Fornece todas as modalidades dos postos.""" + PARQUES = 'PARQUES' POSTO_FIXO = 'POSTO FIXO' POSTO_VOLANTE = 'POSTO VOLANTE' @@ -38,6 +38,8 @@ class Modality(Enum): class Situation(Enum): + """Fornece todas as situações que um posto pode estar.""" + NAO_FUNCIONANDO = 'NÃO FUNCIONANDO' SEM_FILA = 'SEM FILA' FILA_PEQUENA = 'FILA PEQUENA' @@ -46,6 +48,8 @@ class Situation(Enum): class Immunizing(Enum): + """Fornece todos os imunizantes disponíveis nos postos.""" + ASTRAZENECA = 'astrazeneca' INTERCAMBIALIDADE = 'intercambialidade' PFIZER = 'pfizer' @@ -57,14 +61,16 @@ class Immunizing(Enum): class District(Enum): + """Fornece todos os distritos onde há um ou mais postos.""" + AGUA_RASA = 'Água Rasa' ALTO_DE_PINHEIROS = 'Alto de Pinheiros' ANHANGUERA = 'Anhanguera' ARICANDUVA = 'Aricanduva' ARTUR_ALVIM = 'Artur Alvim' - BELA_VISTA = 'Bela Vista' + BELA_VISTA = 'Bela Vista' BELEM = 'Belém' - BOM_RETIRO = 'Bom Retiro' + BOM_RETIRO = 'Bom Retiro' BRASILANDIA = 'Brasilândia' BRAS = 'Brás' BUTANTA = 'Butantã' diff --git a/filometro/filometro.py b/filometro/filometro.py index e2841df..d876053 100644 --- a/filometro/filometro.py +++ b/filometro/filometro.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ filometro.filometro ------------------- @@ -8,17 +7,10 @@ __all__ = ['Filometro'] -from filometro import __version__ -from filometro import __author__ - from typing import List -import json - from dataclasses import asdict -from pandas import DataFrame - from filometro.deolhonafila import APIDeOlhoNaFila from filometro.dataclasses import Posto @@ -30,60 +22,59 @@ from filometro.enums import District -def _posto_dict_to_posto_object(posto_dict: dict) -> Posto: - """Converte um dict com informações de um posto em um objeto Posto.""" +def _posto_factory(data: dict) -> Posto: + """Cria um objeto Posto com base no dicionário de dados fornecido.""" return Posto( - equipment=posto_dict['equipamento'], - address=posto_dict['endereco'], - district=posto_dict['distrito'], - zone=posto_dict['crs'], - astrazeneca=posto_dict['astrazeneca'], - coronavac=posto_dict['coronavac'], - coronavac_pediatrica=posto_dict['corona_ped'], - pfizer=posto_dict['pfizer'], - pfizer_pediatrica=posto_dict['pfizer_ped'], - janssen=posto_dict['janssen'], - influenza=posto_dict['influenza'], - intercambialidade=posto_dict['intercambialidade'], - situation=posto_dict['status_fila'], - modality=posto_dict['tipo_posto'], - last_update=posto_dict['data_hora'], - _index_situation=posto_dict['indice_fila'], - _id_district=posto_dict['id_distrito'], - _id_zone=posto_dict['id_crs'], - _id_tb_unidades=posto_dict['id_tb_unidades'], - _id_modality=posto_dict['id_tipo_posto'] + equipment=data['equipamento'], + address=data['endereco'], + district=data['distrito'], + zone=data['crs'], + astrazeneca=data['astrazeneca'], + coronavac=data['coronavac'], + coronavac_pediatrica=data['corona_ped'], + pfizer=data['pfizer'], + pfizer_pediatrica=data['pfizer_ped'], + janssen=data['janssen'], + influenza=data['influenza'], + intercambialidade=data['intercambialidade'], + situation=data['status_fila'], + modality=data['tipo_posto'], + last_update=data['data_hora'], + _index_situation=data['indice_fila'], + _id_district=data['id_distrito'], + _id_zone=data['id_crs'], + _id_tb_unidades=data['id_tb_unidades'], + _id_modality=data['id_tipo_posto'] ) -def _postos_dicts_to_postos_objects(postos_dicts: List[dict]) -> List[Posto]: - """Converte uma lista de dict com informações de vários postos em - uma lista de objetos Posto.""" - - postos_objects = [] - for posto_dict in postos_dicts: - posto_object = _posto_dict_to_posto_object(posto_dict) - postos_objects.append(posto_object) +def _postos_factory(data_list: List[dict]) -> List[Posto]: + """Cria uma lista de objetos Posto com base na lista de dados fornecido.""" - return postos_objects + return [_posto_factory(d) for d in data_list] class Filometro(): - """Filometro é a API príncipal do pacote. + """Filometro é a API príncipal do pacote. Fornence os métodos para coletar e filtrar os dados dos postos. """ - def __init__(self) -> None: - self._api = APIDeOlhoNaFila() + def __init__(self, _api: APIDeOlhoNaFila = None) -> None: + self._api = _api or APIDeOlhoNaFila() self._postos = self._load_postos() + def __repr__(self) -> str: + return f'{self.__class__.__name__}(postos={len(self._postos)})' + + def __str__(self) -> str: + return repr(self) + def _load_postos(self) -> List[Posto]: - postos_dicts = self._api.get_data() - postos_objects = _postos_dicts_to_postos_objects(postos_dicts) - return postos_objects + data_list = self._api.get_data() + return _postos_factory(data_list) def reload(self) -> None: """Recarregar os dados com as informações mais recentes.""" @@ -94,7 +85,7 @@ def reload(self) -> None: def all_postos(self) -> List[Posto]: """Retorna os dados de todos os postos.""" - return self._postos + return self._postos.copy() def all_postos_open(self) -> List[Posto]: """Retorna uma lista com todos os postos abertos no momento da busca.""" @@ -132,29 +123,9 @@ def by_situation(self, situation: Situation) -> List[Posto]: def by_immunizing(self, immunizing: Immunizing) -> List[Posto]: """Retorna os dados dos postos por imunizante selecionado.""" - return [posto for posto in self._postos if posto.__dict__[immunizing.value] == '1'] + return [posto for posto in self._postos if asdict(posto).get(immunizing.value) == '1'] def to_dict(self) -> List[dict]: """Retorna uma lista de dict contendo os dados de todos os postos.""" return [asdict(posto) for posto in self.all_postos()] - - def to_json(self, *args, **kwargs) -> str: - """Retorna uma string json contendo os dados de todos os postos. - - Argumentos para configurar o retorno em json são aceitos: - - >>> filometro.to_json(indent=4) - - Consulte a documentação do pacote json para saber quais argumentos - são suportados. - """ - - data = self.to_dict() - - return json.dumps(data, *args, **kwargs) - - def to_dataframe(self) -> DataFrame: - """Retorna um DataFrame contendo os dados de todos os postos.""" - - return DataFrame(self.all_postos()) diff --git a/setup.py b/setup.py index d403a75..4bec4cb 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import os import sys from shutil import rmtree @@ -21,7 +19,7 @@ class PublishCommand(Command): @staticmethod def print_status(msg): """Prints message in bold and yellow.""" - print('\033[1;33m{m}\033[0m'.format(m=msg)) + print(f'\033[1;33m{msg}\033[0m') def initialize_options(self): pass @@ -38,9 +36,10 @@ def run(self): except OSError: pass - + self.print_status('Build Source and Wheel distribution…') - os.system('{python} setup.py sdist bdist_wheel'.format(python=sys.executable)) + python = sys.executable + os.system(f'{python} setup.py sdist bdist_wheel') self.print_status('Uploading the package to PyPi via Twine…') os.system('twine upload --config-file .pypirc --repository pypi dist/*') @@ -55,7 +54,7 @@ def run(self): setup( name='filometro', version=__version__, - description='Um wrapper Python do site "De Olho na Fila": obtenha os dados dos postos de vacinação da covid-19 em São Paulo', + description='Obtenha os dados dos postos de vacinação da covid-19 em São Paulo', long_description=long_description, long_description_content_type='text/markdown', license='MIT License', @@ -66,8 +65,7 @@ def run(self): exclude=('tests',) ), install_requires=[ - 'requests>=2.27.1', - 'pandas>=1.4.1' + 'requests>=2.27.1' ], zip_safe=False, python_requires='>=3.7', @@ -78,10 +76,11 @@ def run(self): }, keywords=[ 'filometro', 'de-olho-na-fila', 'data', 'sao-paulo', - 'covid-19', 'vacina', 'vacinasampa', 'python', 'wrapper' + 'covid-19', 'vacina', 'vacinasampa', 'python', 'wrapper', + 'coronavirus' ], classifiers=[ - 'Development Status :: 4 - Beta', + 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'License :: OSI Approved :: MIT License', @@ -100,4 +99,4 @@ def run(self): cmdclass={ 'publish': PublishCommand } -) \ No newline at end of file +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_deolhonafila.py b/tests/test_deolhonafila.py new file mode 100644 index 0000000..24e7728 --- /dev/null +++ b/tests/test_deolhonafila.py @@ -0,0 +1,53 @@ +"""Testes do módulo filometro.deolhonafila.""" + +import unittest + +from filometro.deolhonafila import APIDeOlhoNaFila + + +class TestAPIDeOlhoNaFila(unittest.TestCase): + """Testes da classe APIDeOlhoNaFila.""" + + def setUp(self): + self.api = APIDeOlhoNaFila() + + def test_if_has_the_endpoint_attribute_with_the_valid_value(self): + valid_endpoint = 'https://deolhonafila.prefeitura.sp.gov.br/processadores/dados.php' + + self.assertTrue(hasattr(self.api, 'endpoint')) + self.assertEqual(self.api.endpoint, valid_endpoint) + + def test_if_has_the_payload_attribute_with_the_valid_value(self): + valid_payload = {'dados': 'dados'} + + self.assertTrue(hasattr(self.api, 'payload')) + self.assertEqual(self.api.payload, valid_payload) + + def test_if_has_the_headers_attribute_with_the_valid_value(self): + valid_headers = { + 'User-Agent': ( # implicit concatenation + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)' + 'Chrome/95.0.4638.69 Safari/537.36' + ), + 'Host': 'deolhonafila.prefeitura.sp.gov.br', + 'Connection': 'keep-alive', + 'Content-Length': '11', + 'sec-ch-ua': '"Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99"', + 'Accept': 'application/json, text/javascript, */*; q=0.01', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-Requested-With': 'XMLHttpRequest', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + 'Origin': 'https://deolhonafila.prefeitura.sp.gov.br', + 'Sec-Fetch-Site': 'same-origin', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Dest': 'empty', + 'Referer': 'https://deolhonafila.prefeitura.sp.gov.br/', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'pt,en-US;q=0.9,en;q=0.8,de;q=0.7', + 'dnt': '1', + 'sec-gpc': '1' + } + + self.assertTrue(hasattr(self.api, 'headers')) + self.assertEqual(self.api.headers, valid_headers) diff --git a/tests/test_filometro.py b/tests/test_filometro.py new file mode 100644 index 0000000..3ed6a0d --- /dev/null +++ b/tests/test_filometro.py @@ -0,0 +1,213 @@ +"""Testes do módulo filometro.filometro.""" + +import unittest + +from typing import List + +from filometro.dataclasses import Posto + +from filometro.enums import Zone +from filometro.enums import Modality +from filometro.enums import District +from filometro.enums import Situation +from filometro.enums import Immunizing + +from filometro.filometro import _posto_factory +from filometro.filometro import _postos_factory +from filometro.filometro import Filometro + + +class FakeAPIDeOlhoNaFila: + def __init__(self) -> None: + self._data = [ + { + 'equipamento': 'test', + 'endereco': 'test', + 'distrito': 'Campo Limpo', + 'crs': 'SUL', + 'astrazeneca': '0', + 'coronavac': '0', + 'corona_ped': '0', + 'pfizer': '0', + 'pfizer_ped': '0', + 'janssen': '0', + 'influenza': '0', + 'intercambialidade': '0', + 'status_fila': 'NÃO FUNCIONANDO', + 'tipo_posto': 'POSTO FIXO', + 'data_hora': 'test', + 'indice_fila': 'test', + 'id_distrito': 'test', + 'id_crs': 'test', + 'id_tb_unidades': 'test', + 'id_tipo_posto': 'test' + }, + { + 'equipamento': 'test', + 'endereco': 'test', + 'distrito': 'Brás', + 'crs': 'LESTE', + 'astrazeneca': '1', + 'coronavac': '1', + 'corona_ped': '1', + 'pfizer': '1', + 'pfizer_ped': '1', + 'janssen': '1', + 'influenza': '1', + 'intercambialidade': '1', + 'status_fila': 'FILA GRANDE', + 'tipo_posto': 'POSTO FIXO', + 'data_hora': 'test', + 'indice_fila': 'test', + 'id_distrito': 'test', + 'id_crs': 'test', + 'id_tb_unidades': 'test', + 'id_tipo_posto': 'test' + } + ] + + def get_data(self) -> List[dict]: + return self._data.copy() + + +class TestPostoFactory(unittest.TestCase): + """Testes para criação de objetos Posto.""" + + def setUp(self): + self.data = { + 'equipamento': 'test', + 'endereco': 'test', + 'distrito': 'test', + 'crs': 'test', + 'astrazeneca': 'test', + 'coronavac': 'test', + 'corona_ped': 'test', + 'pfizer': 'test', + 'pfizer_ped': 'test', + 'janssen': 'test', + 'influenza': 'test', + 'intercambialidade': 'test', + 'status_fila': 'test', + 'tipo_posto': 'test', + 'data_hora': 'test', + 'indice_fila': 'test', + 'id_distrito': 'test', + 'id_crs': 'test', + 'id_tb_unidades': 'test', + 'id_tipo_posto': 'test' + } + + def test_create_posto_object(self): + posto = _posto_factory(self.data) + + self.assertIsInstance(posto, Posto) + + def test_raise_exception_with_missing_mandatory_key(self): + self.data.pop('equipamento') + + with self.assertRaises(KeyError): + _posto_factory(self.data) + + def test_create_a_list_of_postos_objects(self): + number_of_postos = 5 + data_list = [self.data.copy() for _ in range(number_of_postos)] + + postos = _postos_factory(data_list) + + self.assertIsInstance(postos, list) + + postos_length = len(postos) + self.assertEqual(postos_length, number_of_postos) + + for posto in postos: + with self.subTest(): + self.assertIsInstance(posto, Posto) + + +class TestFilometro(unittest.TestCase): + """Testes relacionados a classe Filometro.""" + + def setUp(self): + self.filometro = Filometro(_api=FakeAPIDeOlhoNaFila()) + self.total_of_postos = len(self.filometro._postos) + + def test_get_all_postos(self): + postos = self.filometro.all_postos() + + self.assertIsInstance(postos, list) + + num_of_postos = len(postos) + self.assertEqual(num_of_postos, self.total_of_postos) + + def test_return_from_all_postos_cannot_reference_internal_data_list(self): + postos = self.filometro.all_postos() + + self.assertIsNot(postos, self.filometro._postos) + + def test_get_all_postos_open(self): + postos = self.filometro.all_postos_open() + + num_of_postos = len(postos) + expected_number_of_postos_open = 1 + + self.assertEqual(num_of_postos, expected_number_of_postos_open) + + def test_get_all_postos_closed(self): + postos = self.filometro.all_postos_closed() + + num_of_postos = len(postos) + expected_number_of_postos_closed = 1 + + self.assertEqual(num_of_postos, expected_number_of_postos_closed) + + def test_get_all_postos_from_a_specific_zone(self): + postos = self.filometro.by_zone(Zone.SUL) + + num_of_postos = len(postos) + expected_number_of_postos_from_sul_zone = 1 + + self.assertEqual(num_of_postos, expected_number_of_postos_from_sul_zone) + + def test_get_all_postos_from_a_specific_modality(self): + postos = self.filometro.by_modality(Modality.POSTO_FIXO) + + num_of_postos = len(postos) + expected_number_of_postos_fixos = 2 + + self.assertEqual(num_of_postos, expected_number_of_postos_fixos) + + def test_get_all_postos_from_a_specific_district(self): + postos = self.filometro.by_district(District.CAMPO_LIMPO) + + num_of_postos = len(postos) + expected_number_of_postos_from_campo_limpo_district = 1 + + self.assertEqual(num_of_postos, expected_number_of_postos_from_campo_limpo_district) + + def test_get_all_postos_from_a_specific_situation(self): + postos = self.filometro.by_situation(Situation.FILA_GRANDE) + + num_of_postos = len(postos) + expected_number_of_postos_from_situation = 1 + + self.assertEqual(num_of_postos, expected_number_of_postos_from_situation) + + def test_get_all_postos_from_a_specific_immunizing(self): + postos = self.filometro.by_immunizing(Immunizing.CORONAVAC) + + num_of_postos = len(postos) + expected_number_of_postos_from_coronavac_immunizing = 1 + + self.assertEqual(num_of_postos, expected_number_of_postos_from_coronavac_immunizing) + + def test_convert_postos_to_dict(self): + data = self.filometro.to_dict() + + self.assertIsInstance(data, list) + + num_of_data = len(data) + self.assertEqual(num_of_data, self.total_of_postos) + + for d in data: + with self.subTest(): + self.assertIsInstance(d, dict)