From fc8149e3125f6146a6c815f3ea17c9340c90e653 Mon Sep 17 00:00:00 2001 From: Viktor Vorobev Date: Mon, 21 Jul 2025 12:49:41 +0200 Subject: [PATCH 01/12] tests: ntrip_browser: fix test for valid caster Since NET and STR endpoints have no Longitude and Latitude, the distance can not be calculated. --- tests/test_ntripbrowser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_ntripbrowser.py b/tests/test_ntripbrowser.py index 3090dfb..9125de2 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', From c2976c66e83d6d9293583925b78d4e28e1f16778 Mon Sep 17 00:00:00 2001 From: Viktor Vorobev Date: Mon, 21 Jul 2025 12:51:23 +0200 Subject: [PATCH 02/12] repo: update python-flavored gitignore --- .gitignore | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) 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 From 9c816810f7befeda0e5ed9e964b1b655bb7ae654 Mon Sep 17 00:00:00 2001 From: Viktor Vorobev Date: Mon, 21 Jul 2025 13:46:07 +0200 Subject: [PATCH 03/12] tests: add test for checking distance to mount point --- tests/test_ntripbrowser.py | 51 ++++++++++++++++++++++++++++++++++++++ tests/testing_content.py | 5 ++++ 2 files changed, 56 insertions(+) diff --git a/tests/test_ntripbrowser.py b/tests/test_ntripbrowser.py index 9125de2..b98b5bf 100644 --- a/tests/test_ntripbrowser.py +++ b/tests/test_ntripbrowser.py @@ -246,3 +246,54 @@ 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 diff --git a/tests/testing_content.py b/tests/testing_content.py index 0cec3bb..26c085b 100644 --- a/tests/testing_content.py +++ b/tests/testing_content.py @@ -15,3 +15,8 @@ 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' From 5e114343f79a024fa5a2ab1e827d55b221a12833 Mon Sep 17 00:00:00 2001 From: Viktor Vorobev Date: Mon, 21 Jul 2025 13:57:08 +0200 Subject: [PATCH 04/12] tests: add test for case when ntrip mount point has no coordinates --- tests/test_ntripbrowser.py | 31 +++++++++++++++++++++++++++++++ tests/testing_content.py | 5 +++++ 2 files changed, 36 insertions(+) diff --git a/tests/test_ntripbrowser.py b/tests/test_ntripbrowser.py index b98b5bf..b684544 100644 --- a/tests/test_ntripbrowser.py +++ b/tests/test_ntripbrowser.py @@ -297,3 +297,34 @@ def test_max_dist_trimmed(): 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 26c085b..49ede6b 100644 --- a/tests/testing_content.py +++ b/tests/testing_content.py @@ -20,3 +20,8 @@ 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' From 16d0023bb2df50fdb5d1bedff4ae1dd9017fb06a Mon Sep 17 00:00:00 2001 From: Viktor Vorobev Date: Mon, 21 Jul 2025 13:57:58 +0200 Subject: [PATCH 05/12] ntripbrowser: ntripbrowser: fix distance calculation exception In some cases there is no information about base coordinates, and thus it is impossible to calculate the distance to the point. Because of that we need this additional check --- ntripbrowser/ntripbrowser.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ntripbrowser/ntripbrowser.py b/ntripbrowser/ntripbrowser.py index 36ed263..950d254 100644 --- a/ntripbrowser/ntripbrowser.py +++ b/ntripbrowser/ntripbrowser.py @@ -132,6 +132,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) @@ -254,11 +256,18 @@ def _form_ntrip_entries(self, ntrip_tables): @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:] 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 { @@ -309,6 +318,9 @@ def _trim_outlying(self, ntrip_dictionary): def _trim_outlying_casters(self, ntrip_type_dictionary): def by_distance(row): + 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']) From 53f9661e3d717c1606cb561e5cdb3a0f1ead683f Mon Sep 17 00:00:00 2001 From: Viktor Vorobev Date: Mon, 21 Jul 2025 14:04:28 +0200 Subject: [PATCH 06/12] pipfile: update dependencies to work with python3.12 --- Pipfile | 12 +- Pipfile.lock | 622 +++++++++++++++++++++++++++++---------------------- 2 files changed, 363 insertions(+), 271 deletions(-) diff --git a/Pipfile b/Pipfile index 289039c..28508ce 100644 --- a/Pipfile +++ b/Pipfile @@ -6,16 +6,16 @@ name = "pypi" [dev-packages] scandir = "*" pathlib2 = "*" -pytest = "==4.6.0" -pylint = "==1.9.3" +pytest = "*" +pylint = "*" "flake8" = "*" mock = "*" -tox = "==3.0.0" +tox = "*" [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..542f792 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "a51bb4f44b1a54e4bb4b536b1095a7a2dc9f4e1996dbf9436ff4e86a05ff8d6d" + "sha256": "280b6919617ffb58e120a95adb45a5d26d08472a1f743c2adf1b8c89c32281c9" }, "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,260 +278,216 @@ }, "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" + "sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce", + "sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec" ], - "version": "==1.6.6" + "markers": "python_full_version >= '3.9.0'", + "version": "==3.3.11" }, - "atomicwrites": { + "cachetools": { "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e", + "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587" ], - "version": "==1.3.0" + "markers": "python_version >= '3.9'", + "version": "==6.1.0" }, - "attrs": { - "hashes": [ - "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", - "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" - ], - "version": "==19.1.0" - }, - "backports.functools-lru-cache": { + "chardet": { "hashes": [ - "sha256:9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a", - "sha256:f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd" + "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", + "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970" ], - "markers": "python_version == '2.7'", - "version": "==1.5" + "markers": "python_version >= '3.7'", + "version": "==5.2.0" }, - "configparser": { + "colorama": { "hashes": [ - "sha256:45d1272aad6cfd7a8a06cf5c73f2ceb6a190f6acc1fa707e7f82a4c053b28b18", - "sha256:bc37850f0cc42a1725a796ef7d92690651bf1af37d744cc63161dac62cabee17" + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" ], - "markers": "python_version < '3'", - "version": "==3.8.1" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==0.4.6" }, - "contextlib2": { + "dill": { "hashes": [ - "sha256:509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48", - "sha256:f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00" + "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", + "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049" ], - "markers": "python_version < '3'", - "version": "==0.5.5" + "markers": "python_version >= '3.8'", + "version": "==0.4.0" }, - "entrypoints": { + "distlib": { "hashes": [ - "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", - "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" + "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", + "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d" ], - "version": "==0.3" + "version": "==0.4.0" }, - "enum34": { + "filelock": { "hashes": [ - "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", - "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", - "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", - "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" + "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", + "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de" ], - "markers": "python_version < '3.4'", - "version": "==1.1.6" + "markers": "python_version >= '3.9'", + "version": "==3.18.0" }, "flake8": { "hashes": [ - "sha256:19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", - "sha256:8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696" + "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", + "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872" ], "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" + "markers": "python_version >= '3.9'", + "version": "==7.3.0" }, - "futures": { + "iniconfig": { "hashes": [ - "sha256:49b3f5b064b6e3afc3316421a3f25f66c137ae88f068abbf72830170033c5e16", - "sha256:7e033af76a5e35f58e56da7a91e687706faf4e7bdfb2cbc3f2cca6b9bcda9794" + "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", + "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760" ], - "markers": "python_version < '3.2'", - "version": "==3.3.0" - }, - "importlib-metadata": { - "hashes": [ - "sha256:23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", - "sha256:80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3" - ], - "version": "==0.19" + "markers": "python_version >= '3.8'", + "version": "==2.1.0" }, "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" + "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", + "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615" + ], + "markers": "python_full_version >= '3.9.0'", + "version": "==6.0.1" }, "mccabe": { "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", + "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" ], - "version": "==0.6.1" + "markers": "python_version >= '3.6'", + "version": "==0.7.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" + "markers": "python_version >= '3.8'", + "version": "==25.0" }, "pathlib2": { "hashes": [ - "sha256:2156525d6576d21c4dcaddfa427fae887ef89a7a9de5cbfe0728b3aafa78427e", - "sha256:446014523bb9be5c28128c4d2a10ad6bb60769e78bd85658fe44a450674e0ef8" + "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b", + "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641" ], "index": "pypi", - "version": "==2.3.4" + "version": "==2.3.7.post1" }, - "pluggy": { + "platformdirs": { "hashes": [ - "sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", - "sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c" + "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", + "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4" ], - "version": "==0.12.0" + "markers": "python_version >= '3.9'", + "version": "==4.3.8" }, - "py": { + "pluggy": { "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" ], - "version": "==1.8.0" + "markers": "python_version >= '3.9'", + "version": "==1.6.0" }, "pycodestyle": { "hashes": [ - "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", - "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", + "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d" ], - "version": "==2.5.0" + "markers": "python_version >= '3.9'", + "version": "==2.14.0" }, "pyflakes": { "hashes": [ - "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", - "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" + "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", + "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f" ], - "version": "==2.1.1" + "markers": "python_version >= '3.9'", + "version": "==3.4.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" }, "pylint": { "hashes": [ - "sha256:09bc539f85706f2cca720a7ddf28f5c6cf8185708d6cb5bbf7a90a32c3b3b0aa", - "sha256:b8471105f12c73a1b9eee2bb2474080370e062a7290addd215eb34bc4dfe9fd8" + "sha256:2b11de8bde49f9c5059452e0c310c079c746a0a8eeaa789e5aa966ecc23e4559", + "sha256:43860aafefce92fca4cf6b61fe199cdc5ae54ea28f9bf4cd49de267b5195803d" ], "index": "pypi", - "version": "==1.9.3" + "markers": "python_full_version >= '3.9.0'", + "version": "==3.3.7" }, - "pyparsing": { + "pyproject-api": { "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" + "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335", + "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948" ], - "version": "==2.4.2" + "markers": "python_version >= '3.9'", + "version": "==1.9.1" }, "pytest": { "hashes": [ - "sha256:52fa94b4ac81d2f063ee05e303acedf5c605e15dc0f4eef468b5c137f77241c3", - "sha256:5467f37a0d6bb0b4e684b71af268e005996b9eaaefe54e3d64d86afd90da8d78" + "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", + "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c" ], "index": "pypi", - "version": "==4.6.0" + "markers": "python_version >= '3.9'", + "version": "==8.4.1" }, "scandir": { "hashes": [ @@ -388,64 +506,38 @@ "index": "pypi", "version": "==1.10.0" }, - "singledispatch": { + "six": { "hashes": [ - "sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c", - "sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], - "markers": "python_version < '3.4'", - "version": "==3.4.0.3" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==1.17.0" }, - "six": { + "tomlkit": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", + "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0" ], - "version": "==1.12.0" + "markers": "python_version >= '3.8'", + "version": "==0.13.3" }, "tox": { "hashes": [ - "sha256:96efa09710a3daeeb845561ebbe1497641d9cef2ee0aea30db6969058b2bda2f", - "sha256:9ee7de958a43806402a38c0d2aa07fa8553f4d2c20a15b140e9f771c2afeade0" + "sha256:3e2f5c0a00523a58666690108b66820150f6435cb6e4dd95caf21bb52133c1d1", + "sha256:442347b1a415733850f097e7e78b8c5f38b5e1719f8b7205aade5d055f08068c" ], "index": "pypi", - "version": "==3.0.0" - }, - "typing": { - "hashes": [ - "sha256:38566c558a0a94d6531012c8e917b1b8518a41e418f7f15f00e129cc80162ad3", - "sha256:53765ec4f83a2b720214727e319607879fec4acde22c4fbb54fa2604e79e44ce", - "sha256:84698954b4e6719e912ef9a42a2431407fe3755590831699debda6fba92aac55" - ], - "markers": "python_version < '3.5'", - "version": "==3.7.4" + "markers": "python_version >= '3.9'", + "version": "==4.28.0" }, "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" + "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56", + "sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0" ], - "version": "==0.5.2" + "markers": "python_version >= '3.8'", + "version": "==20.32.0" } } } From a291391f7a682e1695e599c66641d64ba60f2cff Mon Sep 17 00:00:00 2001 From: Viktor Vorobev Date: Mon, 21 Jul 2025 14:05:31 +0200 Subject: [PATCH 07/12] code-quality: bump submodule --- code-quality | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 737566315ef2230513c713e4596253604ae33216 Mon Sep 17 00:00:00 2001 From: Viktor Vorobev Date: Mon, 21 Jul 2025 14:11:56 +0200 Subject: [PATCH 08/12] repo: replace pylint and flake8 with ruff --- Makefile | 8 +++- Pipfile | 3 +- Pipfile.lock | 101 +++++++++++++-------------------------------------- 3 files changed, 33 insertions(+), 79 deletions(-) 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 28508ce..c43f4a0 100644 --- a/Pipfile +++ b/Pipfile @@ -7,10 +7,9 @@ name = "pypi" scandir = "*" pathlib2 = "*" pytest = "*" -pylint = "*" -"flake8" = "*" mock = "*" tox = "*" +ruff = "*" [packages] cchardet = "==2.2.0a2" diff --git a/Pipfile.lock b/Pipfile.lock index 542f792..a9f5e44 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "280b6919617ffb58e120a95adb45a5d26d08472a1f743c2adf1b8c89c32281c9" + "sha256": "c9619eed38f91c4bca148760cc3a833ef08a189fc9f91ea9f2456f852239c3f5" }, "pipfile-spec": 6, "requires": {}, @@ -310,14 +310,6 @@ } }, "develop": { - "astroid": { - "hashes": [ - "sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce", - "sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec" - ], - "markers": "python_full_version >= '3.9.0'", - "version": "==3.3.11" - }, "cachetools": { "hashes": [ "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e", @@ -342,14 +334,6 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", "version": "==0.4.6" }, - "dill": { - "hashes": [ - "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", - "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049" - ], - "markers": "python_version >= '3.8'", - "version": "==0.4.0" - }, "distlib": { "hashes": [ "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", @@ -365,15 +349,6 @@ "markers": "python_version >= '3.9'", "version": "==3.18.0" }, - "flake8": { - "hashes": [ - "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", - "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==7.3.0" - }, "iniconfig": { "hashes": [ "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", @@ -382,22 +357,6 @@ "markers": "python_version >= '3.8'", "version": "==2.1.0" }, - "isort": { - "hashes": [ - "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", - "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615" - ], - "markers": "python_full_version >= '3.9.0'", - "version": "==6.0.1" - }, - "mccabe": { - "hashes": [ - "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", - "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" - ], - "markers": "python_version >= '3.6'", - "version": "==0.7.0" - }, "mock": { "hashes": [ "sha256:4e460e818629b4b173f32d08bf30d3af8123afbb8e04bb5707a1fd4799e503f0", @@ -439,22 +398,6 @@ "markers": "python_version >= '3.9'", "version": "==1.6.0" }, - "pycodestyle": { - "hashes": [ - "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", - "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d" - ], - "markers": "python_version >= '3.9'", - "version": "==2.14.0" - }, - "pyflakes": { - "hashes": [ - "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", - "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f" - ], - "markers": "python_version >= '3.9'", - "version": "==3.4.0" - }, "pygments": { "hashes": [ "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", @@ -463,15 +406,6 @@ "markers": "python_version >= '3.8'", "version": "==2.19.2" }, - "pylint": { - "hashes": [ - "sha256:2b11de8bde49f9c5059452e0c310c079c746a0a8eeaa789e5aa966ecc23e4559", - "sha256:43860aafefce92fca4cf6b61fe199cdc5ae54ea28f9bf4cd49de267b5195803d" - ], - "index": "pypi", - "markers": "python_full_version >= '3.9.0'", - "version": "==3.3.7" - }, "pyproject-api": { "hashes": [ "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335", @@ -489,6 +423,31 @@ "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", + "markers": "python_version >= '3.7'", + "version": "==0.12.4" + }, "scandir": { "hashes": [ "sha256:2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e", @@ -514,14 +473,6 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.17.0" }, - "tomlkit": { - "hashes": [ - "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", - "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0" - ], - "markers": "python_version >= '3.8'", - "version": "==0.13.3" - }, "tox": { "hashes": [ "sha256:3e2f5c0a00523a58666690108b66820150f6435cb6e4dd95caf21bb52133c1d1", From c976a3b7bcce050a146c136ba6baac4b2eaf1074 Mon Sep 17 00:00:00 2001 From: Viktor Vorobev Date: Mon, 21 Jul 2025 14:13:43 +0200 Subject: [PATCH 09/12] ntripbrowser: fix format errors and drop python2 --- ntripbrowser/__init__.py | 18 ++++-- ntripbrowser/browser.py | 83 ++++++++++++++------------ ntripbrowser/constants.py | 46 ++++++++++++--- ntripbrowser/ntripbrowser.py | 110 +++++++++++++++++++---------------- 4 files changed, 156 insertions(+), 101 deletions(-) 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 950d254..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) @@ -133,7 +136,7 @@ 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)}') + 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) @@ -161,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: @@ -178,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 @@ -199,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): @@ -226,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) @@ -239,19 +248,19 @@ 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 @@ -259,34 +268,35 @@ 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))) - + 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'): + 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 @@ -302,26 +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): - distance = row['Distance'] + distance = row["Distance"] if distance is None: return False - return row['Distance'] < self.maxdist + 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 From 0c780050840fafd395aa29c1862c9674b2b42512 Mon Sep 17 00:00:00 2001 From: Viktor Vorobev Date: Mon, 21 Jul 2025 14:23:42 +0200 Subject: [PATCH 10/12] repo: introduce github workflow --- .github/workflows/ci.yaml | 39 +++++++++++++++++++++++++++++++++++++++ .travis.yml | 20 -------------------- 2 files changed, 39 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/ci.yaml delete mode 100644 .travis.yml 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/.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 From 9e10810544c10ce5bb415fdce9a13a541a300fb5 Mon Sep 17 00:00:00 2001 From: Viktor Vorobev Date: Mon, 21 Jul 2025 14:25:14 +0200 Subject: [PATCH 11/12] pipfile: drop tox and other redundant dependencies --- Pipfile | 3 -- Pipfile.lock | 107 +-------------------------------------------------- tox.ini | 9 ----- 3 files changed, 1 insertion(+), 118 deletions(-) delete mode 100644 tox.ini diff --git a/Pipfile b/Pipfile index c43f4a0..61eeaff 100644 --- a/Pipfile +++ b/Pipfile @@ -4,11 +4,8 @@ verify_ssl = true name = "pypi" [dev-packages] -scandir = "*" -pathlib2 = "*" pytest = "*" mock = "*" -tox = "*" ruff = "*" [packages] diff --git a/Pipfile.lock b/Pipfile.lock index a9f5e44..ce9c657 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c9619eed38f91c4bca148760cc3a833ef08a189fc9f91ea9f2456f852239c3f5" + "sha256": "8df8eb243140afb4d9d7d96ab411721629dad094cf61549997f6b97f9a1f3993" }, "pipfile-spec": 6, "requires": {}, @@ -310,45 +310,6 @@ } }, "develop": { - "cachetools": { - "hashes": [ - "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e", - "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587" - ], - "markers": "python_version >= '3.9'", - "version": "==6.1.0" - }, - "chardet": { - "hashes": [ - "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", - "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970" - ], - "markers": "python_version >= '3.7'", - "version": "==5.2.0" - }, - "colorama": { - "hashes": [ - "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", - "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", - "version": "==0.4.6" - }, - "distlib": { - "hashes": [ - "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", - "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d" - ], - "version": "==0.4.0" - }, - "filelock": { - "hashes": [ - "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", - "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de" - ], - "markers": "python_version >= '3.9'", - "version": "==3.18.0" - }, "iniconfig": { "hashes": [ "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", @@ -374,22 +335,6 @@ "markers": "python_version >= '3.8'", "version": "==25.0" }, - "pathlib2": { - "hashes": [ - "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b", - "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641" - ], - "index": "pypi", - "version": "==2.3.7.post1" - }, - "platformdirs": { - "hashes": [ - "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", - "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4" - ], - "markers": "python_version >= '3.9'", - "version": "==4.3.8" - }, "pluggy": { "hashes": [ "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", @@ -406,14 +351,6 @@ "markers": "python_version >= '3.8'", "version": "==2.19.2" }, - "pyproject-api": { - "hashes": [ - "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335", - "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948" - ], - "markers": "python_version >= '3.9'", - "version": "==1.9.1" - }, "pytest": { "hashes": [ "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", @@ -447,48 +384,6 @@ "index": "pypi", "markers": "python_version >= '3.7'", "version": "==0.12.4" - }, - "scandir": { - "hashes": [ - "sha256:2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e", - "sha256:2ae41f43797ca0c11591c0c35f2f5875fa99f8797cb1a1fd440497ec0ae4b022", - "sha256:2b8e3888b11abb2217a32af0766bc06b65cc4a928d8727828ee68af5a967fa6f", - "sha256:2c712840c2e2ee8dfaf36034080108d30060d759c7b73a01a52251cc8989f11f", - "sha256:4d4631f6062e658e9007ab3149a9b914f3548cb38bfb021c64f39a025ce578ae", - "sha256:67f15b6f83e6507fdc6fca22fedf6ef8b334b399ca27c6b568cbfaa82a364173", - "sha256:7d2d7a06a252764061a020407b997dd036f7bd6a175a5ba2b345f0a357f0b3f4", - "sha256:8c5922863e44ffc00c5c693190648daa6d15e7c1207ed02d6f46a8dcc2869d32", - "sha256:92c85ac42f41ffdc35b6da57ed991575bdbe69db895507af88b9f499b701c188", - "sha256:b24086f2375c4a094a6b51e78b4cf7ca16c721dcee2eddd7aa6494b42d6d519d", - "sha256:cb925555f43060a1745d0a321cca94bcea927c50114b623d73179189a4e100ac" - ], - "index": "pypi", - "version": "==1.10.0" - }, - "six": { - "hashes": [ - "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", - "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.17.0" - }, - "tox": { - "hashes": [ - "sha256:3e2f5c0a00523a58666690108b66820150f6435cb6e4dd95caf21bb52133c1d1", - "sha256:442347b1a415733850f097e7e78b8c5f38b5e1719f8b7205aade5d055f08068c" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==4.28.0" - }, - "virtualenv": { - "hashes": [ - "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56", - "sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0" - ], - "markers": "python_version >= '3.8'", - "version": "==20.32.0" } } } 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 From d99646cbc199ad4d695088d1a40529148ce75865 Mon Sep 17 00:00:00 2001 From: Viktor Vorobev Date: Mon, 21 Jul 2025 14:27:00 +0200 Subject: [PATCH 12/12] readme: update readme --- README.md | 102 +++++++++++++++++++++++++----------------------------- 1 file changed, 48 insertions(+), 54 deletions(-) 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 +```