diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..f8aa07d --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,39 @@ +name: CI checks + +on: + pull_request: + branches: [master] + push: + branches: [master] + +jobs: + ci_checks: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12", "3.13"] + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + - name: Setup up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + sudo apt install -y libcurl4-openssl-dev + - name: Setup pip and pipenv + run: | + python3 -m pip install --upgrade pip + pip install pipenv + pipenv sync --dev --python ${{ matrix.python-version }} + pipenv run pip install -e . + - name: Run tests + run: make test + - name: Lint + run: make lint + - name: Style-check + run: make style-check diff --git a/.gitignore b/.gitignore index 224354e..cc708d0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,206 @@ build/ *.tox *.pytest* *.idea +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# 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 + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.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 +#poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +#pdm.lock +#pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +#pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# 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 +.envrc +.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/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +.vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 56f9cc1..0000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -language: python -cache: pip -python: -- '2.7' -- '3.6' -before_install: -- sudo apt-get install libcurl4-openssl-dev -install: -- make init; -- make install; -script: -- make test; - -jobs: - include: - - stage: style-check - python: 3.6 - script: - - make style-check - - make lint diff --git a/Makefile b/Makefile index dcc5dce..45246f1 100644 --- a/Makefile +++ b/Makefile @@ -5,10 +5,14 @@ init: pipenv install --dev style-check: - pipenv run flake8 --config code-quality/python/flake8 ntripbrowser + pipenv run ruff format --check --config code-quality/python/ruff.toml ntripbrowser lint: - pipenv run pylint --rcfile code-quality/python/pylintrc ntripbrowser + pipenv run ruff check --config code-quality/python/ruff.toml ntripbrowser + +reformat: + pipenv run ruff format --config code-quality/python/ruff.toml ntripbrowser + pipenv run ruff check --fix --config code-quality/python/ruff.toml ntripbrowser test: pipenv run pytest tests diff --git a/Pipfile b/Pipfile index 289039c..61eeaff 100644 --- a/Pipfile +++ b/Pipfile @@ -4,18 +4,14 @@ verify_ssl = true name = "pypi" [dev-packages] -scandir = "*" -pathlib2 = "*" -pytest = "==4.6.0" -pylint = "==1.9.3" -"flake8" = "*" +pytest = "*" mock = "*" -tox = "==3.0.0" +ruff = "*" [packages] -cchardet = "==2.1.4" +cchardet = "==2.2.0a2" geopy = "==1.14.0" pager = "==3.3" -pycurl = "==7.43.0.1" -texttable = "==1.2.1" +pycurl = "==7.45.2" +texttable = "==1.7.0" cachecontrol = ">=0.12.4" diff --git a/Pipfile.lock b/Pipfile.lock index 82ad41b..ce9c657 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "a51bb4f44b1a54e4bb4b536b1095a7a2dc9f4e1996dbf9436ff4e86a05ff8d6d" + "sha256": "8df8eb243140afb4d9d7d96ab411721629dad094cf61549997f6b97f9a1f3993" }, "pipfile-spec": 6, "requires": {}, @@ -16,59 +16,177 @@ "default": { "cachecontrol": { "hashes": [ - "sha256:cef77effdf51b43178f6a2d3b787e3734f98ade253fa3187f3bb7315aaa42ff7" + "sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11", + "sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae" ], "index": "pypi", - "version": "==0.12.5" + "markers": "python_version >= '3.9'", + "version": "==0.14.3" }, "cchardet": { "hashes": [ - "sha256:079aa02a14072874d943a671ba778a9def5b0e3cedc2ac9f59308526cfb31472", - "sha256:3e048a21688dcb4c797f40c8deb3600887bcaf435620256fd8becd4252012750", - "sha256:41fced7a6f05ef859fe3eac89fc2120aca3cbbfd2b6c803bed3ee4bf02956903", - "sha256:440903d5dca3d326f4b841e7fa760b6af1be4f950ead1a6ff77b76eaa46f0cd3", - "sha256:50170f346527c5df4d3cb94648ca187c666e61c0db6e510b984e867c44709d8b", - "sha256:6c55a6e7bc7337671c9f1ad90746c0efb2b2979ff4305c7ca1d7d381f05174c1", - "sha256:7f581ea172b252034f745dfd49733966b73b73907bdef0b47ad5f2008b797d54", - "sha256:80f7b087198827e60c81574c321b12f89188eae626ae1567d66808928be42f88", - "sha256:8ba753ff73ca2f3554999a0e027eab9450f6ffdb7e92e1b4e13b52be89995349", - "sha256:9ad8f61d6d1ca37bd4b954ad92d461ea4f58d0dc413b0790a5abed7c09e54996", - "sha256:a35bd23cedbaa87cc9300af1dd10bb03fda41894045fbca7bfdf1d350b813f25", - "sha256:a8feb9a7def2310e18c27e485a21a38669abe8c2e36b93c6ce1a1363495d4cdf", - "sha256:aa9dd4cee8a5210a6d0a7b263b98dc50637e00401fc4a5ad3ce2dbef54fdfa02", - "sha256:ab9858a0673262e467619df91f425cfef0590dcf5deef5c0c7945e9dc4dbd7d8", - "sha256:b09a488bbb35be95f82845e3c4312be9025e8377975b027eee67e0b39445e070", - "sha256:b2893d558761b3534cddf5a49ba8d77df3d8f964d7b14680b925f4a85fc13476", - "sha256:b5a8f9b229a30cd2432572d15e169483bc47c24418772ff58d0585050631c2fd", - "sha256:bded54eeccd5f810bc69e076b3d9a35819a92e5e0559ad274b9ae9061b1b881d", - "sha256:cbc206061e69561af6e4cba11f99abd928346c6b5bcdc83eb32ae40e9fc23a5f", - "sha256:cc9745e0400da4cfb49f075e7819f22473b66443f953427058fee2c7b9547cc0", - "sha256:db30bf3825702c07fc55a290d41663fd8151f870642a15667bbabf81fff21e0b", - "sha256:eeeb1b95bb5851dda93ee522860a0e6066d47921cb1d540cb778346e37e5a524", - "sha256:f1c3919fb71ac5da3aeee42c5b731c99dcd2beed71db7fdc28ca993c173f0402" + "sha256:078083933bc9ce86eddf35af3fd590232c826462b29fbe3da7cd669dd177871d", + "sha256:173dc7d2aa051cdff01a1bbe3a681e50bf3cc2fcb88d0575c37c07943a3f0a6c", + "sha256:193c82df2d471f021e0813f8af611cf0067fb7fe51637723209836a76728e593", + "sha256:1b0c70355a6acff57e8a07aa5607747b3668a1166f499a14492a8e06f3ee93b7", + "sha256:23add63d7bb1efd484128f1ce53ad4ca807ba1b8d04622a341ad2776f5734a79", + "sha256:299e80c86efbd02d52449f0c296cdb9839e0c607992ccef33659d98518cb76db", + "sha256:2efddfe92a2f74dce18b783962dc8b4bf36ed2b42137ad4aa792635dcd3be73b", + "sha256:365b549190ec28819ec594007203ba471770b8dd8b4737ac4f6bbefeb1c4e6e7", + "sha256:39c8f33c277080836fd7dfd47fbfcf8641d2ee2af6db460a92877a7742868427", + "sha256:40d8046004bd4ca9a3ed20ac4862a75740729daecc44ffa78e3fa2f402a32835", + "sha256:4761d842377c924f2f58a5cabaadd317dc74b3a4c04b56e389c1901ff766854e", + "sha256:4cb82e1b5d343850a854dede7fa9915ac1d9c1030a3f798248fbacca40b79379", + "sha256:4eca9d72232ce573557ae165b3eec153611979961ec209bc2fc376a7bb7c0776", + "sha256:516f8c81e6ac703aa192f4c79adc42c78fc450bd61f1fb271348b227a9535515", + "sha256:529f2faa7c723de0df22c2dbbd07b228804cfa4ea4f6ff85e4323683a2061bee", + "sha256:54280ab70b6345bd06a3274fd2991a8bc9840b8da3727cf5ace2efe5ecd06ff3", + "sha256:54feb6f5c2f54051ec3af55c6b9d0ae0870689abcaec191daf5287d2c2050730", + "sha256:5a78c4fcf431c0629c25dd605c2eca7e6c553d919ac39301485c44b3cacba6c1", + "sha256:6623be177efd3231fc8f7bd2da4bbc558f5fd1cd099ec0db83a913999cd257f6", + "sha256:677aa6ff5f69ba899ba0ab40080d3e60503240cd8b4de4de44985a627b8d5f76", + "sha256:7a9e5ba63bdee6ce9bb97492d973fa236e2c0fc01f8ae13329c08e7861ffe119", + "sha256:7ca99c25e1a1f02ff35f00562dca781b3782db0e3163a0c3bab52cb57688674a", + "sha256:83596074a4852ad7002621315a9c0662a1270a3747aec6d9c830ac8816c9190b", + "sha256:8bfe2093e841e6d033476c8aa10acdfb4d2dd5d7594435e3798f3d1c9cd2d09f", + "sha256:a8bc49d95ecaf1a70c03885bc990bfed320ffc3edeabd0d8f614a2a83b4ccc3b", + "sha256:b26ebd946e552a12fb1e60d474e09d6e742b52d360d12308d5ca6c810e05278c", + "sha256:bb54703825d95d7c52511f3b984b2a25a8942f6a92b64cb8474ab521e2df4cc4", + "sha256:bc861d83c917aaa5ab2cf756ad5960ec6896a14e3ee345e16592b1dc3996bdae", + "sha256:bd1b48918e955ec9dc156e9596bcf4b833756edc1d6a7ee0d6569ffabc98b15b", + "sha256:c5ecc5b9ffba2f5cb262d62155b46b742b0bdc79abfd9cc5d2892e2d7748da4e", + "sha256:c6cc1e026bcfa14f2a2cd2d6b53fbb57d4bf2de446951c87d9c52d91f9a1dbba", + "sha256:c936b6843770511341d7ea78b33d4fec46238edd4e10d545e24323d04585b270", + "sha256:d2a30977c53af58904e39ee1b221c0034ff8cee9e6a409bd39e4705ce3d0398b", + "sha256:d66584cf2c6b72172b624dcb8669d0ac5dfe7e115c8c4a52333782a87c25ca8e", + "sha256:dd72bb1872eca9d279055bc7c3491a62ce1db7bfd330b7d48d7239a59c2f9823", + "sha256:e0d4c1f700ad422c55f7b2448b4b21eba46336fc34069dd8a8473a618b5ff621", + "sha256:e46d924bd4496b8bfb0f417255d527d529a82f83a5b9398eb005129d8877a6ee", + "sha256:e835ca2d2b4119597b4ee2cf360052f6ac94fcefc1c799ef6f0344c4470e4c9c", + "sha256:e9e215140963abd978f2e8ebd09c9644f2f17c032a7040880fbb28aea2fd7db2", + "sha256:f1bf76a9cd29dc570efc8e063fbbc1f1130c89a62024f555691779467ef8480e", + "sha256:f1e2ac0b6ecfcb5600a92d6f1aa05c8b8c0d86ff3525b32bac938138a2028de4", + "sha256:f35778033ce4663d2ce506f8926bdadf66a9ab9dea490e675b7221a2dfd41923", + "sha256:f91ee677d02265f96c90c2b5b6d234bd564466cf3472afa5f9ca4f436fef1712", + "sha256:fcc2ad43c9d9b05d499f9472277d3e927f6630353fa295d93d5c6919b6108ae0", + "sha256:ff6c1a5c0e71dfdb1d351bec43bc5d715a5df7796b9c67a9e26d7aa64f52fd00" ], "index": "pypi", - "version": "==2.1.4" + "markers": "python_version >= '3.9'", + "version": "==2.2.0a2" }, "certifi": { "hashes": [ - "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", - "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" - ], - "version": "==2019.6.16" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" + "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", + "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995" + ], + "markers": "python_version >= '3.7'", + "version": "==2025.7.14" + }, + "charset-normalizer": { + "hashes": [ + "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", + "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", + "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", + "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", + "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", + "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", + "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d", + "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", + "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184", + "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", + "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", + "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64", + "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", + "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", + "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", + "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344", + "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", + "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", + "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", + "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", + "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", + "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", + "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", + "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", + "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", + "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", + "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", + "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", + "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58", + "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", + "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", + "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2", + "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", + "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", + "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", + "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", + "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", + "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f", + "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", + "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", + "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", + "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", + "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", + "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", + "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", + "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", + "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4", + "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", + "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", + "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", + "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", + "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", + "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", + "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", + "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", + "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", + "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", + "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa", + "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", + "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", + "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", + "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", + "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", + "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", + "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02", + "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", + "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", + "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", + "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", + "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", + "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", + "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", + "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", + "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", + "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", + "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", + "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", + "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", + "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", + "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", + "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", + "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", + "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", + "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", + "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", + "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", + "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", + "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da", + "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", + "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f", + "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", + "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.2" }, "geographiclib": { "hashes": [ - "sha256:635da648fce80a57b81b28875d103dacf7deb12a3f5f7387ba7d39c51e096533" + "sha256:8f441c527b0b8a26cd96c965565ff0513d1e4d9952b704bf449409e5015c77b7", + "sha256:ac400d672b8954b0306bca890b088bb8ba2a757dc8133cca0b878f34b33b2740" ], - "version": "==1.49" + "version": "==1.52" }, "geopy": { "hashes": [ @@ -80,32 +198,76 @@ }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" ], - "version": "==2.8" + "markers": "python_version >= '3.6'", + "version": "==3.10" }, "msgpack": { "hashes": [ - "sha256:26cb40116111c232bc235ce131cc3b4e76549088cb154e66a2eb8ff6fcc907ec", - "sha256:300fd3f2c664a3bf473d6a952f843b4a71454f4c592ed7e74a36b205c1782d28", - "sha256:3129c355342853007de4a2a86e75eab966119733eb15748819b6554363d4e85c", - "sha256:31f6d645ee5a97d59d3263fab9e6be76f69fa131cddc0d94091a3c8aca30d67a", - "sha256:3ce7ef7ee2546c3903ca8c934d09250531b80c6127e6478781ae31ed835aac4c", - "sha256:4008c72f5ef2b7936447dcb83db41d97e9791c83221be13d5e19db0796df1972", - "sha256:62bd8e43d204580308d477a157b78d3fee2fb4c15d32578108dc5d89866036c8", - "sha256:70cebfe08fb32f83051971264466eadf183101e335d8107b80002e632f425511", - "sha256:72cb7cf85e9df5251abd7b61a1af1fb77add15f40fa7328e924a9c0b6bc7a533", - "sha256:7c55649965c35eb32c499d17dadfb8f53358b961582846e1bc06f66b9bccc556", - "sha256:86b963a5de11336ec26bc4f839327673c9796b398b9f1fe6bb6150c2a5d00f0f", - "sha256:8c73c9bcdfb526247c5e4f4f6cf581b9bb86b388df82cfcaffde0a6e7bf3b43a", - "sha256:8e68c76c6aff4849089962d25346d6784d38e02baa23ffa513cf46be72e3a540", - "sha256:97ac6b867a8f63debc64f44efdc695109d541ecc361ee2dce2c8884ab37360a1", - "sha256:9d4f546af72aa001241d74a79caec278bcc007b4bcde4099994732e98012c858", - "sha256:a28e69fe5468c9f5251c7e4e7232286d71b7dfadc74f312006ebe984433e9746", - "sha256:fd509d4aa95404ce8d86b4e32ce66d5d706fd6646c205e1c2a715d87078683a2" - ], - "version": "==0.6.1" + "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", + "sha256:1abfc6e949b352dadf4bce0eb78023212ec5ac42f6abfd469ce91d783c149c2a", + "sha256:1b13fe0fb4aac1aa5320cd693b297fe6fdef0e7bea5518cbc2dd5299f873ae90", + "sha256:1d75f3807a9900a7d575d8d6674a3a47e9f227e8716256f35bc6f03fc597ffbf", + "sha256:2fbbc0b906a24038c9958a1ba7ae0918ad35b06cb449d398b76a7d08470b0ed9", + "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157", + "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed", + "sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d", + "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", + "sha256:3a89cd8c087ea67e64844287ea52888239cbd2940884eafd2dcd25754fb72232", + "sha256:40eae974c873b2992fd36424a5d9407f93e97656d999f43fca9d29f820899084", + "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", + "sha256:435807eeb1bc791ceb3247d13c79868deb22184e1fc4224808750f0d7d1affc1", + "sha256:4835d17af722609a45e16037bb1d4d78b7bdf19d6c0128116d178956618c4e88", + "sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752", + "sha256:4d3237b224b930d58e9d83c81c0dba7aacc20fcc2f89c1e5423aa0529a4cd142", + "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", + "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef", + "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", + "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4", + "sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458", + "sha256:61abccf9de335d9efd149e2fff97ed5974f2481b3353772e8e2dd3402ba2bd57", + "sha256:61e35a55a546a1690d9d09effaa436c25ae6130573b6ee9829c37ef0f18d5e78", + "sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd", + "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", + "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce", + "sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558", + "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", + "sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2", + "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8", + "sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0", + "sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295", + "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c", + "sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26", + "sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2", + "sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f", + "sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4", + "sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8", + "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", + "sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338", + "sha256:996f2609ddf0142daba4cefd767d6db26958aac8439ee41db9cc0db9f4c4c3a6", + "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", + "sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0", + "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a", + "sha256:a8ef6e342c137888ebbfb233e02b8fbd689bb5b5fcc59b34711ac47ebd504478", + "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238", + "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", + "sha256:b8f93dcddb243159c9e4109c9750ba5b335ab8d48d9522c5308cd05d7e3ce600", + "sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704", + "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a", + "sha256:bba1be28247e68994355e028dcd668316db30c1f758d3241a7b903ac78dcd285", + "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c", + "sha256:d182dac0221eb8faef2e6f44701812b467c02674a322c739355c39e94730cdbf", + "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b", + "sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2", + "sha256:da8f41e602574ece93dbbda1fab24650d6bf2a24089f9e9dbb4f5730ec1e58ad", + "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", + "sha256:f5be6b6bc52fad84d010cb45433720327ce886009d862f46b26d4d154001994b", + "sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75" + ], + "markers": "python_version >= '3.8'", + "version": "==1.1.1" }, "pager": { "hashes": [ @@ -116,336 +278,112 @@ }, "pycurl": { "hashes": [ - "sha256:2a7d4d7bb0194b1849c62ecaccbc28298a4372c3edbdd3c3e61761d705311925", - "sha256:43231bf2bafde923a6d9bb79e2407342a5f3382c1ef0a3b2e491c6a4e50b91aa", - "sha256:70230a862178cf37d0922c0974b759577e55253c9cfe72298400881d6432cf99", - "sha256:7f457eac8ed02ae06655993399b4cf8c11a5202f3a01b255de300dced8c3e3d5", - "sha256:a75d1d81a5e65cf53ddadd36ca89083f29134e027a310ae0c3e97570789db1db", - "sha256:d0f26b811defeb0b9697afdf4c3aa3cc42781185234e0b73ca58bb7639f6786c", - "sha256:ea44d26aa092c39fe1a07c75adb453ab46c5d1433e8baad02edbf2c1d2bc90f5" + "sha256:5730590be0271364a5bddd9e245c9cc0fb710c4cbacbdd95264a3122d23224ca" ], "index": "pypi", - "version": "==7.43.0.1" + "markers": "python_version >= '3.5'", + "version": "==7.45.2" }, "requests": { "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", + "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422" ], - "version": "==2.22.0" + "markers": "python_version >= '3.8'", + "version": "==2.32.4" }, "texttable": { "hashes": [ - "sha256:c89dc0148ae29645917aab7e970a30d1af565b3ca276cef8ab1a60469f0d8100" + "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638", + "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917" ], "index": "pypi", - "version": "==1.2.1" + "version": "==1.7.0" }, "urllib3": { "hashes": [ - "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", - "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", + "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" ], - "version": "==1.25.3" + "markers": "python_version >= '3.9'", + "version": "==2.5.0" } }, "develop": { - "astroid": { - "hashes": [ - "sha256:87de48a92e29cedf7210ffa853d11441e7ad94cb47bacd91b023499b51cbc756", - "sha256:d25869fc7f44f1d9fb7d24fd7ea0639656f5355fc3089cd1f3d18c6ec6b124c7" - ], - "version": "==1.6.6" - }, - "atomicwrites": { - "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" - ], - "version": "==1.3.0" - }, - "attrs": { - "hashes": [ - "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", - "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" - ], - "version": "==19.1.0" - }, - "backports.functools-lru-cache": { - "hashes": [ - "sha256:9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a", - "sha256:f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd" - ], - "markers": "python_version == '2.7'", - "version": "==1.5" - }, - "configparser": { - "hashes": [ - "sha256:45d1272aad6cfd7a8a06cf5c73f2ceb6a190f6acc1fa707e7f82a4c053b28b18", - "sha256:bc37850f0cc42a1725a796ef7d92690651bf1af37d744cc63161dac62cabee17" - ], - "markers": "python_version < '3'", - "version": "==3.8.1" - }, - "contextlib2": { - "hashes": [ - "sha256:509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48", - "sha256:f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00" - ], - "markers": "python_version < '3'", - "version": "==0.5.5" - }, - "entrypoints": { - "hashes": [ - "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", - "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" - ], - "version": "==0.3" - }, - "enum34": { - "hashes": [ - "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", - "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", - "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", - "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" - ], - "markers": "python_version < '3.4'", - "version": "==1.1.6" - }, - "flake8": { - "hashes": [ - "sha256:19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", - "sha256:8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696" - ], - "index": "pypi", - "version": "==3.7.8" - }, - "funcsigs": { - "hashes": [ - "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", - "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" - ], - "markers": "python_version < '3.0'", - "version": "==1.0.2" - }, - "functools32": { - "hashes": [ - "sha256:89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0", - "sha256:f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d" - ], - "markers": "python_version < '3.2'", - "version": "==3.2.3.post2" - }, - "futures": { - "hashes": [ - "sha256:49b3f5b064b6e3afc3316421a3f25f66c137ae88f068abbf72830170033c5e16", - "sha256:7e033af76a5e35f58e56da7a91e687706faf4e7bdfb2cbc3f2cca6b9bcda9794" - ], - "markers": "python_version < '3.2'", - "version": "==3.3.0" - }, - "importlib-metadata": { - "hashes": [ - "sha256:23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", - "sha256:80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3" - ], - "version": "==0.19" - }, - "isort": { - "hashes": [ - "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", - "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" - ], - "version": "==4.3.21" - }, - "lazy-object-proxy": { - "hashes": [ - "sha256:159a745e61422217881c4de71f9eafd9d703b93af95618635849fe469a283661", - "sha256:23f63c0821cc96a23332e45dfaa83266feff8adc72b9bcaef86c202af765244f", - "sha256:3b11be575475db2e8a6e11215f5aa95b9ec14de658628776e10d96fa0b4dac13", - "sha256:3f447aff8bc61ca8b42b73304f6a44fa0d915487de144652816f950a3f1ab821", - "sha256:4ba73f6089cd9b9478bc0a4fa807b47dbdb8fad1d8f31a0f0a5dbf26a4527a71", - "sha256:4f53eadd9932055eac465bd3ca1bd610e4d7141e1278012bd1f28646aebc1d0e", - "sha256:64483bd7154580158ea90de5b8e5e6fc29a16a9b4db24f10193f0c1ae3f9d1ea", - "sha256:6f72d42b0d04bfee2397aa1862262654b56922c20a9bb66bb76b6f0e5e4f9229", - "sha256:7c7f1ec07b227bdc561299fa2328e85000f90179a2f44ea30579d38e037cb3d4", - "sha256:7c8b1ba1e15c10b13cad4171cfa77f5bb5ec2580abc5a353907780805ebe158e", - "sha256:8559b94b823f85342e10d3d9ca4ba5478168e1ac5658a8a2f18c991ba9c52c20", - "sha256:a262c7dfb046f00e12a2bdd1bafaed2408114a89ac414b0af8755c696eb3fc16", - "sha256:acce4e3267610c4fdb6632b3886fe3f2f7dd641158a843cf6b6a68e4ce81477b", - "sha256:be089bb6b83fac7f29d357b2dc4cf2b8eb8d98fe9d9ff89f9ea6012970a853c7", - "sha256:bfab710d859c779f273cc48fb86af38d6e9210f38287df0069a63e40b45a2f5c", - "sha256:c10d29019927301d524a22ced72706380de7cfc50f767217485a912b4c8bd82a", - "sha256:dd6e2b598849b3d7aee2295ac765a578879830fb8966f70be8cd472e6069932e", - "sha256:e408f1eacc0a68fed0c08da45f31d0ebb38079f043328dce69ff133b95c29dc1" - ], - "version": "==1.4.1" - }, - "mccabe": { + "iniconfig": { "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", + "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760" ], - "version": "==0.6.1" + "markers": "python_version >= '3.8'", + "version": "==2.1.0" }, "mock": { "hashes": [ - "sha256:83657d894c90d5681d62155c82bda9c1187827525880eda8ff5df4ec813437c3", - "sha256:d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8" + "sha256:4e460e818629b4b173f32d08bf30d3af8123afbb8e04bb5707a1fd4799e503f0", + "sha256:7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f" ], "index": "pypi", - "version": "==3.0.5" - }, - "more-itertools": { - "hashes": [ - "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4", - "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc", - "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9" - ], - "markers": "python_version <= '2.7'", - "version": "==5.0.0" + "markers": "python_version >= '3.6'", + "version": "==5.2.0" }, "packaging": { "hashes": [ - "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", - "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" ], - "version": "==19.1" - }, - "pathlib2": { - "hashes": [ - "sha256:2156525d6576d21c4dcaddfa427fae887ef89a7a9de5cbfe0728b3aafa78427e", - "sha256:446014523bb9be5c28128c4d2a10ad6bb60769e78bd85658fe44a450674e0ef8" - ], - "index": "pypi", - "version": "==2.3.4" + "markers": "python_version >= '3.8'", + "version": "==25.0" }, "pluggy": { "hashes": [ - "sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", - "sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c" - ], - "version": "==0.12.0" - }, - "py": { - "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" ], - "version": "==1.8.0" + "markers": "python_version >= '3.9'", + "version": "==1.6.0" }, - "pycodestyle": { + "pygments": { "hashes": [ - "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", - "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" ], - "version": "==2.5.0" - }, - "pyflakes": { - "hashes": [ - "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", - "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" - ], - "version": "==2.1.1" - }, - "pylint": { - "hashes": [ - "sha256:09bc539f85706f2cca720a7ddf28f5c6cf8185708d6cb5bbf7a90a32c3b3b0aa", - "sha256:b8471105f12c73a1b9eee2bb2474080370e062a7290addd215eb34bc4dfe9fd8" - ], - "index": "pypi", - "version": "==1.9.3" - }, - "pyparsing": { - "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" - ], - "version": "==2.4.2" + "markers": "python_version >= '3.8'", + "version": "==2.19.2" }, "pytest": { "hashes": [ - "sha256:52fa94b4ac81d2f063ee05e303acedf5c605e15dc0f4eef468b5c137f77241c3", - "sha256:5467f37a0d6bb0b4e684b71af268e005996b9eaaefe54e3d64d86afd90da8d78" - ], - "index": "pypi", - "version": "==4.6.0" - }, - "scandir": { - "hashes": [ - "sha256:2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e", - "sha256:2ae41f43797ca0c11591c0c35f2f5875fa99f8797cb1a1fd440497ec0ae4b022", - "sha256:2b8e3888b11abb2217a32af0766bc06b65cc4a928d8727828ee68af5a967fa6f", - "sha256:2c712840c2e2ee8dfaf36034080108d30060d759c7b73a01a52251cc8989f11f", - "sha256:4d4631f6062e658e9007ab3149a9b914f3548cb38bfb021c64f39a025ce578ae", - "sha256:67f15b6f83e6507fdc6fca22fedf6ef8b334b399ca27c6b568cbfaa82a364173", - "sha256:7d2d7a06a252764061a020407b997dd036f7bd6a175a5ba2b345f0a357f0b3f4", - "sha256:8c5922863e44ffc00c5c693190648daa6d15e7c1207ed02d6f46a8dcc2869d32", - "sha256:92c85ac42f41ffdc35b6da57ed991575bdbe69db895507af88b9f499b701c188", - "sha256:b24086f2375c4a094a6b51e78b4cf7ca16c721dcee2eddd7aa6494b42d6d519d", - "sha256:cb925555f43060a1745d0a321cca94bcea927c50114b623d73179189a4e100ac" + "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", + "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c" ], "index": "pypi", - "version": "==1.10.0" - }, - "singledispatch": { - "hashes": [ - "sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c", - "sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8" - ], - "markers": "python_version < '3.4'", - "version": "==3.4.0.3" - }, - "six": { - "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" - ], - "version": "==1.12.0" - }, - "tox": { - "hashes": [ - "sha256:96efa09710a3daeeb845561ebbe1497641d9cef2ee0aea30db6969058b2bda2f", - "sha256:9ee7de958a43806402a38c0d2aa07fa8553f4d2c20a15b140e9f771c2afeade0" + "markers": "python_version >= '3.9'", + "version": "==8.4.1" + }, + "ruff": { + "hashes": [ + "sha256:0618ec4442a83ab545e5b71202a5c0ed7791e8471435b94e655b570a5031a98e", + "sha256:0fc426bec2e4e5f4c4f182b9d2ce6a75c85ba9bcdbe5c6f2a74fcb8df437df4b", + "sha256:13efa16df6c6eeb7d0f091abae50f58e9522f3843edb40d56ad52a5a4a4b6873", + "sha256:2abc48f3d9667fdc74022380b5c745873499ff827393a636f7a59da1515e7c57", + "sha256:2b2449dc0c138d877d629bea151bee8c0ae3b8e9c43f5fcaafcd0c0d0726b184", + "sha256:478fccdb82ca148a98a9ff43658944f7ab5ec41c3c49d77cd99d44da019371a1", + "sha256:4de27977827893cdfb1211d42d84bc180fceb7b72471104671c59be37041cf93", + "sha256:55c0f4ca9769408d9b9bac530c30d3e66490bd2beb2d3dae3e4128a1f05c7442", + "sha256:56e45bb11f625db55f9b70477062e6a1a04d53628eda7784dce6e0f55fd549eb", + "sha256:a7dea966bcb55d4ecc4cc3270bccb6f87a337326c9dcd3c07d5b97000dbff41c", + "sha256:a8224cc3722c9ad9044da7f89c4c1ec452aef2cfe3904365025dd2f51daeae0e", + "sha256:afcfa3ab5ab5dd0e1c39bf286d829e042a15e966b3726eea79528e2e24d8371a", + "sha256:be0593c69df9ad1465e8a2d10e3defd111fdb62dcd5be23ae2c06da77e8fcffb", + "sha256:c057ce464b1413c926cdb203a0f858cd52f3e73dcb3270a3318d1630f6395bb3", + "sha256:cb0d261dac457ab939aeb247e804125a5d521b21adf27e721895b0d3f83a0d0a", + "sha256:e64b90d1122dc2713330350626b10d60818930819623abbb56535c6466cce045", + "sha256:e9949d01d64fa3672449a51ddb5d7548b33e130240ad418884ee6efa7a229586", + "sha256:fe0b9e9eb23736b453143d72d2ceca5db323963330d5b7859d60d101147d461a" ], "index": "pypi", - "version": "==3.0.0" - }, - "typing": { - "hashes": [ - "sha256:38566c558a0a94d6531012c8e917b1b8518a41e418f7f15f00e129cc80162ad3", - "sha256:53765ec4f83a2b720214727e319607879fec4acde22c4fbb54fa2604e79e44ce", - "sha256:84698954b4e6719e912ef9a42a2431407fe3755590831699debda6fba92aac55" - ], - "markers": "python_version < '3.5'", - "version": "==3.7.4" - }, - "virtualenv": { - "hashes": [ - "sha256:6cb2e4c18d22dbbe283d0a0c31bb7d90771a606b2cb3415323eea008eaee6a9d", - "sha256:909fe0d3f7c9151b2df0a2cb53e55bdb7b0d61469353ff7a49fd47b0f0ab9285" - ], - "version": "==16.7.2" - }, - "wcwidth": { - "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" - ], - "version": "==0.1.7" - }, - "wrapt": { - "hashes": [ - "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" - ], - "version": "==1.11.2" - }, - "zipp": { - "hashes": [ - "sha256:4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a", - "sha256:8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec" - ], - "version": "==0.5.2" + "markers": "python_version >= '3.7'", + "version": "==0.12.4" } } } diff --git a/README.md b/README.md index e7db1ba..fc294f5 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,58 @@ -# NTRIP Browser [![Build Status](https://travis-ci.com/emlid/ntripbrowser.svg?branch=master)](https://travis-ci.com/emlid/ntripbrowser) +# NTRIP Browser +[![CI checks](https://github.com/emlid/ntripbrowser/actions/workflows/ci.yaml/badge.svg)](https://github.com/emlid/ntripbrowser/actions/workflows/ci.yaml) A Python API for browsing NTRIP (Networked Transport of RTCM via Internet Protocol). ## Requirements - - pager - - geopy - - pycurl - - cchardet - - texttable - - Python 2.6–2.7 & 3.4–3.6 + +- pager +- geopy +- pycurl +- cchardet +- texttable +- Python 3.11+ ## Installation - - make sure that you have `libcurl` installed +- make sure that you have `libcurl` installed - - `pip install ntripbrowser` +- `pip install ntripbrowser` - - or clone and run `make install` +- or clone and run `make install` - - If you are looking for the last Python2 version of this package, checkout onto v2.2.3 tag. Python2 support will be discontinued in future releases. +#### libcurl installation hints -``` - git checkout v2.2.3 -``` +- installation via `apt`: -#### libcurl installation hints + ```bash + apt-get install libssl-dev libcurl4-openssl-dev python-dev + ``` - - installation via `apt`: - - ``` - apt-get install libssl-dev libcurl4-openssl-dev python-dev - ``` - -## Usage +## Usage ``` ntripbrowser [-h] [-p] [-t] [-c] host -positional arguments: +positional arguments: host NTRIP source table host address -optional arguments: - -h, --help Show this help message and exit - -p, --port Set url port. Standard port is 2101 - -t, --timeout Add timeout +optional arguments: + -h, --help Show this help message and exit + -p, --port Set url port. Standard port is 2101 + -t, --timeout Add timeout -c, --coordinates Add NTRIP station distance to this coordinate -M --maxdist Only report stations less than this number of km away from given coordinate - ``` +``` #### CLI workflow example: - ntripbrowser cddis-caster.gsfc.nasa.gov -p 443 -t 5 -c 1.0 2.0 -M 4000 +```bash +ntripbrowser cddis-caster.gsfc.nasa.gov -p 443 -t 5 -c 1.0 2.0 -M 4000 +``` ## Package API + #### Workflow example: ```python @@ -65,54 +64,49 @@ browser.get_mountpoints() #### Arguments: - - `host` +- `host` > NTRIP caster host. > Standard port is 2101, use `:port` optional argument to set another one. #### Optional arguments: - - `port` +- `port` > NTRIP caster port. - - `timeout` - +- `timeout` + > Use `timeout` to define, how long to wait for a connection to NTRIP caster. - - `coordinates` - -> Use `coordinates` to pass your position coordinates in function and get distance to NTRIP station. + +- `coordinates` + +> Use `coordinates` to pass your position coordinates in function and get distance to NTRIP station. > Form of coordiantes must be `(x, y)` or `(x.x, y.y)` of latitude, longitude. - - `maxdist` -> Use `maxdist` to only report stations less than this number of km away from given coordinate. +- `maxdist` + > Use `maxdist` to only report stations less than this number of km away from given coordinate. + > Stations lacking coordinates will not be returned. #### Result As a result you'll get a dictionary consisting of a lists of dictionaries with such structure: -- CAS stations: `"Host", "Port", "ID", "Operator", "NMEA", "Country", "Latitude", "Longitude", "FallbackHost", "FallbackPort", "Site", "Other Details", "Distance"` +- CAS stations: `"Host", "Port", "ID", "Operator", "NMEA", "Country", "Latitude", "Longitude", "FallbackHost", "FallbackPort", "Site", "Other Details", "Distance"` -- NET stations: `"ID", "Operator", "Authentication", "Fee", "Web-Net", "Web-Str", "Web-Reg", "Other Details", "Distance"` +- NET stations: `"ID", "Operator", "Authentication", "Fee", "Web-Net", "Web-Str", "Web-Reg", "Other Details", "Distance"` - STR stations: `"Mountpoint", "ID", "Format", "Format-Details","Carrier", "Nav-System", "Network", "Country", "Latitude", "Longitude", "NMEA", "Solution", "Generator", "Compr-Encryp", "Authentication", "Fee", "Bitrate", "Other Details", "Distance"` #### Exceptions - - `ntripbrowser.NtripbrowserError` - base class for all ntripbrowser exceptions. - - `ntripbrowser.UnableToConnect` - raised when ntripbrowser could not connect to the assigned url. - - `ntripbrowser.NoDataReceivedFromCaster` - raised when ntripbrowser could not find any data on the page. - - `ntripbrowser.ExceededTimeoutError` - raised when connection timeout is exceeded. +- `ntripbrowser.NtripbrowserError` - base class for all ntripbrowser exceptions. +- `ntripbrowser.UnableToConnect` - raised when ntripbrowser could not connect to the assigned url. +- `ntripbrowser.NoDataReceivedFromCaster` - raised when ntripbrowser could not find any data on the page. +- `ntripbrowser.ExceededTimeoutError` - raised when connection timeout is exceeded. ## To test - make test - -#### Known Issues -Tests with `tox` may fail if python*-dev is not installed. -So, you need to install python2.7-dev and python3.6-dev: - - sudo apt-get install python2.7-dev - sudo apt-get install python3.6-dev - - +```bash +make test +``` diff --git a/code-quality b/code-quality index cd799df..b9b552b 160000 --- a/code-quality +++ b/code-quality @@ -1 +1 @@ -Subproject commit cd799df303e688d45c38e00a511fcebe1412963e +Subproject commit b9b552b1e44451f587ba6914877a66427a2cb52b diff --git a/ntripbrowser/__init__.py b/ntripbrowser/__init__.py index c7ed971..6109725 100644 --- a/ntripbrowser/__init__.py +++ b/ntripbrowser/__init__.py @@ -1,8 +1,14 @@ +from .constants import CAS_HEADERS, NET_HEADERS, STR_HEADERS +from .exceptions import ExceededTimeoutError, NoDataReceivedFromCaster, NtripbrowserError, UnableToConnect from .ntripbrowser import NtripBrowser -from .exceptions import (NtripbrowserError, ExceededTimeoutError, NoDataReceivedFromCaster, - UnableToConnect) -from .constants import STR_HEADERS, NET_HEADERS, CAS_HEADERS -__all__ = ['NtripBrowser', 'NtripbrowserError', 'ExceededTimeoutError', - 'NoDataReceivedFromCaster', 'UnableToConnect', - 'STR_HEADERS', 'NET_HEADERS', 'CAS_HEADERS'] +__all__ = [ + "NtripBrowser", + "NtripbrowserError", + "ExceededTimeoutError", + "NoDataReceivedFromCaster", + "UnableToConnect", + "STR_HEADERS", + "NET_HEADERS", + "CAS_HEADERS", +] diff --git a/ntripbrowser/browser.py b/ntripbrowser/browser.py index 49832f5..94b11b0 100644 --- a/ntripbrowser/browser.py +++ b/ntripbrowser/browser.py @@ -1,43 +1,58 @@ import argparse import pydoc -import pager +import pager from texttable import Texttable -from .ntripbrowser import NtripBrowser -from .ntripbrowser import ExceededTimeoutError, UnableToConnect, NoDataReceivedFromCaster -from .ntripbrowser import CAS_HEADERS, STR_HEADERS, NET_HEADERS - +from .ntripbrowser import ( + CAS_HEADERS, + NET_HEADERS, + STR_HEADERS, + ExceededTimeoutError, + NoDataReceivedFromCaster, + NtripBrowser, + UnableToConnect, +) SCREEN_WIDTH = pager.getwidth() def argparser(): - parser = argparse.ArgumentParser(description='Parse NTRIP sourcetable') - parser.add_argument('url', help='NTRIP sourcetable address') - parser.add_argument('-p', '--port', type=int, - help='Set url port. Standard port is 2101', default=2101) - parser.add_argument('-t', '--timeout', type=int, - help='add timeout', default=4) - parser.add_argument('-c', '--coordinates', - help='Add NTRIP station distance to this coordinate', nargs=2) - parser.add_argument('-M', '--maxdist', - help='Only report stations less than this number of km away from given coordinate', - type=float) + parser = argparse.ArgumentParser(description="Parse NTRIP sourcetable") + parser.add_argument("url", help="NTRIP sourcetable address") + parser.add_argument("-p", "--port", type=int, help="Set url port. Standard port is 2101", default=2101) + parser.add_argument("-t", "--timeout", type=int, help="add timeout", default=4) + parser.add_argument("-c", "--coordinates", help="Add NTRIP station distance to this coordinate", nargs=2) + parser.add_argument( + "-M", + "--maxdist", + help="Only report stations less than this number of km away from given coordinate", + type=float, + ) return parser.parse_args() def display_ntrip_table(ntrip_table): - table_cas = compile_ntrip_table(ntrip_table['cas'], CAS_HEADERS) - table_net = compile_ntrip_table(ntrip_table['net'], NET_HEADERS) - table_str = compile_ntrip_table(ntrip_table['str'], STR_HEADERS) - - pydoc.pager(( - 'CAS TABLE'.center(SCREEN_WIDTH, '=') + '\n' + table_cas + 4 * '\n' + - 'NET TABLE'.center(SCREEN_WIDTH, '=') + '\n' + table_net + 4 * '\n' + - 'STR TABLE'.center(SCREEN_WIDTH, '=') + '\n' + table_str - )) + table_cas = compile_ntrip_table(ntrip_table["cas"], CAS_HEADERS) + table_net = compile_ntrip_table(ntrip_table["net"], NET_HEADERS) + table_str = compile_ntrip_table(ntrip_table["str"], STR_HEADERS) + + pydoc.pager( + ( + "CAS TABLE".center(SCREEN_WIDTH, "=") + + "\n" + + table_cas + + 4 * "\n" + + "NET TABLE".center(SCREEN_WIDTH, "=") + + "\n" + + table_net + + 4 * "\n" + + "STR TABLE".center(SCREEN_WIDTH, "=") + + "\n" + + table_str + ) + ) def compile_ntrip_table(table, headers): @@ -56,29 +71,25 @@ def compile_ntrip_table(table, headers): def table_to_string(table): try: - return str(table.draw()).center(SCREEN_WIDTH, ' ') # python3 + return str(table.draw()).center(SCREEN_WIDTH, " ") # python3 except UnicodeEncodeError: - return table.draw().center(SCREEN_WIDTH, ' ') # python2 + return table.draw().center(SCREEN_WIDTH, " ") # python2 except TypeError: - return '' + return "" def main(): args = argparser() browser = NtripBrowser( - args.url, - port=args.port, - timeout=args.timeout, - coordinates=args.coordinates, - maxdist=args.maxdist + args.url, port=args.port, timeout=args.timeout, coordinates=args.coordinates, maxdist=args.maxdist ) try: ntrip_table = browser.get_mountpoints() except ExceededTimeoutError: - print('Connection timed out') + print("Connection timed out") except UnableToConnect: - print('Unable to connect to NTRIP caster') + print("Unable to connect to NTRIP caster") except NoDataReceivedFromCaster: - print('No data received from NTRIP caster') + print("No data received from NTRIP caster") else: display_ntrip_table(ntrip_table) diff --git a/ntripbrowser/constants.py b/ntripbrowser/constants.py index a88d5a6..5cf9a26 100644 --- a/ntripbrowser/constants.py +++ b/ntripbrowser/constants.py @@ -1,14 +1,42 @@ -CAS_HEADERS = ('Host', 'Port', 'ID', 'Operator', - 'NMEA', 'Country', 'Latitude', 'Longitude', - 'FallbackHost', 'FallbackPort', 'Site', 'Other Details', 'Distance') +CAS_HEADERS = ( + "Host", + "Port", + "ID", + "Operator", + "NMEA", + "Country", + "Latitude", + "Longitude", + "FallbackHost", + "FallbackPort", + "Site", + "Other Details", + "Distance", +) -NET_HEADERS = ('ID', 'Operator', 'Authentication', - 'Fee', 'Web-Net', 'Web-Str', 'Web-Reg', 'Other Details', 'Distance') +NET_HEADERS = ("ID", "Operator", "Authentication", "Fee", "Web-Net", "Web-Str", "Web-Reg", "Other Details", "Distance") -STR_HEADERS = ('Mountpoint', 'ID', 'Format', 'Format-Details', - 'Carrier', 'Nav-System', 'Network', 'Country', 'Latitude', - 'Longitude', 'NMEA', 'Solution', 'Generator', 'Compr-Encryp', - 'Authentication', 'Fee', 'Bitrate', 'Other Details', 'Distance') +STR_HEADERS = ( + "Mountpoint", + "ID", + "Format", + "Format-Details", + "Carrier", + "Nav-System", + "Network", + "Country", + "Latitude", + "Longitude", + "NMEA", + "Solution", + "Generator", + "Compr-Encryp", + "Authentication", + "Fee", + "Bitrate", + "Other Details", + "Distance", +) PYCURL_COULD_NOT_RESOLVE_HOST_ERRNO = 6 PYCURL_CONNECTION_FAILED_ERRNO = 7 diff --git a/ntripbrowser/ntripbrowser.py b/ntripbrowser/ntripbrowser.py index 36ed263..7d84cd4 100644 --- a/ntripbrowser/ntripbrowser.py +++ b/ntripbrowser/ntripbrowser.py @@ -30,25 +30,27 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import logging -from geopy.distance import geodesic +from io import BytesIO -import pycurl import cchardet +import pycurl +from geopy.distance import geodesic -try: - from io import BytesIO # Python 3 -except ImportError: - from StringIO import StringIO as BytesIO # Python 2 - -from .constants import (CAS_HEADERS, STR_HEADERS, NET_HEADERS, PYCURL_TIMEOUT_ERRNO, - MULTICURL_SELECT_TIMEOUT, CURLOPT_HTTP09_ALLOWED, NULL_ISLAND_COORDS) -from .exceptions import (ExceededTimeoutError, UnableToConnect, NoDataReceivedFromCaster) - +from .constants import ( + CAS_HEADERS, + CURLOPT_HTTP09_ALLOWED, + MULTICURL_SELECT_TIMEOUT, + NET_HEADERS, + NULL_ISLAND_COORDS, + PYCURL_TIMEOUT_ERRNO, + STR_HEADERS, +) +from .exceptions import ExceededTimeoutError, NoDataReceivedFromCaster, UnableToConnect logger = logging.getLogger(__name__) -class DataFetcher(object): +class DataFetcher: """Fetch data from specified urls, execute custom callback on results. Parameters @@ -67,6 +69,7 @@ class DataFetcher(object): result : Return value of `parser_method` function or None. """ + def __init__(self, urls, timeout, parser_method): self.timeout = timeout self.urls = urls @@ -92,7 +95,7 @@ def setup(self): self._buffers = {} self._curls_failed = [] self._initialize() - logger.info('DataFetcher: curls setup in process') + logger.info("DataFetcher: curls setup in process") for curl in self.curls: self._multicurl.add_handle(curl) @@ -132,6 +135,8 @@ def read_data(self): def _read_multicurl_info(self): _, successful_curls, failed_curls = self._multicurl.info_read() + for suc_curl in successful_curls: + print(f"success = {suc_curl.getinfo(pycurl.EFFECTIVE_URL)}") self._curls_failed.extend(failed_curls) for curl in successful_curls: self._process_successful_curl(curl) @@ -159,7 +164,7 @@ def _process_fetch_failure(self): - Otherwise, there are no failed curls and all curls which are succeeds received no data from the caster, so throw a NoDataReceivedFromCaster. """ - logger.info('DataFetcher: No valid result is received') + logger.info("DataFetcher: No valid result is received") if len(self.urls_processed) == len(self.urls): raise NoDataReceivedFromCaster() for _, error_code, error_text in self._curls_failed: @@ -176,13 +181,19 @@ def teardown(self): self._multicurl.close() for curl in self.curls: curl.close() - logger.info('DataFetcher: Curls are closed succesfully') + logger.info("DataFetcher: Curls are closed succesfully") self._buffers = {} -class NtripBrowser(object): - def __init__(self, host, port=2101, timeout=4, # pylint: disable-msg=too-many-arguments - coordinates=None, maxdist=None): +class NtripBrowser: + def __init__( + self, + host, + port=2101, + timeout=4, + coordinates=None, + maxdist=None, + ): self._host = None self.host = host self.port = port @@ -197,16 +208,16 @@ def host(self): @host.setter def host(self, host): - host = host.replace('http://', '') - host = host.replace('https://', '') + host = host.replace("http://", "") + host = host.replace("https://", "") self._host = host @property def urls(self): - http_url = '{}{}:{}'.format('http://', self.host, self.port) - https_url = '{}{}:{}'.format('https://', self.host, self.port) - http_sourcetable_url = '{}{}'.format(http_url, '/sourcetable.txt') - https_sourcetable_url = '{}{}'.format(https_url, '/sourcetable.txt') + http_url = "{}{}:{}".format("http://", self.host, self.port) + https_url = "{}{}:{}".format("https://", self.host, self.port) + http_sourcetable_url = "{}{}".format(http_url, "/sourcetable.txt") + https_sourcetable_url = "{}{}".format(https_url, "/sourcetable.txt") return [http_url, http_sourcetable_url, https_url, https_sourcetable_url] def get_mountpoints(self): @@ -224,8 +235,8 @@ def _process_raw_data(self, raw_data): @staticmethod def _decode_data(data): - data_encoding = cchardet.detect(data)['encoding'] - return data.decode('utf8' if not data_encoding else data_encoding) + data_encoding = cchardet.detect(data)["encoding"] + return data.decode("utf8" if not data_encoding else data_encoding) def _get_ntrip_tables(self, data): ntrip_tables = self._extract_ntrip_entry_strings(data) @@ -237,47 +248,55 @@ def _get_ntrip_tables(self, data): def _extract_ntrip_entry_strings(raw_table): str_list, cas_list, net_list = [], [], [] for row in raw_table.splitlines(): - if row.startswith('STR'): + if row.startswith("STR"): str_list.append(row) - elif row.startswith('CAS'): + elif row.startswith("CAS"): cas_list.append(row) - elif row.startswith('NET'): + elif row.startswith("NET"): net_list.append(row) return str_list, cas_list, net_list def _form_ntrip_entries(self, ntrip_tables): return { - 'str': self._form_dictionaries(STR_HEADERS, ntrip_tables[0]), - 'cas': self._form_dictionaries(CAS_HEADERS, ntrip_tables[1]), - 'net': self._form_dictionaries(NET_HEADERS, ntrip_tables[2]) + "str": self._form_dictionaries(STR_HEADERS, ntrip_tables[0]), + "cas": self._form_dictionaries(CAS_HEADERS, ntrip_tables[1]), + "net": self._form_dictionaries(NET_HEADERS, ntrip_tables[2]), } @staticmethod def _form_dictionaries(headers, line_list): + if headers == STR_HEADERS: + for line in line_list: + print(line) + def form_line(index): - line = index.split(';', len(headers))[1:] + line = index.split(";", len(headers))[1:] return dict(list(zip(headers, line))) - return [form_line(i) for i in line_list] + new_lines = [form_line(i) for i in line_list] + for line in new_lines: + if not line.get("Latitude") or not line.get("Longitude"): + print(line) + return new_lines def _add_distance(self, ntrip_dictionary): return { - 'cas': self._add_distance_column(ntrip_dictionary.get('cas')), - 'net': self._add_distance_column(ntrip_dictionary.get('net')), - 'str': self._add_distance_column(ntrip_dictionary.get('str')) + "cas": self._add_distance_column(ntrip_dictionary.get("cas")), + "net": self._add_distance_column(ntrip_dictionary.get("net")), + "str": self._add_distance_column(ntrip_dictionary.get("str")), } def _add_distance_column(self, ntrip_type_dictionary): for station in ntrip_type_dictionary: - latlon = self._get_float_coordinates((station.get('Latitude'), station.get('Longitude'))) - station['Distance'] = self._get_distance(latlon) + latlon = self._get_float_coordinates((station.get("Latitude"), station.get("Longitude"))) + station["Distance"] = self._get_distance(latlon) return ntrip_type_dictionary @staticmethod def _get_float_coordinates(obs_point): def to_float(arg): try: - return float(arg.replace(',', '.')) + return float(arg.replace(",", ".")) except (ValueError, AttributeError): return None @@ -293,23 +312,26 @@ def _get_distance(self, obs_point): try: return geodesic(obs_point, self.coordinates).kilometers except ValueError: - logger.warning("Unable calculate the geodesic distance between points, %s, %s", - obs_point, self.coordinates) + logger.warning("Unable calculate the geodesic distance between points, %s, %s", obs_point, self.coordinates) return None def _trim_outlying(self, ntrip_dictionary): if (self.maxdist is not None) and (self.coordinates is not None): return { - 'cas': self._trim_outlying_casters(ntrip_dictionary.get('cas')), - 'net': self._trim_outlying_casters(ntrip_dictionary.get('net')), - 'str': self._trim_outlying_casters(ntrip_dictionary.get('str')) + "cas": self._trim_outlying_casters(ntrip_dictionary.get("cas")), + "net": self._trim_outlying_casters(ntrip_dictionary.get("net")), + "str": self._trim_outlying_casters(ntrip_dictionary.get("str")), } return ntrip_dictionary def _trim_outlying_casters(self, ntrip_type_dictionary): def by_distance(row): - return row['Distance'] < self.maxdist + distance = row["Distance"] + if distance is None: + return False + return row["Distance"] < self.maxdist + inlying_casters = list(filter(by_distance, ntrip_type_dictionary)) - inlying_casters.sort(key=lambda row: row['Distance']) + inlying_casters.sort(key=lambda row: row["Distance"]) return inlying_casters diff --git a/tests/test_ntripbrowser.py b/tests/test_ntripbrowser.py index 3090dfb..b684544 100644 --- a/tests/test_ntripbrowser.py +++ b/tests/test_ntripbrowser.py @@ -222,7 +222,7 @@ def test_add_coordinates(): 'net': [ { 'Authentication': 'B', - 'Distance': 248.57556516798113, + 'Distance': None, 'Fee': 'N', 'ID': 'Str1', 'Operator': 'Str2', @@ -236,7 +236,7 @@ def test_add_coordinates(): { 'Carrier': 'https://example2.htm', 'Country': 'none', - 'Distance': 248.57556516798113, + 'Distance': None, 'Format': 'B', 'Format-Details': 'N', 'ID': 'Str4', @@ -246,3 +246,85 @@ def test_add_coordinates(): } ] } + +def test_max_dist_trimmed(): + near_parsed = { + 'Mountpoint': 'near', + 'ID': 'Rehakka', + 'Format': 'RTCM 3.3', + 'Format-Details': '1004(1),1005(10),1008(10),1012(1),1019(3),1020(2),1033(10),1042(3),1046(1),1077(1),1087(1),1097(1),1127(1),1230(30)', + 'Carrier': '2', + 'Nav-System': 'GPS+GLO+GAL+BDS', + 'Network': 'SNIP', + 'Country': 'FIN', + 'Latitude': '1.1', + 'Longitude': '2.2', + 'NMEA': '1', + 'Solution': '0', + 'Generator': 'sNTRIP', + 'Compr-Encryp': 'none', + 'Authentication': 'B', + 'Fee': 'N', + 'Bitrate': '12220', + 'Other Details': '', + 'Distance': 24.8552454935518, + } + far_parsed = { + 'Mountpoint': 'far', + 'ID': 'Drummond', + 'Format': 'RTCM 3.2', + 'Format-Details': '1005(10),1074(1),1084(1),1094(1),1124(1),1230(1)', + 'Carrier': '2', + 'Nav-System': 'GPS+GLO+GAL+BDS', + 'Network': 'SNIP', + 'Country': 'BRA', + 'Latitude': '10.1', + 'Longitude': '20.2', + 'NMEA': '1', + 'Solution': '0', + 'Generator': 'sNTRIP', + 'Compr-Encryp': 'none', + 'Authentication': 'B', + 'Fee': 'N', + 'Bitrate': '7300', + 'Other Details': '', + 'Distance': 2251.719387114077, + } + test_cases = { + 50: [near_parsed], + 2500: [near_parsed, far_parsed], + } + for maxdist, expected_str in test_cases.items(): + browser = NtripBrowser('test', 1234, coordinates=(1.0, 2.0), maxdist=maxdist) + assert browser._process_raw_data(testing_content.VALID_NTRIP_TRIM_DISTANCE)['str'] == expected_str + + +def test_caster_no_distance_processed(): + near_parsed = { + 'Mountpoint': 'near', + 'ID': 'Rehakka', + 'Format': 'RTCM 3.3', + 'Format-Details': '1004(1),1005(10),1008(10),1012(1),1019(3),1020(2),1033(10),1042(3),1046(1),1077(1),1087(1),1097(1),1127(1),1230(30)', + 'Carrier': '2', + 'Nav-System': 'GPS+GLO+GAL+BDS', + 'Network': 'SNIP', + 'Country': 'FIN', + 'Latitude': '1.1', + 'Longitude': '2.2', + 'NMEA': '1', + 'Solution': '0', + 'Generator': 'sNTRIP', + 'Compr-Encryp': 'none', + 'Authentication': 'B', + 'Fee': 'N', + 'Bitrate': '12220', + 'Other Details': '', + 'Distance': 24.8552454935518, + } + + browser = NtripBrowser('test', 1234, coordinates=(1.0, 2.0), maxdist=50) + assert browser._process_raw_data(testing_content.VALID_NTRIP_NO_BASE_POINT) == { + 'cas': [], + 'net': [], + 'str': [near_parsed], + } diff --git a/tests/testing_content.py b/tests/testing_content.py index 0cec3bb..49ede6b 100644 --- a/tests/testing_content.py +++ b/tests/testing_content.py @@ -15,3 +15,13 @@ b'STR;Str3;Str4;B;N;https://example2.htm;http://example2.htm;http://sample2;none\n' \ b'NET;Str1;Str2;B;N;https://example.htm;http://example.htm;http://sample;none\n' \ b'ENDSOURCETABLE' + +VALID_NTRIP_TRIM_DISTANCE = b'SOURCETABLE 200 OK \n' \ + b'STR;near;Rehakka;RTCM 3.3;1004(1),1005(10),1008(10),1012(1),1019(3),1020(2),1033(10),1042(3),1046(1),1077(1),1087(1),1097(1),1127(1),1230(30);2;GPS+GLO+GAL+BDS;SNIP;FIN;1.1;2.2;1;0;sNTRIP;none;B;N;12220;\n' \ + b'STR;far;Drummond;RTCM 3.2;1005(10),1074(1),1084(1),1094(1),1124(1),1230(1);2;GPS+GLO+GAL+BDS;SNIP;BRA;10.1;20.2;1;0;sNTRIP;none;B;N;7300;\n' \ + b'ENDSOURCETABLE' + +VALID_NTRIP_NO_BASE_POINT = b'SOURCETABLE 200 OK \n' \ + b'STR;near;Rehakka;RTCM 3.3;1004(1),1005(10),1008(10),1012(1),1019(3),1020(2),1033(10),1042(3),1046(1),1077(1),1087(1),1097(1),1127(1),1230(30);2;GPS+GLO+GAL+BDS;SNIP;FIN;1.1;2.2;1;0;sNTRIP;none;B;N;12220;\n' \ + b'STR;no_coords;Chatham-Kent;CMR+;CMR+(1);0;;SNIP;CAN;0.00;0.00;1;0;sNTRIP;none;B;N;0;\n' \ + b'ENDSOURCETABLE' diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 72a90d7..0000000 --- a/tox.ini +++ /dev/null @@ -1,9 +0,0 @@ -[tox] -envlist = py27,py36 -[testenv] -deps= - mock - pytest - pytest-xdist -commands= - pytest -vv --basetemp={envtmpdir} --confcutdir=.. -n 2 {posargs} \ No newline at end of file