diff --git a/.github/assets/images/worldometer.png b/.github/assets/images/worldometer.png index 89bd3bc..88203b5 100644 Binary files a/.github/assets/images/worldometer.png and b/.github/assets/images/worldometer.png differ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..6a03167 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,36 @@ +name: Tests + +on: + push: + branches: [master, dev] + pull_request: + branches: [master, dev] + +jobs: + unittests: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.9', '3.10', '3.11'] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install poetry + run: | + python -m pip install --upgrade pip + pip install poetry + + - name: Install dependencies + run: | + poetry install + + - name: Run unittests + run: | + poetry run pytest --verbose \ No newline at end of file diff --git a/.gitignore b/.gitignore index d0b01cf..b5707a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,195 @@ -# Ignore local directory -.vscode/ +# Created by https://www.toptal.com/developers/gitignore/api/python,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=python,visualstudiocode -# Ignore local secret file -.env -.pypirc - -# Ignore cache +### Python ### +# Byte-compiled / optimized / DLL files __pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so -# Ignore build and distribution directories/files +# Distribution / packaging +.Python build/ +develop-eggs/ dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ *.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix -# Ignore the docs build directory -docs/build/ +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide -# Ignore local tests -local_test.py +# End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode \ No newline at end of file diff --git a/.readthedocs.yml b/.readthedocs.yml index 96244df..22210ff 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -7,19 +7,20 @@ version: 2 # Select the Docker image used for building the docs. build: - image: latest + os: ubuntu-22.04 + tools: + python: "3.8" # Build documentation in the docs/ directory with Sphinx sphinx: builder: html configuration: docs/source/conf.py - fail_on_warning: true + fail_on_warning: false # Build all formats (htmlzip, pdf and epub) formats: all # Configure python for install requirements python: - version: 3.8 install: - requirements: docs/docs_requirements.txt diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 6359526..0000000 --- a/Pipfile +++ /dev/null @@ -1,11 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -requests-html = "0.10.0" - -[dev-packages] -pylint = "2.6.0" -sphinx = "3.5.0" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index d9d8f33..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,738 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "3a37814eebe63bcde3a5c6749cc05a45e39e711f5ed01c0f8953581b81d07d3a" - }, - "pipfile-spec": 6, - "requires": {}, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "appdirs": { - "hashes": [ - "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", - "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" - ], - "version": "==1.4.4" - }, - "beautifulsoup4": { - "hashes": [ - "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30", - "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693" - ], - "markers": "python_version >= '3.6'", - "version": "==4.11.1" - }, - "bs4": { - "hashes": [ - "sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a" - ], - "version": "==0.0.1" - }, - "certifi": { - "hashes": [ - "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", - "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" - ], - "markers": "python_version >= '3.6'", - "version": "==2022.12.7" - }, - "charset-normalizer": { - "hashes": [ - "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", - "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" - ], - "markers": "python_version >= '3.6'", - "version": "==2.1.1" - }, - "colorama": { - "hashes": [ - "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", - "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" - ], - "markers": "platform_system == 'Windows'", - "version": "==0.4.6" - }, - "cssselect": { - "hashes": [ - "sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc", - "sha256:da1885f0c10b60c03ed5eccbb6b68d6eff248d91976fcde348f395d54c9fd35e" - ], - "markers": "python_version >= '3.7'", - "version": "==1.2.0" - }, - "fake-useragent": { - "hashes": [ - "sha256:579c72b18ba792a5bd54ba48e63e464d21933e336472c974091a6757f31bfcdc", - "sha256:9f9b3667d3741ba81e34ebf9a6aa32658ecf7835499257826dd72642af629d59" - ], - "version": "==1.1.1" - }, - "idna": { - "hashes": [ - "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", - "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" - ], - "markers": "python_version >= '3.5'", - "version": "==3.4" - }, - "importlib-metadata": { - "hashes": [ - "sha256:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b", - "sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313" - ], - "markers": "python_version >= '3.7'", - "version": "==5.1.0" - }, - "importlib-resources": { - "hashes": [ - "sha256:32bb095bda29741f6ef0e5278c42df98d135391bee5f932841efc0041f748dc3", - "sha256:c09b067d82e72c66f4f8eb12332f5efbebc9b007c0b6c40818108c9870adc363" - ], - "markers": "python_version < '3.10'", - "version": "==5.10.1" - }, - "lxml": { - "hashes": [ - "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7", - "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726", - "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03", - "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140", - "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a", - "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05", - "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03", - "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419", - "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4", - "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e", - "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67", - "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50", - "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894", - "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf", - "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947", - "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1", - "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd", - "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92", - "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457", - "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74", - "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf", - "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1", - "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4", - "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975", - "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5", - "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe", - "sha256:6943826a0374fb135bb11843594eda9ae150fba9d1d027d2464c713da7c09afe", - "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7", - "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2", - "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409", - "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f", - "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f", - "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5", - "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24", - "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e", - "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4", - "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a", - "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c", - "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f", - "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7", - "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a", - "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c", - "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9", - "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e", - "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab", - "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941", - "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5", - "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45", - "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7", - "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892", - "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746", - "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c", - "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53", - "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184", - "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38", - "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9", - "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b", - "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2", - "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0", - "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b", - "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380", - "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8", - "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1", - "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889", - "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9", - "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f", - "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==4.9.2" - }, - "parse": { - "hashes": [ - "sha256:9ff82852bcb65d139813e2a5197627a94966245c897796760a3a2a8eb66f020b" - ], - "version": "==1.19.0" - }, - "pyee": { - "hashes": [ - "sha256:5c7e60f8df95710dbe17550e16ce0153f83990c00ef744841b43f371ed53ebea", - "sha256:c09f56e36eb10bf23aa2aacf145f690ded75b990a3d9523fd478b005940303d2" - ], - "version": "==8.2.2" - }, - "pyppeteer": { - "hashes": [ - "sha256:11a734d8f02c6b128035aba8faf32748f2016310a6a1cbc6aa5b1e2580742e8f", - "sha256:ddb0d15cb644720160d49abb1ad0d97e87a55581febf1b7531be9e983aad7742" - ], - "markers": "python_version >= '3.7' and python_version < '4'", - "version": "==1.0.2" - }, - "pyquery": { - "hashes": [ - "sha256:1fc33b7699455ed25c75282bc8f80ace1ac078b0dda5a933dacbd8b1c1f83963", - "sha256:a388eefb6bc4a55350de0316fbd97cda999ae669b6743ae5b99102ba54f5aa72" - ], - "version": "==1.4.3" - }, - "requests": { - "hashes": [ - "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", - "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" - ], - "markers": "python_version >= '3.7' and python_version < '4'", - "version": "==2.28.1" - }, - "requests-html": { - "hashes": [ - "sha256:7e929ecfed95fb1d0994bb368295d6d7c4d06b03fcb900c33d7d0b17e6003947", - "sha256:cb8a78cf829c4eca9d6233f28524f65dd2bfaafb4bdbbc407f0a0b8f487df6e2" - ], - "index": "pypi", - "version": "==0.10.0" - }, - "soupsieve": { - "hashes": [ - "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759", - "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d" - ], - "markers": "python_version >= '3.6'", - "version": "==2.3.2.post1" - }, - "tqdm": { - "hashes": [ - "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4", - "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==4.64.1" - }, - "urllib3": { - "hashes": [ - "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc", - "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.13" - }, - "w3lib": { - "hashes": [ - "sha256:0e1198f1b745195b6b3dd1a4cd66011fbf82f30a4d9dabaee1f9e5c86f020274", - "sha256:7fd5bd7980a95d1a8185e867d05f68a591aa281a3ded4590d2641d7b09086ed4" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.1" - }, - "websockets": { - "hashes": [ - "sha256:00213676a2e46b6ebf6045bc11d0f529d9120baa6f58d122b4021ad92adabd41", - "sha256:00c870522cdb69cd625b93f002961ffb0c095394f06ba8c48f17eef7c1541f96", - "sha256:0154f7691e4fe6c2b2bc275b5701e8b158dae92a1ab229e2b940efe11905dff4", - "sha256:05a7233089f8bd355e8cbe127c2e8ca0b4ea55467861906b80d2ebc7db4d6b72", - "sha256:09a1814bb15eff7069e51fed0826df0bc0702652b5cb8f87697d469d79c23576", - "sha256:0cff816f51fb33c26d6e2b16b5c7d48eaa31dae5488ace6aae468b361f422b63", - "sha256:185929b4808b36a79c65b7865783b87b6841e852ef5407a2fb0c03381092fa3b", - "sha256:2fc8709c00704194213d45e455adc106ff9e87658297f72d544220e32029cd3d", - "sha256:33d69ca7612f0ddff3316b0c7b33ca180d464ecac2d115805c044bf0a3b0d032", - "sha256:389f8dbb5c489e305fb113ca1b6bdcdaa130923f77485db5b189de343a179393", - "sha256:38ea7b82bfcae927eeffc55d2ffa31665dc7fec7b8dc654506b8e5a518eb4d50", - "sha256:3d3cac3e32b2c8414f4f87c1b2ab686fa6284a980ba283617404377cd448f631", - "sha256:40e826de3085721dabc7cf9bfd41682dadc02286d8cf149b3ad05bff89311e4f", - "sha256:4239b6027e3d66a89446908ff3027d2737afc1a375f8fd3eea630a4842ec9a0c", - "sha256:45ec8e75b7dbc9539cbfafa570742fe4f676eb8b0d3694b67dabe2f2ceed8aa6", - "sha256:47a2964021f2110116cc1125b3e6d87ab5ad16dea161949e7244ec583b905bb4", - "sha256:48c08473563323f9c9debac781ecf66f94ad5a3680a38fe84dee5388cf5acaf6", - "sha256:4c6d2264f485f0b53adf22697ac11e261ce84805c232ed5dbe6b1bcb84b00ff0", - "sha256:4f72e5cd0f18f262f5da20efa9e241699e0cf3a766317a17392550c9ad7b37d8", - "sha256:56029457f219ade1f2fc12a6504ea61e14ee227a815531f9738e41203a429112", - "sha256:5c1289596042fad2cdceb05e1ebf7aadf9995c928e0da2b7a4e99494953b1b94", - "sha256:62e627f6b6d4aed919a2052efc408da7a545c606268d5ab5bfab4432734b82b4", - "sha256:74de2b894b47f1d21cbd0b37a5e2b2392ad95d17ae983e64727e18eb281fe7cb", - "sha256:7c584f366f46ba667cfa66020344886cf47088e79c9b9d39c84ce9ea98aaa331", - "sha256:7d27a7e34c313b3a7f91adcd05134315002aaf8540d7b4f90336beafaea6217c", - "sha256:7d3f0b61c45c3fa9a349cf484962c559a8a1d80dae6977276df8fd1fa5e3cb8c", - "sha256:82ff5e1cae4e855147fd57a2863376ed7454134c2bf49ec604dfe71e446e2193", - "sha256:84bc2a7d075f32f6ed98652db3a680a17a4edb21ca7f80fe42e38753a58ee02b", - "sha256:884be66c76a444c59f801ac13f40c76f176f1bfa815ef5b8ed44321e74f1600b", - "sha256:8a5cc00546e0a701da4639aa0bbcb0ae2bb678c87f46da01ac2d789e1f2d2038", - "sha256:8dc96f64ae43dde92530775e9cb169979f414dcf5cff670455d81a6823b42089", - "sha256:8f38706e0b15d3c20ef6259fd4bc1700cd133b06c3c1bb108ffe3f8947be15fa", - "sha256:90fcf8929836d4a0e964d799a58823547df5a5e9afa83081761630553be731f9", - "sha256:931c039af54fc195fe6ad536fde4b0de04da9d5916e78e55405436348cfb0e56", - "sha256:932af322458da7e4e35df32f050389e13d3d96b09d274b22a7aa1808f292fee4", - "sha256:942de28af58f352a6f588bc72490ae0f4ccd6dfc2bd3de5945b882a078e4e179", - "sha256:9bc42e8402dc5e9905fb8b9649f57efcb2056693b7e88faa8fb029256ba9c68c", - "sha256:a7a240d7a74bf8d5cb3bfe6be7f21697a28ec4b1a437607bae08ac7acf5b4882", - "sha256:a9f9a735deaf9a0cadc2d8c50d1a5bcdbae8b6e539c6e08237bc4082d7c13f28", - "sha256:ae5e95cfb53ab1da62185e23b3130e11d64431179debac6dc3c6acf08760e9b1", - "sha256:b029fb2032ae4724d8ae8d4f6b363f2cc39e4c7b12454df8df7f0f563ed3e61a", - "sha256:b0d15c968ea7a65211e084f523151dbf8ae44634de03c801b8bd070b74e85033", - "sha256:b343f521b047493dc4022dd338fc6db9d9282658862756b4f6fd0e996c1380e1", - "sha256:b627c266f295de9dea86bd1112ed3d5fafb69a348af30a2422e16590a8ecba13", - "sha256:b9968694c5f467bf67ef97ae7ad4d56d14be2751000c1207d31bf3bb8860bae8", - "sha256:ba089c499e1f4155d2a3c2a05d2878a3428cf321c848f2b5a45ce55f0d7d310c", - "sha256:bbccd847aa0c3a69b5f691a84d2341a4f8a629c6922558f2a70611305f902d74", - "sha256:bc0b82d728fe21a0d03e65f81980abbbcb13b5387f733a1a870672c5be26edab", - "sha256:c57e4c1349fbe0e446c9fa7b19ed2f8a4417233b6984277cce392819123142d3", - "sha256:c94ae4faf2d09f7c81847c63843f84fe47bf6253c9d60b20f25edfd30fb12588", - "sha256:c9b27d6c1c6cd53dc93614967e9ce00ae7f864a2d9f99fe5ed86706e1ecbf485", - "sha256:d210abe51b5da0ffdbf7b43eed0cfdff8a55a1ab17abbec4301c9ff077dd0342", - "sha256:d58804e996d7d2307173d56c297cf7bc132c52df27a3efaac5e8d43e36c21c48", - "sha256:d6a4162139374a49eb18ef5b2f4da1dd95c994588f5033d64e0bbfda4b6b6fcf", - "sha256:da39dd03d130162deb63da51f6e66ed73032ae62e74aaccc4236e30edccddbb0", - "sha256:db3c336f9eda2532ec0fd8ea49fef7a8df8f6c804cdf4f39e5c5c0d4a4ad9a7a", - "sha256:dd500e0a5e11969cdd3320935ca2ff1e936f2358f9c2e61f100a1660933320ea", - "sha256:dd9becd5fe29773d140d68d607d66a38f60e31b86df75332703757ee645b6faf", - "sha256:e0cb5cc6ece6ffa75baccfd5c02cffe776f3f5c8bf486811f9d3ea3453676ce8", - "sha256:e23173580d740bf8822fd0379e4bf30aa1d5a92a4f252d34e893070c081050df", - "sha256:e3a686ecb4aa0d64ae60c9c9f1a7d5d46cab9bfb5d91a2d303d00e2cd4c4c5cc", - "sha256:e789376b52c295c4946403bd0efecf27ab98f05319df4583d3c48e43c7342c2f", - "sha256:edc344de4dac1d89300a053ac973299e82d3db56330f3494905643bb68801269", - "sha256:eef610b23933c54d5d921c92578ae5f89813438fded840c2e9809d378dc765d3", - "sha256:f2c38d588887a609191d30e902df2a32711f708abfd85d318ca9b367258cfd0c", - "sha256:f55b5905705725af31ccef50e55391621532cd64fbf0bc6f4bac935f0fccec46", - "sha256:f5fc088b7a32f244c519a048c170f14cf2251b849ef0e20cbbb0fdf0fdaf556f", - "sha256:fe10ddc59b304cb19a1bdf5bd0a7719cbbc9fbdd57ac80ed436b709fcf889106", - "sha256:ff64a1d38d156d429404aaa84b27305e957fd10c30e5880d1765c9480bea490f" - ], - "markers": "python_version >= '3.7'", - "version": "==10.4" - }, - "zipp": { - "hashes": [ - "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa", - "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766" - ], - "markers": "python_version >= '3.7'", - "version": "==3.11.0" - } - }, - "develop": { - "alabaster": { - "hashes": [ - "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", - "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" - ], - "version": "==0.7.12" - }, - "astroid": { - "hashes": [ - "sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907", - "sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7" - ], - "markers": "python_full_version >= '3.7.2'", - "version": "==2.12.13" - }, - "babel": { - "hashes": [ - "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe", - "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6" - ], - "markers": "python_version >= '3.6'", - "version": "==2.11.0" - }, - "certifi": { - "hashes": [ - "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", - "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" - ], - "markers": "python_version >= '3.6'", - "version": "==2022.12.7" - }, - "charset-normalizer": { - "hashes": [ - "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", - "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" - ], - "markers": "python_version >= '3.6'", - "version": "==2.1.1" - }, - "colorama": { - "hashes": [ - "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", - "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" - ], - "markers": "platform_system == 'Windows'", - "version": "==0.4.6" - }, - "dill": { - "hashes": [ - "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0", - "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373" - ], - "markers": "python_version >= '3.7'", - "version": "==0.3.6" - }, - "docutils": { - "hashes": [ - "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6", - "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc" - ], - "markers": "python_version >= '3.7'", - "version": "==0.19" - }, - "idna": { - "hashes": [ - "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", - "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" - ], - "markers": "python_version >= '3.5'", - "version": "==3.4" - }, - "imagesize": { - "hashes": [ - "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", - "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.4.1" - }, - "importlib-metadata": { - "hashes": [ - "sha256:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b", - "sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313" - ], - "markers": "python_version >= '3.7'", - "version": "==5.1.0" - }, - "isort": { - "hashes": [ - "sha256:dd8bbc5c0990f2a095d754e50360915f73b4c26fc82733eb5bfc6b48396af4d2", - "sha256:e486966fba83f25b8045f8dd7455b0a0d1e4de481e1d7ce4669902d9fb85e622" - ], - "markers": "python_version >= '3.7'", - "version": "==5.11.2" - }, - "jinja2": { - "hashes": [ - "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", - "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" - ], - "markers": "python_version >= '3.7'", - "version": "==3.1.2" - }, - "lazy-object-proxy": { - "hashes": [ - "sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada", - "sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d", - "sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7", - "sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe", - "sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd", - "sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c", - "sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858", - "sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288", - "sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec", - "sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f", - "sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891", - "sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c", - "sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25", - "sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156", - "sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8", - "sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f", - "sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e", - "sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0", - "sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b" - ], - "markers": "python_version >= '3.7'", - "version": "==1.8.0" - }, - "markupsafe": { - "hashes": [ - "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", - "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", - "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", - "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", - "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", - "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", - "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", - "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", - "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", - "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", - "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", - "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", - "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", - "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", - "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", - "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", - "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", - "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", - "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", - "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", - "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", - "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", - "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", - "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", - "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", - "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", - "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", - "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", - "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", - "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", - "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", - "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", - "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", - "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", - "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", - "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", - "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", - "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", - "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", - "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.1" - }, - "mccabe": { - "hashes": [ - "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", - "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" - ], - "markers": "python_version >= '3.6'", - "version": "==0.7.0" - }, - "packaging": { - "hashes": [ - "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3", - "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3" - ], - "markers": "python_version >= '3.7'", - "version": "==22.0" - }, - "platformdirs": { - "hashes": [ - "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca", - "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e" - ], - "markers": "python_version >= '3.7'", - "version": "==2.6.0" - }, - "pygments": { - "hashes": [ - "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1", - "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42" - ], - "markers": "python_version >= '3.6'", - "version": "==2.13.0" - }, - "pylint": { - "hashes": [ - "sha256:ea82cd6a1e11062dc86d555d07c021b0fb65afe39becbe6fe692efd6c4a67443", - "sha256:ec4a87c33da054ab86a6c79afa6771dc8765cb5631620053e727fcf3ef8cbed7" - ], - "index": "pypi", - "version": "==2.15.8" - }, - "pytz": { - "hashes": [ - "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427", - "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2" - ], - "version": "==2022.6" - }, - "requests": { - "hashes": [ - "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", - "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" - ], - "markers": "python_version >= '3.7' and python_version < '4'", - "version": "==2.28.1" - }, - "snowballstemmer": { - "hashes": [ - "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", - "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" - ], - "version": "==2.2.0" - }, - "sphinx": { - "hashes": [ - "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d", - "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5" - ], - "index": "pypi", - "version": "==5.3.0" - }, - "sphinxcontrib-applehelp": { - "hashes": [ - "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", - "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.2" - }, - "sphinxcontrib-devhelp": { - "hashes": [ - "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", - "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.2" - }, - "sphinxcontrib-htmlhelp": { - "hashes": [ - "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07", - "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2" - ], - "markers": "python_version >= '3.6'", - "version": "==2.0.0" - }, - "sphinxcontrib-jsmath": { - "hashes": [ - "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", - "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.1" - }, - "sphinxcontrib-qthelp": { - "hashes": [ - "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", - "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.3" - }, - "sphinxcontrib-serializinghtml": { - "hashes": [ - "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", - "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952" - ], - "markers": "python_version >= '3.5'", - "version": "==1.1.5" - }, - "tomli": { - "hashes": [ - "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" - ], - "markers": "python_version < '3.11'", - "version": "==2.0.1" - }, - "tomlkit": { - "hashes": [ - "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b", - "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73" - ], - "markers": "python_version >= '3.6'", - "version": "==0.11.6" - }, - "typing-extensions": { - "hashes": [ - "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", - "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" - ], - "markers": "python_version < '3.10'", - "version": "==4.4.0" - }, - "urllib3": { - "hashes": [ - "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc", - "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.13" - }, - "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" - }, - "zipp": { - "hashes": [ - "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa", - "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766" - ], - "markers": "python_version >= '3.7'", - "version": "==3.11.0" - } - } -} diff --git a/README.md b/README.md index cc6ad60..05ee39b 100644 --- a/README.md +++ b/README.md @@ -1,159 +1,136 @@ -

- Logo Worldometer - Scraping & API -

+

+ Worldometer package logo +
+ Get live, population, geography, projected, and historical data from around the world. +

+---

+ Metadata +
- PyPI - Status + PyPI - Version - - PyPI + + Total Downloads - - GitHub release (latest by date) + + License MIT + +

+ +

+ Status +
+ + PyPI - Status Documentation Status - - License MIT -

-# Index +## Index - [About](#about) - - [worldometers.info](#worldometersinfo) - - [How it works?](#how-it-works) - [Install](#install) +- [Documentation](#documentation) - [Demo](#demo) +- [worldometers.info](#worldometersinfo) +- [Data Sources](#data-sources) - [Contributions](#contributions) - [License](#license) -# About +## About -[Worldometer](https://github.com/matheusfelipeog/worldometer) is a python module that collects data from [worldometers.info](https://www.worldometers.info/) and provides a simple and self-explanatory interface for using the data. +The [`worldometer`](https://github.com/matheusfelipeog/worldometer) package accesses various counters and live data available throughout the [worldometers.info](https://www.worldometers.info/) website and provides them through simple and self-describing classes, methods and attributes. -## worldometers.info +Access data on: -> Worldometer is run by an international team of developers, researchers, and volunteers with the goal of making world statistics available in a thought-provoking and time relevant format to a wide audience around the world. It is published by a small and independent digital media company based in the United States. We have no political, governmental, or corporate affiliation. Furthermore, we have no investors, donors, grants, or backers of any type. We are completely independent and self-financed through automated programmatic advertising sold in real time on multiple ad exchanges. +- The world 🌍 +- Population 👥 +- Geography 🗺️ +- Projections 🔮 +- Historical 📜 -More info: [worldometers.info/about](https://www.worldometers.info/about/) -## How it works? +## Install -> **[Adapted]:** For the data, is elaborate instead a real-time estimate through a proprietary algorithm which processes the latest data and projections provided by the most reputable organizations and statistical offices in the world. +Use `pip` to install the worldometer package: -More info about data source: [worldometers.info/sources](https://www.worldometers.info/sources/) +```bash +$ pip install worldometer +``` -# Install +## Documentation -First, create a directory and enter it: +See the docs for more information and its API at: [worldometer.readthedocs.io](https://worldometer.readthedocs.io/) -```bash -$ mkdir my_project && cd my_project -``` -Create a virtual environment to avoid breaking dependence on other projects. +## Demo -This project uses [`pipenv`](https://pipenv.pypa.io/en/latest/), it already does it alone ;) +> [!NOTE] +> *The first time you run any function/method or class, it will download Chromium to `~/.local/share/pyppeteer` directory. It only happens once. After, it will only open the chromium to render the contents of worldometers.info.* -```bash -$ pipenv install worldometer -``` +Get the data from the live counters available on the [homepage](https://www.worldometers.info/): -But you can use `virtualenv` + `pip` if you prefer: +```python +>>> from worldometer.world import WorldCounters -```bash -$ virtualenv venv && source venv/Scripts/activate -``` +>>> wc = WorldCounters() -Now install: +>>> wc.world_population.current_population +8065299074 -```bash -$ pip install worldometer +>>> wc.government_and_economics.computers_produced_this_year +180248430 + +>>> wc.society_and_media.internet_users_in_the_world_today +5895566559 ``` +Reload data to get the latest: -# Demo +```python +>>> wc.reload_data() +>>> wc.world_population.current_population +8065300592 +``` -*The first time you run any function/method or class, it will download Chromium to its home directory (for example, `~/.pyppeteer/`). It only happens once.* +Get help and view information about mapped sections: -*After, it will only open the chromium to render the contents of worldometers.* +```python +>>> help(wc) +``` -**Simple API usage:** -```python ->>> import worldometer +## worldometers.info + +> Worldometer is run by an international team of developers, researchers, and volunteers with the goal of making world statistics available in a thought-provoking and time relevant format to a wide audience around the world. It is published by a small and independent digital media company based in the United States. We have no political, governmental, or corporate affiliation. Furthermore, we have no investors, donors, grants, or backers of any type. We are completely independent and self-financed through automated programmatic advertising sold in real time on multiple ad exchanges. ->>> worldometer.current_world_population() -{'current_world_population': 7845085923} +

+ worldometers.info/about +

->>> worldometer.tweets_sent_today() -{'tweets_sent_today': 4539558} ->>> worldometer.get_metric_of(label='computers_produced_this_year') -{'computers_produced_this_year': 27760858} -``` +## Data Sources -**Or complete use with Worldometer Class:** +> **[adapted]:** worldometers.info collects its statistics and data from the most reputable national and international organizations, including the United Nations, the World Health Organization, the Food and Agriculture Organization, OECD and others. +> +> Each Worldometer counter has its specific set of sources, which are listed on its dedicated page (accessible by clicking on the counter text link, when available). +> +> Data, estimates, and projections displayed on worldometers.info counters are for the most part provided by organizations included in the following list of United Nations Statistics Division's partners. -```python ->>> from worldometer import Worldometer ->>> w = Worldometer() - ->>> w.what_is_here() -{'categories': 8, 'labels': 63, 'metrics': 63} - ->>> w.categories() -[ - 'world_population', - 'government_and_economics', - 'society_and_media', - ... # compressed -] - ->>> w.metrics_labels() -[ - 'current_world_population', - 'births_this_year', - 'births_today', - 'deaths_this_year', - 'deaths_today', - 'net_population_growth_this_year', - ... # compressed -] - ->>> w.metrics() -[ - 7845087963, - 15741371, - 5676, - 6608605, - 2383, - 9132766, - ... # compressed -] - ->>> w.metrics_with_labels() -{ - 'abortions_this_year': 4785492, - 'bicycles_produced_this_year': 17070566, - 'births_this_year': 15741371, - 'births_today': 5676, - 'blog_posts_written_today': 110171, - 'cars_produced_this_year': 8999185, - 'cellular_phones_sold_today': 98846, - ...: ... # compressed -} -``` +

+ worldometers.info/sources +

-# Contributions +## Contributions All contributions are welcome! @@ -164,6 +141,6 @@ Do you have a solution to the problem? [Send me a PR](https://github.com/matheus Did you like this project? [Click on the star ⭐](https://github.com/matheusfelipeog/worldometer/stargazers) -# License +## License This project is using the MIT license, see in [MIT LICENSE](https://github.com/matheusfelipeog/worldometer/blob/master/LICENSE). diff --git a/docs/docs_requirements.txt b/docs/docs_requirements.txt index 421ec2e..5637830 100644 --- a/docs/docs_requirements.txt +++ b/docs/docs_requirements.txt @@ -1 +1,2 @@ -requests-html==0.10.0 \ No newline at end of file +requests-html==0.10.0 +pandas \ No newline at end of file diff --git a/docs/source/_static/favicon.ico b/docs/source/_static/favicon.ico new file mode 100644 index 0000000..aa370fc Binary files /dev/null and b/docs/source/_static/favicon.ico differ diff --git a/docs/source/_static/style.css b/docs/source/_static/style.css new file mode 100644 index 0000000..b7e4e17 --- /dev/null +++ b/docs/source/_static/style.css @@ -0,0 +1,3 @@ +div.body #worldometer h1 { + display: none; +} \ No newline at end of file diff --git a/docs/source/_static/worldometer-icon.png b/docs/source/_static/worldometer-icon.png new file mode 100644 index 0000000..6fbaba4 Binary files /dev/null and b/docs/source/_static/worldometer-icon.png differ diff --git a/docs/source/_static/worldometer.png b/docs/source/_static/worldometer.png index 89bd3bc..88203b5 100644 Binary files a/docs/source/_static/worldometer.png and b/docs/source/_static/worldometer.png differ diff --git a/docs/source/api/geography.rst b/docs/source/api/geography.rst new file mode 100644 index 0000000..ae25947 --- /dev/null +++ b/docs/source/api/geography.rst @@ -0,0 +1,121 @@ +geography package +================= + +.. contents:: Index + :depth: 2 + +The `geography` package provides access to various data related to geographic information about the world, regions, and their countries. + +To understand the significance of each of these data sets, their sources, and any other related information, please visit the official page at https://www.worldometers.info/geography/how-many-countries-are-there-in-the-world + + +Countries in the world +---------------------- + +.. code-block:: + + >>> from worldometer.world.geography import ( + WorldCountries, + AsiaCountries, + AfricaCountries, + EuropeCountries, + LatinAmericanAndTheCaribbeanCountries, + NorthernAmericanCountries, + OceaniaCountries + ) + + >>> wc = WorldCountries() + + >>> wc.total + 195 + + >>> wc.countries()[0] + WorldCountriesData( + idx=1, + country='India', + population=1428627663, + world_share='17.76 %', + land_area=2973190 + ) + + >>> ac = AfricaCountries() + + >>> ac.total + 54 + + >>> ac.countries()[0] + CountryData( + idx=1, + country='Nigeria', + population=223804632, + subregion='Western Africa' + ) + + >>> ac.dependencies()[0] + DependencyData( + idx=1, + territory='Réunion', + population=981796, + dependency_of='France' + ) + +.. module:: worldometer.world.geography.countries + +.. autoclass:: WorldCountries + :members: + +.. autoclass:: AsiaCountries + :members: + :inherited-members: + +.. autoclass:: AfricaCountries + :members: + :inherited-members: + +.. autoclass:: EuropeCountries + :members: + :inherited-members: + +.. autoclass:: LatinAmericanAndTheCaribbeanCountries + :members: + :inherited-members: + +.. autoclass:: NorthernAmericanCountries + :members: + :inherited-members: + +.. autoclass:: OceaniaCountries + :members: + :inherited-members: + +.. autoclass:: WorldCountriesData +.. autoclass:: CountryData +.. autoclass:: DependencyData + + +Largest countries in the world +------------------------------ + +.. code-block:: + + >>> from worldometer.world.geography import LargestCountries + + >>> lc = LargestCountries() + + >>> lc.data[0] + LargestCountriesData( + idx=1, + country='Russia', + total_area_km2=17098242, + total_area_mi2=6601665, + land_area_km2=16376870, + land_area_mi2=6323142, + percentage_of_world_landmass='11.0 %' + ) + +.. module:: worldometer.world.geography.largest_countries + +.. autoclass:: LargestCountries + :members: + +.. autoclass:: LargestCountriesData diff --git a/docs/source/api/population.rst b/docs/source/api/population.rst new file mode 100644 index 0000000..dc48464 --- /dev/null +++ b/docs/source/api/population.rst @@ -0,0 +1,308 @@ +population package +================== + +.. contents:: Index + :depth: 2 + +The population package provides access to various data related to world populations, regions, and their countries. Additionally, it includes historical data ranging from 1950 to the present day and future projections extending to 2050. + +To understand the significance of each of these data sets, their sources, and any other related information, please visit the official page at https://www.worldometers.info/population + + +Population by country +--------------------- + +.. code-block:: + + >>> from worldometer.world.population import CountriesByPopulation + + >>> cp = CountriesByPopulation() + + >>> cp.data[0] + CountriesByPopulationData( + idx=1, + country='India', + population=1428627663, + yearly_change='0.81 %', + net_change=11454490, + density=481, + land_area=2973190, + migrants=-486136, + fertility_rate=2.0, + median_age=28.0, + urban_population='36 %', + world_share='17.76 %' + ) + +.. module:: worldometer.world.population.countries_by_population + +.. autoclass:: CountriesByPopulation + :members: + +.. autoclass:: CountriesByPopulationData + + +Population by region +-------------------- + +.. code-block:: + + >>> from worldometer.world.population import WorldPopulationByRegion + + >>> pr = WorldPopulationByRegion() + + >>> pr.current()[0] + CurrentWorldPopulationByRegionData( + idx=1, + region='Asia', + population=4753079727, + yearly_change='0.64 %', + net_change=30444963, + density=153, + area=31033131, + migrants=-1487191, + fertility_rate=1.934, + median_age=32, + urban_population='52.6 %', + world_share='59.1 %' + ) + + >>> pr.past()[0] + PastWorldPopulationByRegionData( + idx=1, + region='Asia', + population=1379048370, + world_share='55.2 %' + ) + + >>> pr.future()[0] + FutureWorldPopulationByRegionData( + idx=1, + region='Asia', + population=5292947571, + world_share='54.5 %' + ) + +.. module:: worldometer.world.population.by_region + +.. autoclass:: WorldPopulationByRegion + :members: + +.. autoclass:: CurrentWorldPopulationByRegionData +.. autoclass:: PastWorldPopulationByRegionData +.. autoclass:: FutureWorldPopulationByRegionData + + +Population by year +------------------ + +.. code-block:: + + >>> from worldometer.world.population import WorldPopulationByYear + + >>> py = WorldPopulationByYear() + + >>> py.data[0] + WorldPopulationByYearData( + year=2023, + world_population=8045311447, + yearly_change='0.88 %', + net_change=70206291.0, + density=54.0 + ) + +.. module:: worldometer.world.population.by_year + +.. autoclass:: WorldPopulationByYear + :members: + +.. autoclass:: WorldPopulationByYearData + + +Largest cities in the world +--------------------------- + +.. code-block:: + + >>> from worldometer.world.population import LargestCities + + >>> lc = LargestCities() + + >>> lc.data[0] + LargestCitiesData( + rank=1, + urban_area='Tokyo-Yokohama', + population_estimate=37843000, + country='Japan', + land_area=8547, + density=4400 + ) + +.. module:: worldometer.world.population.largest_cities + +.. autoclass:: LargestCities + :members: + +.. autoclass:: LargestCitiesData + + +Most populous countries +----------------------- + +.. code-block:: + + >>> from worldometer.world.population import MostPopulousCountries + + >>> pc = MostPopulousCountries() + + >>> pc.current()[0] + CurrentMostPopulousCountriesData( + idx=1, + country='India', + population=1428627663, + yearly_change='0.81 %', + world_share='17.8 %' + ) + + >>> pc.past()[0] + PastMostPopulousCountriesData( + idx=1, + country='China', + population=543979233, + world_share='21.8 %', + rank='(2)' + ) + + >>> pc.future()[0] + FutureMostPopulousCountriesData( + idx=1, + country='India', + population=1670490596, + world_share='17.2 %', + rank='(1)' + ) + +.. module:: worldometer.world.population.most_populous_countries + +.. autoclass:: MostPopulousCountries + :members: + +.. autoclass:: CurrentMostPopulousCountriesData +.. autoclass:: PastMostPopulousCountriesData +.. autoclass:: FutureMostPopulousCountriesData + + +World population projections +---------------------------- + +.. code-block:: + + >>> from worldometer.world.population import WorldPopulationProjections + + >>> pp = WorldPopulationProjections() + + >>> pp.data[0] + WorldPopulationProjectionsData( + year=2023, + world_population=8045311447, + yearly_change='0.88 %', + net_change=70206291, + density=54 + ) + +.. module:: worldometer.world.population.projections + +.. autoclass:: WorldPopulationProjections + :members: + +.. autoclass:: WorldPopulationProjectionsData + + +Regions population +------------------ + +.. code-block:: + + >>> from worldometer.world.population import ( + AsiaPopulation, + AfricaPopulation, + EuropePopulation, + LatinAmericanAndTheCaribbeanPopulation, + NorthernAmericanPopulation, + OceaniaPopulation + ) + + >>> ap = AsiaPopulation() + + >>> ap.live() + 4762699828 + + >>> ap.subregions()[0] + SubregionData( + area='Southern Asia', + population='(2,027,578,876)' + ) + + >>> ap.historical()[0] + HistoricalData( + year=2023, + population=4753079727, + yearly_change_percent='0.64 %', + yearly_change=30444963, + migrants=-1487191, + median_age=31.9, + fertility_rate=1.93, + density=153, + urban_population_percent='52.6 %', + urban_population=2500201501, + world_share='59.1 %', + world_population=8045311447, + rank=nan + ) + + >>> ap.forecast()[0] + ForecastData( + year=2025, + population=4816249054, + yearly_change_percent='0.64 %', + yearly_change=30384996, + migrants=-1555419, + median_age=32.7, + fertility_rate=1.93, + density=155, + urban_population_percent='53.8 %', + urban_population=2589655469, + world_share='61.4 %', + world_population=8191988453, + rank=nan + ) + +.. module:: worldometer.world.population.regions + +.. autoclass:: AsiaPopulation + :members: + :inherited-members: + +.. autoclass:: AfricaPopulation + :members: + :inherited-members: + +.. autoclass:: EuropePopulation + :members: + :inherited-members: + +.. autoclass:: LatinAmericanAndTheCaribbeanPopulation + :members: + :inherited-members: + +.. autoclass:: NorthernAmericanPopulation + :members: + :inherited-members: + +.. autoclass:: OceaniaPopulation + :members: + :inherited-members: + +.. autoclass:: SubregionData +.. autoclass:: HistoricalData +.. autoclass:: ForecastData diff --git a/docs/source/api/world.rst b/docs/source/api/world.rst new file mode 100644 index 0000000..d5ac904 --- /dev/null +++ b/docs/source/api/world.rst @@ -0,0 +1,72 @@ +world package +============= + +.. contents:: Index + :depth: 2 + +`world` is the main API of the `worldometer` package, where you can access various live counters and data available on the https://www.worldometers.info website through self-descriptive classes, methods, and attributes. + + +Live Counters +------------- + +.. module:: worldometer.world.counters + + +Get the data provided by the live counters. + +Each section is represented by an attribute in the :class:`WorldCounters` class, and these attributes are instances of data classes that store the counter values. + +Each of these data classes has attributes that describe the data stored in them. + +.. code-block:: + + >>> from worldometer.world import WorldCounters + + >>> wc = WorldCounters() + + >>> wc.world_population.current_population + 8065299074 + + >>> wc.government_and_economics.computers_produced_this_year + 180248430 + + >>> wc.society_and_media.internet_users_in_the_world_today + 5895566559 + +.. autoclass:: WorldCounters + +.. autoclass:: WorldPopulation +.. autoclass:: GovernmentAndEconomics +.. autoclass:: SocietyAndMedia +.. autoclass:: Environment +.. autoclass:: Food +.. autoclass:: Water +.. autoclass:: Energy +.. autoclass:: Health + + +Country Codes +------------- + +.. module:: worldometer.world.country_codes + +All countries have specific codes that represent them in some way. Get all of these codes with the :class:`CountryCodes` class:: + + >>> from worldometer.world import CountryCodes + + >>> cc = CountryCodes() + + >>> cc.data[0] + CountryCodesData( + country='Afghanistan', + calling_code='93', + three_letter_iso='AF', + two_letter_iso='AFG', + three_digit_iso_numeric=4 + ) + +.. autoclass:: CountryCodes + :members: + +.. autoclass:: CountryCodesData diff --git a/docs/source/api/worldometer.rst b/docs/source/api/worldometer.rst new file mode 100644 index 0000000..ee31f0b --- /dev/null +++ b/docs/source/api/worldometer.rst @@ -0,0 +1 @@ +.. automodule:: worldometer diff --git a/docs/source/conf.py b/docs/source/conf.py index 061d7fe..6864050 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,10 +1,14 @@ -# Configuration file for the Sphinx documentation builder. +# test documentation build configuration file, created by +# sphinx-quickstart on Sun Jun 26 00:00:43 2016. # -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- +# This file is executed through importlib.import_module with +# the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -12,38 +16,109 @@ # import os import sys -sys.path.insert(0, os.path.abspath('../..')) - +sys.path.insert(0, os.path.abspath(os.path.join('..', '..'))) -# -- Project information ----------------------------------------------------- +import worldometer # noqa -project = 'worldometer' -copyright = '2021, Matheus Felipe' -author = 'Matheus Felipe' +# -- General configuration ------------------------------------------------ -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. +# If your documentation needs a minimal Sphinx version, state it here. # -# The short X.Y version. -version = 'v1.0' -# The full version, including alpha/beta/rc tags. -release = 'v1.0.1' - -# -- General configuration --------------------------------------------------- +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', - 'sphinx.ext.napoleon' + 'sphinx.ext.napoleon', + 'sphinx.ext.duration' ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] -# -- Options for HTML output ------------------------------------------------- +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +# +# source_encoding = 'utf-8-sig' + +# The master toctree document. +root_doc = 'index' + +# General information about the project. +project = 'worldometer' +copyright = '2023, Matheus Felipe' +author = worldometer.__author__ + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = worldometer.__version__ +# The full version, including alpha/beta/rc tags. +release = worldometer.__version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# +# today = '' +# +# Else, today_fmt is used as the format for a strftime call. +# +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# These patterns also affect html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. @@ -52,20 +127,69 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the -# documentation . +# documentation. +# html_theme_options = { - 'logo': 'worldometer.png', - 'description': 'Get current metrics in the world with this python module.', + 'touch_icon': 'worldometer-icon.png', + 'description': 'Get live, population, geography, projected, and historical data from around the world.', 'fixed_sidebar': True, 'github_user': 'matheusfelipeog', 'github_repo': 'worldometer', 'github_banner': True, 'github_button': True, 'github_type': 'star', - 'show_powered_by': False + 'show_powered_by': False, } +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. +# " v documentation" by default. +# +# html_title = 'test vtest' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# +# html_logo = './path/to/logo' + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +html_favicon = './_static/favicon.ico' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# A list of CSS files. The entry must be a filename string or a tuple containing +# the filename string and the attributes dictionary. The filename must be +# relative to the html_static_path, or a full URI with scheme like +# https://example.org/style.css. The attributes is used for attributes +# of tag. It defaults to an empty list. +html_css_files = ['style.css'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None + # Custom sidebar templates, maps document names to template names. +# html_sidebars = { 'index': [ 'about.html', @@ -76,7 +200,6 @@ ], '**': [ 'about.html', - 'localtoc.html', 'navigation.html', 'relations.html', 'sourcelink.html', @@ -84,7 +207,156 @@ ] } -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] \ No newline at end of file +# Additional templates that should be rendered to pages, maps page names to +# template names. +# +# html_additional_pages = {} + +# If false, no module index is generated. +# +# html_domain_indices = True + +# If false, no index is generated. +# +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# +html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# +html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# +html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' +# +html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +# htmlhelp_basename = 'testdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +# latex_documents = [ +# (root_doc, 'test.tex', 'test Documentation', +# 'test', 'manual'), +# ] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +# latex_logo = None + +# If true, show page references after internal links. +# +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# If false, no module index is generated. +# +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +# man_pages = [ +# (root_doc, 'test', 'test Documentation', +# [author], 1) +# ] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +# texinfo_documents = [ +# (root_doc, 'test', 'test Documentation', +# author, 'test', 'One line description of project.', +# 'Miscellaneous'), +# ] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False + +# If false, do not generate in manual @ref nodes. +# +# texinfo_cross_references = False diff --git a/docs/source/index.rst b/docs/source/index.rst index 5c8c100..35b3ec5 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,190 +3,128 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Worldometer: Scraping & API -=========================== +.. this title is hidden with css -.. image:: https://img.shields.io/pypi/status/worldometer - :alt: PyPI - Status - :target: https://pypi.org/project/worldometer/ +Worldometer +=========== + +.. image:: https://raw.githubusercontent.com/matheusfelipeog/worldometer/master/.github/assets/images/worldometer.png + :alt: Worldometer package logo + :width: 800px + :align: center + +*Get live, population, geography, projected, and historical data from around the world.* .. image:: https://img.shields.io/pypi/v/worldometer - :alt: PyPI + :alt: PyPI - Version :target: https://pypi.org/project/worldometer/ -.. image:: https://img.shields.io/github/v/release/matheusfelipeog/worldometer - :alt: GitHub release (latest by date) - :target: https://github.com/matheusfelipeog/worldometer/releases - -.. image:: https://readthedocs.org/projects/worldometer/badge/?version=latest - :alt: Documentation Status - :target: https://worldometer.readthedocs.io/en/latest/?badge=latest +.. image:: https://pepy.tech/badge/worldometer + :alt: Total Downloads + :target: https://pepy.tech/project/worldometer .. image:: https://img.shields.io/github/license/matheusfelipeog/worldometer :alt: License MIT :target: https://github.com/matheusfelipeog/worldometer/blob/master/LICENSE -Get current metrics in the world with the python **worldometer** module - ------------------------------------------------------------------------ - - -Index ------ - -- `About <#id1>`_ - - - `worldometers <#id2>`_ - - `How it works? <#id3>`_ - -- `Install <#id4>`_ -- `Demo <#id5>`_ -- `Contributions <#id6>`_ -- `License <#id7>`_ -- `Indices and tables <#id8>`_ +.. image:: https://img.shields.io/pypi/status/worldometer + :alt: PyPI - Status + :target: https://pypi.org/project/worldometer/ -**Worldometer Docs:** +.. image:: https://readthedocs.org/projects/worldometer/badge/?version=latest + :alt: Documentation Status + :target: https://worldometer.readthedocs.io/en/latest/?badge=latest -.. toctree:: - :maxdepth: 1 - worldometer_core - worldometer_api +----------------------------------------------------------------------- About ----- -`Worldometer `_ is a python module that collects data from `worldometers.info `_ and provides a simple and self-explanatory interface for using the data. +The `worldometer `_ package accesses various counters and live data available throughout the `worldometers.info `_ website and provides them through simple and self-describing classes, methods and attributes. -worldometers.info -^^^^^^^^^^^^^^^^^ - - "Worldometer is run by an international team of developers, researchers, and volunteers with the goal of making world statistics available in a thought-provoking and time relevant format to a wide audience around the world. It is published by a small and independent digital media company based in the United States. We have no political, governmental, or corporate affiliation. Furthermore, we have no investors, donors, grants, or backers of any type. We are completely independent and self-financed through automated programmatic advertising sold in real time on multiple ad exchanges." - -More info: `worldometers.info/about `_ - -How it works? -^^^^^^^^^^^^^ +Access data on: - **[Adapted]:** "For the data, is elaborate instead a real-time estimate through a proprietary algorithm which processes the latest data and projections provided by the most reputable organizations and statistical offices in the world." - -More info about data source: `worldometers.info/sources `_ +- The world 🌍 +- Population 👥 +- Geography 🗺️ +- Projections 🔮 +- Historical 📜 Install ------- -First, create a directory and enter it:: - - $ mkdir my_project && cd my_project - -Create a virtual environment to avoid breaking dependence on other projects. - -This project uses `pipenv `_, it already does it alone 😆:: - - $ pipenv install worldometer +Use ``pip`` to install the worldometer package:: + $ pip install worldometer -But you can use ``virtualenv`` + ``pip`` if you prefer:: - $ virtualenv venv && source venv/Scripts/activate +API Reference +------------- +The worldometer package is divided into several specific sub-packages for each dataset. So if you are looking for information on a specific package, class, or method, this part of the documentation is for you. -Now install:: +.. toctree:: + :maxdepth: 2 - $ pip install worldometer + worldometer + world + population + geography Demo ---- .. note:: - The first time you run any function/method or class, it will download Chromium to its home directory (for example, ``~/.pyppeteer/``). It only happens once. - - After, it will only open the chromium to render the contents of worldometers. - -**Simple API usage:** + The first time you run any function/method or class, it will download Chromium to ``~/.local/share/pyppeteer`` directory. It only happens once. After, it will only open the chromium to render the contents of worldometers.info. -*See all function in:* `worldometer.api `_ +Get the data from the live counters available on the `homepage `_:: -Get metrics using simplified functions: + >>> from worldometer.world import WorldCounters - >>> import worldometer + >>> wc = WorldCounters() - >>> worldometer.current_world_population() - {'current_world_population': 7845085923} + >>> wc.world_population.current_population + 8065299074 - >>> worldometer.tweets_sent_today() - {'tweets_sent_today': 4539558} + >>> wc.government_and_economics.computers_produced_this_year + 180248430 -Get metrics by passing the corresponding label: + >>> wc.society_and_media.internet_users_in_the_world_today + 5895566559 - >>> worldometer.get_metric_of(label='computers_produced_this_year') - {'computers_produced_this_year': 27760858} +Reload data to get the latest:: + >>> wc.reload_data() + >>> wc.world_population.current_population + 8065300592 -**Or complete use with Worldometer Class:** +Get help and view information about mapped sections:: -*See all methods in:* `worldometer.core `_ + >>> help(wc) ->>> from worldometer import Worldometer ->>> w = Worldometer() -Get the number of categories, labels and metrics in ``w`` object: - - >>> w.what_is_here() - {'categories': 8, 'labels': 63, 'metrics': 63} +worldometers.info +----------------- -Get all categories used: + "Worldometer is run by an international team of developers, researchers, and volunteers with the goal of making world statistics available in a thought-provoking and time relevant format to a wide audience around the world. It is published by a small and independent digital media company based in the United States. We have no political, governmental, or corporate affiliation. Furthermore, we have no investors, donors, grants, or backers of any type. We are completely independent and self-financed through automated programmatic advertising sold in real time on multiple ad exchanges." - >>> w.categories() - [ - 'world_population', - 'government_and_economics', - 'society_and_media', - ... # compressed - ] +More info: `worldometers.info/about `_ -Get all labels used: - >>> w.metrics_labels() - [ - 'current_world_population', - 'births_this_year', - 'births_today', - 'deaths_this_year', - 'deaths_today', - 'net_population_growth_this_year', - ... # compressed - ] +Data Sources +------------ -Get all metrics used: + **[adapted]:** "worldometers.info collects its statistics and data from the most reputable national and international organizations, including the United Nations, the World Health Organization, the Food and Agriculture Organization, OECD and others. - >>> w.metrics() - [ - 7845087963, - 15741371, - 5676, - 6608605, - 2383, - 9132766, - ... # compressed - ] + Each Worldometer counter has its specific set of sources, which are listed on its dedicated page (accessible by clicking on the counter text link, when available). -Get all metrics with labels in dict format: + Data, estimates, and projections displayed on worldometers.info counters are for the most part provided by organizations included in the following list of United Nations Statistics Division's partners." ->>> w.metrics_with_labels() -{ - 'abortions_this_year': 4785492, - 'bicycles_produced_this_year': 17070566, - 'births_this_year': 15741371, - 'births_today': 5676, - 'blog_posts_written_today': 110171, - 'cars_produced_this_year': 8999185, - 'cellular_phones_sold_today': 98846, - ...: ... # compressed -} +More info about data source: `worldometers.info/sources `_ Contributions diff --git a/docs/source/worldometer_api.rst b/docs/source/worldometer_api.rst deleted file mode 100644 index 270cd4d..0000000 --- a/docs/source/worldometer_api.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. automodule:: worldometer.api - :members: diff --git a/docs/source/worldometer_core.rst b/docs/source/worldometer_core.rst deleted file mode 100644 index 14f656c..0000000 --- a/docs/source/worldometer_core.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. automodule:: worldometer.core - :members: diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..1fa92b1 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1169 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "alabaster" +version = "0.7.13" +description = "A configurable sidebar-enabled Sphinx theme" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = "*" +files = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] + +[[package]] +name = "babel" +version = "2.13.1" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, + {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "beautifulsoup4" +version = "4.12.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "bs4" +version = "0.0.1" +description = "Dummy package for Beautiful Soup" +optional = false +python-versions = "*" +files = [ + {file = "bs4-0.0.1.tar.gz", hash = "sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"}, +] + +[package.dependencies] +beautifulsoup4 = "*" + +[[package]] +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"}, + {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "cssselect" +version = "1.2.0" +description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cssselect-1.2.0-py2.py3-none-any.whl", hash = "sha256:da1885f0c10b60c03ed5eccbb6b68d6eff248d91976fcde348f395d54c9fd35e"}, + {file = "cssselect-1.2.0.tar.gz", hash = "sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc"}, +] + +[[package]] +name = "docutils" +version = "0.20.1" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.3" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "fake-useragent" +version = "1.3.0" +description = "Up-to-date simple useragent faker with real world database" +optional = false +python-versions = "*" +files = [ + {file = "fake-useragent-1.3.0.tar.gz", hash = "sha256:0b3a223b4c03e3df46b0e9ff53ad26cf4690f68871396b9c59a7fa6ee830c395"}, + {file = "fake_useragent-1.3.0-py3-none-any.whl", hash = "sha256:73cee1d10bcd1deb25a15e916f6674c537d2d9088ecb4d7af98c2619f83827d1"}, +] + +[package.dependencies] +importlib-resources = {version = ">=5.0", markers = "python_version < \"3.10\""} + +[[package]] +name = "html5lib" +version = "1.1" +description = "HTML parser based on the WHATWG HTML specification" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, + {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, +] + +[package.dependencies] +six = ">=1.9" +webencodings = "*" + +[package.extras] +all = ["chardet (>=2.2)", "genshi", "lxml"] +chardet = ["chardet (>=2.2)"] +genshi = ["genshi"] +lxml = ["lxml"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.8.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "importlib-resources" +version = "6.1.0" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.1.0-py3-none-any.whl", hash = "sha256:aa50258bbfa56d4e33fbd8aa3ef48ded10d1735f11532b8df95388cc6bdb7e83"}, + {file = "importlib_resources-6.1.0.tar.gz", hash = "sha256:9d48dcccc213325e810fd723e7fbb45ccb39f6cf5c31f00cf2b965f5f10f3cb9"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "lxml" +version = "4.9.3" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +files = [ + {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, + {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, + {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, + {file = "lxml-4.9.3-cp27-cp27m-win32.whl", hash = "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7"}, + {file = "lxml-4.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1"}, + {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, + {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, + {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, + {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, + {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, + {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, + {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, + {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, + {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, + {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, + {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, + {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, + {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, + {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, + {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, + {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, + {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, + {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, + {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7"}, + {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2"}, + {file = "lxml-4.9.3-cp35-cp35m-win32.whl", hash = "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d"}, + {file = "lxml-4.9.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833"}, + {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, + {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287"}, + {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458"}, + {file = "lxml-4.9.3-cp36-cp36m-win32.whl", hash = "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477"}, + {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, + {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, + {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, + {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, + {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, + {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, + {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, + {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, + {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, + {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, + {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, + {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, + {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, + {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, + {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, + {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=0.29.35)"] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "numpy" +version = "1.26.1" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = "<3.13,>=3.9" +files = [ + {file = "numpy-1.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82e871307a6331b5f09efda3c22e03c095d957f04bf6bc1804f30048d0e5e7af"}, + {file = "numpy-1.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdd9ec98f0063d93baeb01aad472a1a0840dee302842a2746a7a8e92968f9575"}, + {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d78f269e0c4fd365fc2992c00353e4530d274ba68f15e968d8bc3c69ce5f5244"}, + {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab9163ca8aeb7fd32fe93866490654d2f7dda4e61bc6297bf72ce07fdc02f67"}, + {file = "numpy-1.26.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:78ca54b2f9daffa5f323f34cdf21e1d9779a54073f0018a3094ab907938331a2"}, + {file = "numpy-1.26.1-cp310-cp310-win32.whl", hash = "sha256:d1cfc92db6af1fd37a7bb58e55c8383b4aa1ba23d012bdbba26b4bcca45ac297"}, + {file = "numpy-1.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2984cb6caaf05294b8466966627e80bf6c7afd273279077679cb010acb0e5ab"}, + {file = "numpy-1.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd7837b2b734ca72959a1caf3309457a318c934abef7a43a14bb984e574bbb9a"}, + {file = "numpy-1.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c59c046c31a43310ad0199d6299e59f57a289e22f0f36951ced1c9eac3665b9"}, + {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d58e8c51a7cf43090d124d5073bc29ab2755822181fcad978b12e144e5e5a4b3"}, + {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6081aed64714a18c72b168a9276095ef9155dd7888b9e74b5987808f0dd0a974"}, + {file = "numpy-1.26.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:97e5d6a9f0702c2863aaabf19f0d1b6c2628fbe476438ce0b5ce06e83085064c"}, + {file = "numpy-1.26.1-cp311-cp311-win32.whl", hash = "sha256:b9d45d1dbb9de84894cc50efece5b09939752a2d75aab3a8b0cef6f3a35ecd6b"}, + {file = "numpy-1.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:3649d566e2fc067597125428db15d60eb42a4e0897fc48d28cb75dc2e0454e53"}, + {file = "numpy-1.26.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d1bd82d539607951cac963388534da3b7ea0e18b149a53cf883d8f699178c0f"}, + {file = "numpy-1.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd5ced4e5a96dac6725daeb5242a35494243f2239244fad10a90ce58b071d24"}, + {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03fb25610ef560a6201ff06df4f8105292ba56e7cdd196ea350d123fc32e24e"}, + {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcfaf015b79d1f9f9c9fd0731a907407dc3e45769262d657d754c3a028586124"}, + {file = "numpy-1.26.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e509cbc488c735b43b5ffea175235cec24bbc57b227ef1acc691725beb230d1c"}, + {file = "numpy-1.26.1-cp312-cp312-win32.whl", hash = "sha256:af22f3d8e228d84d1c0c44c1fbdeb80f97a15a0abe4f080960393a00db733b66"}, + {file = "numpy-1.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f42284ebf91bdf32fafac29d29d4c07e5e9d1af862ea73686581773ef9e73a7"}, + {file = "numpy-1.26.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb894accfd16b867d8643fc2ba6c8617c78ba2828051e9a69511644ce86ce83e"}, + {file = "numpy-1.26.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e44ccb93f30c75dfc0c3aa3ce38f33486a75ec9abadabd4e59f114994a9c4617"}, + {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9696aa2e35cc41e398a6d42d147cf326f8f9d81befcb399bc1ed7ffea339b64e"}, + {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b411040beead47a228bde3b2241100454a6abde9df139ed087bd73fc0a4908"}, + {file = "numpy-1.26.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e11668d6f756ca5ef534b5be8653d16c5352cbb210a5c2a79ff288e937010d5"}, + {file = "numpy-1.26.1-cp39-cp39-win32.whl", hash = "sha256:d1d2c6b7dd618c41e202c59c1413ef9b2c8e8a15f5039e344af64195459e3104"}, + {file = "numpy-1.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:59227c981d43425ca5e5c01094d59eb14e8772ce6975d4b2fc1e106a833d5ae2"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06934e1a22c54636a059215d6da99e23286424f316fddd979f5071093b648668"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76ff661a867d9272cd2a99eed002470f46dbe0943a5ffd140f49be84f68ffc42"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6965888d65d2848e8768824ca8288db0a81263c1efccec881cb35a0d805fcd2f"}, + {file = "numpy-1.26.1.tar.gz", hash = "sha256:c8c6c72d4a9f831f328efb1312642a1cafafaa88981d9ab76368d50d07d93cbe"}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pandas" +version = "2.1.1" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58d997dbee0d4b64f3cb881a24f918b5f25dd64ddf31f467bb9b67ae4c63a1e4"}, + {file = "pandas-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02304e11582c5d090e5a52aec726f31fe3f42895d6bfc1f28738f9b64b6f0614"}, + {file = "pandas-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffa8f0966de2c22de408d0e322db2faed6f6e74265aa0856f3824813cf124363"}, + {file = "pandas-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1f84c144dee086fe4f04a472b5cd51e680f061adf75c1ae4fc3a9275560f8f4"}, + {file = "pandas-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ce97667d06d69396d72be074f0556698c7f662029322027c226fd7a26965cb"}, + {file = "pandas-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:4c3f32fd7c4dccd035f71734df39231ac1a6ff95e8bdab8d891167197b7018d2"}, + {file = "pandas-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e2959720b70e106bb1d8b6eadd8ecd7c8e99ccdbe03ee03260877184bb2877d"}, + {file = "pandas-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25e8474a8eb258e391e30c288eecec565bfed3e026f312b0cbd709a63906b6f8"}, + {file = "pandas-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8bd1685556f3374520466998929bade3076aeae77c3e67ada5ed2b90b4de7f0"}, + {file = "pandas-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc3657869c7902810f32bd072f0740487f9e030c1a3ab03e0af093db35a9d14e"}, + {file = "pandas-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:05674536bd477af36aa2effd4ec8f71b92234ce0cc174de34fd21e2ee99adbc2"}, + {file = "pandas-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:b407381258a667df49d58a1b637be33e514b07f9285feb27769cedb3ab3d0b3a"}, + {file = "pandas-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c747793c4e9dcece7bb20156179529898abf505fe32cb40c4052107a3c620b49"}, + {file = "pandas-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3bcad1e6fb34b727b016775bea407311f7721db87e5b409e6542f4546a4951ea"}, + {file = "pandas-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5ec7740f9ccb90aec64edd71434711f58ee0ea7f5ed4ac48be11cfa9abf7317"}, + {file = "pandas-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29deb61de5a8a93bdd033df328441a79fcf8dd3c12d5ed0b41a395eef9cd76f0"}, + {file = "pandas-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f99bebf19b7e03cf80a4e770a3e65eee9dd4e2679039f542d7c1ace7b7b1daa"}, + {file = "pandas-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:84e7e910096416adec68075dc87b986ff202920fb8704e6d9c8c9897fe7332d6"}, + {file = "pandas-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366da7b0e540d1b908886d4feb3d951f2f1e572e655c1160f5fde28ad4abb750"}, + {file = "pandas-2.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e50e72b667415a816ac27dfcfe686dc5a0b02202e06196b943d54c4f9c7693e"}, + {file = "pandas-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc1ab6a25da197f03ebe6d8fa17273126120874386b4ac11c1d687df288542dd"}, + {file = "pandas-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0dbfea0dd3901ad4ce2306575c54348d98499c95be01b8d885a2737fe4d7a98"}, + {file = "pandas-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0489b0e6aa3d907e909aef92975edae89b1ee1654db5eafb9be633b0124abe97"}, + {file = "pandas-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:4cdb0fab0400c2cb46dafcf1a0fe084c8bb2480a1fa8d81e19d15e12e6d4ded2"}, + {file = "pandas-2.1.1.tar.gz", hash = "sha256:fecb198dc389429be557cde50a2d46da8434a17fe37d7d41ff102e3987fd947b"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.1" + +[package.extras] +all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] +aws = ["s3fs (>=2022.05.0)"] +clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"] +compression = ["zstandard (>=0.17.0)"] +computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"] +feather = ["pyarrow (>=7.0.0)"] +fss = ["fsspec (>=2022.05.0)"] +gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"] +hdf5 = ["tables (>=3.7.0)"] +html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"] +mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"] +parquet = ["pyarrow (>=7.0.0)"] +performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"] +plot = ["matplotlib (>=3.6.1)"] +postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"] +spss = ["pyreadstat (>=1.1.5)"] +sql-other = ["SQLAlchemy (>=1.4.36)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.8.0)"] + +[[package]] +name = "parse" +version = "1.19.1" +description = "parse() is the opposite of format()" +optional = false +python-versions = "*" +files = [ + {file = "parse-1.19.1-py2.py3-none-any.whl", hash = "sha256:371ed3800dc63983832159cc9373156613947707bc448b5215473a219dbd4362"}, + {file = "parse-1.19.1.tar.gz", hash = "sha256:cc3a47236ff05da377617ddefa867b7ba983819c664e1afe46249e5b469be464"}, +] + +[[package]] +name = "pluggy" +version = "1.3.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pyee" +version = "8.2.2" +description = "A port of node.js's EventEmitter to python." +optional = false +python-versions = "*" +files = [ + {file = "pyee-8.2.2-py2.py3-none-any.whl", hash = "sha256:c09f56e36eb10bf23aa2aacf145f690ded75b990a3d9523fd478b005940303d2"}, + {file = "pyee-8.2.2.tar.gz", hash = "sha256:5c7e60f8df95710dbe17550e16ce0153f83990c00ef744841b43f371ed53ebea"}, +] + +[[package]] +name = "pygments" +version = "2.16.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pyppeteer" +version = "1.0.2" +description = "Headless chrome/chromium automation library (unofficial port of puppeteer)" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "pyppeteer-1.0.2-py3-none-any.whl", hash = "sha256:11a734d8f02c6b128035aba8faf32748f2016310a6a1cbc6aa5b1e2580742e8f"}, + {file = "pyppeteer-1.0.2.tar.gz", hash = "sha256:ddb0d15cb644720160d49abb1ad0d97e87a55581febf1b7531be9e983aad7742"}, +] + +[package.dependencies] +appdirs = ">=1.4.3,<2.0.0" +certifi = ">=2021" +importlib-metadata = ">=1.4" +pyee = ">=8.1.0,<9.0.0" +tqdm = ">=4.42.1,<5.0.0" +urllib3 = ">=1.25.8,<2.0.0" +websockets = ">=10.0,<11.0" + +[[package]] +name = "pyquery" +version = "2.0.0" +description = "A jquery-like library for python" +optional = false +python-versions = "*" +files = [ + {file = "pyquery-2.0.0-py3-none-any.whl", hash = "sha256:8dfc9b4b7c5f877d619bbae74b1898d5743f6ca248cfd5d72b504dd614da312f"}, + {file = "pyquery-2.0.0.tar.gz", hash = "sha256:963e8d4e90262ff6d8dec072ea97285dc374a2f69cad7776f4082abcf6a1d8ae"}, +] + +[package.dependencies] +cssselect = ">=1.2.0" +lxml = ">=2.1" + +[package.extras] +test = ["pytest", "pytest-cov", "requests", "webob", "webtest"] + +[[package]] +name = "pytest" +version = "7.4.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2023.3.post1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-html" +version = "0.10.0" +description = "HTML Parsing for Humans." +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "requests-html-0.10.0.tar.gz", hash = "sha256:7e929ecfed95fb1d0994bb368295d6d7c4d06b03fcb900c33d7d0b17e6003947"}, + {file = "requests_html-0.10.0-py3-none-any.whl", hash = "sha256:cb8a78cf829c4eca9d6233f28524f65dd2bfaafb4bdbbc407f0a0b8f487df6e2"}, +] + +[package.dependencies] +bs4 = "*" +fake-useragent = "*" +parse = "*" +pyppeteer = ">=0.0.14" +pyquery = "*" +requests = "*" +w3lib = "*" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "soupsieve" +version = "2.5" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, +] + +[[package]] +name = "sphinx" +version = "7.2.6" +description = "Python documentation generator" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"}, + {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.18.1,<0.21" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.14" +requests = ">=2.25.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.9" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] +test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools (>=67.0)"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.7" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_applehelp-1.0.7-py3-none-any.whl", hash = "sha256:094c4d56209d1734e7d252f6e0b3ccc090bd52ee56807a5d9315b19c122ab15d"}, + {file = "sphinxcontrib_applehelp-1.0.7.tar.gz", hash = "sha256:39fdc8d762d33b01a7d8f026a3b7d71563ea3b72787d5f00ad8465bd9d6dfbfa"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.5" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_devhelp-1.0.5-py3-none-any.whl", hash = "sha256:fe8009aed765188f08fcaadbb3ea0d90ce8ae2d76710b7e29ea7d047177dae2f"}, + {file = "sphinxcontrib_devhelp-1.0.5.tar.gz", hash = "sha256:63b41e0d38207ca40ebbeabcf4d8e51f76c03e78cd61abe118cf4435c73d4212"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.4" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_htmlhelp-2.0.4-py3-none-any.whl", hash = "sha256:8001661c077a73c29beaf4a79968d0726103c5605e27db92b9ebed8bab1359e9"}, + {file = "sphinxcontrib_htmlhelp-2.0.4.tar.gz", hash = "sha256:6c26a118a05b76000738429b724a0568dbde5b72391a688577da08f11891092a"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.6" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_qthelp-1.0.6-py3-none-any.whl", hash = "sha256:bf76886ee7470b934e363da7a954ea2825650013d367728588732c7350f49ea4"}, + {file = "sphinxcontrib_qthelp-1.0.6.tar.gz", hash = "sha256:62b9d1a186ab7f5ee3356d906f648cacb7a6bdb94d201ee7adf26db55092982d"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.9" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_serializinghtml-1.1.9-py3-none-any.whl", hash = "sha256:9b36e503703ff04f20e9675771df105e58aa029cfcbc23b8ed716019b7416ae1"}, + {file = "sphinxcontrib_serializinghtml-1.1.9.tar.gz", hash = "sha256:0c64ff898339e1fac29abd2bf5f11078f3ec413cfe9c046d3120d7ca65530b54"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tqdm" +version = "4.66.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, + {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "tzdata" +version = "2023.3" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, + {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, +] + +[[package]] +name = "urllib3" +version = "1.26.18" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, + {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, +] + +[package.extras] +brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "w3lib" +version = "2.1.2" +description = "Library of web-related functions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "w3lib-2.1.2-py3-none-any.whl", hash = "sha256:c4432926e739caa8e3f49f5de783f336df563d9490416aebd5d39fb896d264e7"}, + {file = "w3lib-2.1.2.tar.gz", hash = "sha256:ed5b74e997eea2abe3c1321f916e344144ee8e9072a6f33463ee8e57f858a4b1"}, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +optional = false +python-versions = "*" +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "websockets" +version = "10.4" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "websockets-10.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d58804e996d7d2307173d56c297cf7bc132c52df27a3efaac5e8d43e36c21c48"}, + {file = "websockets-10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc0b82d728fe21a0d03e65f81980abbbcb13b5387f733a1a870672c5be26edab"}, + {file = "websockets-10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba089c499e1f4155d2a3c2a05d2878a3428cf321c848f2b5a45ce55f0d7d310c"}, + {file = "websockets-10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33d69ca7612f0ddff3316b0c7b33ca180d464ecac2d115805c044bf0a3b0d032"}, + {file = "websockets-10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62e627f6b6d4aed919a2052efc408da7a545c606268d5ab5bfab4432734b82b4"}, + {file = "websockets-10.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ea7b82bfcae927eeffc55d2ffa31665dc7fec7b8dc654506b8e5a518eb4d50"}, + {file = "websockets-10.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e0cb5cc6ece6ffa75baccfd5c02cffe776f3f5c8bf486811f9d3ea3453676ce8"}, + {file = "websockets-10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae5e95cfb53ab1da62185e23b3130e11d64431179debac6dc3c6acf08760e9b1"}, + {file = "websockets-10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7c584f366f46ba667cfa66020344886cf47088e79c9b9d39c84ce9ea98aaa331"}, + {file = "websockets-10.4-cp310-cp310-win32.whl", hash = "sha256:b029fb2032ae4724d8ae8d4f6b363f2cc39e4c7b12454df8df7f0f563ed3e61a"}, + {file = "websockets-10.4-cp310-cp310-win_amd64.whl", hash = "sha256:8dc96f64ae43dde92530775e9cb169979f414dcf5cff670455d81a6823b42089"}, + {file = "websockets-10.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47a2964021f2110116cc1125b3e6d87ab5ad16dea161949e7244ec583b905bb4"}, + {file = "websockets-10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e789376b52c295c4946403bd0efecf27ab98f05319df4583d3c48e43c7342c2f"}, + {file = "websockets-10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d3f0b61c45c3fa9a349cf484962c559a8a1d80dae6977276df8fd1fa5e3cb8c"}, + {file = "websockets-10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f55b5905705725af31ccef50e55391621532cd64fbf0bc6f4bac935f0fccec46"}, + {file = "websockets-10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00c870522cdb69cd625b93f002961ffb0c095394f06ba8c48f17eef7c1541f96"}, + {file = "websockets-10.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f38706e0b15d3c20ef6259fd4bc1700cd133b06c3c1bb108ffe3f8947be15fa"}, + {file = "websockets-10.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f2c38d588887a609191d30e902df2a32711f708abfd85d318ca9b367258cfd0c"}, + {file = "websockets-10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fe10ddc59b304cb19a1bdf5bd0a7719cbbc9fbdd57ac80ed436b709fcf889106"}, + {file = "websockets-10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:90fcf8929836d4a0e964d799a58823547df5a5e9afa83081761630553be731f9"}, + {file = "websockets-10.4-cp311-cp311-win32.whl", hash = "sha256:b9968694c5f467bf67ef97ae7ad4d56d14be2751000c1207d31bf3bb8860bae8"}, + {file = "websockets-10.4-cp311-cp311-win_amd64.whl", hash = "sha256:a7a240d7a74bf8d5cb3bfe6be7f21697a28ec4b1a437607bae08ac7acf5b4882"}, + {file = "websockets-10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:74de2b894b47f1d21cbd0b37a5e2b2392ad95d17ae983e64727e18eb281fe7cb"}, + {file = "websockets-10.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3a686ecb4aa0d64ae60c9c9f1a7d5d46cab9bfb5d91a2d303d00e2cd4c4c5cc"}, + {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d15c968ea7a65211e084f523151dbf8ae44634de03c801b8bd070b74e85033"}, + {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00213676a2e46b6ebf6045bc11d0f529d9120baa6f58d122b4021ad92adabd41"}, + {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e23173580d740bf8822fd0379e4bf30aa1d5a92a4f252d34e893070c081050df"}, + {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:dd500e0a5e11969cdd3320935ca2ff1e936f2358f9c2e61f100a1660933320ea"}, + {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4239b6027e3d66a89446908ff3027d2737afc1a375f8fd3eea630a4842ec9a0c"}, + {file = "websockets-10.4-cp37-cp37m-win32.whl", hash = "sha256:8a5cc00546e0a701da4639aa0bbcb0ae2bb678c87f46da01ac2d789e1f2d2038"}, + {file = "websockets-10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a9f9a735deaf9a0cadc2d8c50d1a5bcdbae8b6e539c6e08237bc4082d7c13f28"}, + {file = "websockets-10.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c1289596042fad2cdceb05e1ebf7aadf9995c928e0da2b7a4e99494953b1b94"}, + {file = "websockets-10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0cff816f51fb33c26d6e2b16b5c7d48eaa31dae5488ace6aae468b361f422b63"}, + {file = "websockets-10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dd9becd5fe29773d140d68d607d66a38f60e31b86df75332703757ee645b6faf"}, + {file = "websockets-10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45ec8e75b7dbc9539cbfafa570742fe4f676eb8b0d3694b67dabe2f2ceed8aa6"}, + {file = "websockets-10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f72e5cd0f18f262f5da20efa9e241699e0cf3a766317a17392550c9ad7b37d8"}, + {file = "websockets-10.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185929b4808b36a79c65b7865783b87b6841e852ef5407a2fb0c03381092fa3b"}, + {file = "websockets-10.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d27a7e34c313b3a7f91adcd05134315002aaf8540d7b4f90336beafaea6217c"}, + {file = "websockets-10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:884be66c76a444c59f801ac13f40c76f176f1bfa815ef5b8ed44321e74f1600b"}, + {file = "websockets-10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:931c039af54fc195fe6ad536fde4b0de04da9d5916e78e55405436348cfb0e56"}, + {file = "websockets-10.4-cp38-cp38-win32.whl", hash = "sha256:db3c336f9eda2532ec0fd8ea49fef7a8df8f6c804cdf4f39e5c5c0d4a4ad9a7a"}, + {file = "websockets-10.4-cp38-cp38-win_amd64.whl", hash = "sha256:48c08473563323f9c9debac781ecf66f94ad5a3680a38fe84dee5388cf5acaf6"}, + {file = "websockets-10.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:40e826de3085721dabc7cf9bfd41682dadc02286d8cf149b3ad05bff89311e4f"}, + {file = "websockets-10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:56029457f219ade1f2fc12a6504ea61e14ee227a815531f9738e41203a429112"}, + {file = "websockets-10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5fc088b7a32f244c519a048c170f14cf2251b849ef0e20cbbb0fdf0fdaf556f"}, + {file = "websockets-10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc8709c00704194213d45e455adc106ff9e87658297f72d544220e32029cd3d"}, + {file = "websockets-10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0154f7691e4fe6c2b2bc275b5701e8b158dae92a1ab229e2b940efe11905dff4"}, + {file = "websockets-10.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c6d2264f485f0b53adf22697ac11e261ce84805c232ed5dbe6b1bcb84b00ff0"}, + {file = "websockets-10.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9bc42e8402dc5e9905fb8b9649f57efcb2056693b7e88faa8fb029256ba9c68c"}, + {file = "websockets-10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:edc344de4dac1d89300a053ac973299e82d3db56330f3494905643bb68801269"}, + {file = "websockets-10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:84bc2a7d075f32f6ed98652db3a680a17a4edb21ca7f80fe42e38753a58ee02b"}, + {file = "websockets-10.4-cp39-cp39-win32.whl", hash = "sha256:c94ae4faf2d09f7c81847c63843f84fe47bf6253c9d60b20f25edfd30fb12588"}, + {file = "websockets-10.4-cp39-cp39-win_amd64.whl", hash = "sha256:bbccd847aa0c3a69b5f691a84d2341a4f8a629c6922558f2a70611305f902d74"}, + {file = "websockets-10.4-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:82ff5e1cae4e855147fd57a2863376ed7454134c2bf49ec604dfe71e446e2193"}, + {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d210abe51b5da0ffdbf7b43eed0cfdff8a55a1ab17abbec4301c9ff077dd0342"}, + {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:942de28af58f352a6f588bc72490ae0f4ccd6dfc2bd3de5945b882a078e4e179"}, + {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b27d6c1c6cd53dc93614967e9ce00ae7f864a2d9f99fe5ed86706e1ecbf485"}, + {file = "websockets-10.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3d3cac3e32b2c8414f4f87c1b2ab686fa6284a980ba283617404377cd448f631"}, + {file = "websockets-10.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:da39dd03d130162deb63da51f6e66ed73032ae62e74aaccc4236e30edccddbb0"}, + {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389f8dbb5c489e305fb113ca1b6bdcdaa130923f77485db5b189de343a179393"}, + {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09a1814bb15eff7069e51fed0826df0bc0702652b5cb8f87697d469d79c23576"}, + {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff64a1d38d156d429404aaa84b27305e957fd10c30e5880d1765c9480bea490f"}, + {file = "websockets-10.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b343f521b047493dc4022dd338fc6db9d9282658862756b4f6fd0e996c1380e1"}, + {file = "websockets-10.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:932af322458da7e4e35df32f050389e13d3d96b09d274b22a7aa1808f292fee4"}, + {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a4162139374a49eb18ef5b2f4da1dd95c994588f5033d64e0bbfda4b6b6fcf"}, + {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c57e4c1349fbe0e446c9fa7b19ed2f8a4417233b6984277cce392819123142d3"}, + {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b627c266f295de9dea86bd1112ed3d5fafb69a348af30a2422e16590a8ecba13"}, + {file = "websockets-10.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:05a7233089f8bd355e8cbe127c2e8ca0b4ea55467861906b80d2ebc7db4d6b72"}, + {file = "websockets-10.4.tar.gz", hash = "sha256:eef610b23933c54d5d921c92578ae5f89813438fded840c2e9809d378dc765d3"}, +] + +[[package]] +name = "zipp" +version = "3.17.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.9, <3.12" +content-hash = "d0a7253d05c9fae987b2d7314a2fdac5a4de7d25ae6e6127d03e201192e408bd" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7f46518 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +[tool.poetry] +name = "worldometer" +version = "2.0.0" +description = "Get live, population, geography, projected, and historical data from around the world." +authors = ["Matheus Felipe "] +license = "MIT" +readme = "README.md" +homepage = "https://github.com/matheusfelipeog/worldometer" +repository = "https://github.com/matheusfelipeog/worldometer" +documentation = "https://worldometer.readthedocs.io" +packages = [{include = "worldometer"}] +keywords = [ + "worldometer", "worldometers", "worldometer-api", "worldometer-scraping", + "world-data", "world-metrics", "metrics", "real-time-data", "real-time-metrics", + "api", "scraping", "requests-html", "livedata", "historical", "historical-data", + "live", "world", "data" +] +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'License :: OSI Approved :: MIT License', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: Implementation', + 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Libraries :: Python Modules' +] + +[tool.poetry.urls] +"Bug Tracker" = "https://github.com/matheusfelipeog/worldometer/issues" +"Pull Requests" = "https://github.com/matheusfelipeog/worldometer/pulls" + +[tool.poetry.dependencies] +python = ">=3.9, <3.12" +requests-html = "^0.10.0" +pandas = "^2.1.1" +html5lib = "^1.1" + +[tool.poetry.group.dev.dependencies] +pytest = "^7.4.3" +sphinx = "^7.2.6" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/setup.py b/setup.py index 7e6cd26..7706e7c 100644 --- a/setup.py +++ b/setup.py @@ -1,38 +1,34 @@ -# -*- coding: utf-8 -*- - import os -from setuptools import setup, find_packages - -from worldometer.__about__ import __version__, __author__, __email__ +from setuptools import find_packages, setup -NAME = 'worldometer' -URL = 'https://github.com/matheusfelipeog/worldometer' -DESCRIPTION = 'Worldometer Scraping & API - Get world metrics from worldometers.info' +import worldometer here = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(here, 'README.md'), mode='r', encoding='utf-8') as f: - long_description = f.read() + long_description = '\n' + f.read() setup( - name=NAME, - version=__version__, - description=DESCRIPTION, + name='worldometer', + version=worldometer.__version__, + description='Get live, population, geography, projected, and historical data from around the world.', long_description=long_description, long_description_content_type='text/markdown', license='MIT License', - author=__author__, - author_email=__email__, - url=URL, + author=worldometer.__author__, + author_email='matheusfelipeog@protonmail.com', + url='https://github.com/matheusfelipeog/worldometer', packages=find_packages(), install_requires=[ - 'requests-html' + 'requests-html', + 'pandas', + 'html5lib' ], zip_safe=False, - python_requires='>=3.6', + python_requires='>=3.8', project_urls={ "Bug Tracker": "https://github.com/matheusfelipeog/worldometer/issues", "Documentation": "https://worldometer.readthedocs.io", @@ -51,10 +47,10 @@ 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules' diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_scraper/__init__.py b/tests/test_scraper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_scraper/test_parser.py b/tests/test_scraper/test_parser.py new file mode 100644 index 0000000..74d033f --- /dev/null +++ b/tests/test_scraper/test_parser.py @@ -0,0 +1,235 @@ +import pytest + +from worldometer.scraper.parser import ( + get_rts_counters_only_with_last_value_key, + get_html_tables_data +) +from worldometer.scraper.exceptions import ( + ColumnNamesLengthError, + HTMLTablesNotFoundError +) + + +@pytest.fixture +def fake_rts_counters_object(): + return { + 'a': { + 'k1': {}, + 'k2': 'test', + 'last_value': 1 + }, + 'b': { + 'k1': {}, + 'k2': 'test', + 'last_value': 1.0 + }, + 'c': { + 'k1': {}, + 'k2': 'test', + 'last_value': None + } + } + + +@pytest.fixture +def fake_html(): + return """ + + + + HTML to Tests + + + + + + + + + + + + + + + + + + + + + + + + + +
ABCD
test11.0
test11.0
+ + + + + + + + + + + + + + + + + + + + + + + + +
ABCD
test11.0
test11.0
+ + +""" + + +def test_get_rts_counters_only_with_last_value_key(fake_rts_counters_object: dict): + + rts_counters = get_rts_counters_only_with_last_value_key(fake_rts_counters_object) + + assert isinstance(rts_counters, dict) + assert set(rts_counters.keys()) == set(fake_rts_counters_object.keys()) + assert all( + isinstance(v, (int, float, type(None))) + for v in rts_counters.values() + ) + + +def test_empty_rts_counters_object_passed(): + empty_rts_counters = {} + + rts_counters = get_rts_counters_only_with_last_value_key(empty_rts_counters) + + assert isinstance(rts_counters, dict) + assert len(rts_counters) == 0 + + +def test_get_html_tables_data(fake_html: str): + num_expected_tables = 2 + attrs = None + new_column_names = [('A1', 'B1', 'C1', 'D1'), ('A2', 'B2', 'C2', 'D2')] + + data = get_html_tables_data( + html=fake_html, + attrs=attrs, + new_column_names=new_column_names + ) + + assert isinstance(data, list) + assert len(data) == num_expected_tables + + all_table_data = [td for td in data] + assert all( + isinstance(td, list) for td in all_table_data + ), 'Table with wrong type. Each data table must be a list.' + + data_rows = [ + data_row + for td in all_table_data + for data_row in td + ] + assert all( + isinstance(dr, dict) + for dr in data_rows + ), 'Data row with wrong type. Each row of data must be a dict.' + + assert all( + tuple(dr.keys()) in new_column_names + for dr in data_rows + ), 'The column names are wrong. They are expected to match the column names passed.' + + data_rows_values = [ + value + for dr in data_rows + for value in dr.values() + ] + assert all( + isinstance(value, (int, float, str)) + for value in data_rows_values + ), 'The column value is not of a supported type. It is expected to be int, float or str.' + + +def test_get_html_tables_data_with_attrs(fake_html: str): + num_expected_tables = 1 + attrs = {'class': 'table'} + new_column_names = [('A1', 'B1', 'C1', 'D1')] + + data = get_html_tables_data( + html=fake_html, + attrs=attrs, + new_column_names=new_column_names # type: ignore + ) + + assert isinstance(data, list) + assert len(data) == num_expected_tables + + table_data = data[0] + assert isinstance(table_data, list) + + assert all( + isinstance(data_row, dict) + for data_row in table_data + ), 'Data row with wrong type. Each row of data must be a dict.' + + assert all( + tuple(dr.keys()) in new_column_names + for dr in table_data + ), 'The column names are wrong. They are expected to match the column names passed.' + + data_rows_values = [ + value + for dr in table_data + for value in dr.values() + ] + assert all( + isinstance(value, (int, float, str)) + for value in data_rows_values + ), 'The column value is not of a supported type. It is expected to be int, float or str.' + + +@pytest.mark.parametrize( + 'new_column_names', + [ + [], + [('A1', 'B1', 'C1', 'D1')], + [('A1',), ('A2',), ('A3',)], + [tuple(), tuple()], + [('A1', 'B1'), ('A2', 'B2')], + [('A1', 'B1', 'C1', 'D1', 'E1'), ('A2', 'B2', 'C2', 'D2', 'E2')] + ] +) +def test_get_html_tables_data_with_wrong_length_of_new_column_names(fake_html, new_column_names): + with pytest.raises(ColumnNamesLengthError): + get_html_tables_data(fake_html, attrs=None, new_column_names=new_column_names) + + +def test_get_html_tables_data_when_there_is_no_html_table(): + html = """ + + + + HTML to Tests + + + + + """ + + with pytest.raises(HTMLTablesNotFoundError): + get_html_tables_data(html, attrs=None, new_column_names=[]) + + +def test_get_html_tables_data_when_there_is_no_html_document(): + with pytest.raises(ValueError): + get_html_tables_data(html='', attrs=None, new_column_names=[]) diff --git a/tests/test_scraper/test_utils.py b/tests/test_scraper/test_utils.py new file mode 100644 index 0000000..7f448a3 --- /dev/null +++ b/tests/test_scraper/test_utils.py @@ -0,0 +1,36 @@ +import pytest + +from worldometer.scraper.utils import make_url + + +@pytest.fixture +def base_url(): + return 'https://www.example.com' + + +def test_make_url(base_url): + url = make_url(base_url) + + assert url == base_url + + +@pytest.mark.parametrize( + 'path_url', + ['/', '/a', '/a/b', '/a/b/', '/a1', '/a2/', '/a?arg=1'] +) +def test_make_url_with_path_url_param(base_url, path_url): + url = make_url(base_url, path_url) + + expected_url = base_url + path_url + + assert url == expected_url + + +@pytest.mark.parametrize( + 'path_url', + ['', 'a', 'a/', 'a/b/', 'a?arg=1'] +) +def test_make_url_with_invalid_path_url_param(base_url, path_url): + url = make_url(base_url, path_url) + + assert url == base_url diff --git a/worldometer/__about__.py b/worldometer/__about__.py deleted file mode 100644 index 92f14d2..0000000 --- a/worldometer/__about__.py +++ /dev/null @@ -1,5 +0,0 @@ -# -*- coding: utf-8 -*- - -__version__ = '1.0.1' -__author__ = 'Matheus Felipe' -__email__ = 'matheusfelipeog@protonmail.com' diff --git a/worldometer/__init__.py b/worldometer/__init__.py index 8eba56a..0a1b17f 100644 --- a/worldometer/__init__.py +++ b/worldometer/__init__.py @@ -1,84 +1,184 @@ -# -*- coding: utf-8 -*- - """ -worldometer module ------------------- +worldometer package +------------------- -Get metrics from around the world in multiple categories. +The `worldometer` package accesses various counters and live data available +throughout the https://www.worldometers.info website and provides them +through self-describing classes, methods and attributes. Examples -------- -You can use the simplified API to collect the data: +Get the data from the live counters available on the homepage:: ->>> import worldometer + >>> from worldometer.world import WorldCounters ->>> worldometer.current_world_population() -{'current_world_population': 7845085923} + >>> wc = WorldCounters() ->>> worldometer.tweets_sent_today() -{'tweets_sent_today': 4539558} + >>> wc.world_population.current_population + 8065299074 ->>> worldometer.get_metric_of(label='computers_produced_this_year') -{'computers_produced_this_year': 27760858} + >>> wc.government_and_economics.computers_produced_this_year + 180248430 -Or using Worldometer Class: + >>> wc.society_and_media.internet_users_in_the_world_today + 5895566559 ->>> from worldometer import Worldometer ->>> w = Worldometer() +Reload data to get the latest:: ->>> w.what_is_here() -{'categories': 8, 'labels': 63, 'metrics': 63} + >>> wc.reload_data() + >>> wc.world_population.current_population + 8065300592 ->>> w.categories() -[ - 'world_population', - 'government_and_economics', - 'society_and_media', - ... -] +Get help and view information about mapped sections:: + + >>> help(wc) + +Notes +----- +Check https://www.worldometers.info/about for more information about +the data source, how live counters work, and more related information. +""" ->>> w.metrics_labels() -[ +__all__ = [ + 'Worldometer', + 'get_metric_of', + 'update_metrics', 'current_world_population', 'births_this_year', 'births_today', 'deaths_this_year', 'deaths_today', 'net_population_growth_this_year', - ... -] - ->>> w.metrics() -[ - 7845087963, - 15741371, - 5676, - 6608605, - 2383, - 9132766, - ... + 'net_population_growth_today', + 'public_healthcare_expenditure_today', + 'public_education_expenditure_today', + 'public_military_expenditure_today', + 'cars_produced_this_year', + 'bicycles_produced_this_year', + 'computers_produced_this_year', + 'new_book_titles_published_this_year', + 'newspapers_circulated_today', + 'tv_sets_sold_worldwide_today', + 'cellular_phones_sold_today', + 'money_spent_on_videogames_today', + 'internet_users_in_the_world_today', + 'emails_sent_today', + 'blog_posts_written_today', + 'tweets_sent_today', + 'google_searches_today', + 'forest_loss_this_year', + 'land_lost_to_soil_erosion_this_year', + 'co2_emissions_this_year', + 'desertification_this_year', + 'toxic_chemicals_released_in_the_environment_this_year', + 'undernourished_people_in_the_world', + 'overweight_people_in_the_world', + 'obese_people_in_the_world', + 'people_who_died_of_hunger_today', + 'money_spent_for_obesity_related_diseases_in_the_usa_today', + 'money_spent_on_weight_loss_programs_in_the_usa_today', + 'water_used_this_year', + 'deaths_caused_by_water_related_diseases_this_year', + 'people_with_no_access_to_a_safe_drinking_water_source', + 'energy_used_today', + 'non_renewable_sources', + 'renewable_sources', + 'solar_energy_striking_earth_today', + 'oil_pumped_today', + 'oil_left', + 'days_to_the_end_of_oil', + 'natural_gas_left', + 'days_to_the_end_of_natural_gas', + 'coal_left', + 'days_to_the_end_of_coal', + 'communicable_disease_deaths_this_year', + 'seasonal_flu_deaths_this_year', + 'deaths_of_children_under_5_this_year', + 'abortions_this_year', + 'deaths_of_mothers_during_birth_this_year', + 'hiv_aids_infected_people', + 'deaths_caused_by_hiv_aids_this_year', + 'deaths_caused_by_cancer_this_year', + 'deaths_caused_by_malaria_this_year', + 'cigarettes_smoked_today', + 'deaths_caused_by_smoking_this_year', + 'deaths_caused_by_alcohol_this_year', + 'suicides_this_year', + 'money_spent_on_illegal_drugs_this_year', + 'road_traffic_accident_fatalities_this_year' ] ->>> w.metrics_with_labels() -{ - 'abortions_this_year': 4785492, - 'bicycles_produced_this_year': 17070566, - 'births_this_year': 15741371, - 'births_today': 5676, - 'blog_posts_written_today': 110171, - 'cars_produced_this_year': 8999185, - 'cellular_phones_sold_today': 98846, - ...: ... -} -""" - - -__all__ = [ - 'core', - 'api' -] - -from .__about__ import __version__, __author__, __email__ - -from .core import Worldometer -from .api import * +__version__ = '2.0.0' +__author__ = 'Matheus Felipe' + +# TODO: The worldometer.core module is deprecated. +# TODO: The old API (worldometer.api) is deprecated. +# This will be removed in the future. +from worldometer.core import Worldometer +from worldometer.api import ( + get_metric_of, + update_metrics, + current_world_population, + births_this_year, + births_today, + deaths_this_year, + deaths_today, + net_population_growth_this_year, + net_population_growth_today, + public_healthcare_expenditure_today, + public_education_expenditure_today, + public_military_expenditure_today, + cars_produced_this_year, + bicycles_produced_this_year, + computers_produced_this_year, + new_book_titles_published_this_year, + newspapers_circulated_today, + tv_sets_sold_worldwide_today, + cellular_phones_sold_today, + money_spent_on_videogames_today, + internet_users_in_the_world_today, + emails_sent_today, + blog_posts_written_today, + tweets_sent_today, + google_searches_today, + forest_loss_this_year, + land_lost_to_soil_erosion_this_year, + co2_emissions_this_year, + desertification_this_year, + toxic_chemicals_released_in_the_environment_this_year, + undernourished_people_in_the_world, + overweight_people_in_the_world, + obese_people_in_the_world, + people_who_died_of_hunger_today, + money_spent_for_obesity_related_diseases_in_the_usa_today, + money_spent_on_weight_loss_programs_in_the_usa_today, + water_used_this_year, + deaths_caused_by_water_related_diseases_this_year, + people_with_no_access_to_a_safe_drinking_water_source, + energy_used_today, + non_renewable_sources, + renewable_sources, + solar_energy_striking_earth_today, + oil_pumped_today, + oil_left, + days_to_the_end_of_oil, + natural_gas_left, + days_to_the_end_of_natural_gas, + coal_left, + days_to_the_end_of_coal, + communicable_disease_deaths_this_year, + seasonal_flu_deaths_this_year, + deaths_of_children_under_5_this_year, + abortions_this_year, + deaths_of_mothers_during_birth_this_year, + hiv_aids_infected_people, + deaths_caused_by_hiv_aids_this_year, + deaths_caused_by_cancer_this_year, + deaths_caused_by_malaria_this_year, + cigarettes_smoked_today, + deaths_caused_by_smoking_this_year, + deaths_caused_by_alcohol_this_year, + suicides_this_year, + money_spent_on_illegal_drugs_this_year, + road_traffic_accident_fatalities_this_year +) diff --git a/worldometer/api.py b/worldometer/api.py index e4c75ab..365302b 100644 --- a/worldometer/api.py +++ b/worldometer/api.py @@ -19,7 +19,7 @@ {'computers_produced_this_year': 27760858} There is also a simplified and self-explanatory API that provides -functions corresponding to the labels. +functions corresponding to the labels. Autocomplete tools from the editors/IDE will help you use these functions without having to decorate all labels: @@ -99,18 +99,17 @@ 'road_traffic_accident_fatalities_this_year' ] +from worldometer import Worldometer +from worldometer.core import _deprecated_api -from .core import Worldometer -from .__about__ import __version__, __author__, __email__ - - -__w = Worldometer() +_cached_metrics = {} +@_deprecated_api def get_metric_of(label: str) -> dict: """Get metric of label specified. - + Parameters ---------- label @@ -127,7 +126,11 @@ def get_metric_of(label: str) -> dict: {'current_world_population': 7845085923} """ - metrics = __w.metrics_with_labels() + if not _cached_metrics: + w = Worldometer() + _cached_metrics.update(w.metrics_with_labels()) + + metrics = _cached_metrics.copy() if label not in metrics: raise Exception(f'This label "{label}" is invalid, please use a valid label.') @@ -135,322 +138,385 @@ def get_metric_of(label: str) -> dict: return {label: metrics[label]} +@_deprecated_api def update_metrics() -> None: """Update metrics of worldometer.""" - - __w.update_metrics() + _cached_metrics.clear() +@_deprecated_api def current_world_population() -> dict: """Get number of current world population.""" - return get_metric_of(label='current_world_population') + return get_metric_of(label='current_population') +@_deprecated_api def births_this_year() -> dict: """Get number of births this year.""" return get_metric_of(label='births_this_year') +@_deprecated_api def births_today() -> dict: """Get number of births today.""" return get_metric_of(label='births_today') +@_deprecated_api def deaths_this_year() -> dict: """Get number of deaths this year.""" return get_metric_of(label='deaths_this_year') +@_deprecated_api def deaths_today() -> dict: """Get number of deaths today.""" return get_metric_of(label='deaths_today') +@_deprecated_api def net_population_growth_this_year() -> dict: """Get number of net population growth this year.""" return get_metric_of(label='net_population_growth_this_year') +@_deprecated_api def net_population_growth_today() -> dict: """Get number of net population growth today.""" return get_metric_of(label='net_population_growth_today') +@_deprecated_api def public_healthcare_expenditure_today() -> dict: """Get number of public healthcare expenditure today.""" return get_metric_of(label='public_healthcare_expenditure_today') +@_deprecated_api def public_education_expenditure_today() -> dict: """Get number of public education expenditure today.""" return get_metric_of(label='public_education_expenditure_today') +@_deprecated_api def public_military_expenditure_today() -> dict: """Get number of public military expenditure today.""" return get_metric_of(label='public_military_expenditure_today') +@_deprecated_api def cars_produced_this_year() -> dict: """Get number of cars produced this year.""" return get_metric_of(label='cars_produced_this_year') +@_deprecated_api def bicycles_produced_this_year() -> dict: """Get number of bicycles produced this year.""" return get_metric_of(label='bicycles_produced_this_year') +@_deprecated_api def computers_produced_this_year() -> dict: """Get number of computers produced this year.""" return get_metric_of(label='computers_produced_this_year') +@_deprecated_api def new_book_titles_published_this_year() -> dict: """Get number of new book titles published this year.""" return get_metric_of(label='new_book_titles_published_this_year') +@_deprecated_api def newspapers_circulated_today() -> dict: """Get number of newspapers circulated today.""" return get_metric_of(label='newspapers_circulated_today') +@_deprecated_api def tv_sets_sold_worldwide_today() -> dict: """Get number of tv sets sold worldwide today.""" return get_metric_of(label='tv_sets_sold_worldwide_today') +@_deprecated_api def cellular_phones_sold_today() -> dict: """Get number of cellular phones sold today.""" return get_metric_of(label='cellular_phones_sold_today') +@_deprecated_api def money_spent_on_videogames_today() -> dict: """Get number of money spent on videogames today.""" return get_metric_of(label='money_spent_on_videogames_today') +@_deprecated_api def internet_users_in_the_world_today() -> dict: """Get number of internet users in the world today.""" return get_metric_of(label='internet_users_in_the_world_today') +@_deprecated_api def emails_sent_today() -> dict: """Get number of emails sent today.""" return get_metric_of(label='emails_sent_today') +@_deprecated_api def blog_posts_written_today() -> dict: """Get number of blog posts written today.""" return get_metric_of(label='blog_posts_written_today') +@_deprecated_api def tweets_sent_today() -> dict: """Get number of tweets sent today.""" return get_metric_of(label='tweets_sent_today') +@_deprecated_api def google_searches_today() -> dict: """Get number of google searches today.""" return get_metric_of(label='google_searches_today') +@_deprecated_api def forest_loss_this_year() -> dict: """Get number of forest loss this year.""" return get_metric_of(label='forest_loss_this_year') +@_deprecated_api def land_lost_to_soil_erosion_this_year() -> dict: """Get number of land lost to soil erosion this year.""" return get_metric_of(label='land_lost_to_soil_erosion_this_year') +@_deprecated_api def co2_emissions_this_year() -> dict: """Get number of co2 emissions this year.""" return get_metric_of(label='co2_emissions_this_year') +@_deprecated_api def desertification_this_year() -> dict: """Get number of desertification this year.""" return get_metric_of(label='desertification_this_year') +@_deprecated_api def toxic_chemicals_released_in_the_environment_this_year() -> dict: """Get number of toxic chemicals released in the environment this year.""" return get_metric_of(label='toxic_chemicals_released_in_the_environment_this_year') +@_deprecated_api def undernourished_people_in_the_world() -> dict: """Get number of undernourished people in the world.""" return get_metric_of(label='undernourished_people_in_the_world') +@_deprecated_api def overweight_people_in_the_world() -> dict: """Get number of overweight people in the world.""" return get_metric_of(label='overweight_people_in_the_world') +@_deprecated_api def obese_people_in_the_world() -> dict: """Get number of obese people in the world.""" return get_metric_of(label='obese_people_in_the_world') +@_deprecated_api def people_who_died_of_hunger_today() -> dict: """Get number of people who died of hunger today.""" return get_metric_of(label='people_who_died_of_hunger_today') +@_deprecated_api def money_spent_for_obesity_related_diseases_in_the_usa_today() -> dict: """Get number of money spent for obesity related diseases in the usa today.""" return get_metric_of(label='money_spent_for_obesity_related_diseases_in_the_usa_today') +@_deprecated_api def money_spent_on_weight_loss_programs_in_the_usa_today() -> dict: """Get number of money spent on weight loss programs in the usa today.""" return get_metric_of(label='money_spent_on_weight_loss_programs_in_the_usa_today') +@_deprecated_api def water_used_this_year() -> dict: """Get number of water used this year.""" return get_metric_of(label='water_used_this_year') +@_deprecated_api def deaths_caused_by_water_related_diseases_this_year() -> dict: """Get number of deaths caused by water related diseases this year.""" return get_metric_of(label='deaths_caused_by_water_related_diseases_this_year') +@_deprecated_api def people_with_no_access_to_a_safe_drinking_water_source() -> dict: """Get number of people with no access to a safe drinking water source.""" return get_metric_of(label='people_with_no_access_to_a_safe_drinking_water_source') +@_deprecated_api def energy_used_today() -> dict: """Get number of energy used today.""" return get_metric_of(label='energy_used_today') +@_deprecated_api def non_renewable_sources() -> dict: """Get number of non renewable sources.""" - return get_metric_of(label='non-renewable_sources') + return get_metric_of(label='non_renewable_sources') +@_deprecated_api def renewable_sources() -> dict: """Get number of renewable sources.""" return get_metric_of(label='renewable_sources') +@_deprecated_api def solar_energy_striking_earth_today() -> dict: """Get number of solar energy striking earth today.""" return get_metric_of(label='solar_energy_striking_earth_today') +@_deprecated_api def oil_pumped_today() -> dict: """Get number of oil pumped today.""" return get_metric_of(label='oil_pumped_today') +@_deprecated_api def oil_left() -> dict: """Get number of oil left.""" return get_metric_of(label='oil_left') +@_deprecated_api def days_to_the_end_of_oil() -> dict: """Get number of days to the end of oil.""" return get_metric_of(label='days_to_the_end_of_oil') +@_deprecated_api def natural_gas_left() -> dict: """Get number of natural gas left.""" return get_metric_of(label='natural_gas_left') +@_deprecated_api def days_to_the_end_of_natural_gas() -> dict: """Get number of days to the end of natural gas.""" return get_metric_of(label='days_to_the_end_of_natural_gas') +@_deprecated_api def coal_left() -> dict: """Get number of coal left.""" return get_metric_of(label='coal_left') +@_deprecated_api def days_to_the_end_of_coal() -> dict: """Get number of days to the end of coal.""" return get_metric_of(label='days_to_the_end_of_coal') +@_deprecated_api def communicable_disease_deaths_this_year() -> dict: """Get number of communicable disease deaths this year.""" return get_metric_of(label='communicable_disease_deaths_this_year') +@_deprecated_api def seasonal_flu_deaths_this_year() -> dict: """Get number of seasonal flu deaths this year.""" return get_metric_of(label='seasonal_flu_deaths_this_year') +@_deprecated_api def deaths_of_children_under_5_this_year() -> dict: """Get number of deaths of children under 5 this year.""" return get_metric_of(label='deaths_of_children_under_5_this_year') +@_deprecated_api def abortions_this_year() -> dict: """Get number of abortions this year.""" return get_metric_of(label='abortions_this_year') +@_deprecated_api def deaths_of_mothers_during_birth_this_year() -> dict: """Get number of deaths of mothers during birth this year.""" return get_metric_of(label='deaths_of_mothers_during_birth_this_year') +@_deprecated_api def hiv_aids_infected_people() -> dict: """Get number of hiv aids infected people.""" - return get_metric_of(label='hiv/aids_infected_people') + return get_metric_of(label='hiv_aids_infected_people') +@_deprecated_api def deaths_caused_by_hiv_aids_this_year() -> dict: """Get number of deaths caused by hiv aids this year.""" - return get_metric_of(label='deaths_caused_by_hiv/aids_this_year') + return get_metric_of(label='deaths_caused_by_hiv_aids_this_year') +@_deprecated_api def deaths_caused_by_cancer_this_year() -> dict: """Get number of deaths caused by cancer this year.""" return get_metric_of(label='deaths_caused_by_cancer_this_year') +@_deprecated_api def deaths_caused_by_malaria_this_year() -> dict: """Get number of deaths caused by malaria this year.""" return get_metric_of(label='deaths_caused_by_malaria_this_year') +@_deprecated_api def cigarettes_smoked_today() -> dict: """Get number of cigarettes smoked today.""" return get_metric_of(label='cigarettes_smoked_today') +@_deprecated_api def deaths_caused_by_smoking_this_year() -> dict: """Get number of deaths caused by smoking this year.""" return get_metric_of(label='deaths_caused_by_smoking_this_year') +@_deprecated_api def deaths_caused_by_alcohol_this_year() -> dict: """Get number of deaths caused by alcohol this year.""" return get_metric_of(label='deaths_caused_by_alcohol_this_year') +@_deprecated_api def suicides_this_year() -> dict: """Get number of suicides this year.""" return get_metric_of(label='suicides_this_year') +@_deprecated_api def money_spent_on_illegal_drugs_this_year() -> dict: """Get number of money spent on illegal drugs this year.""" return get_metric_of(label='money_spent_on_illegal_drugs_this_year') +@_deprecated_api def road_traffic_accident_fatalities_this_year() -> dict: """Get number of road traffic accident fatalities this year.""" return get_metric_of(label='road_traffic_accident_fatalities_this_year') diff --git a/worldometer/core.py b/worldometer/core.py index 042cf6b..2425340 100644 --- a/worldometer/core.py +++ b/worldometer/core.py @@ -24,7 +24,7 @@ Get all metrics with labels in dict format: >>> w.metrics_with_labels() -{ +{ 'abortions_this_year': 4785492, 'bicycles_produced_this_year': 17070566, 'births_this_year': 15741371, @@ -38,23 +38,19 @@ __all__ = ['Worldometer'] -from .__about__ import __version__, __author__, __email__ - -import re - -from requests_html import HTML, HTMLSession +import warnings +from functools import wraps +from dataclasses import asdict +from worldometer.world import WorldCounters # Constant variables, used in the Worldometer module. URL = 'https://www.worldometers.info/' -_CSS_SELECTOR_OF_COUNTER_NUMBERS = '.counter-number' -_CSS_SELECTOR_OF_COUNTER_ITEM = '.counter-item, .counter-item-double' - _METRICS_LABELS = { 'world_population': [ - 'current_world_population', + 'current_population', 'births_this_year', 'births_today', 'deaths_this_year', @@ -104,7 +100,7 @@ ], 'energy': [ 'energy_used_today', - 'non-renewable_sources', + 'non_renewable_sources', 'renewable_sources', 'solar_energy_striking_earth_today', 'oil_pumped_today', @@ -121,8 +117,8 @@ 'deaths_of_children_under_5_this_year', 'abortions_this_year', 'deaths_of_mothers_during_birth_this_year', - 'hiv/aids_infected_people', - 'deaths_caused_by_hiv/aids_this_year', + 'hiv_aids_infected_people', + 'deaths_caused_by_hiv_aids_this_year', 'deaths_caused_by_cancer_this_year', 'deaths_caused_by_malaria_this_year', 'cigarettes_smoked_today', @@ -135,10 +131,24 @@ } +def _deprecated_api(func_or_class): + @wraps(func_or_class) + def decorated(*args, **kwargs): + warnings.warn( + f'{func_or_class.__name__}() is deprecated. Use WorldCounters() instead.' + ' It will be removed in a future release.', + DeprecationWarning, + stacklevel=2 + ) + return func_or_class(*args, **kwargs) + return decorated + + +@_deprecated_api class Worldometer(object): """This is the core class of the worldometer module. - All functions/methods of the worldometer module use + All functions/methods of the worldometer module use this class to build a Worldometer Object to access the collected data. Get more info about its attributes/methods using the ``help`` function: @@ -161,33 +171,30 @@ class Worldometer(builtins.object) | ---------- | timeout | Seconds of wait for processing. - | + | | (...) """ def __init__(self, timeout: int = 30): """Initializer of Worldometer class. - + Parameters ---------- timeout Seconds of wait for processing. """ - self.__r = None # Stores the response with html code for later rendering self.__timeout = timeout self._metrics = self.collect_metrics() - - def __str__(self): + def __str__(self): c, l, m = self.what_is_here().values() return f'Worldometer has {c} categories, {l} labels and {m} metrics' def __repr__(self): - c, l, m = self.what_is_here().values() return ( @@ -197,7 +204,7 @@ def __repr__(self): def metrics(self) -> list: """Get all metrics of worldometer. - + Returns ------- list @@ -208,7 +215,7 @@ def metrics(self) -> list: >>> from worldometer import Worldometer >>> w = Worldometer() >>> w.metrics() - [ + [ 7845087963, 15741371, 5676, @@ -218,75 +225,43 @@ def metrics(self) -> list: ... ] """ - return self._metrics.copy() - def _get_html(self, url: str) -> str: + def _get_html(self, url: str) -> None: """Get the html code from the specified url and return its rendered content. """ - session = HTMLSession() - - try: - # Get html page and render dynamic content - self.__r = session.get(url, timeout=self.__timeout) - self.__r.html.render(timeout=self.__timeout) - - return self.__r.html.raw_html - - except Exception as err: - raise Exception(err) - @staticmethod - def find_metrics_in_html(html_code: str) -> list: + def find_metrics_in_html(html_code: str) -> None: """Find worldometer metrics in html code. - + Parameters ---------- html_code Receive html code. - + Returns ------- list A list of not sanitized metrics of str type. """ - html = HTML(html=html_code) - - # Get only text of all requests_html.Element object - metrics = [metric.text for metric in html.find(_CSS_SELECTOR_OF_COUNTER_NUMBERS)] - - return metrics - @staticmethod - def sanitize_metrics(metric_list: list) -> list: + def sanitize_metrics(metric_list: list) -> None: """Sanitize all metrics in list. - + Parameters ---------- metric_list Receive list of metrics in str type. - + Returns ------- list A list of sanitized metrics of int type. """ - sanitized_metrics = [] - - for metric in metric_list: - - # Get only the number and convert to int - found = re.search(r'[0-9,]+', metric).group() - number = int(found.replace(',', '')) - - sanitized_metrics.append(number) - - return sanitized_metrics - def collect_metrics(self) -> list: """Collects all metrics from the worldometer site. @@ -295,30 +270,31 @@ def collect_metrics(self) -> list: list A list of metrics of int type. """ + wc = WorldCounters() + + metrics = { + **asdict(wc.world_population), + **asdict(wc.government_and_economics), + **asdict(wc.society_and_media), + **asdict(wc.environment), + **asdict(wc.food), + **asdict(wc.water), + **asdict(wc.energy), + **asdict(wc.health) + } - if self.__r is None: - html = self._get_html(url=URL) - else: - html = self.__r.html.raw_html - - metrics = self.find_metrics_in_html(html_code=html) - sanitized_metrics = self.sanitize_metrics(metric_list=metrics) + metrics_values = list(metrics.values()) - return sanitized_metrics + return metrics_values def update_metrics(self) -> None: """Update metrics of worldometer.""" - - if self.__r is not None: - self.__r.html.render(timeout=self.__timeout) - self._metrics = self.collect_metrics() - else: - raise Exception('There are no metrics. Collect them to update.') + self._metrics = self.collect_metrics() @staticmethod def metrics_labels(with_categories=False) -> list: """Return metrics labels of worldometer. - + Parameters ---------- with_categories @@ -328,7 +304,7 @@ def metrics_labels(with_categories=False) -> list: ------- list A list of labels if ``with_categories`` parameter is False. - + A dict of labels with categories if ``with_categories`` parameter is True. Example @@ -336,7 +312,7 @@ def metrics_labels(with_categories=False) -> list: >>> from worldometer import Worldometer >>> w = Worldometer() >>> w.metrics_labels() - [ + [ 'current_world_population', 'births_this_year', 'births_today', @@ -346,9 +322,8 @@ def metrics_labels(with_categories=False) -> list: ... ] """ - if with_categories: - return _METRICS_LABELS + return _METRICS_LABELS # type: ignore only_metrics = [] for metrics in _METRICS_LABELS.values(): @@ -359,7 +334,7 @@ def metrics_labels(with_categories=False) -> list: @staticmethod def categories() -> list: """Return categories of worldometer. - + Returns ------- list @@ -370,19 +345,18 @@ def categories() -> list: >>> from worldometer import Worldometer >>> w = Worldometer() >>> w.categories() - [ + [ 'world_population', 'government_and_economics', 'society_and_media', ... ] """ - return [category for category in _METRICS_LABELS.keys()] def metrics_with_labels(self, with_categories: bool = False) -> dict: """Return metrics with labels in key-value structure. - + Parameters ---------- with_categories @@ -398,7 +372,7 @@ def metrics_with_labels(self, with_categories: bool = False) -> dict: >>> from worldometer import Worldometer >>> w = Worldometer() >>> w.metrics_with_labels() - { + { 'abortions_this_year': 4785492, 'bicycles_produced_this_year': 17070566, 'births_this_year': 15741371, @@ -409,11 +383,10 @@ def metrics_with_labels(self, with_categories: bool = False) -> dict: ...: ... } """ - metrics = self.metrics() if with_categories: - + m_with_l = {} # Storage all structure of metrics with labels metrics_labels = self.metrics_labels(with_categories=True).copy() @@ -424,29 +397,29 @@ def metrics_with_labels(self, with_categories: bool = False) -> dict: for label in metrics_labels[category]: - # Storage of a metric on the key label as the index + # Storage of a metric on the key label as the index # increases +1 to iterate through the entire list of metrics m_with_l[category][label] = metrics[idx] idx += 1 - + return m_with_l else: - + labels = self.metrics_labels().copy() - + return dict( zip(labels, metrics) ) def what_is_here(self) -> dict: """Return what is here in object. - + Returns ------- dict Number of categories, labels and metrics. - + Example ------- >>> from worldometer import Worldometer @@ -454,13 +427,8 @@ def what_is_here(self) -> dict: >>> w.what_is_here() {'categories': 8, 'labels': 63, 'metrics': 63} """ - - c = len(self.categories()) - l = len(self.metrics_labels()) - m = len(self.metrics()) - return { - 'categories': c, - 'labels': l, - 'metrics': m + 'categories': len(self.categories()), + 'labels': len(self.metrics_labels()), + 'metrics': len(self.metrics()) } diff --git a/worldometer/scraper/__init__.py b/worldometer/scraper/__init__.py new file mode 100644 index 0000000..0d44c6b --- /dev/null +++ b/worldometer/scraper/__init__.py @@ -0,0 +1,9 @@ +__all__ = [ + 'get_data_tables', + 'get_rts_counters_object' +] + +from worldometer.scraper.controller import ( + get_data_tables, + get_rts_counters_object +) diff --git a/worldometer/scraper/browser.py b/worldometer/scraper/browser.py new file mode 100644 index 0000000..4eede75 --- /dev/null +++ b/worldometer/scraper/browser.py @@ -0,0 +1,35 @@ +from typing import Any + +from requests_html import HTML, HTMLSession + +# pyppeteer is used by requests_html internally +from pyppeteer.errors import ElementHandleError, TimeoutError + +from worldometer.scraper.exceptions import ScriptRunnerError + + +class Browser: + + def __init__(self) -> None: + self.session = HTMLSession() + + def get_page_content(self, url: str, timeout: int = 30) -> HTML: + res = self.session.get(url, timeout=timeout) + + # requests-html does not have the type hint set correctly in + # some modules (session.get is one of them), so type checkers + # report a problem. I will probably trade this package in the future. + html_obj = res.html # type: ignore + return html_obj + + def render_page(self, html_obj: HTML) -> None: + html_obj.render() + + def run_js_script(self, html_obj: HTML, script: str) -> Any: + try: + script_return = html_obj.render(script=script) + + except (ElementHandleError, TimeoutError) as err: + raise ScriptRunnerError('Could not evaluate provided js script in HTML.') from err + + return script_return # type: ignore diff --git a/worldometer/scraper/consts.py b/worldometer/scraper/consts.py new file mode 100644 index 0000000..3e8212c --- /dev/null +++ b/worldometer/scraper/consts.py @@ -0,0 +1 @@ +BASE_URL = 'https://www.worldometers.info' diff --git a/worldometer/scraper/controller.py b/worldometer/scraper/controller.py new file mode 100644 index 0000000..1233127 --- /dev/null +++ b/worldometer/scraper/controller.py @@ -0,0 +1,45 @@ +from typing import Dict, List, Optional, Tuple, Union + +from worldometer.scraper.browser import Browser + +from worldometer.scraper.parser import ( + get_rts_counters_only_with_last_value_key, + get_html_tables_data +) + +from worldometer.scraper.utils import make_url + +from worldometer.scraper.consts import BASE_URL + + +browser = Browser() + + +def get_rts_counters_object( + path_url: Optional[str] = None +) -> Dict[str, Union[int, float, None]]: + url = make_url(BASE_URL, path_url) + html = browser.get_page_content(url) + script_return = browser.run_js_script(html, script='() => rts_counters') + rts_counters = get_rts_counters_only_with_last_value_key(rts_counters=script_return) + return rts_counters + + +def get_data_tables( + path_url: str, + new_column_names: List[Tuple[str, ...]], + render: bool = False, + attrs: Optional[Dict[str, str]] = {'class': 'table'} +) -> List[List[dict]]: + url = make_url(BASE_URL, path_url) + html = browser.get_page_content(url) + + if render: + browser.render_page(html) + + data = get_html_tables_data( + html=html.html, + new_column_names=new_column_names, + attrs=attrs + ) + return data diff --git a/worldometer/scraper/exceptions.py b/worldometer/scraper/exceptions.py new file mode 100644 index 0000000..91b171f --- /dev/null +++ b/worldometer/scraper/exceptions.py @@ -0,0 +1,22 @@ +class ScraperException(Exception): + """Base exception of the scraper package.""" + + +class BrowserError(ScraperException): + """Generic browser error.""" + + +class ScriptRunnerError(BrowserError): + """Could not evaluate provided js script.""" + + +class ParserError(ScraperException): + """Generic parser error.""" + + +class HTMLTablesNotFoundError(ParserError): + """No HTML tables found by the parsing engine.""" + + +class ColumnNamesLengthError(ParserError): + """Length of column names different than expected.""" diff --git a/worldometer/scraper/parser.py b/worldometer/scraper/parser.py new file mode 100644 index 0000000..d1996d0 --- /dev/null +++ b/worldometer/scraper/parser.py @@ -0,0 +1,62 @@ +from typing import Dict, List, Optional, Tuple, Union + +from io import StringIO + +import pandas as pd + +from worldometer.scraper.exceptions import ( + ColumnNamesLengthError, + HTMLTablesNotFoundError +) + + +def get_rts_counters_only_with_last_value_key( + rts_counters: Dict[str, dict] +) -> Dict[str, Union[int, float, None]]: + return { + key: subdict.get('last_value') or None + for key, subdict in rts_counters.items() + } + + +def get_html_tables_data( + html: str, + attrs: Optional[Dict[str, str]], + new_column_names: List[Tuple[str, ...]] +) -> List[List[dict]]: + data = [] + + try: + dfs = pd.read_html(io=StringIO(html), attrs=attrs, flavor='bs4') + + dfs_len = len(dfs) + col_names_len = len(new_column_names) + + if dfs_len != col_names_len: + raise ColumnNamesLengthError( + f'{col_names_len} tuples of column names for {dfs_len} table' + ) + + for idx, df in enumerate(dfs): + + col_len = len(df.columns) + new_col_len = len(new_column_names[idx]) + + if col_len != new_col_len: + raise ColumnNamesLengthError( + f'Table in position {idx} expected {col_len} column names but received {new_col_len}' + ) + + df.columns = new_column_names[idx] + + data.append(df.to_dict(orient='records')) + + except ValueError as err: + expected_error_message = 'No tables found' + + if expected_error_message in err.args: + raise HTMLTablesNotFoundError('No HTML tables found') from err + + raise + + return data diff --git a/worldometer/scraper/utils.py b/worldometer/scraper/utils.py new file mode 100644 index 0000000..95b32f1 --- /dev/null +++ b/worldometer/scraper/utils.py @@ -0,0 +1,10 @@ +import re + +from typing import Optional + + +def make_url(base_url: str, path_url: Optional[str] = None) -> str: + url = base_url + if path_url and re.match(r'^/{1}.*', path_url): + url += path_url + return url diff --git a/worldometer/world/__init__.py b/worldometer/world/__init__.py new file mode 100644 index 0000000..670fa16 --- /dev/null +++ b/worldometer/world/__init__.py @@ -0,0 +1,21 @@ +""" +world package +------------- + +`world` is the main API of the `worldometer` package, +where you can access various live counters and data +available on the https://www.worldometers.info website +through self-descriptive classes, methods, and attributes. +""" + +__all__ = [ + 'geography', + 'population', + 'CountryCodes', + 'WorldCounters' +] + +from worldometer.world import geography +from worldometer.world import population +from worldometer.world.country_codes import CountryCodes +from worldometer.world.counters import WorldCounters diff --git a/worldometer/world/counters.py b/worldometer/world/counters.py new file mode 100644 index 0000000..4d89c8f --- /dev/null +++ b/worldometer/world/counters.py @@ -0,0 +1,356 @@ +from dataclasses import InitVar, dataclass, field +from typing import Dict, Union + +from worldometer.scraper import get_rts_counters_object + + +CounterValueType = Union[int, float, None] +CounterValuesType = Dict[str, CounterValueType] + + +class WorldCounters: + """Contains a reference to each section of the home page counters. + + Attributes + ---------- + source_path : str + The source path for the counters. + world_population : WorldPopulation + An instance of the `WorldPopulation` class that stores all counters + related to population data. + government_and_economics : GovernmentAndEconomics + An instance of the `GovernmentAndEconomics` class that stores all + counters related to government and economic data. + society_and_media : SocietyAndMedia + An instance of the `SocietyAndMedia` class that stores all counters + related to society and media data. + environment : Environment + An instance of the `Environment` class that stores all counters related + to environmental data. + food : Food + An instance of the `Food` class that stores all counters related to + food data. + water : Water + An instance of the `Water` class that stores all counters related to + water data. + energy : Energy + An instance of the `Energy` class that stores all counters related to + energy data. + health : Health + An instance of the `Health` class that stores all counters related to + health data. + + Notes + ----- + For precise and up-to-date information on each section and its counters, + please check the `worldometers homepage `_. + """ + source_path = '/' + + def __init__(self) -> None: + self._data = self._load_data() + self._init_counters() + + def _load_data(self) -> CounterValuesType: + rts_counters = get_rts_counters_object(path_url=self.source_path) + return rts_counters + + def _init_counters(self) -> None: + self.world_population = WorldPopulation(self._data) + self.government_and_economics = GovernmentAndEconomics(self._data) + self.society_and_media = SocietyAndMedia(self._data) + self.environment = Environment(self._data) + self.food = Food(self._data) + self.water = Water(self._data) + self.energy = Energy(self._data) + self.health = Health(self._data) + + def reload_data(self) -> None: + """Reload all counters data. This loads the available updated data.""" + self._data = self._load_data() + self._init_counters() + + +@dataclass +class WorldPopulation: + """Counters related to world population data. + + Attributes + ---------- + current_population : Union[int, float, None] + births_today : Union[int, float, None] + births_this_year : Union[int, float, None] + deaths_today : Union[int, float, None] + deaths_this_year : Union[int, float, None] + net_population_growth_today : Union[int, float, None] + net_population_growth_this_year : Union[int, float, None] + """ + _data: InitVar[CounterValuesType] + current_population: CounterValueType = field(init=False) + births_today: CounterValueType = field(init=False) + births_this_year: CounterValueType = field(init=False) + deaths_today: CounterValueType = field(init=False) + deaths_this_year: CounterValueType = field(init=False) + net_population_growth_today: CounterValueType = field(init=False) + net_population_growth_this_year: CounterValueType = field(init=False) + + def __post_init__(self, _data: CounterValuesType) -> None: + self.current_population = _data.get('current_population') + self.births_today = _data.get('births_today') + self.births_this_year = _data.get('births_this_year') + self.deaths_today = _data.get('dth1s_today') + self.deaths_this_year = _data.get('dth1s_this_year') + self.net_population_growth_today = _data.get('absolute_growth') + self.net_population_growth_this_year = _data.get('absolute_growth_year') + + +@dataclass +class GovernmentAndEconomics: + """Counters related to government and economic data. + + Attributes + ---------- + public_healthcare_expenditure_today : Union[int, float, None] + public_education_expenditure_today : Union[int, float, None] + public_military_expenditure_today : Union[int, float, None] + cars_produced_this_year : Union[int, float, None] + bicycles_produced_this_year : Union[int, float, None] + computers_produced_this_year : Union[int, float, None] + """ + _data: InitVar[CounterValuesType] + public_healthcare_expenditure_today: CounterValueType = field(init=False) + public_education_expenditure_today: CounterValueType = field(init=False) + public_military_expenditure_today: CounterValueType = field(init=False) + cars_produced_this_year: CounterValueType = field(init=False) + bicycles_produced_this_year: CounterValueType = field(init=False) + computers_produced_this_year: CounterValueType = field(init=False) + + def __post_init__(self, _data: CounterValuesType) -> None: + self.public_healthcare_expenditure_today = _data.get('gov_expenditures_health') + self.public_education_expenditure_today = _data.get('gov_expenditures_education') + self.public_military_expenditure_today = _data.get('gov_expenditures_military') + self.cars_produced_this_year = _data.get('automobile_produced') + self.bicycles_produced_this_year = _data.get('bicycle_produced') + self.computers_produced_this_year = _data.get('computers_sold') + + +@dataclass +class SocietyAndMedia: + """Counters related to society and media data. + + Attributes + ---------- + new_book_titles_published_this_year : Union[int, float, None] + newspapers_circulated_today : Union[int, float, None] + tv_sets_sold_worldwide_today : Union[int, float, None] + cellular_phones_sold_today : Union[int, float, None] + money_spent_on_videogames_today : Union[int, float, None] + internet_users_in_the_world_today : Union[int, float, None] + emails_sent_today : Union[int, float, None] + blog_posts_written_today : Union[int, float, None] + tweets_sent_today : Union[int, float, None] + google_searches_today : Union[int, float, None] + """ + _data: InitVar[CounterValuesType] + new_book_titles_published_this_year: CounterValueType = field(init=False) + newspapers_circulated_today: CounterValueType = field(init=False) + tv_sets_sold_worldwide_today: CounterValueType = field(init=False) + cellular_phones_sold_today: CounterValueType = field(init=False) + money_spent_on_videogames_today: CounterValueType = field(init=False) + internet_users_in_the_world_today: CounterValueType = field(init=False) + emails_sent_today: CounterValueType = field(init=False) + blog_posts_written_today: CounterValueType = field(init=False) + tweets_sent_today: CounterValueType = field(init=False) + google_searches_today: CounterValueType = field(init=False) + + def __post_init__(self, _data: CounterValuesType) -> None: + self.new_book_titles_published_this_year = _data.get('books_published') + self.newspapers_circulated_today = _data.get('newspapers_circulated') + self.tv_sets_sold_worldwide_today = _data.get('tv') + self.cellular_phones_sold_today = _data.get('cellular') + self.money_spent_on_videogames_today = _data.get('videogames') + self.internet_users_in_the_world_today = _data.get('internet_users') + self.emails_sent_today = _data.get('em') + self.blog_posts_written_today = _data.get('blog_posts') + self.tweets_sent_today = _data.get('tweets') + self.google_searches_today = _data.get('google_searches') + + +@dataclass +class Environment: + """Counters related to environmental data. + + Attributes + ---------- + forest_loss_this_year : Union[int, float, None] + land_lost_to_soil_erosion_this_year : Union[int, float, None] + co2_emissions_this_year : Union[int, float, None] + desertification_this_year : Union[int, float, None] + toxic_chemicals_released_in_the_environment_this_year : Union[int, float, None] + """ + _data: InitVar[CounterValuesType] + forest_loss_this_year: CounterValueType = field(init=False) + land_lost_to_soil_erosion_this_year: CounterValueType = field(init=False) + co2_emissions_this_year: CounterValueType = field(init=False) + desertification_this_year: CounterValueType = field(init=False) + toxic_chemicals_released_in_the_environment_this_year: CounterValueType = field(init=False) + + def __post_init__(self, _data: CounterValuesType) -> None: + self.forest_loss_this_year = _data.get('forest_loss') + self.land_lost_to_soil_erosion_this_year = _data.get('soil_erosion') + self.co2_emissions_this_year = _data.get('co2_emissions') + self.desertification_this_year = _data.get('desert_land_formed') + self.toxic_chemicals_released_in_the_environment_this_year = _data.get('tox_chem') + + +@dataclass +class Food: + """Counters related to food data. + + Attributes + ---------- + undernourished_people_in_the_world : Union[int, float, None] + overweight_people_in_the_world : Union[int, float, None] + obese_people_in_the_world : Union[int, float, None] + people_who_died_of_hunger_today : Union[int, float, None] + money_spent_for_obesity_related_diseases_in_the_usa_today : Union[int, float, None] + money_spent_on_weight_loss_programs_in_the_usa_today : Union[int, float, None] + """ + _data: InitVar[CounterValuesType] + undernourished_people_in_the_world: CounterValueType = field(init=False) + overweight_people_in_the_world: CounterValueType = field(init=False) + obese_people_in_the_world: CounterValueType = field(init=False) + people_who_died_of_hunger_today: CounterValueType = field(init=False) + money_spent_for_obesity_related_diseases_in_the_usa_today: CounterValueType = field(init=False) + money_spent_on_weight_loss_programs_in_the_usa_today: CounterValueType = field(init=False) + + def __post_init__(self, _data: CounterValuesType) -> None: + self.undernourished_people_in_the_world = _data.get('undernourished') + self.overweight_people_in_the_world = _data.get('overweight') + self.obese_people_in_the_world = _data.get('obese') + self.people_who_died_of_hunger_today = _data.get('dth1_hunger') + self.money_spent_for_obesity_related_diseases_in_the_usa_today = _data.get('obesity_spending') + self.money_spent_on_weight_loss_programs_in_the_usa_today = _data.get('spending_on_weight_loss') + + +@dataclass +class Water: + """Counters related to water data. + + Attributes + ---------- + water_used_this_year : Union[int, float, None] + deaths_caused_by_water_related_diseases_this_year : Union[int, float, None] + people_with_no_access_to_a_safe_drinking_water_source : Union[int, float, None] + """ + _data: InitVar[CounterValuesType] + water_used_this_year: CounterValueType = field(init=False) + deaths_caused_by_water_related_diseases_this_year: CounterValueType = field(init=False) + people_with_no_access_to_a_safe_drinking_water_source: CounterValueType = field(init=False) + + def __post_init__(self, _data: CounterValuesType) -> None: + self.water_used_this_year = _data.get('water_consumed') + self.deaths_caused_by_water_related_diseases_this_year = _data.get('water_disax') + self.people_with_no_access_to_a_safe_drinking_water_source = _data.get('nowater_population') + + +@dataclass +class Energy: + """Counters related to energy data. + + Attributes + ---------- + energy_used_today : Union[int, float, None] + non_renewable_sources : Union[int, float, None] + renewable_sources : Union[int, float, None] + solar_energy_striking_earth_today : Union[int, float, None] + oil_pumped_today : Union[int, float, None] + oil_left : Union[int, float, None] + days_to_the_end_of_oil : Union[int, float, None] + natural_gas_left : Union[int, float, None] + days_to_the_end_of_natural_gas : Union[int, float, None] + coal_left : Union[int, float, None] + days_to_the_end_of_coal : Union[int, float, None] + """ + _data: InitVar[CounterValuesType] + energy_used_today: CounterValueType = field(init=False) + non_renewable_sources: CounterValueType = field(init=False) + renewable_sources: CounterValueType = field(init=False) + solar_energy_striking_earth_today: CounterValueType = field(init=False) + oil_pumped_today: CounterValueType = field(init=False) + oil_left: CounterValueType = field(init=False) + days_to_the_end_of_oil: CounterValueType = field(init=False) + natural_gas_left: CounterValueType = field(init=False) + days_to_the_end_of_natural_gas: CounterValueType = field(init=False) + coal_left: CounterValueType = field(init=False) + days_to_the_end_of_coal: CounterValueType = field(init=False) + + def __post_init__(self, _data: CounterValuesType) -> None: + self.energy_used_today = _data.get('energy_used') + self.non_renewable_sources = _data.get('energy_nonren') + self.renewable_sources = _data.get('energy_ren') + self.solar_energy_striking_earth_today = _data.get('solar_energy') + self.oil_pumped_today = _data.get('oil_consumption') + self.oil_left = _data.get('oil_reserves') + self.days_to_the_end_of_oil = _data.get('oil_days') + self.natural_gas_left = _data.get('gas_reserves') + self.days_to_the_end_of_natural_gas = _data.get('gas_days') + self.coal_left = _data.get('coal_reserves') + self.days_to_the_end_of_coal = _data.get('coal_days') + + +@dataclass +class Health: + """Counters related to health data. + + Attributes + ---------- + communicable_disease_deaths_this_year : Union[int, float, None] + seasonal_flu_deaths_this_year : Union[int, float, None] + deaths_of_children_under_5_this_year : Union[int, float, None] + abortions_this_year : Union[int, float, None] + deaths_of_mothers_during_birth_this_year : Union[int, float, None] + hiv_aids_infected_people : Union[int, float, None] + deaths_caused_by_hiv_aids_this_year : Union[int, float, None] + deaths_caused_by_cancer_this_year : Union[int, float, None] + deaths_caused_by_malaria_this_year : Union[int, float, None] + cigarettes_smoked_today : Union[int, float, None] + deaths_caused_by_smoking_this_year : Union[int, float, None] + deaths_caused_by_alcohol_this_year : Union[int, float, None] + suicides_this_year : Union[int, float, None] + money_spent_on_illegal_drugs_this_year : Union[int, float, None] + road_traffic_accident_fatalities_this_year : Union[int, float, None] + """ + _data: InitVar[CounterValuesType] + communicable_disease_deaths_this_year: CounterValueType = field(init=False) + seasonal_flu_deaths_this_year: CounterValueType = field(init=False) + deaths_of_children_under_5_this_year: CounterValueType = field(init=False) + abortions_this_year: CounterValueType = field(init=False) + deaths_of_mothers_during_birth_this_year: CounterValueType = field(init=False) + hiv_aids_infected_people: CounterValueType = field(init=False) + deaths_caused_by_hiv_aids_this_year: CounterValueType = field(init=False) + deaths_caused_by_cancer_this_year: CounterValueType = field(init=False) + deaths_caused_by_malaria_this_year: CounterValueType = field(init=False) + cigarettes_smoked_today: CounterValueType = field(init=False) + deaths_caused_by_smoking_this_year: CounterValueType = field(init=False) + deaths_caused_by_alcohol_this_year: CounterValueType = field(init=False) + suicides_this_year: CounterValueType = field(init=False) + money_spent_on_illegal_drugs_this_year: CounterValueType = field(init=False) + road_traffic_accident_fatalities_this_year: CounterValueType = field(init=False) + + def __post_init__(self, _data: CounterValuesType) -> None: + self.communicable_disease_deaths_this_year = _data.get('dth1s_communicable_disaxs') + self.seasonal_flu_deaths_this_year = _data.get('dth1s_flu') + self.deaths_of_children_under_5_this_year = _data.get('dth1s_children') + self.abortions_this_year = _data.get('ab') + self.deaths_of_mothers_during_birth_this_year = _data.get('dth1s_maternal') + self.hiv_aids_infected_people = _data.get('infections_hiv') + self.deaths_caused_by_hiv_aids_this_year = _data.get('dth1s_ads') + self.deaths_caused_by_cancer_this_year = _data.get('dth1s_cancer') + self.deaths_caused_by_malaria_this_year = _data.get('dth1s_malarial') + self.cigarettes_smoked_today = _data.get('cigarettes_smoked') + self.deaths_caused_by_smoking_this_year = _data.get('dth1s_cigarettes') + self.deaths_caused_by_alcohol_this_year = _data.get('dth1s_alchool') + self.suicides_this_year = _data.get('sui') + self.money_spent_on_illegal_drugs_this_year = _data.get('drug_spending') + self.road_traffic_accident_fatalities_this_year = _data.get('dth1s_cars') diff --git a/worldometer/world/country_codes.py b/worldometer/world/country_codes.py new file mode 100644 index 0000000..9fcd746 --- /dev/null +++ b/worldometer/world/country_codes.py @@ -0,0 +1,76 @@ +from copy import deepcopy +from dataclasses import dataclass +from typing import List + +from worldometer.scraper import get_data_tables + + +@dataclass +class CountryCodesData: + """Represents a data row from the respective table. + + Attributes + ---------- + country: str + calling_code: str + three_letter_iso: str + two_letter_iso: str + three_digit_iso_numeric: str + """ + country: str + calling_code: str + three_letter_iso: str + two_letter_iso: str + three_digit_iso_numeric: str + + _table_position = 0 + + +class CountryCodes: + """Represents the data table of some codes used by each country. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original table. + + Notes + ----- + Check the source table in + `Worldometers Country Codes `_. + """ + source_path = '/country-codes' + new_column_names = ( + 'country', + 'calling_code', + 'three_letter_iso', + 'two_letter_iso', + 'three_digit_iso_numeric' + ) + + def __init__(self) -> None: + self._data = self._load_data() + + def _load_data(self) -> List[CountryCodesData]: + dts = get_data_tables( + path_url=self.source_path, + new_column_names=[ + self.new_column_names + ] + ) + return [ + CountryCodesData(**data_row) + for data_row in dts[CountryCodesData._table_position] + ] + + @property + def data(self) -> List[CountryCodesData]: + """Get a list of all the data from the table. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data) diff --git a/worldometer/world/geography/__init__.py b/worldometer/world/geography/__init__.py new file mode 100644 index 0000000..da75559 --- /dev/null +++ b/worldometer/world/geography/__init__.py @@ -0,0 +1,33 @@ +""" +geography package +----------------- + +The `geography` package provides access to various data related +to geographic information about the world, regions, and their countries. + +To understand the significance of each of these data sets, +their sources, and any other related information, +please visit the official page at https://www.worldometers.info/geography/how-many-countries-are-there-in-the-world +""" + +__all__ = [ + 'LargestCountries', + 'WorldCountries', + 'AsiaCountries', + 'AfricaCountries', + 'EuropeCountries', + 'LatinAmericanAndTheCaribbeanCountries', + 'NorthernAmericanCountries', + 'OceaniaCountries' +] + +from worldometer.world.geography.largest_countries import LargestCountries +from worldometer.world.geography.countries import ( + WorldCountries, + AsiaCountries, + AfricaCountries, + EuropeCountries, + LatinAmericanAndTheCaribbeanCountries, + NorthernAmericanCountries, + OceaniaCountries +) diff --git a/worldometer/world/geography/countries.py b/worldometer/world/geography/countries.py new file mode 100644 index 0000000..75694ed --- /dev/null +++ b/worldometer/world/geography/countries.py @@ -0,0 +1,309 @@ +from copy import deepcopy +from dataclasses import dataclass +from typing import List, Tuple + +from worldometer.scraper import get_data_tables + + +@dataclass +class WorldCountriesData: + """Represents a data row from the respective table. + + Attributes + ---------- + idx: int + country: str + population: int + world_share: str + land_area: int + """ + idx: int + country: str + population: int + world_share: str + land_area: int + + _table_position = 0 + + +@dataclass +class CountryData: + """Represents a data row from the respective table. + + Attributes + ---------- + idx: int + country: str + population: int + subregion: str + """ + idx: int + country: str + population: int + subregion: str + + _table_position = 0 + + +@dataclass +class DependencyData: + """Represents a data row from the respective table. + + Attributes + ---------- + idx: int + territory: str + population: int + dependency_of: str + """ + idx: int + territory: str + population: int + dependency_of: str + + _table_position = 1 + + +class WorldCountries: + """Represents the data table of a list of countries in the world. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original table. + total : int + The total number of countries in the world. + + + Notes + ----- + Check the source table in + `List of countries `_. + """ + source_path = '/geography/how-many-countries-are-there-in-the-world' + new_column_names = ( + 'idx', + 'country', + 'population', + 'world_share', + 'land_area' + ) + + def __init__(self) -> None: + self._data = self._load_data() + self.total = len(self._data) + + def _load_data(self) -> List[WorldCountriesData]: + dts = get_data_tables( + path_url=self.source_path, + new_column_names=[ + self.new_column_names + ] + ) + return [ + WorldCountriesData(**data_row) + for data_row in dts[WorldCountriesData._table_position] + ] + + def countries(self) -> List[WorldCountriesData]: + """Get a list of all the countries' data from the table. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data) + + +class _RegionCountries: + + source_path = '[override]' + new_column_names = ( + ( + 'idx', + 'country', + 'population', + 'subregion' + ), + ( + 'idx', + 'territory', + 'population', + 'dependency_of' + ) + ) + + def __init__(self) -> None: + self._data = self._load_data() + self.total = len(self._data[CountryData._table_position]) + + def _load_data( + self + ) -> Tuple[ + List[CountryData], + List[DependencyData] + ]: + dts = get_data_tables( + path_url=self.source_path, + new_column_names=[ + *self.new_column_names + ] + ) + + return ( + [ + CountryData(**data_row) + for data_row in dts[CountryData._table_position] + ], + [ + DependencyData(**data_row) + for data_row in dts[DependencyData._table_position] + ] + ) + + def countries(self) -> List[CountryData]: + """Get a list of all the data for countries in the + region from the table. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data[CountryData._table_position]) # type: ignore + + def dependencies(self) -> List[DependencyData]: + """Get a list of all the data for dependencies in the + region from the table. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data[DependencyData._table_position]) # type: ignore + + +class AsiaCountries(_RegionCountries): + """Represents the data tables of Asia's countries. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original tables. + total : int + The total number of countries in the region. + + Notes + ----- + Check the source tables in + `Countries in Asia `_. + """ + source_path = '/geography/how-many-countries-in-asia' + + +class AfricaCountries(_RegionCountries): + """Represents the data tables of Africa's countries. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original tables. + total : int + The total number of countries in the region. + + Notes + ----- + Check the source tables in + `Countries in Africa `_. + """ + source_path = '/geography/how-many-countries-in-africa' + + +class EuropeCountries(_RegionCountries): + """Represents the data tables of Europe's countries. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original tables. + total : int + The total number of countries in the region. + + Notes + ----- + Check the source tables in + `Countries in Europe `_. + """ + source_path = '/geography/how-many-countries-in-europe' + + +class LatinAmericanAndTheCaribbeanCountries(_RegionCountries): + """Represents the data tables of Latin American And The Caribbean countries. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original tables. + total : int + The total number of countries in the region. + + Notes + ----- + Check the source tables in + `Countries in Latin American And The + Caribbean `_. + """ + source_path = '/geography/how-many-countries-in-latin-america' + + +class NorthernAmericanCountries(_RegionCountries): + """Represents the data tables of Northern American countries. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original tables. + total : int + The total number of countries in the region. + + Notes + ----- + Check the source tables in + `Countries in Northern American `_. + """ + source_path = '/geography/how-many-countries-in-northern-america' + + +class OceaniaCountries(_RegionCountries): + """Represents the data tables of the Oceania countries. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original tables. + total : int + The total number of countries in the region. + + Notes + ----- + Check the source tables in + `Countries in Oceania `_. + """ + source_path = '/geography/how-many-countries-in-oceania' diff --git a/worldometer/world/geography/largest_countries.py b/worldometer/world/geography/largest_countries.py new file mode 100644 index 0000000..a5a8a6e --- /dev/null +++ b/worldometer/world/geography/largest_countries.py @@ -0,0 +1,82 @@ +from copy import deepcopy +from dataclasses import dataclass +from typing import List + +from worldometer.scraper import get_data_tables + + +@dataclass +class LargestCountriesData: + """Represents a data row from the respective table. + + Attributes + ---------- + idx: int + country: str + total_area_km2: int + total_area_mi2: int + land_area_km2: int + land_area_mi2: int + percentage_of_world_landmass: str + """ + idx: int + country: str + total_area_km2: int + total_area_mi2: int + land_area_km2: int + land_area_mi2: int + percentage_of_world_landmass: str + + _table_position = 0 + + +class LargestCountries: + """Represents the data table of the largest countries in the world (by area). + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original table. + + Notes + ----- + Check the source table in the + `Largest Countries in the World `_. + """ + source_path = '/geography/largest-countries-in-the-world' + new_column_names = ( + 'idx', + 'country', + 'total_area_km2', + 'total_area_mi2', + 'land_area_km2', + 'land_area_mi2', + 'percentage_of_world_landmass' + ) + + def __init__(self) -> None: + self._data = self._load_data() + + def _load_data(self) -> List[LargestCountriesData]: + dts = get_data_tables( + path_url=self.source_path, + new_column_names=[ + self.new_column_names + ] + ) + return [ + LargestCountriesData(**data_row) + for data_row in dts[LargestCountriesData._table_position] + ] + + @property + def data(self) -> List[LargestCountriesData]: + """Get a list of all the data from the table. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data) diff --git a/worldometer/world/population/__init__.py b/worldometer/world/population/__init__.py new file mode 100644 index 0000000..9f44edb --- /dev/null +++ b/worldometer/world/population/__init__.py @@ -0,0 +1,43 @@ +""" +population package +------------------ + +The `population` package provides access to various data related +to world populations, regions, and their countries. Additionally, +it includes historical data ranging from 1950 to the present day +and future projections extending to 2050. + +To understand the significance of each of these data sets, +their sources, and any other related information, +please visit the official page at https://www.worldometers.info/population +""" + +__all__ = [ + 'CountriesByPopulation', + 'LargestCities', + 'MostPopulousCountries', + 'WorldPopulationByRegion', + 'WorldPopulationByYear', + 'WorldPopulationProjections', + 'AsiaPopulation', + 'AfricaPopulation', + 'EuropePopulation', + 'LatinAmericanAndTheCaribbeanPopulation', + 'NorthernAmericanPopulation', + 'OceaniaPopulation' +] + +from worldometer.world.population.countries_by_population import CountriesByPopulation +from worldometer.world.population.largest_cities import LargestCities +from worldometer.world.population.most_populous_countries import MostPopulousCountries +from worldometer.world.population.by_region import WorldPopulationByRegion +from worldometer.world.population.by_year import WorldPopulationByYear +from worldometer.world.population.projections import WorldPopulationProjections +from worldometer.world.population.regions import ( + AsiaPopulation, + AfricaPopulation, + EuropePopulation, + LatinAmericanAndTheCaribbeanPopulation, + NorthernAmericanPopulation, + OceaniaPopulation +) diff --git a/worldometer/world/population/by_region.py b/worldometer/world/population/by_region.py new file mode 100644 index 0000000..ce6812e --- /dev/null +++ b/worldometer/world/population/by_region.py @@ -0,0 +1,187 @@ +from copy import deepcopy +from dataclasses import dataclass +from typing import List, Tuple + +from worldometer.scraper import get_data_tables + + +@dataclass +class CurrentWorldPopulationByRegionData: + """Represents a data row from the respective table. + + Attributes + ---------- + idx: int + region: str + population: int + yearly_change: str + net_change: int + density: int + area: int + migrants: int + fertility_rate: float + median_age: int + urban_population: str + world_share: str + """ + idx: int + region: str + population: int + yearly_change: str + net_change: int + density: int + area: int + migrants: int + fertility_rate: float + median_age: int + urban_population: str + world_share: str + + _table_position = 0 + + +@dataclass +class PastWorldPopulationByRegionData: + """Represents a data row from the respective table. + + Attributes + ---------- + idx: int + region: str + population: int + world_share: str + """ + idx: int + region: str + population: int + world_share: str + + _table_position = 1 + + +@dataclass +class FutureWorldPopulationByRegionData: + """Represents a data row from the respective table. + + Attributes + ---------- + idx: int + region: str + population: int + world_share: str + """ + idx: int + region: str + population: int + world_share: str + + _table_position = 2 + + +class WorldPopulationByRegion: + """Represents the data table of regions in the world by population. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original table. + + Notes + ----- + Check the source table in + `Regions in the world by population `_. + """ + source_path = '/world-population/population-by-region' + new_column_names = ( + ( + 'idx', + 'region', + 'population', + 'yearly_change', + 'net_change', + 'density', + 'area', + 'migrants', + 'fertility_rate', + 'median_age', + 'urban_population', + 'world_share' + ), + ( + 'idx', + 'region', + 'population', + 'world_share' + ), + ( + 'idx', + 'region', + 'population', + 'world_share' + ) + ) + + def __init__(self) -> None: + self._data = self._load_data() + + def _load_data( + self + ) -> Tuple[ + List[CurrentWorldPopulationByRegionData], + List[PastWorldPopulationByRegionData], + List[FutureWorldPopulationByRegionData] + ]: + dts = get_data_tables( + path_url=self.source_path, + new_column_names=[ + *self.new_column_names + ] + ) + + return ( + [ + CurrentWorldPopulationByRegionData(**data_row) + for data_row in dts[CurrentWorldPopulationByRegionData._table_position] + ], + [ + PastWorldPopulationByRegionData(**data_row) + for data_row in dts[PastWorldPopulationByRegionData._table_position] + ], + [ + FutureWorldPopulationByRegionData(**data_row) + for data_row in dts[FutureWorldPopulationByRegionData._table_position] + ] + ) + + def current(self) -> List[CurrentWorldPopulationByRegionData]: + """Get a list of all the current data from the table. + + These data are related to the current year. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data[CurrentWorldPopulationByRegionData._table_position]) # type: ignore + + def past(self) -> List[PastWorldPopulationByRegionData]: + """Get a list of all historical data from the table. + + These data pertain to the year 1950. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data[PastWorldPopulationByRegionData._table_position]) # type: ignore + + def future(self) -> List[FutureWorldPopulationByRegionData]: + """Get a list of all future data from the table. + + These data are an estimate for the year 2050. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data[FutureWorldPopulationByRegionData._table_position]) # type: ignore diff --git a/worldometer/world/population/by_year.py b/worldometer/world/population/by_year.py new file mode 100644 index 0000000..fa83ee8 --- /dev/null +++ b/worldometer/world/population/by_year.py @@ -0,0 +1,76 @@ +from copy import deepcopy +from dataclasses import dataclass +from typing import List + +from worldometer.scraper import get_data_tables + + +@dataclass +class WorldPopulationByYearData: + """Represents a data row from the respective table. + + Attributes + ---------- + year: int + world_population: int + yearly_change: str + net_change: float + density: float + """ + year: int + world_population: int + yearly_change: str + net_change: float + density: float + + _table_position = 0 + + +class WorldPopulationByYear: + """Represents the data table of the world population by year. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original table. + + Notes + ----- + Check the source table in the + `world population by year `_. + """ + source_path = '/world-population/world-population-by-year' + new_column_names = ( + 'year', + 'world_population', + 'yearly_change', + 'net_change', + 'density' + ) + + def __init__(self) -> None: + self._data = self._load_data() + + def _load_data(self) -> List[WorldPopulationByYearData]: + dts = get_data_tables( + path_url=self.source_path, + new_column_names=[ + self.new_column_names + ] + ) + return [ + WorldPopulationByYearData(**data_row) + for data_row in dts[WorldPopulationByYearData._table_position] + ] + + @property + def data(self) -> List[WorldPopulationByYearData]: + """Get a list of all the data from the table. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data) diff --git a/worldometer/world/population/countries_by_population.py b/worldometer/world/population/countries_by_population.py new file mode 100644 index 0000000..09f32a4 --- /dev/null +++ b/worldometer/world/population/countries_by_population.py @@ -0,0 +1,97 @@ +from copy import deepcopy +from dataclasses import dataclass +from typing import List + +from worldometer.scraper import get_data_tables + + +@dataclass +class CountriesByPopulationData: + """Represents a data row from the respective table. + + Attributes + ---------- + idx: int + country: str + population: int + yearly_change: str + net_change: int + density: int + land_area: int + migrants: int + fertility_rate: float + median_age: float + urban_population: str + world_share: str + """ + idx: int + country: str + population: int + yearly_change: str + net_change: int + density: int + land_area: int + migrants: int + fertility_rate: float + median_age: float + urban_population: str + world_share: str + + _table_position = 0 + + +class CountriesByPopulation: + """Represents the data table of countries in the world by population. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original table. + + Notes + ----- + Check the source table in + `Countries in the world by population `_. + """ + source_path = '/world-population/population-by-country' + new_column_names = ( + 'idx', + 'country', + 'population', + 'yearly_change', + 'net_change', + 'density', + 'land_area', + 'migrants', + 'fertility_rate', + 'median_age', + 'urban_population', + 'world_share' + ) + + def __init__(self) -> None: + self._data = self._load_data() + + def _load_data(self) -> List[CountriesByPopulationData]: + dts = get_data_tables( + path_url=self.source_path, + new_column_names=[ + self.new_column_names + ] + ) + return [ + CountriesByPopulationData(**data_row) + for data_row in dts[CountriesByPopulationData._table_position] + ] + + @property + def data(self) -> List[CountriesByPopulationData]: + """Get a list of all the data from the table. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data) diff --git a/worldometer/world/population/largest_cities.py b/worldometer/world/population/largest_cities.py new file mode 100644 index 0000000..a83c16a --- /dev/null +++ b/worldometer/world/population/largest_cities.py @@ -0,0 +1,80 @@ +from copy import deepcopy +from dataclasses import dataclass +from typing import List + +from worldometer.scraper import get_data_tables + + +@dataclass +class LargestCitiesData: + """Represents a data row from the respective table. + + Attributes + ---------- + rank: int + urban_area: str + population_estimate: str + country: str + land_area: int + density: int + """ + rank: int + urban_area: str + population_estimate: str + country: str + land_area: int + density: int + + _table_position = 0 + + +class LargestCities: + """Represents the data table of the largest cities in the world. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original table. + + Notes + ----- + Check the source table in the + `largest cities in the world `_. + """ + source_path = '/population/largest-cities-in-the-world' + new_column_names = ( + 'rank', + 'urban_area', + 'population_estimate', + 'country', + 'land_area', + 'density' + ) + + def __init__(self) -> None: + self._data = self._load_data() + + def _load_data(self) -> List[LargestCitiesData]: + dts = get_data_tables( + path_url=self.source_path, + new_column_names=[ + self.new_column_names + ], + attrs=None + ) + return [ + LargestCitiesData(**data_row) + for data_row in dts[LargestCitiesData._table_position] + ] + + @property + def data(self) -> List[LargestCitiesData]: + """Get a list of all the data from the table. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data) diff --git a/worldometer/world/population/most_populous_countries.py b/worldometer/world/population/most_populous_countries.py new file mode 100644 index 0000000..10edd26 --- /dev/null +++ b/worldometer/world/population/most_populous_countries.py @@ -0,0 +1,172 @@ +from copy import deepcopy +from dataclasses import dataclass +from typing import List, Tuple + +from worldometer.scraper import get_data_tables + + +@dataclass +class CurrentMostPopulousCountriesData: + """Represents a data row from the respective table. + + Attributes + ---------- + idx: int + country: str + population: int + yearly_change: str + world_share: str + """ + idx: int + country: str + population: int + yearly_change: str + world_share: str + + _table_position = 0 + + +@dataclass +class PastMostPopulousCountriesData: + """Represents a data row from the respective table. + + Attributes + ---------- + idx: int + country: str + population: int + world_share: str + rank: str + """ + idx: int + country: str + population: int + world_share: str + rank: str + + _table_position = 1 + + +@dataclass +class FutureMostPopulousCountriesData: + """Represents a data row from the respective table. + + Attributes + ---------- + idx: int + country: str + population: int + world_share: str + rank: str + """ + idx: int + country: str + population: int + world_share: str + rank: str + + _table_position = 2 + + +class MostPopulousCountries: + """Represents the data table of most populous countries in the world. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original table. + + Notes + ----- + Check the source table in + `Most populous countries in the world `_. + """ + source_path = '/population/most-populous-countries' + new_column_names = ( + ( + 'idx', + 'country', + 'population', + 'yearly_change', + 'world_share' + ), + ( + 'idx', + 'country', + 'population', + 'world_share', + 'rank' + ), + ( + 'idx', + 'country', + 'population', + 'world_share', + 'rank' + ) + ) + + def __init__(self) -> None: + self._data = self._load_data() + + def _load_data( + self + ) -> Tuple[ + List[CurrentMostPopulousCountriesData], + List[PastMostPopulousCountriesData], + List[FutureMostPopulousCountriesData] + ]: + dts = get_data_tables( + path_url=self.source_path, + new_column_names=[ + *self.new_column_names + ] + ) + + return ( + [ + CurrentMostPopulousCountriesData(**data_row) + for data_row in dts[CurrentMostPopulousCountriesData._table_position] + ], + [ + PastMostPopulousCountriesData(**data_row) + for data_row in dts[PastMostPopulousCountriesData._table_position] + ], + [ + FutureMostPopulousCountriesData(**data_row) + for data_row in dts[FutureMostPopulousCountriesData._table_position] + ] + ) + + def current(self) -> List[CurrentMostPopulousCountriesData]: + """Get a list of all the current data from the table. + + These data are related to the current year. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data[CurrentMostPopulousCountriesData._table_position]) # type: ignore + + def past(self) -> List[PastMostPopulousCountriesData]: + """Get a list of all historical data from the table. + + These data pertain to the year 1950. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data[PastMostPopulousCountriesData._table_position]) # type: ignore + + def future(self) -> List[FutureMostPopulousCountriesData]: + """Get a list of all future data from the table. + + These data are an estimate for the year 2050. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data[FutureMostPopulousCountriesData._table_position]) # type: ignore diff --git a/worldometer/world/population/projections.py b/worldometer/world/population/projections.py new file mode 100644 index 0000000..9b7a4ab --- /dev/null +++ b/worldometer/world/population/projections.py @@ -0,0 +1,76 @@ +from copy import deepcopy +from dataclasses import dataclass +from typing import List + +from worldometer.scraper import get_data_tables + + +@dataclass +class WorldPopulationProjectionsData: + """Represents a data row from the respective table. + + Attributes + ---------- + year: int + world_population: int + yearly_change: str + net_change: int + density: int + """ + year: int + world_population: int + yearly_change: str + net_change: int + density: int + + _table_position = 0 + + +class WorldPopulationProjections: + """Represents the data table of the world population projections. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original table. + + Notes + ----- + Check the source table in + `World Population Projections `_. + """ + source_path = '/world-population/world-population-projections' + new_column_names = ( + 'year', + 'world_population', + 'yearly_change', + 'net_change', + 'density' + ) + + def __init__(self) -> None: + self._data = self._load_data() + + def _load_data(self) -> List[WorldPopulationProjectionsData]: + dts = get_data_tables( + path_url=self.source_path, + new_column_names=[ + self.new_column_names + ] + ) + return [ + WorldPopulationProjectionsData(**data_row) + for data_row in dts[WorldPopulationProjectionsData._table_position] + ] + + @property + def data(self) -> List[WorldPopulationProjectionsData]: + """Get a list of all the data from the table. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data) diff --git a/worldometer/world/population/regions.py b/worldometer/world/population/regions.py new file mode 100644 index 0000000..5df45ac --- /dev/null +++ b/worldometer/world/population/regions.py @@ -0,0 +1,318 @@ +from copy import deepcopy +from dataclasses import dataclass +from typing import List, Tuple, Union + +from worldometer.scraper import get_data_tables, get_rts_counters_object + + +@dataclass +class SubregionData: + """Represents a data row from the respective table. + + Attributes + ---------- + area: int + population: str + """ + area: int + population: str + + _table_position = 0 + + +@dataclass +class HistoricalData: + """Represents a data row from the respective table. + + Attributes + ---------- + year: int + population: int + yearly_change_percent: str + yearly_change: int + migrants: int + median_age: float + fertility_rate: float + density: int + urban_population_percent: str + urban_population: int + world_share: str + world_population: int + rank: int + """ + year: int + population: int + yearly_change_percent: str + yearly_change: int + migrants: int + median_age: float + fertility_rate: float + density: int + urban_population_percent: str + urban_population: int + world_share: str + world_population: int + rank: int + + _table_position = 1 + + +@dataclass +class ForecastData: + """Represents a data row from the respective table. + + Attributes + ---------- + year: int + population: int + yearly_change_percent: str + yearly_change: int + migrants: int + median_age: float + fertility_rate: float + density: int + urban_population_percent: str + urban_population: int + world_share: str + world_population: int + rank: int + """ + year: int + population: int + yearly_change_percent: str + yearly_change: int + migrants: int + median_age: float + fertility_rate: float + density: int + urban_population_percent: str + urban_population: int + world_share: str + world_population: int + rank: int + + _table_position = 2 + + +class _RegionPopulation: + + _key_rts_counters = '[override]' + source_path = '[override]' + new_column_names = ( + ( + 'area', + 'population' + ), + ( + 'year', + 'population', + 'yearly_change_percent', + 'yearly_change', + 'migrants', + 'median_age', + 'fertility_rate', + 'density', + 'urban_population_percent', + 'urban_population', + 'world_share', + 'world_population', + 'rank' + ), + ( + 'year', + 'population', + 'yearly_change_percent', + 'yearly_change', + 'migrants', + 'median_age', + 'fertility_rate', + 'density', + 'urban_population_percent', + 'urban_population', + 'world_share', + 'world_population', + 'rank' + ) + ) + + def __init__(self) -> None: + self._data = self._load_data() + + def _load_data( + self + ) -> Tuple[ + List[SubregionData], + List[HistoricalData], + List[ForecastData] + ]: + dts = get_data_tables( + path_url=self.source_path, + new_column_names=[ + *self.new_column_names + ] + ) + + return ( + [ + SubregionData(**data_row) + for data_row in dts[SubregionData._table_position] + ], + [ + HistoricalData(**data_row) + for data_row in dts[HistoricalData._table_position] + ], + [ + ForecastData(**data_row) + for data_row in dts[ForecastData._table_position] + ] + ) + + def live(self) -> Union[int, float, None]: + """Get a live population counter for the respective region.""" + rts_counters = get_rts_counters_object(path_url=self.source_path) + return rts_counters.get(self._key_rts_counters) + + def subregions(self) -> List[SubregionData]: + """Get a list of all the subregions' data from the table. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data[SubregionData._table_position]) # type: ignore + + def historical(self) -> List[HistoricalData]: + """Get a list of all historical data from the table. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data[HistoricalData._table_position]) # type: ignore + + def forecast(self) -> List[ForecastData]: + """Get a list of all forecast data from the table. + + Each index in the list contains an object representing + a data row of the table. + """ + return deepcopy(self._data[ForecastData._table_position]) # type: ignore + + +class AsiaPopulation(_RegionPopulation): + """Represents the data tables of Asia's populations. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original tables. + + Notes + ----- + Check the source tables in + `Asia Population `_. + """ + _key_rts_counters = 'asia-population' + source_path = '/world-population/asia-population' + + +class AfricaPopulation(_RegionPopulation): + """Represents the data tables of Africa's populations. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original tables. + + Notes + ----- + Check the source tables in + `Africa Population `_. + """ + _key_rts_counters = 'africa-population' + source_path = '/world-population/africa-population' + + +class EuropePopulation(_RegionPopulation): + """Represents the data tables of Europe's populations. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original tables. + + Notes + ----- + Check the source tables in + `Europe Population `_. + """ + _key_rts_counters = 'europe-population' + source_path = '/world-population/europe-population' + + +class LatinAmericanAndTheCaribbeanPopulation(_RegionPopulation): + """Represents the data tables of Latin American and Caribbean populations. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original tables. + + Notes + ----- + Check the source tables in + `Latin America and the Caribbean + Population `_. + """ + _key_rts_counters = 'latin-america-and-the-caribbean-population' + source_path = '/world-population/latin-america-and-the-caribbean-population' + + +class NorthernAmericanPopulation(_RegionPopulation): + """Represents the data tables of Northern American populations. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original tables. + + Notes + ----- + Check the source tables in + `Northern American Population `_. + """ + _key_rts_counters = 'northern-america-population' + source_path = '/world-population/northern-america-population' + + +class OceaniaPopulation(_RegionPopulation): + """Represents the data tables of the Oceania populations. + + Attributes + ---------- + source_path : str + The data source path. + new_column_names : tuple + The new column names that will be used to replace those + of the original tables. + + Notes + ----- + Check the source tables in + `Oceania Population `_. + """ + _key_rts_counters = 'oceania-population' + source_path = '/world-population/oceania-population'