From 2ea99c8c177179829f578519b00429a8d2f95509 Mon Sep 17 00:00:00 2001 From: Philipp Horstenkamp Date: Mon, 11 Sep 2023 19:49:08 +0200 Subject: [PATCH 01/11] =?UTF-8?q?Removed=20unneded=20lines=20=E2=98=BA=20(?= =?UTF-8?q?#113)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aki_prj23_transparenzregister/config/config_providers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/aki_prj23_transparenzregister/config/config_providers.py b/src/aki_prj23_transparenzregister/config/config_providers.py index 82c5c27..47d1db3 100644 --- a/src/aki_prj23_transparenzregister/config/config_providers.py +++ b/src/aki_prj23_transparenzregister/config/config_providers.py @@ -23,7 +23,6 @@ class ConfigProvider(metaclass=abc.ABCMeta): Returns: PostgreConnectionString: Connection details """ - raise NotImplementedError @abc.abstractmethod def get_mongo_connection_string(self) -> MongoConnection: @@ -35,7 +34,6 @@ class ConfigProvider(metaclass=abc.ABCMeta): Returns: MongoConnection: Connection details """ - raise NotImplementedError class JsonFileConfigProvider(ConfigProvider): From d64f53eca909e048e7a4cc9c762324f2052e7d84 Mon Sep 17 00:00:00 2001 From: Philipp Horstenkamp Date: Mon, 11 Sep 2023 20:21:35 +0200 Subject: [PATCH 02/11] Script for the transfer of data from Mongo to SQL (#80) --- .pre-commit-config.yaml | 2 + CONTRIBUTING.md | 2 +- poetry.lock | 134 +++- pyproject.toml | 33 +- .../models/company.py | 17 +- .../utils/data_transfer.py | 355 +++++++++ .../utils/enum_types.py | 69 ++ .../utils/enumy_types.py | 27 - .../utils/sql/connector.py | 7 + .../utils/sql/entities.py | 4 +- .../utils/string_tools.py | 18 + tests/apps/enrich_company_financials_test.py | 6 +- tests/config/config_providers_test.py | 6 + tests/conftest.py | 116 +++ .../data_extraction/bundesanzeiger_test.py | 1 + tests/utils/data_transfer_test.py | 744 ++++++++++++++++++ tests/utils/enum_types_test.py | 40 + tests/utils/mongo/mongo_test.py | 5 + tests/utils/mongo/news_mongo_service_test.py | 7 + tests/utils/sql/connector_test.py | 2 + tests/utils/sql/entities_test.py | 8 +- tests/utils/string_tools_test.py | 35 + 22 files changed, 1565 insertions(+), 73 deletions(-) create mode 100644 src/aki_prj23_transparenzregister/utils/data_transfer.py create mode 100644 src/aki_prj23_transparenzregister/utils/enum_types.py delete mode 100644 src/aki_prj23_transparenzregister/utils/enumy_types.py create mode 100644 src/aki_prj23_transparenzregister/utils/string_tools.py create mode 100644 tests/conftest.py create mode 100644 tests/utils/data_transfer_test.py create mode 100644 tests/utils/enum_types_test.py create mode 100644 tests/utils/string_tools_test.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c3c7048..cc41585 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -65,6 +65,8 @@ repos: - types-setuptools - types-requests - types-pyOpenSSL + - types-cachetools + - loguru-mypy - repo: https://github.com/frnmst/md-toc rev: 8.2.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac2c1ed..e575007 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Dev Setup -- [Install Python 3.11](https://www.python.org/downloads/release/python-3111/) +- [Install Python 3.11](https://www.python.org/downloads/release/python-3115/) - [Install Poetry](https://python-poetry.org/docs/#installation) - [Install GiT](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) - [Configure GiT](https://support.atlassian.com/bitbucket-cloud/docs/configure-your-dvcs-username-for-commits/) diff --git a/poetry.lock b/poetry.lock index e267d74..16ee761 100644 --- a/poetry.lock +++ b/poetry.lock @@ -229,14 +229,34 @@ lxml = ["lxml"] [[package]] name = "black" -version = "23.9.0" +version = "23.9.1" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "black-23.9.0-py3-none-any.whl", hash = "sha256:9366c1f898981f09eb8da076716c02fd021f5a0e63581c66501d68a2e4eab844"}, - {file = "black-23.9.0.tar.gz", hash = "sha256:3511c8a7e22ce653f89ae90dfddaf94f3bb7e2587a245246572d3b9c92adf066"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, + {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, + {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, + {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, + {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, + {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, + {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, + {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, + {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, + {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, + {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, ] [package.dependencies] @@ -307,6 +327,18 @@ dev = ["CacheControl[filecache,redis]", "black", "build", "cherrypy", "mypy", "p filecache = ["filelock (>=3.8.0)"] redis = ["redis (>=2.10.5)"] +[[package]] +name = "cachetools" +version = "5.3.1" +description = "Extensible memoizing collections and decorators" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.3.1-py3-none-any.whl", hash = "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590"}, + {file = "cachetools-5.3.1.tar.gz", hash = "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b"}, +] + [[package]] name = "certifi" version = "2023.7.22" @@ -1278,6 +1310,18 @@ files = [ {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, ] +[[package]] +name = "et-xmlfile" +version = "1.1.0" +description = "An implementation of lxml.xmlfile for the standard library" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, + {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, +] + [[package]] name = "exceptiongroup" version = "1.1.3" @@ -2319,14 +2363,14 @@ testing = ["black", "isort", "pytest (>=6,!=7.0.0)", "pytest-xdist (>=2)", "twin [[package]] name = "loguru" -version = "0.7.1" +version = "0.7.2" description = "Python logging made (stupidly) simple" category = "main" optional = false python-versions = ">=3.5" files = [ - {file = "loguru-0.7.1-py3-none-any.whl", hash = "sha256:046bf970cb3cad77a28d607cbf042ac25a407db987a1e801c7f7e692469982f9"}, - {file = "loguru-0.7.1.tar.gz", hash = "sha256:7ba2a7d81b79a412b0ded69bd921e012335e80fd39937a633570f273a343579e"}, + {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"}, + {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}, ] [package.dependencies] @@ -2334,7 +2378,22 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] -dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "pre-commit (==3.3.1)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] +dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] + +[[package]] +name = "loguru-mypy" +version = "0.0.4" +description = "" +category = "dev" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "loguru-mypy-0.0.4.tar.gz", hash = "sha256:1f1767d7737f1825295ce147f7e751f91837f5759b3c2f41801adc65691aeed4"}, + {file = "loguru_mypy-0.0.4-py3-none-any.whl", hash = "sha256:98e044be509887a314e683a1e851813310b396be48388c1fe4de97a2eac99d4d"}, +] + +[package.dependencies] +typing-extensions = "*" [[package]] name = "lxml" @@ -3105,6 +3164,21 @@ packaging = "*" protobuf = "*" sympy = "*" +[[package]] +name = "openpyxl" +version = "3.1.2" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "openpyxl-3.1.2-py2.py3-none-any.whl", hash = "sha256:f91456ead12ab3c6c2e9491cf33ba6d08357d802192379bb482f1033ade496f5"}, + {file = "openpyxl-3.1.2.tar.gz", hash = "sha256:a6f5977418eff3b2d5500d54d9db50c8277a368436f4e4f8ddb1be3422870184"}, +] + +[package.dependencies] +et-xmlfile = "*" + [[package]] name = "outcome" version = "1.2.0" @@ -3292,6 +3366,26 @@ files = [ [package.dependencies] ptyprocess = ">=0.5" +[[package]] +name = "pgeocode" +version = "0.4.1" +description = "Approximate geocoding" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pgeocode-0.4.1-py3-none-any.whl", hash = "sha256:0cc3916d75c41ffcd910ccc2252235a66c627346502cba5d2e97b6ea0aa83257"}, + {file = "pgeocode-0.4.1.tar.gz", hash = "sha256:08f35dedf79957769641c7137aa9cc189e1bb63033226372dce372b14973e8b2"}, +] + +[package.dependencies] +numpy = "*" +pandas = "*" +requests = "*" + +[package.extras] +fuzzy = ["thefuzz"] + [[package]] name = "pickleshare" version = "0.7.5" @@ -3564,14 +3658,14 @@ virtualenv = ">=20.10.0" [[package]] name = "prettytable" -version = "3.8.0" +version = "3.9.0" description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "prettytable-3.8.0-py3-none-any.whl", hash = "sha256:03481bca25ae0c28958c8cd6ac5165c159ce89f7ccde04d5c899b24b68bb13b7"}, - {file = "prettytable-3.8.0.tar.gz", hash = "sha256:031eae6a9102017e8c7c7906460d150b7ed78b20fd1d8c8be4edaf88556c07ce"}, + {file = "prettytable-3.9.0-py3-none-any.whl", hash = "sha256:a71292ab7769a5de274b146b276ce938786f56c31cf7cea88b6f3775d82fe8c8"}, + {file = "prettytable-3.9.0.tar.gz", hash = "sha256:f4ed94803c23073a90620b201965e5dc0bccf1760b7a7eaf3158cab8aaffdf34"}, ] [package.dependencies] @@ -5508,6 +5602,18 @@ exceptiongroup = "*" trio = ">=0.11" wsproto = ">=0.14" +[[package]] +name = "types-cachetools" +version = "5.3.0.6" +description = "Typing stubs for cachetools" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-cachetools-5.3.0.6.tar.gz", hash = "sha256:595f0342d246c8ba534f5a762cf4c2f60ecb61e8002b8b2277fd5cf791d4e851"}, + {file = "types_cachetools-5.3.0.6-py3-none-any.whl", hash = "sha256:f7f8a25bfe306f2e6bc2ad0a2f949d9e72f2d91036d509c36d3810bf728bc6e1"}, +] + [[package]] name = "types-pyopenssl" version = "23.2.0.2" @@ -5727,14 +5833,14 @@ files = [ [[package]] name = "websocket-client" -version = "1.6.2" +version = "1.6.3" description = "WebSocket client for Python with low level API options" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "websocket-client-1.6.2.tar.gz", hash = "sha256:53e95c826bf800c4c465f50093a8c4ff091c7327023b10bfaff40cf1ef170eaa"}, - {file = "websocket_client-1.6.2-py3-none-any.whl", hash = "sha256:ce54f419dfae71f4bdba69ebe65bf7f0a93fe71bc009ad3a010aacc3eebad537"}, + {file = "websocket-client-1.6.3.tar.gz", hash = "sha256:3aad25d31284266bcfcfd1fd8a743f63282305a364b8d0948a43bd606acc652f"}, + {file = "websocket_client-1.6.3-py3-none-any.whl", hash = "sha256:6cfc30d051ebabb73a5fa246efdcc14c8fbebbd0330f8984ac3bb6d9edd2ad03"}, ] [package.extras] @@ -5808,4 +5914,4 @@ ingest = ["selenium"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "05d03e1ed3bdaa638f75c853dd28fb501a92d9209e757daf732abd56c78f8332" +content-hash = "f15e3b3171f0b6b22635f5c9de7635114c99447c5b3d41f8b1596d005fe1dce8" diff --git a/pyproject.toml b/pyproject.toml index 99f7e31..b69d034 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ source = ["src"] [tool.mypy] disallow_untyped_defs = true -exclude = ".ipynb_checkpoints, .mypy_cache, .mytest_cache, build" +exclude = [".ipynb_checkpoints", ".mypy_cache", ".mytest_cache", "build", "venv", ".venv", "Jupyter"] follow_imports = "silent" ignore_missing_imports = true install_types = true @@ -35,20 +35,22 @@ readme = "README.md" version = "0.1.0" [tool.poetry.dependencies] -SQLAlchemy = {version = "^1.4.46", extras = ["mypy"]} -dash = "^2.11.1" -dash-bootstrap-components = "^1.4.2" +SQLAlchemy = {version = "^1.4.49", extras = ["mypy"]} +cachetools = "^5.3.1" +dash = "^2.13.0" +dash-bootstrap-components = "^1.5.0" deutschland = {git = "https://github.com/TrisNol/deutschland.git", branch = "hotfix/python-3.11-support"} loguru = "^0.7.0" -matplotlib = "^3.7.1" -plotly = "^5.14.1" +matplotlib = "^3.7.2" +pgeocode = "^0.4.0" +plotly = "^5.16.1" psycopg2-binary = "^2.9.7" -pymongo = "^4.4.1" +pymongo = "^4.5.0" python = "^3.11" python-dotenv = "^1.0.0" seaborn = "^0.12.2" -selenium = "^4.10.0" -tqdm = "^4.65.0" +selenium = "^4.12.0" +tqdm = "^4.66.1" [tool.poetry.extras] ingest = ["selenium"] @@ -57,6 +59,7 @@ ingest = ["selenium"] black = {extras = ["jupyter"], version = "^23.9.0"} jupyterlab = "^4.0.5" nbconvert = "^7.8.0" +openpyxl = "^3.1.2" pre-commit = "^3.4.0" rise = "^5.7.1" @@ -64,7 +67,7 @@ rise = "^5.7.1" jupyter = "^1.0.0" myst-parser = "^1.0.0" nbsphinx = "^0.9.2" -sphinx = "^6.2.1" +sphinx = "*" sphinx-copybutton = "^0.5.2" sphinx-rtd-theme = "^1.3.0" sphinx_autodoc_typehints = "*" @@ -73,11 +76,13 @@ sphinxcontrib-napoleon = "^0.7" [tool.poetry.group.lint.dependencies] black = "^23.9.0" +loguru-mypy = "^0.0.4" mypy = "^1.5.1" -pandas-stubs = "^2.0.3.230814" +pandas-stubs = "^2.0.1.230501" pip-audit = "^2.6.1" pip-licenses = "^4.3.2" ruff = "^0.0.287" +types-cachetools = "^5.3.0.6" types-pyOpenSSL = "*" types-requests = "^2.31.0.2" types-setuptools = "*" @@ -90,6 +95,10 @@ pytest-cov = "^4.1.0" pytest-mock = "^3.11.1" pytest-repeat = "^0.9.1" +[tool.poetry.scripts] +data-transfer = "aki_prj23_transparenzregister.utils.data_transfer:transfer_data" +reset-sql = "aki_prj23_transparenzregister.utils.sql.connector:reset_all_tables" + [tool.ruff] exclude = [ ".bzr", @@ -127,7 +136,7 @@ unfixable = ["B"] builtins-ignorelist = ["id"] [tool.ruff.per-file-ignores] -"tests/*.py" = ["S101", "D100", "D101", "D107", "D103"] +"tests/*.py" = ["S101", "SLF001", "S311", "D103"] [tool.ruff.pydocstyle] convention = "google" diff --git a/src/aki_prj23_transparenzregister/models/company.py b/src/aki_prj23_transparenzregister/models/company.py index d160826..11c0a5b 100644 --- a/src/aki_prj23_transparenzregister/models/company.py +++ b/src/aki_prj23_transparenzregister/models/company.py @@ -1,5 +1,4 @@ """Company model.""" -from abc import ABC from dataclasses import asdict, dataclass from enum import Enum @@ -34,12 +33,8 @@ class Location: @dataclass -class CompanyRelationship(ABC): - """_summary_. - - Args: - ABC (_type_): _description_ - """ +class CompanyRelationship: + """_summary_.""" role: RelationshipRoleEnum location: Location @@ -92,6 +87,8 @@ class YearlyResult: @dataclass class Company: + """_summary_.""" + """Company dataclass.""" id: CompanyID @@ -102,9 +99,5 @@ class Company: # yearly_results: list[FinancialResults] def to_dict(self) -> dict: - """_summary_. - - Returns: - dict: _description_ - """ + """_summary_.""" return asdict(self) diff --git a/src/aki_prj23_transparenzregister/utils/data_transfer.py b/src/aki_prj23_transparenzregister/utils/data_transfer.py new file mode 100644 index 0000000..2d4f129 --- /dev/null +++ b/src/aki_prj23_transparenzregister/utils/data_transfer.py @@ -0,0 +1,355 @@ +"""This module contains the data transfer and refinement functionalities between staging and production DB.""" +import sys +from datetime import date +from typing import Any + +import sqlalchemy as sa +from cachetools import LRUCache, cached +from loguru import logger +from sqlalchemy.orm import Session +from tqdm import tqdm + +from aki_prj23_transparenzregister.config.config_providers import JsonFileConfigProvider +from aki_prj23_transparenzregister.utils.enum_types import RelationTypeEnum +from aki_prj23_transparenzregister.utils.mongo.company_mongo_service import ( + CompanyMongoService, +) +from aki_prj23_transparenzregister.utils.mongo.connector import MongoConnector +from aki_prj23_transparenzregister.utils.sql import entities +from aki_prj23_transparenzregister.utils.sql.connector import ( + get_session, + reset_all_tables, +) +from aki_prj23_transparenzregister.utils.string_tools import simplify_string + + +class DataInvalidError(ValueError): + """This error is thrown if a db entry can't be parsed for the production db.""" + + def __init__(self, message: str) -> None: + """Argument of the error to be parsed along.""" + super().__init__(message) + + +def _refine_district_court_entry(name: str, city: str | None) -> tuple[str, str]: + """Refines the district court entry and tests for consistency. + + Args: + name: The name of the court. + city: The city where the cort is placed. + + Returns: + A tuple containing cort name and court city. + """ + if not name: + raise DataInvalidError("There is no court name.") + if not name.startswith("Amtsgericht "): + raise DataInvalidError( + f"The name of the district court does not start correctly: {name}" + ) + if not city or city not in name.split(" ", 1)[1]: + city = name.split(" ", 1)[1].strip() + return name, city + + +def _read_district_court_id(name: str, city: str, db: Session) -> int | None: + """Reads a district court id for a company if the district court is registered. + + Args: + name: The name of the court. + city: The name of the city where the court is placed. + db: A session to connect to an SQL db via SQLAlchemy. + + + Returns: + The district court id as an int if the district court is known. + Otherwise, returns None. + """ + return ( + db.query(entities.DistrictCourt.id) + .filter(entities.DistrictCourt.name == name) + .filter(entities.DistrictCourt.city == city) + .scalar() + ) + + +def _read_person_id( + name: str, surname: str, date_of_birth: date, db: Session +) -> int | None: + """Reads a person id if the person is already registered. + + Args: + name: The first name of the person. + surname: The last name of the person. + date_of_birth: The date the person was born. + db: A session to connect to an SQL db via SQLAlchemy. + + Returns: + The district court id as an int if the district court is known. + Otherwise, returns None. + """ + return ( + db.query(entities.Person.id) + .filter(entities.Person.name == name) + .filter(entities.Person.surname == surname) + .filter(entities.Person.date_of_birth == date_of_birth) + .scalar() + ) + + +@cached(cache=LRUCache(maxsize=1000), key=lambda name, city, db: hash((name, city))) # type: ignore +def get_district_court_id(name: str, city: str | None, db: Session) -> int: + """Determines the id of a district court. + + Determines the id of a district court and adds an entry to the table if no entry and id could be found. + A lru_cache is used to increase the speed of this application. + + Args: + name: The name of the district court. + city: The name where the court is located. + db: A session to connect to an SQL db via SQLAlchemy. + + Returns: + The id / privat key of a district court in the SQL-database. + """ + name, city = _refine_district_court_entry(name, city) + court_id = _read_district_court_id(name, city, db) + if court_id is not None: + return court_id + court = entities.DistrictCourt(name=name, city=city) + db.add(court) + db.commit() + return court.id # type: ignore + + +@cached(cache=LRUCache(maxsize=2000), key=lambda name, surname, date_of_birth, db: hash((name, surname, date_of_birth))) # type: ignore +def get_person_id( + name: str, surname: str, date_of_birth: date | str, db: Session +) -> int: + """Identifies the id of and court. + + Identifies the id of a district court and adds an entry to the table if no entry and id could be found. + A lru_cache is used to increase the speed of this application. + + Args: + name: The first name of the person. + surname: The last name of the person. + date_of_birth: The date the person was born. + db: A session to connect to an SQL db via SQLAlchemy. + + Returns: + The id / privat key of a district court in the SQL-database. + """ + if isinstance(date_of_birth, str) and date_of_birth: + date_of_birth = date.fromisoformat(date_of_birth) + if not name or not surname or not date_of_birth: + raise DataInvalidError( + f'At least one of the three values name: "{name}", surname: "{surname}" or date_of_birth: "{date_of_birth}" is empty.' + ) + assert isinstance(date_of_birth, date) # noqa: S101 + person_id = _read_person_id(name, surname, date_of_birth, db) + if person_id is not None: + return person_id + person = entities.Person(name=name, surname=surname, date_of_birth=date_of_birth) + db.add(person) + db.commit() + return person.id # type: ignore + + +@cached(cache=LRUCache(maxsize=5000), key=lambda name, zip_code, city, db: hash((name, zip_code, city))) # type: ignore +def get_company_id( + name: str, zip_code: str | None, city: str | None, db: Session +) -> int: + """Queries the id of a company. + + Args: + name: The HR entry of the company. + zip_code: The zip code where the company can be found. + city: The city where the company is found in. + db: A session to connect to an SQL db via SQLAlchemy. + + Returns: + The id / privat key of a company. + """ + if not name: + raise DataInvalidError("The name must be given and contain at least one sign.") + zip_code = simplify_string(zip_code) + city = simplify_string(city) + company_id = ( + db.query(entities.Company.id) + .filter( + sa.or_(entities.Company.zip_code == zip_code, entities.Company.city == city) + ) + .filter(entities.Company.name == name) + .scalar() + ) + if company_id is None and zip_code is None and city is None: + company_id = ( + db.query(entities.Company.id) + .filter(entities.Company.name == name) + .scalar() # todo ensure uniqueness + ) + if company_id is None: + raise KeyError(f"No corresponding company could be found to {name}.") + return company_id + + +@logger.catch(level="WARNING", reraise=True) +def add_company(company: dict[str, Any], db: Session) -> None: + """Add a company with all its data found in the mongodb company entry. + + Args: + company: The company to add. + db: A session to connect to an SQL db via SQLAlchemy. + """ + court_id = get_district_court_id(**company["id"]["district_court"], db=db) + location = company["location"] + name = simplify_string(company.get("name")) + if not name: + raise DataInvalidError( + "The company name needs to be valid (not empty and not only whitespace)." + ) + company_entry = entities.Company( + court_id=court_id, + hr=company["id"]["hr_number"].strip().replace(" ", " ").replace(" ", " "), + name=name, + city=simplify_string(location.get("city")), + zip_code=simplify_string(location.get("zip_code")), + street=simplify_string(location.get("street")), + last_update=company["last_update"], + ) + db.add(company_entry) + db.commit() + logger.debug(f"Added the company entry {company['name']} to the db.") + + +def add_companies(companies: list[dict[str, Any]], db: Session) -> None: + """Adds a company to the database. + + Args: + companies: The company to be added. + db: A session to connect to an SQL db via SQLAlchemy. + """ + data_invalid, error_count = 0, 0 + for company in tqdm(companies, desc="Companies added"): + try: + add_company(company, db) + except DataInvalidError: + data_invalid += 1 + except Exception: + error_count += 1 + db.rollback() + if error_count + data_invalid: + logger.warning( + f"When adding companies {error_count + data_invalid} problems occurred " + f"{data_invalid} where caused by invalid data." + ) + else: + logger.info("When adding companies no problems occurred.") + + +@logger.catch(level="WARNING", reraise=True) +def add_relationship( + relationship: dict[str, Any], company_id: int, db: Session +) -> None: + """Adds a relationship to a company. + + Args: + relationship: The relationship and the relationship partner. + company_id: The company id the relations is rooted in. + db: A session to connect to an SQL db via SQLAlchemy. + """ + relation_type = RelationTypeEnum.get_enum_from_name(relationship.get("role")) + relation: entities.CompanyRelation | entities.PersonRelation + if "date_of_birth" in relationship: + name = relationship["name"] + person_id = get_person_id( + name["firstname"], + name["lastname"], + relationship["date_of_birth"], + db, + ) + relation = entities.PersonRelation( + person_id=person_id, + company_id=company_id, + relation=relation_type, + ) + else: + relation_to: int = get_company_id( + relationship["description"], + relationship["location"]["zip_code"], + relationship["location"]["city"], + db=db, + ) + if company_id == relation_to: + raise DataInvalidError( + "For a valid relation both parties can't be the same entity." + ) + relation = entities.CompanyRelation( + company_id=company_id, + relation=relation_type, + company2_id=relation_to, + ) + db.add(relation) + db.commit() + + +def add_relationships(companies: list[dict[str, dict]], db: Session) -> None: + """Add a list of companies to the database. + + Args: + companies: Companies to be added to the db. + db: A session to connect to an SQL db via SQLAlchemy. + """ + total: int = sum(len(company.get("relationships", [])) for company in companies) + with tqdm( + total=total, + desc="Company connections added", + ) as pbar: + for company in companies: + relationships: list[dict[str, Any]] = company.get("relationships", []) # type: ignore + try: + company_id: int = get_company_id( + company["name"], # type: ignore + company["location"]["zip_code"], + company["location"]["city"], + db=db, + ) + except Exception: + pbar.update(len(relationships)) + db.rollback() + continue + + for relationship in relationships: + try: + add_relationship(relationship, company_id=company_id, db=db) + except Exception: + db.rollback() + pbar.update() + + logger.info("Company connections added.") + + +def transfer_data(db: Session | None) -> None: + """This functions transfers all the data from a production environment to a staging environment.""" + if db is None: + db = get_session(JsonFileConfigProvider("./secrets.json")) + logger.remove() + logger.add(sys.stdout, level="INFO") + logger.add("data-transfer.log", level="INFO", retention=5) + + reset_all_tables(db) + mongo_connector = MongoConnector( + JsonFileConfigProvider("./secrets.json").get_mongo_connection_string() + ) + mongo_company = CompanyMongoService(mongo_connector) + companies: list[dict[str, Any]] = mongo_company.get_all() # type: ignore + del mongo_company + + add_companies(companies, db) + add_relationships(companies, db) + db.close() + + +if __name__ == "__main__": + transfer_data(get_session(JsonFileConfigProvider("./secrets.json"))) diff --git a/src/aki_prj23_transparenzregister/utils/enum_types.py b/src/aki_prj23_transparenzregister/utils/enum_types.py new file mode 100644 index 0000000..51e9799 --- /dev/null +++ b/src/aki_prj23_transparenzregister/utils/enum_types.py @@ -0,0 +1,69 @@ +"""Collection of enumeration types for the whole project.""" +import enum + + +class RelationTypeEnum(enum.IntEnum): + """RelationTypeEnum.""" + + GESCHAEFTSFUEHRER = enum.auto() + KOMMANDITIST = enum.auto() + VORSTAND = enum.auto() + PROKURIST = enum.auto() + LIQUIDATOR = enum.auto() + INHABER = enum.auto() + PERSOENLICH_HAFTENDER_GESELLSCHAFTER = enum.auto() + PARTNER = enum.auto() + DIREKTOR = enum.auto() + + RECHTSNACHFOLGER = enum.auto() + ORGANISATION = enum.auto() + + @staticmethod + def get_enum_from_name(relation_name: str | None) -> "RelationTypeEnum": + """Translates relation name into a RelationTypeEnum. + + If no translation can be found a warning is given. + + Args: + relation_name: The name of the relation to be translated. + + Returns: + The identified translation or None if no translation can be found. + """ + if relation_name is None: + raise ValueError("A relation type needs to be given.") + relation_name = ( + relation_name.strip() + .replace("(in)", "") + .replace("(r)", "r") + .strip() + .lower() + ) + name = { + "geschäftsführer": RelationTypeEnum.GESCHAEFTSFUEHRER, + "kommanditist": RelationTypeEnum.KOMMANDITIST, + "vorstand": RelationTypeEnum.VORSTAND, + "vorstandsvorsitzender": RelationTypeEnum.VORSTAND, + "prokurist": RelationTypeEnum.PROKURIST, + "liquidator": RelationTypeEnum.LIQUIDATOR, + "inhaber": RelationTypeEnum.INHABER, + "persönlich haftender gesellschafter": RelationTypeEnum.PERSOENLICH_HAFTENDER_GESELLSCHAFTER, + "organisation": RelationTypeEnum.ORGANISATION, + "partner": RelationTypeEnum.PARTNER, + "direktor": RelationTypeEnum.DIREKTOR, + "geschäftsführender direktor": RelationTypeEnum.DIREKTOR, + "mitglied des leitungsorgans": RelationTypeEnum.VORSTAND, + "rechtsnachfolger": RelationTypeEnum.RECHTSNACHFOLGER, + }.get(relation_name) + if name is not None: + return name + raise ValueError(f'Relation type "{relation_name}" is not yet implemented!') + + +class SentimentTypeEnum(enum.Enum): + """SentimentTypeEnum.""" + + employee_voting = "employee_voting" + sustainability = "sustainability" + environmental_aspects = "environmental_aspects" + perception = "perception" diff --git a/src/aki_prj23_transparenzregister/utils/enumy_types.py b/src/aki_prj23_transparenzregister/utils/enumy_types.py deleted file mode 100644 index 30901de..0000000 --- a/src/aki_prj23_transparenzregister/utils/enumy_types.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Collection of enumeration types for the whole project.""" -import enum - - -class RelationTypeEnum(enum.IntEnum): - """RelationTypeEnum.""" - - EXECUTIVE = enum.auto() - AUDITOR = enum.auto() - SUPERVISORY_BOARD = enum.auto() - MANAGING_DIRECTOR = enum.auto() - AUTHORIZED_REPRESENTATIVE = enum.auto() - FINAL_AUDITOR = enum.auto() - - PARTICIPATES_WITH = enum.auto() - HAS_SHARES_OF = enum.auto() - IS_SUPPLIED_BY = enum.auto() - WORKS_WITH = enum.auto() - - -class SentimentTypeEnum(enum.Enum): - """SentimentTypeEnum.""" - - employee_voting = "employee_voting" - sustainability = "sustainability" - environmental_aspects = "environmental_aspects" - perception = "perception" diff --git a/src/aki_prj23_transparenzregister/utils/sql/connector.py b/src/aki_prj23_transparenzregister/utils/sql/connector.py index b2ef367..3986d45 100644 --- a/src/aki_prj23_transparenzregister/utils/sql/connector.py +++ b/src/aki_prj23_transparenzregister/utils/sql/connector.py @@ -81,6 +81,13 @@ def init_db(db: Session) -> None: Base.metadata.create_all(db.bind) +def reset_all_tables(db: Session) -> None: + """Drops all SQL tables and recreates them.""" + logger.info("Resetting all PostgreSQL tables.") + Base.metadata.drop_all(db.bind) + init_db(db) + + if __name__ == "__main__": """Main flow creating tables""" init_db(get_session(JsonFileConfigProvider("./secrets.json"))) diff --git a/src/aki_prj23_transparenzregister/utils/sql/entities.py b/src/aki_prj23_transparenzregister/utils/sql/entities.py index 2bb90f1..fefdf83 100644 --- a/src/aki_prj23_transparenzregister/utils/sql/entities.py +++ b/src/aki_prj23_transparenzregister/utils/sql/entities.py @@ -3,7 +3,7 @@ from datetime import datetime import sqlalchemy as sa -from aki_prj23_transparenzregister.utils.enumy_types import ( +from aki_prj23_transparenzregister.utils.enum_types import ( RelationTypeEnum, SentimentTypeEnum, ) @@ -16,7 +16,6 @@ class DistrictCourt(Base): """DistrictCourt.""" __tablename__ = "district_court" - id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) city = sa.Column(sa.String(100), nullable=False) name = sa.Column(sa.String(100), nullable=False, unique=True) @@ -54,6 +53,7 @@ class Person(Base): __tablename__ = "person" __table_args__ = (sa.UniqueConstraint("name", "surname", "date_of_birth"),) + # TODO add a constraint that asks for a minlength of 2 for name and surname id = sa.Column(sa.Integer, primary_key=True) name = sa.Column(sa.String(100), nullable=False) diff --git a/src/aki_prj23_transparenzregister/utils/string_tools.py b/src/aki_prj23_transparenzregister/utils/string_tools.py new file mode 100644 index 0000000..be399f0 --- /dev/null +++ b/src/aki_prj23_transparenzregister/utils/string_tools.py @@ -0,0 +1,18 @@ +"""Contains functions fot string manipulation.""" + + +def simplify_string(string_to_simplify: str | None) -> str | None: + """Simplifies a string to None if no valid sting is found. + + Args: + string_to_simplify: The string to simplify. + + Returns: + The simplified string or None if the string was empty. + """ + if string_to_simplify is not None: + if isinstance(string_to_simplify, str): + string_to_simplify = string_to_simplify.strip() + else: + raise TypeError("The string to simplify is not a string.") + return string_to_simplify if string_to_simplify else None diff --git a/tests/apps/enrich_company_financials_test.py b/tests/apps/enrich_company_financials_test.py index 22232bd..4368fe9 100644 --- a/tests/apps/enrich_company_financials_test.py +++ b/tests/apps/enrich_company_financials_test.py @@ -18,7 +18,8 @@ def test_import_enrich_company_financials() -> None: @patch( "aki_prj23_transparenzregister.apps.enrich_company_financials.CompanyMongoService" ) -def test_work(mock_compnay_service: Mock, mock_bundesanzeiger: Mock) -> None: +def test_work(mock_company_service: Mock, mock_bundesanzeiger: Mock) -> None: + """Tests the readout of the company financials.""" mock_bundesanzeiger.return_value = pd.DataFrame( [ { @@ -28,9 +29,8 @@ def test_work(mock_compnay_service: Mock, mock_bundesanzeiger: Mock) -> None: } ] ) - # mock_compnay_service.add_yearly_resreturn_value enrich_company_financials.work( {"_id": "", "name": "ABC AG", "location": {"city": "Haltern am See"}}, - mock_compnay_service, + mock_company_service, ) assert enrich_company_financials diff --git a/tests/config/config_providers_test.py b/tests/config/config_providers_test.py index 0d00cfd..60ebb3e 100644 --- a/tests/config/config_providers_test.py +++ b/tests/config/config_providers_test.py @@ -1,3 +1,4 @@ +"""Tests the config provers.""" import json from unittest.mock import mock_open, patch @@ -10,11 +11,13 @@ from aki_prj23_transparenzregister.config.config_providers import ( def test_json_provider_init_fail() -> None: + """Tests the file not found error if an unknown filepath is given for the JsonFileConfigProvider.""" with pytest.raises(FileNotFoundError): JsonFileConfigProvider("file-that-does-not-exist") def test_json_provider_init_no_json() -> None: + """Tests if a non json file throws the correct error.""" with patch("os.path.isfile") as mock_isfile, patch( "builtins.open", mock_open(read_data="fhdaofhdoas") ): @@ -24,6 +27,7 @@ def test_json_provider_init_no_json() -> None: def test_json_provider_init() -> None: + """Tests the JsonFileConfigProvider creation.""" data = {"hello": "world"} input_data = json.dumps(data) with patch("os.path.isfile") as mock_isfile: @@ -34,6 +38,7 @@ def test_json_provider_init() -> None: def test_json_provider_get_postgres() -> None: + """Tests if the config provider can return the postgre config string.""" data = { "postgres": { "username": "user", @@ -56,6 +61,7 @@ def test_json_provider_get_postgres() -> None: def test_json_provider_get_mongo() -> None: + """Tests the JsonConfigProvider for the mongo db.""" data = { "mongo": { "username": "user", diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..b8da9d9 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,116 @@ +"""Global configurations and definitions for pytest.""" +import datetime +import os +from collections.abc import Generator +from inspect import getmembers, isfunction + +import pytest +from sqlalchemy.engine import Engine +from sqlalchemy.orm import Session + +from aki_prj23_transparenzregister.utils import data_transfer +from aki_prj23_transparenzregister.utils.sql import entities +from aki_prj23_transparenzregister.utils.sql.connector import get_session, init_db + + +@pytest.fixture(autouse=True) +def _clear_caches() -> Generator[None, None, None]: + """A function that clears all caches after each test. + + All the modules containing the cached functions need to be listed in the modules tuple. + """ + yield + # https://stackoverflow.com/a/139198/11003343 + modules = (data_transfer,) + functions = [ + function + for module in modules + for name, function in getmembers(module, isfunction) + if function.__dict__.get("cache") is not None + ] + # https://cachetools.readthedocs.io/en/stable/?highlight=clear#memoizing-decorators + for function in functions: + function.cache.clear() # type: ignore + + +@pytest.fixture() +def empty_db() -> Generator[Session, None, None]: + """Generates a db Session to a sql_lite db.""" + if os.path.exists("test-db.db"): + os.remove("test-db.db") + db = get_session("sqlite:///test-db.db") + init_db(db) + yield db + db.close() + bind = db.bind + assert isinstance(bind, Engine) + bind.dispose() + os.remove("test-db.db") + + +@pytest.fixture() +def full_db(empty_db: Session) -> Session: + """Fills a db with some test data.""" + empty_db.add_all( + [ + entities.DistrictCourt(name="Amtsgericht Bochum", city="Bochum"), + entities.DistrictCourt(name="Amtsgericht Dortmund", city="Dortmund"), + entities.Person( + name="Max", + surname="Mustermann", + date_of_birth=datetime.date(2023, 1, 1), + ), + entities.Person( + name="Sabine", + surname="Mustermann", + date_of_birth=datetime.date(2023, 1, 1), + ), + entities.Person( + name="Some Firstname", + surname="Some Surname", + date_of_birth=datetime.date(2023, 1, 1), + ), + entities.Person( + name="Some Firstname", + surname="Some Surname", + date_of_birth=datetime.date(2023, 1, 2), + ), + entities.Person( + name="Other Firstname", + surname="Other Surname", + date_of_birth=datetime.date(2023, 1, 2), + ), + ] + ) + empty_db.commit() + empty_db.add_all( + [ + entities.Company( + hr="HRB 123", + court_id=2, + name="Some Company GmbH", + street="Sesamstr.", + zip_code="12345", + city="TV City", + last_update=datetime.date.fromisoformat("2023-01-01"), + ), + entities.Company( + hr="HRB 123", + court_id=1, + name="Other Company GmbH", + street="Sesamstr.", + zip_code="12345", + city="TV City", + last_update=datetime.date.fromisoformat("2023-01-01"), + ), + entities.Company( + hr="HRB 12", + court_id=2, + name="Third Company GmbH", + last_update=datetime.date.fromisoformat("2023-01-01"), + ), + ] + ) + empty_db.commit() + # print(pd.read_sql_table("company", empty_db.bind).to_string()) + return empty_db diff --git a/tests/utils/data_extraction/bundesanzeiger_test.py b/tests/utils/data_extraction/bundesanzeiger_test.py index 30e8007..8829bbd 100644 --- a/tests/utils/data_extraction/bundesanzeiger_test.py +++ b/tests/utils/data_extraction/bundesanzeiger_test.py @@ -1,3 +1,4 @@ +"""Tests if the bundesanzeiger can be accessed and read.""" from unittest.mock import Mock, patch import pandas as pd diff --git a/tests/utils/data_transfer_test.py b/tests/utils/data_transfer_test.py new file mode 100644 index 0000000..f2deb8d --- /dev/null +++ b/tests/utils/data_transfer_test.py @@ -0,0 +1,744 @@ +"""Test the transfer functions from mongodb to sql.""" +import random +import string +from datetime import date +from typing import Any + +import numpy as np +import pandas as pd +import pytest +import sqlalchemy as sa +from pytest_mock import MockerFixture +from sqlalchemy.engine import Engine +from sqlalchemy.orm import Session + +from aki_prj23_transparenzregister.utils import data_transfer + + +@pytest.mark.parametrize( + ("original", "expected"), + [ + ( + {"name": "Amtsgericht Herne", "city": "Herne"}, + {"name": "Amtsgericht Herne", "city": "Herne"}, + ), + ( + {"name": "Amtsgericht Herne", "city": ""}, + {"name": "Amtsgericht Herne", "city": "Herne"}, + ), + ( + {"name": "Amtsgericht Herne", "city": None}, + {"name": "Amtsgericht Herne", "city": "Herne"}, + ), + ( + {"name": "Amtsgericht Herne", "city": "Something Wrong"}, + {"name": "Amtsgericht Herne", "city": "Herne"}, + ), + ( + {"name": "Amtsgericht Herne", "city": "NoName"}, + {"name": "Amtsgericht Herne", "city": "Herne"}, + ), + ], +) +def test_refine_district_court_entry(original: dict, expected: dict) -> None: + """Tests the transformation/the cleaning of the district court entry.""" + assert data_transfer._refine_district_court_entry( + **{"name": "Amtsgericht Herne", "city": "Herne"} + ) == tuple(expected.values()) + + +@pytest.mark.parametrize( + "defect_data", + [ + {"name": "Wrong Herne", "city": "Herne"}, + {"name": "Wrong Herne", "city": "NoName"}, + {"city": "Herne", "name": None}, + {"city": "Herne", "name": ""}, + ], +) +def test_refine_district_court_entry_defect_data(defect_data: dict[str, str]) -> None: + """Tests if an error is thrown if the district court data can't be corrected.""" + with pytest.raises(data_transfer.DataInvalidError): + data_transfer._refine_district_court_entry(**defect_data) + + +@pytest.mark.repeat(3) +def test_empty_db_fixture(empty_db: Session) -> None: + """Checks if the db can be created.""" + assert isinstance(empty_db, Session) + + +@pytest.mark.parametrize( + ("name", "city", "id"), + [ + ("Amtsgericht Bochum", "Bochum", 1), + ("Amtsgericht Dortmund", "Dortmund", 2), + ("Amtsgericht Iserlohn", "Iserlohn", None), + ], +) +def test__read_district_court_id( + name: str, city: str, id: int | None, full_db: Session +) -> None: + """Tests if the district court id can be read.""" + assert data_transfer._read_district_court_id(name, city, full_db) == id + + +@pytest.mark.parametrize( + ("firstname", "surname", "date_str", "id"), + [ + ("Max", "Mustermann", "2023-01-01", 1), + ("Sabine", "Mustermann", "2023-01-01", 2), + ("Some Firstname", "Some Surname", "2023-01-01", 3), + ("Some Firstname", "Some Surname", "2023-01-02", 4), + ("Other Firstname", "Other Surname", "2023-01-02", 5), + (None, "Other Surname", "2023-01-02", None), + ("Does not exist", "Other Surname", "2023-01-02", None), + ("Other Firstname", "Does not exists", "2023-01-02", None), + ("Other Firstname", "Other Surname", "1900-01-02", None), + ("Other Firstname", None, "2023-01-02", None), + ], +) +def test__read_person_id( + firstname: str, surname: str, date_str: str, id: int | None, full_db: Session +) -> None: + """Tests if the person id can be read.""" + assert ( + data_transfer._read_person_id( + firstname, surname, date.fromisoformat(date_str), full_db + ) + == id + ) + + +@pytest.mark.parametrize( + ("name", "city", "id"), + [ + ("Amtsgericht Bochum", "Bochum", 1), + ("Amtsgericht Dortmund", "Dortmund", 2), + ("Amtsgericht Iserlohn", "Iserlohn", 3), + ("Amtsgericht Köln", "Köln", 3), + ], +) +def test_get_district_court_id(name: str, city: str, id: int, full_db: Session) -> None: + """Tests if a court id can be returned and the court automatically be added if not yet part of the db.""" + assert data_transfer.get_district_court_id(name, city, full_db) == id + + +@pytest.mark.parametrize( + ("firstname", "surname", "date_str", "id"), + [ + ("Max", "Mustermann", "2023-01-01", 1), + ("Sabine", "Mustermann", "2023-01-01", 2), + ("Some Firstname", "Some Surname", "2023-01-01", 3), + ("Some Firstname", "Some Surname", "2023-01-02", 4), + ("Other Firstname", "Other Surname", "2023-01-02", 5), + ("Does not exist", "Other Surname", "2023-01-02", 6), + ("Other Firstname", "Does not exists", "2023-01-02", 6), + ("Other Firstname", "Other Surname", "1900-01-02", 6), + ], +) +def test_get_person_id( + firstname: str, surname: str, date_str: str, id: int, full_db: Session +) -> None: + """Tests if a person id can be returned and the court automatically be added if not yet part of the db.""" + assert ( + data_transfer.get_person_id( + firstname, surname, date.fromisoformat(date_str), full_db + ) + == id + ) + + +@pytest.mark.parametrize( + ("firstname", "surname", "date_str"), + [ + ("", "Other Surname", "2023-01-02"), + ("Other Firstname", "", "2023-01-02"), + ("Other Firstname", "Other Surname", ""), + ], +) +def test_get_person_id_value_check( + firstname: str, surname: str, date_str: str | None, full_db: Session +) -> None: + """Tests if errors on adding persons can be found.""" + with pytest.raises( + data_transfer.DataInvalidError, match="At least one of the three values name:" + ): + data_transfer.get_person_id( + firstname, + surname, + date.fromisoformat(date_str) if date_str else None, # type: ignore + full_db, + ) + + +@pytest.mark.parametrize( + ("name", "zip_code", "city", "id"), + [ + ("Some Company GmbH", "", "", 1), + ("Some Company GmbH", "12345", "", 1), + ("Some Company GmbH", "12345", "TV City", 1), + ("Some Company GmbH", "", "TV City", 1), + ("Other Company GmbH", "", "", 2), + ("Other Company GmbH", "12345", "", 2), + ("Other Company GmbH", "12345", "TV City", 2), + ("Other Company GmbH", "", "TV City", 2), + ("Third Company GmbH", "", "", 3), + ], +) +def test_get_company_id( + name: str, zip_code: str, city: str, id: int | None, full_db: Session +) -> None: + """Tests if the company id can be returned correctly.""" + assert data_transfer.get_company_id(name, zip_code, city, full_db) == id + + +@pytest.mark.parametrize( + ("name", "zip_code", "city"), + [ + ("Does not exist", "", ""), + ("Does not exist", "41265", ""), + ("Does not exist", "", "Some City"), + ("Other Company GmbH", "TV City", "54321"), + ("Other Company GmbH", "OtherCity", "12345"), + ("Other Company GmbH", "OtherCity", "54321"), + ], +) +def test_get_company_id_not_found( + name: str, + zip_code: str, + city: str, + full_db: Session, +) -> None: + """Test the accessing of missing companies.""" + with pytest.raises(KeyError): + data_transfer.get_company_id(name, zip_code, city, full_db) + + +@pytest.mark.parametrize("name", ["", None]) +def test_get_company_id_nameless(name: str | None, full_db: Session) -> None: + """Test accessing a company without valid name.""" + with pytest.raises(data_transfer.DataInvalidError): + data_transfer.get_company_id(name, "zip_code", "city", full_db) # type: ignore + + +def get_random_string(length: int) -> str: + """Creates a random string of a defined length. + + Args: + length: The length of the string to generate. + + Returns: + The generated string. + """ + letters = string.digits + string.ascii_letters + " " + return "".join(random.choice(letters) for _ in range(length)) + + +def get_random_zip() -> str: + """Creates a random zip.""" + letters = string.digits + return "".join(random.choice(letters) for _ in range(5)) + + +def company_generator(seed: int) -> dict[str, Any]: + """Generates a random company entry.""" + random.seed(seed) + if random.choice([True, False]): + city = "Dortmund" + else: + city = get_random_string(random.randint(5, 30)) + return { + "id": { + "district_court": { + "name": f"Amtsgericht {city}", + "city": city if random.choice([True, False]) else None, + }, + "hr_number": get_random_string(7), + }, + "name": get_random_string(random.randint(3, 150)), + "location": { + "city": city if random.choice([True, False]) else None, + "zip_code": get_random_zip() if random.choice([True, False]) else None, + "street": get_random_string(20) if random.choice([True, False]) else None, + }, + "last_update": date(random.randint(2000, 2023), 1, 1), + } + + +@pytest.mark.parametrize("seed", list(range(70, 75))) +def test_add_company(seed: int, full_db: Session) -> None: + """Tests the addition of a company to the db.""" + company = company_generator(seed) + data_transfer.add_company(company, full_db) + + +@pytest.mark.parametrize("seed", list(range(5))) +@pytest.mark.parametrize("overwrite", ["", None, " "]) +def test_add_company_broken_name( + seed: int, overwrite: str | None, full_db: Session +) -> None: + """Tests what happens if a company has a broken / empty name.""" + company = company_generator(seed) + company["name"] = overwrite + if overwrite is None: + with pytest.raises( + data_transfer.DataInvalidError, + match="The company name needs to be valid ", + ): + data_transfer.add_company(company, full_db) + + +@pytest.mark.parametrize("seed", list(range(5))) +@pytest.mark.parametrize("overwrite", ["", None, " "]) +def test_add_company_broken_city( + seed: int, overwrite: str | None, full_db: Session +) -> None: + """Tests a broken / empty city entry.""" + company = company_generator(seed) + company["location"]["city"] = overwrite + data_transfer.add_company(company, full_db) + + +@pytest.mark.parametrize("seed", list(range(5))) +@pytest.mark.parametrize("overwrite", ["", None, " "]) +def test_add_company_broken_zip_code( + seed: int, overwrite: str | None, full_db: Session +) -> None: + """Tests how to add a company if the zip_code is broken / empty.""" + company = company_generator(seed) + company["location"]["zip_code"] = overwrite + data_transfer.add_company(company, full_db) + + +@pytest.mark.parametrize("seed", list(range(5))) +@pytest.mark.parametrize("overwrite", [None]) +def test_add_company_broken_date( + seed: int, overwrite: str | None, full_db: Session +) -> None: + """Tests how the company dadd function deals with a missing date.""" + company = company_generator(seed) + company["last_update"] = overwrite + with pytest.raises(sa.exc.IntegrityError): + data_transfer.add_company(company, full_db) + + +@pytest.mark.parametrize("seed", list(range(5))) +@pytest.mark.parametrize("overwrite", ["", None, " "]) +def test_add_company_broken_district_court( + seed: int, overwrite: str | None, full_db: Session, mocker: MockerFixture +) -> None: + """Test a broken district court entry.""" + company = company_generator(seed) + company["id"]["district_court"]["name"] = overwrite + company["id"]["district_court"]["city"] = get_random_string(10) + with pytest.raises( + data_transfer.DataInvalidError, + match="There is no court name|The name of the district court does not start correctly", + ): + data_transfer.add_company(company, full_db) + + +@pytest.mark.parametrize("seed", list(range(0, 25, 5))) +def test_add_companies(seed: int, mocker: MockerFixture, full_db: Session) -> None: + """Test to add multiple companies.""" + rnd_generator = np.random.default_rng(seed) + companies: list[dict[str, Any]] = [ + company_generator(_) + for _ in set( + rnd_generator.integers(0, 1000, size=rnd_generator.integers(1, 30)).tolist() + ) + ] + spy_warning = mocker.spy(data_transfer.logger, "warning") + spy_info = mocker.spy(data_transfer.logger, "info") + spy_debug = mocker.spy(data_transfer.logger, "debug") + data_transfer.add_companies(companies, full_db) + spy_info.assert_called_once_with("When adding companies no problems occurred.") + spy_warning.assert_not_called() + assert spy_debug.call_count == len(companies) + + +@pytest.mark.parametrize("seed", list(range(1, 25, 5))) +def test_add_companies_duplicate( + seed: int, mocker: MockerFixture, full_db: Session +) -> None: + """Test to add multiple companies.""" + rnd_generator = np.random.default_rng(seed) + companies: list[dict[str, Any]] = [ + company_generator(_) + for _ in set( + rnd_generator.integers(0, 1000, size=rnd_generator.integers(4, 30)).tolist() + ) + ] + unique_companies = len(companies) + companies += companies[-3:] + spy_warning = mocker.spy(data_transfer.logger, "warning") + spy_info = mocker.spy(data_transfer.logger, "info") + spy_debug = mocker.spy(data_transfer.logger, "debug") + data_transfer.add_companies(companies, full_db) + spy_info.assert_not_called() + spy_warning.assert_called_once_with( + "When adding companies 3 problems occurred 0 where caused by invalid data." + ) + assert spy_debug.call_count == unique_companies + + +@pytest.mark.parametrize("seed", list(range(2, 25, 5))) +def test_add_companies_corrupted_data( + seed: int, mocker: MockerFixture, full_db: Session +) -> None: + """Test to add multiple companies.""" + rnd_generator = np.random.default_rng(seed) + companies: list[dict[str, Any]] = [ + company_generator(_) + for _ in set( + rnd_generator.integers(0, 1000, size=rnd_generator.integers(4, 30)).tolist() + ) + ] + companies[len(companies) // 2]["name"] = "" + spy_warning = mocker.spy(data_transfer.logger, "warning") + spy_info = mocker.spy(data_transfer.logger, "info") + spy_debug = mocker.spy(data_transfer.logger, "debug") + data_transfer.add_companies(companies, full_db) + spy_info.assert_not_called() + spy_warning.assert_called_once_with( + "When adding companies 1 problems occurred 1 where caused by invalid data." + ) + assert spy_debug.call_count == len(companies) - 1 + + +@pytest.mark.parametrize("company_id", list(range(5))) +def test_add_relationship_no_relation(company_id: int, full_db: Session) -> None: + """Tests if an error is thrown if the relation type/role is not defined.""" + with pytest.raises(ValueError, match="A relation type needs to be given."): + data_transfer.add_relationship({}, company_id, full_db) + + +@pytest.mark.parametrize("company_id", list(range(5))) +def test_add_relationship_unknown_relation(company_id: int, full_db: Session) -> None: + """Tests if an error is thrown if the relation type/role is unknown.""" + with pytest.raises(ValueError, match="Relation type .* is not yet implemented!"): + data_transfer.add_relationship( + {"role": "something strange"}, company_id, full_db + ) + + +@pytest.mark.parametrize("company_id", [1, 2, 3]) +@pytest.mark.parametrize( + ("firstname", "surname", "date_of_birth"), + [ + ("Max", "Mustermann", "2023-01-01"), + ("Some Firstname", "Some Surname", "2023-01-01"), + ("Other Firstname", "Other Surname", "1900-01-02"), + ], +) +@pytest.mark.parametrize("role", ["Partner", "direktor", "liquidator"]) +def test_add_relationship_person( # noqa: PLR0913 + firstname: str, + surname: str, + date_of_birth: str, + full_db: Session, + company_id: int, + role: str, +) -> None: + """Tests if a personal relation can be added.""" + relation = { + "name": { + "firstname": firstname, + "lastname": surname, + }, + "date_of_birth": date.fromisoformat(date_of_birth), + "role": role, + } + data_transfer.add_relationship(relation, company_id, full_db) + + +@pytest.mark.parametrize("company_id", [1, 2, 3]) +@pytest.mark.parametrize( + ("firstname", "surname", "date_of_birth"), + [ + ("Max", None, "2023-01-01"), + (None, "Some Surname", "2023-01-01"), + ("Other Firstname", "Other Surname", None), + ], +) +@pytest.mark.parametrize("role", ["Partner"]) +def test_add_relationship_person_missing_data( # noqa: PLR0913 + firstname: str, + surname: str, + date_of_birth: str, + full_db: Session, + company_id: int, + role: str, + mocker: MockerFixture, +) -> None: + """Tests if a personal relation can be added.""" + mocker.spy(data_transfer.logger, "warning") + relation = { + "name": { + "firstname": firstname, + "lastname": surname, + }, + "date_of_birth": date_of_birth if date_of_birth else None, + "role": role, + } + with pytest.raises( + data_transfer.DataInvalidError, match="At least one of the three values name:" + ): + data_transfer.add_relationship(relation, company_id, full_db) + + +@pytest.mark.parametrize( + ("company_name", "city", "zip_code", "company_id"), + [ + ("Some Company GmbH", None, None, 2), + ("Some Company GmbH", None, "12345", 2), + ("Some Company GmbH", "TV City", None, 3), + ("Some Company GmbH", "TV City", "12345", 2), + ("Some Company GmbH", "Strange City", "12345", 2), + ("Some Company GmbH", "TV City", "?????", 2), + ("Third Company GmbH", None, None, 1), + ], +) +def test_add_relationship_company( + company_id: int, + company_name: str, + city: str | None, + zip_code: str | None, + full_db: Session, +) -> None: + """Tests if a relationship to another company can be added.""" + data_transfer.add_relationship( + { + "description": company_name, + "location": { + "zip_code": zip_code, + "city": city, + }, + "role": "organisation", + }, + company_id, + full_db, + ) + + +@pytest.mark.parametrize( + ("company_name", "city", "zip_code", "company_id"), + [ + ("Some Company GmbH", None, None, 1), + ("Some Company GmbH", "TV City", "12345", 1), + ("Some Company GmbH", "TV City", None, 1), + ("Third Company GmbH", None, None, 3), + ], +) +def test_add_relationship_company_self_reference( + company_id: int, + company_name: str, + city: str | None, + zip_code: str | None, + full_db: Session, +) -> None: + """Tests if a company referencing a relationship with itself throws an error.""" + with pytest.raises( + data_transfer.DataInvalidError, + match="For a valid relation both parties can't be the same entity.", + ): + data_transfer.add_relationship( + { + "description": company_name, + "location": { + "zip_code": zip_code, + "city": city, + }, + "role": "organisation", + }, + company_id, + full_db, + ) + + +@pytest.mark.parametrize( + ("company_name", "city", "zip_code", "company_id"), + [ + ("Unknown GmbH", None, None, 2), + ("Some Company GmbH", "Strange city", "?????", 2), + ], +) +def test_add_relationship_company_unknown( + company_id: int, + company_name: str, + city: str | None, + zip_code: str | None, + full_db: Session, +) -> None: + """Tests if a relationship to another company can be added.""" + with pytest.raises( + KeyError, match=f"No corresponding company could be found to {company_name}." + ): + data_transfer.add_relationship( + { + "description": company_name, + "location": { + "zip_code": zip_code, + "city": city, + }, + "role": "organisation", + }, + company_id, + full_db, + ) + + +@pytest.mark.parametrize("empty_relations", [[], [{}], [{"relationship": []}]]) +def test_add_relationships_none(empty_relations: list, full_db: Session) -> None: + """Testing what happens if an empty relation is added.""" + data_transfer.add_relationships([], full_db) + + +@pytest.mark.working_on() +@pytest.mark.parametrize( + "documents", + [ + [ + { + "_id": {"$oid": "649f16a2ecc"}, + "id": { + "hr_number": "HRB 123", + "district_court": { + "name": "Amtsgericht Dortmund", + "city": "Dortmund", + }, + }, + "location": { + "city": "TV City", + "zip_code": "12345", + "street": "Sesamstr.", + "house_number": "1", + }, + "name": "Some Company GmbH", + "last_update": "2023-05-04", + "relationships": [ + { + "name": {"firstname": "Second person", "lastname": "Köstser"}, + "date_of_birth": "1961-02-09", + "location": {"city": "Stuttgart"}, + "role": "Geschäftsführer", + }, + { + "name": {"firstname": "First Person", "lastname": "Jifpa"}, + "date_of_birth": "1976-04-20", + "location": {"city": "Stuttgart"}, + "role": "Geschäftsführer", + }, + { + "name": {"firstname": "", "lastname": "Jiapa"}, + "date_of_birth": "1976-04-20", + "location": {"city": "Stuttgart"}, + "role": "Geschäftsführer", + }, + { + "name": {"firstname": "Something", "lastname": ""}, + "date_of_birth": "12i3u", + "location": {"city": "Stuttgart"}, + "role": "Geschäftsführer", + }, + { + "name": {"firstname": "First Person", "lastname": "Jipha"}, + "date_of_birth": "1976-04-20", + }, + ], + "yearly_results": {}, + } + ] + ], +) +def test_relationships(documents: list[dict[str, Any]], full_db: Session) -> None: + """Testing to add lots of relations.""" + data_transfer.add_relationships(documents, full_db) + bind = full_db.bind + assert isinstance(bind, Engine) + pd.testing.assert_frame_equal( + pd.read_sql_table("company", bind), + pd.DataFrame( + { + "id": {0: 1, 1: 2, 2: 3}, + "hr": {0: "HRB 123", 1: "HRB 123", 2: "HRB 12"}, + "court_id": {0: 2, 1: 1, 2: 2}, + "name": { + 0: "Some Company GmbH", + 1: "Other Company GmbH", + 2: "Third Company GmbH", + }, + "street": {0: "Sesamstr.", 1: "Sesamstr.", 2: None}, + "zip_code": {0: "12345", 1: "12345", 2: None}, + "city": {0: "TV City", 1: "TV City", 2: None}, + "last_update": { + 0: pd.Timestamp("2023-01-01 00:00:00"), + 1: pd.Timestamp("2023-01-01 00:00:00"), + 2: pd.Timestamp("2023-01-01 00:00:00"), + }, + "sector": {0: None, 1: None, 2: None}, + } + ), + ) + assert len(pd.read_sql_table("company_relation", bind).index) == 0 + pd.testing.assert_frame_equal( + pd.read_sql_table("person_relation", bind), + pd.DataFrame({"id": {0: 1, 1: 2}, "person_id": {0: 6, 1: 7}}), + ) + pd.testing.assert_frame_equal( + pd.read_sql_table("relation", bind), + pd.DataFrame( + { + "id": {0: 1, 1: 2}, + "company_id": {0: 1, 1: 1}, + "date_from": {0: pd.NaT, 1: pd.NaT}, + "date_to": {0: pd.NaT, 1: pd.NaT}, + "relation": {0: "GESCHAEFTSFUEHRER", 1: "GESCHAEFTSFUEHRER"}, + } + ), + ) + pd.testing.assert_frame_equal( + pd.read_sql_table("person", bind), + pd.DataFrame( + { + "id": {0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7}, + "name": { + 0: "Max", + 1: "Sabine", + 2: "Some Firstname", + 3: "Some Firstname", + 4: "Other Firstname", + 5: "Second person", + 6: "First Person", + }, + "surname": { + 0: "Mustermann", + 1: "Mustermann", + 2: "Some Surname", + 3: "Some Surname", + 4: "Other Surname", + 5: "Köstser", + 6: "Jifpa", + }, + "date_of_birth": { + 0: pd.Timestamp("2023-01-01 00:00:00"), + 1: pd.Timestamp("2023-01-01 00:00:00"), + 2: pd.Timestamp("2023-01-01 00:00:00"), + 3: pd.Timestamp("2023-01-02 00:00:00"), + 4: pd.Timestamp("2023-01-02 00:00:00"), + 5: pd.Timestamp("1961-02-09 00:00:00"), + 6: pd.Timestamp("1976-04-20 00:00:00"), + }, + "works_for": { + 0: None, + 1: None, + 2: None, + 3: None, + 4: None, + 5: None, + 6: None, + }, + } + ), + ) diff --git a/tests/utils/enum_types_test.py b/tests/utils/enum_types_test.py new file mode 100644 index 0000000..f9f744c --- /dev/null +++ b/tests/utils/enum_types_test.py @@ -0,0 +1,40 @@ +"""Tests for the enumeration types.""" +import pytest + +from aki_prj23_transparenzregister.utils import enum_types + + +def test_import() -> None: + """Tests if enum_types can be imported.""" + assert enum_types + + +@pytest.mark.parametrize("relation_name", ["Vorstand", "Prokurist", "Direktor"]) +@pytest.mark.parametrize("changes", ["lower", "upper", None]) +def test_relation_type_enum_from_string( + relation_name: str, changes: str | None +) -> None: + """Tests the transformation of a name to an enumeration type.""" + if changes == "lower": + relation_name = relation_name.lower() + elif changes == "upper": + relation_name = relation_name.upper() + + assert isinstance( + enum_types.RelationTypeEnum.get_enum_from_name(relation_name), + enum_types.RelationTypeEnum, + ) + + +@pytest.mark.parametrize("relation_name", ["does Not Exists", "Also not"]) +@pytest.mark.parametrize("changes", ["lower", "upper", None]) +def test_relation_type_enum_from_string_wrong( + relation_name: str, changes: str | None +) -> None: + """Tests the transformation of a name to an enumeration type if no equivalent can be found.""" + if changes == "lower": + relation_name = relation_name.lower() + elif changes == "upper": + relation_name = relation_name.upper() + with pytest.raises(ValueError, match='Relation type ".*" is not yet implemented!'): + enum_types.RelationTypeEnum.get_enum_from_name(relation_name) diff --git a/tests/utils/mongo/mongo_test.py b/tests/utils/mongo/mongo_test.py index 51a1abe..eb63a4b 100644 --- a/tests/utils/mongo/mongo_test.py +++ b/tests/utils/mongo/mongo_test.py @@ -1,3 +1,4 @@ +"""Tests for connecting to the mongodb.""" from unittest.mock import patch from aki_prj23_transparenzregister.utils.mongo.connector import ( @@ -7,21 +8,25 @@ from aki_prj23_transparenzregister.utils.mongo.connector import ( def test_get_conn_string_no_credentials() -> None: + """Tests the mongo connection string generation.""" conn = MongoConnection("localhost", "", 27017, None, None) assert conn.get_conn_string() == "mongodb://localhost:27017" def test_get_conn_string_no_port_but_credentials() -> None: + """Tests the mongo connection string generation.""" conn = MongoConnection("localhost", "", None, "admin", "password") assert conn.get_conn_string() == "mongodb+srv://admin:password@localhost" def test_get_conn_simple() -> None: + """Tests the mongo connection string generation.""" conn = MongoConnection("localhost", "", None, None, None) assert conn.get_conn_string() == "mongodb+srv://localhost" def test_mongo_connector() -> None: + """Tests the MongoConnector.""" with patch("pymongo.MongoClient") as mock_mongo_client: expected_result = 42 mock_mongo_client.return_value = {"db": expected_result} diff --git a/tests/utils/mongo/news_mongo_service_test.py b/tests/utils/mongo/news_mongo_service_test.py index ddf1564..9257c51 100644 --- a/tests/utils/mongo/news_mongo_service_test.py +++ b/tests/utils/mongo/news_mongo_service_test.py @@ -1,3 +1,4 @@ +"""Tests for the mongo news service.""" from unittest.mock import Mock, patch import pytest @@ -50,6 +51,7 @@ def test_init(mock_mongo_connector: Mock, mock_collection: Mock) -> None: def test_get_all(mock_mongo_connector: Mock, mock_collection: Mock) -> None: + """Tests the get_all function from the mongo connector.""" mock_mongo_connector.database = {"news": mock_collection} service = MongoNewsService(mock_mongo_connector) @@ -60,6 +62,7 @@ def test_get_all(mock_mongo_connector: Mock, mock_collection: Mock) -> None: def test_get_by_id_with_result( mock_mongo_connector: Mock, mock_collection: Mock ) -> None: + """Tests the get_by_id_with_result function from the mongo connector.""" mock_mongo_connector.database = {"news": mock_collection} service = MongoNewsService(mock_mongo_connector) @@ -72,6 +75,7 @@ def test_get_by_id_with_result( def test_get_by_id_no_result(mock_mongo_connector: Mock, mock_collection: Mock) -> None: + """Test if the mongo connector can get an object by id.""" mock_mongo_connector.database = {"news": mock_collection} service = MongoNewsService(mock_mongo_connector) @@ -80,6 +84,7 @@ def test_get_by_id_no_result(mock_mongo_connector: Mock, mock_collection: Mock) def test_insert(mock_mongo_connector: Mock, mock_collection: Mock) -> None: + """Tests the insert function from the mongo connector.""" mock_mongo_connector.database = {"news": mock_collection} service = MongoNewsService(mock_mongo_connector) @@ -92,6 +97,7 @@ def test_insert(mock_mongo_connector: Mock, mock_collection: Mock) -> None: def test_transform_ingoing() -> None: + """Tests the transform_ingoing function from the mongo connector.""" news = News("42", None, None, None, None) # type: ignore result = MongoEntryTransformer.transform_ingoing(news) assert result["_id"] == "42" @@ -99,6 +105,7 @@ def test_transform_ingoing() -> None: def test_transform_outgoing() -> None: + """Tests the transform_outgoing function from the mongo connector.""" data = { "_id": "4711", "title": "Hello", diff --git a/tests/utils/sql/connector_test.py b/tests/utils/sql/connector_test.py index 658ba57..a671883 100644 --- a/tests/utils/sql/connector_test.py +++ b/tests/utils/sql/connector_test.py @@ -1,3 +1,4 @@ +"""Tests the sql connector.""" import os.path from collections.abc import Generator from typing import Any @@ -16,6 +17,7 @@ from aki_prj23_transparenzregister.utils.sql.connector import ( def test_get_engine_pg() -> None: + """Tests the creation of a postgre engine.""" conn_args = PostgreConnectionString("", "", "", "", 42) with patch( "aki_prj23_transparenzregister.utils.sql.connector.sa.create_engine" diff --git a/tests/utils/sql/entities_test.py b/tests/utils/sql/entities_test.py index 14bc361..bcd3068 100644 --- a/tests/utils/sql/entities_test.py +++ b/tests/utils/sql/entities_test.py @@ -1,4 +1,8 @@ -def test_import() -> None: - from aki_prj23_transparenzregister.utils.sql import entities +"""Tests for the sql entities.""" +from aki_prj23_transparenzregister.utils.sql import entities + + +def test_import() -> None: # + """Tests if the entities can be imported.""" assert entities diff --git a/tests/utils/string_tools_test.py b/tests/utils/string_tools_test.py new file mode 100644 index 0000000..26a7b1b --- /dev/null +++ b/tests/utils/string_tools_test.py @@ -0,0 +1,35 @@ +"""Tests for the string tool module.""" +from typing import Any + +import pytest + +from aki_prj23_transparenzregister.utils import string_tools + + +def test_import() -> None: + """Tests if the import is possible.""" + assert string_tools + + +@pytest.mark.parametrize( + ("value", "expected"), + [ + ("None ", "None"), + (" ", None), + ("", None), + ("\t", None), + ("\n", None), + (" Some String ", "Some String"), + ("Some String", "Some String"), + ], +) +def test_simplify_string(value: str | None, expected: str | None) -> None: + """Tests the sting simplification.""" + assert string_tools.simplify_string(value) == expected + + +@pytest.mark.parametrize("value", [0, 0.1, True, ("1",), {}, set()]) +def test_simplify_string_type_error(value: Any) -> None: + """Tests if the type error is thrown when the value is the wrong type.""" + with pytest.raises(TypeError): + assert string_tools.simplify_string(value) From ba44b082b81914c97805c53f00c39bbe99c931db Mon Sep 17 00:00:00 2001 From: Philipp Horstenkamp Date: Mon, 11 Sep 2023 21:10:36 +0200 Subject: [PATCH 03/11] Data transfer script (#114) Transfers data betwenn two sql instances. Limited in data volume. Should be good enough for now. --------- Co-authored-by: Tim --- README.md | 8 +++-- .../utils/sql/connector.py | 24 ++++++++++++- tests/utils/data_extraction/__init__.py | 1 - tests/utils/sql/connector_test.py | 34 +++++++++++++++++++ 4 files changed, 62 insertions(+), 5 deletions(-) delete mode 100644 tests/utils/data_extraction/__init__.py diff --git a/README.md b/README.md index 1e5e308..b9585eb 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ See the [CONTRIBUTING.md](CONTRIBUTING.md) about how code should be formatted an ## DB Connection settings -To connect to the SQL db see [sql/connector.py](./src/aki_prj23_transparenzregister/utils/postgres/connector.py) +To connect to the SQL db see [sql/connector.py](./src/aki_prj23_transparenzregister/utils/sql/connector.py) To connect to the Mongo db see [connect] Create a `secrets.json` in the root of this repo with the following structure (values to be replaces by desired config): @@ -38,8 +38,10 @@ Create a `secrets.json` in the root of this repo with the following structure (v } ``` -Alternatively, the secrets can be provided as environment variables. One option to do so is to add a `.env` file with the following layout: -```ini +Alternatively, the secrets can be provided as environment variables. One option to do so is to add a `.env` file with +the following layout: + +``` PYTHON_POSTGRES_USERNAME=postgres PYTHON_POSTGRES_PASSWORD=postgres PYTHON_POSTGRES_HOST=localhost diff --git a/src/aki_prj23_transparenzregister/utils/sql/connector.py b/src/aki_prj23_transparenzregister/utils/sql/connector.py index 3986d45..b3e5d23 100644 --- a/src/aki_prj23_transparenzregister/utils/sql/connector.py +++ b/src/aki_prj23_transparenzregister/utils/sql/connector.py @@ -1,11 +1,13 @@ """Module containing connection utils for PostgreSQL DB.""" import re +import pandas as pd import sqlalchemy as sa from loguru import logger from sqlalchemy.engine import URL, Engine from sqlalchemy.orm import Session, declarative_base, sessionmaker from sqlalchemy.pool import SingletonThreadPool +from tqdm import tqdm from aki_prj23_transparenzregister.config.config_providers import ( ConfigProvider, @@ -83,11 +85,31 @@ def init_db(db: Session) -> None: def reset_all_tables(db: Session) -> None: """Drops all SQL tables and recreates them.""" - logger.info("Resetting all PostgreSQL tables.") + logger.info("Resetting all SQL tables.") Base.metadata.drop_all(db.bind) init_db(db) +@logger.catch(reraise=True) +def transfer_db(*, source: Session, destination: Session) -> None: + """Transfers the data from on db to another db. + + Args: + source: A session to a source db data should be copied from. + destination: A session to a db where the data should be copied to. + """ + reset_all_tables(destination) + init_db(destination) + sbind = source.bind + dbind = destination.bind + assert isinstance(sbind, Engine) # noqa: S101 + assert isinstance(dbind, Engine) # noqa: S101 + for table in tqdm(Base.metadata.sorted_tables): + pd.read_sql_table(str(table), sbind).to_sql( + str(table), dbind, if_exists="append", index=False + ) + + if __name__ == "__main__": """Main flow creating tables""" init_db(get_session(JsonFileConfigProvider("./secrets.json"))) diff --git a/tests/utils/data_extraction/__init__.py b/tests/utils/data_extraction/__init__.py deleted file mode 100644 index 0388525..0000000 --- a/tests/utils/data_extraction/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for data_extraction.""" diff --git a/tests/utils/sql/connector_test.py b/tests/utils/sql/connector_test.py index a671883..6dbd9ba 100644 --- a/tests/utils/sql/connector_test.py +++ b/tests/utils/sql/connector_test.py @@ -4,15 +4,19 @@ from collections.abc import Generator from typing import Any from unittest.mock import Mock, patch +import pandas as pd import pytest from sqlalchemy.engine import Engine +from sqlalchemy.orm import Session from aki_prj23_transparenzregister.config.config_providers import JsonFileConfigProvider from aki_prj23_transparenzregister.config.config_template import PostgreConnectionString from aki_prj23_transparenzregister.utils.sql.connector import ( + Base, get_pg_engine, get_session, init_db, + transfer_db, ) @@ -27,6 +31,36 @@ def test_get_engine_pg() -> None: assert get_pg_engine(conn_args) == result +@pytest.fixture() +def destination_db() -> Generator[Session, None, None]: + """Generates a db Session to a sqlite db to copy data to.""" + if os.path.exists("secondary.db"): + os.remove("secondary.db") + db = get_session("sqlite:///secondary.db") + init_db(db) + yield db + db.close() + bind = db.bind + assert isinstance(bind, Engine) + bind.dispose() + os.remove("secondary.db") + + +def test_transfer_db(full_db: Session, destination_db: Session) -> None: + """Tests if the data transfer between two sql tables works.""" + transfer_db(source=full_db, destination=destination_db) + sbind = full_db.bind + dbind = destination_db.bind + assert isinstance(sbind, Engine) + assert isinstance(dbind, Engine) + + for table in Base.metadata.sorted_tables: + pd.testing.assert_frame_equal( + pd.read_sql_table(str(table), dbind), + pd.read_sql_table(str(table), sbind), + ) + + @pytest.fixture() def delete_sqlite_table() -> Generator[str, None, None]: """Cleans a path before and deletes the table after a test. From 507647d164c0aa6bc2d4e35c6b69b1c5e7bf0053 Mon Sep 17 00:00:00 2001 From: Philipp Horstenkamp Date: Wed, 13 Sep 2023 19:01:09 +0200 Subject: [PATCH 04/11] Added custom 404 error code page in the sphinx docs (#115) --- documentations/conf.py | 3 +++ poetry.lock | 49 +++++++++++++++++++++--------------------- pyproject.toml | 2 +- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/documentations/conf.py b/documentations/conf.py index 7f6dfc7..e23b762 100644 --- a/documentations/conf.py +++ b/documentations/conf.py @@ -43,6 +43,7 @@ extensions: Final[list[str]] = [ "sphinx.ext.viewcode", "IPython.sphinxext.ipython_console_highlighting", "sphinxcontrib.mermaid", + "notfound.extension", ] # templates_path : Final[list[str]] = ["_templates"] @@ -86,3 +87,5 @@ intersphinx_mapping: Final[dict[str, tuple[str, None]]] = { "scikit-learn": ("https://scikit-learn.org/stable/", None), "sphinx": ("https://docs.sympy.org/latest/", None), } + +notfound_urls_prefix = "/" diff --git a/poetry.lock b/poetry.lock index 16ee761..095c857 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3366,26 +3366,6 @@ files = [ [package.dependencies] ptyprocess = ">=0.5" -[[package]] -name = "pgeocode" -version = "0.4.1" -description = "Approximate geocoding" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pgeocode-0.4.1-py3-none-any.whl", hash = "sha256:0cc3916d75c41ffcd910ccc2252235a66c627346502cba5d2e97b6ea0aa83257"}, - {file = "pgeocode-0.4.1.tar.gz", hash = "sha256:08f35dedf79957769641c7137aa9cc189e1bb63033226372dce372b14973e8b2"}, -] - -[package.dependencies] -numpy = "*" -pandas = "*" -requests = "*" - -[package.extras] -fuzzy = ["thefuzz"] - [[package]] name = "pickleshare" version = "0.7.5" @@ -4914,20 +4894,20 @@ win32 = ["pywin32"] [[package]] name = "setuptools" -version = "68.2.0" +version = "68.2.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-68.2.0-py3-none-any.whl", hash = "sha256:af3d5949030c3f493f550876b2fd1dd5ec66689c4ee5d5344f009746f71fd5a8"}, - {file = "setuptools-68.2.0.tar.gz", hash = "sha256:00478ca80aeebeecb2f288d3206b0de568df5cd2b8fada1209843cc9a8d88a48"}, + {file = "setuptools-68.2.1-py3-none-any.whl", hash = "sha256:eff96148eb336377ab11beee0c73ed84f1709a40c0b870298b0d058828761bae"}, + {file = "setuptools-68.2.1.tar.gz", hash = "sha256:56ee14884fd8d0cd015411f4a13f40b4356775a0aefd9ebc1d3bfb9a1acb32f1"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "shapely" @@ -5131,6 +5111,25 @@ sphinx = ">=1.8" code-style = ["pre-commit (==2.12.1)"] rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] +[[package]] +name = "sphinx-notfound-page" +version = "1.0.0" +description = "Sphinx extension to build a 404 page with absolute URLs" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinx_notfound_page-1.0.0-py3-none-any.whl", hash = "sha256:40a5741a6b07245a08fe55dbbd603ad6719e191b1419ab2e5337c706ebd16554"}, + {file = "sphinx_notfound_page-1.0.0.tar.gz", hash = "sha256:14cd388956de5cdf8710ab4ff31776ef8d85759c4f46014ee30f368e83bd3a3b"}, +] + +[package.dependencies] +sphinx = ">=5" + +[package.extras] +doc = ["sphinx-autoapi", "sphinx-rtd-theme", "sphinx-tabs", "sphinxemoji"] +test = ["tox"] + [[package]] name = "sphinx-rtd-theme" version = "1.3.0" @@ -5914,4 +5913,4 @@ ingest = ["selenium"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "f15e3b3171f0b6b22635f5c9de7635114c99447c5b3d41f8b1596d005fe1dce8" +content-hash = "8bb7730a3c0b9881f7d4cdadff6d1edadfec8e4244be0c530f072bd8ea17dc15" diff --git a/pyproject.toml b/pyproject.toml index b69d034..cb0c2be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,6 @@ dash-bootstrap-components = "^1.5.0" deutschland = {git = "https://github.com/TrisNol/deutschland.git", branch = "hotfix/python-3.11-support"} loguru = "^0.7.0" matplotlib = "^3.7.2" -pgeocode = "^0.4.0" plotly = "^5.16.1" psycopg2-binary = "^2.9.7" pymongo = "^4.5.0" @@ -69,6 +68,7 @@ myst-parser = "^1.0.0" nbsphinx = "^0.9.2" sphinx = "*" sphinx-copybutton = "^0.5.2" +sphinx-notfound-page = "^1.0.0" sphinx-rtd-theme = "^1.3.0" sphinx_autodoc_typehints = "*" sphinxcontrib-mermaid = "^0.9.2" From fea31e543b1464502885b46f71d544c6778c39de Mon Sep 17 00:00:00 2001 From: Philipp Horstenkamp Date: Wed, 13 Sep 2023 19:14:36 +0200 Subject: [PATCH 05/11] Bugfix update for transfer of company data from mongo to sql (#121) Fixed the following errors: - Typo in readme.md - Mongo delivers the last_update of an company as a string not as date. Added: - Entrypoint description in readme.md --- .gitignore | 1 + README.md | 9 ++++++++- .../utils/data_transfer.py | 14 +++++++++++--- tests/utils/data_transfer_test.py | 2 +- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 5371c9d..a5206be 100644 --- a/.gitignore +++ b/.gitignore @@ -217,3 +217,4 @@ replay_pid* /unit-test-results.xml /lbr-audit.md /.ruff_cache/ +/Jupyter/test.ipynb diff --git a/README.md b/README.md index b9585eb..1f11c25 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,13 @@ See the [CONTRIBUTING.md](CONTRIBUTING.md) about how code should be formatted and what kind of rules we set ourselves. +## Available entrypoints + +The project has currently the following entrypoint available: + +- data-transfer > Transfers all the data from the mongodb into the sql db to make it available as production data. +- reset-sql > Resets all sql tables in the connected db. + ## DB Connection settings To connect to the SQL db see [sql/connector.py](./src/aki_prj23_transparenzregister/utils/sql/connector.py) @@ -20,7 +27,7 @@ To connect to the Mongo db see [connect] Create a `secrets.json` in the root of this repo with the following structure (values to be replaces by desired config): ```json - { +{ "postgres": { "username": "postgres", "password": "postgres", diff --git a/src/aki_prj23_transparenzregister/utils/data_transfer.py b/src/aki_prj23_transparenzregister/utils/data_transfer.py index 2d4f129..1ca6ed0 100644 --- a/src/aki_prj23_transparenzregister/utils/data_transfer.py +++ b/src/aki_prj23_transparenzregister/utils/data_transfer.py @@ -209,6 +209,9 @@ def add_company(company: dict[str, Any], db: Session) -> None: raise DataInvalidError( "The company name needs to be valid (not empty and not only whitespace)." ) + last_update: date | None = ( + date.fromisoformat(company["last_update"]) if company["last_update"] else None + ) company_entry = entities.Company( court_id=court_id, hr=company["id"]["hr_number"].strip().replace(" ", " ").replace(" ", " "), @@ -216,7 +219,7 @@ def add_company(company: dict[str, Any], db: Session) -> None: city=simplify_string(location.get("city")), zip_code=simplify_string(location.get("zip_code")), street=simplify_string(location.get("street")), - last_update=company["last_update"], + last_update=last_update, ) db.add(company_entry) db.commit() @@ -335,7 +338,12 @@ def transfer_data(db: Session | None) -> None: if db is None: db = get_session(JsonFileConfigProvider("./secrets.json")) logger.remove() - logger.add(sys.stdout, level="INFO") + logger.add( + sys.stdout, + level="INFO", + catch=True, + format="{time:YYYY-MM-DD HH:mm:ss} {level} {message}", + ) logger.add("data-transfer.log", level="INFO", retention=5) reset_all_tables(db) @@ -352,4 +360,4 @@ def transfer_data(db: Session | None) -> None: if __name__ == "__main__": - transfer_data(get_session(JsonFileConfigProvider("./secrets.json"))) + transfer_data(get_session("sqlite:///local-test-data.db")) diff --git a/tests/utils/data_transfer_test.py b/tests/utils/data_transfer_test.py index f2deb8d..351cf83 100644 --- a/tests/utils/data_transfer_test.py +++ b/tests/utils/data_transfer_test.py @@ -262,7 +262,7 @@ def company_generator(seed: int) -> dict[str, Any]: "zip_code": get_random_zip() if random.choice([True, False]) else None, "street": get_random_string(20) if random.choice([True, False]) else None, }, - "last_update": date(random.randint(2000, 2023), 1, 1), + "last_update": date(random.randint(2000, 2023), 1, 1).isoformat(), } From 56b628026422299b6df1fce18cc592596918cc66 Mon Sep 17 00:00:00 2001 From: Philipp Horstenkamp Date: Sun, 17 Sep 2023 13:45:08 +0200 Subject: [PATCH 06/11] Transfer financials from staging to sql (#129) --- .../config/config_providers.py | 6 +- .../utils/data_transfer.py | 95 ++++++- .../utils/sql/entities.py | 29 +- tests/utils/data_transfer_test.py | 266 +++++++++++++++++- 4 files changed, 374 insertions(+), 22 deletions(-) diff --git a/src/aki_prj23_transparenzregister/config/config_providers.py b/src/aki_prj23_transparenzregister/config/config_providers.py index 47d1db3..be818c2 100644 --- a/src/aki_prj23_transparenzregister/config/config_providers.py +++ b/src/aki_prj23_transparenzregister/config/config_providers.py @@ -42,7 +42,7 @@ class JsonFileConfigProvider(ConfigProvider): __data__: dict = {} def __init__(self, file_path: str): - """Constructor reading it's data from given .json file. + """Constructor reading its data from a given .json file. Args: file_path (str): PATH to .json file containing config @@ -76,7 +76,7 @@ class JsonFileConfigProvider(ConfigProvider): ) def get_mongo_connection_string(self) -> MongoConnection: - """Read MongodB connection string from .json file added in constructor. + """Read MongoDB connection string from .json file added in constructor. Returns: MongoConnection: Connection details @@ -122,7 +122,7 @@ class EnvironmentConfigProvider(ConfigProvider): ) def get_mongo_connection_string(self) -> MongoConnection: - """Read MongodB connection string from environment variables. + """Read MongoDB connection string from environment variables. Returns: MongoConnection: Connection details diff --git a/src/aki_prj23_transparenzregister/utils/data_transfer.py b/src/aki_prj23_transparenzregister/utils/data_transfer.py index 1ca6ed0..038a0ab 100644 --- a/src/aki_prj23_transparenzregister/utils/data_transfer.py +++ b/src/aki_prj23_transparenzregister/utils/data_transfer.py @@ -304,13 +304,15 @@ def add_relationships(companies: list[dict[str, dict]], db: Session) -> None: companies: Companies to be added to the db. db: A session to connect to an SQL db via SQLAlchemy. """ - total: int = sum(len(company.get("relationships", [])) for company in companies) + total: int = sum(len(company.get("relationships", "")) for company in companies) with tqdm( total=total, desc="Company connections added", ) as pbar: for company in companies: - relationships: list[dict[str, Any]] = company.get("relationships", []) # type: ignore + relationships: list[dict[str, Any]] = company.get("relationships", "") # type: ignore + if not relationships: + continue try: company_id: int = get_company_id( company["name"], # type: ignore @@ -333,20 +335,93 @@ def add_relationships(companies: list[dict[str, dict]], db: Session) -> None: logger.info("Company connections added.") -def transfer_data(db: Session | None) -> None: +# yearly_results +def add_annual_report(company_id: int, year: int, report: dict, db: Session) -> None: + """Ads a annual financial report to the SQL database. + + The added report is linked with the company. + # TODO add a link to the accountant. + + Args: + company_id: The SQL id of the company. + year: The year of the result. + report: The result that was + db: A session to connect to an SQL db via SQLAlchemy. + """ + if not report.get("auditors") and not report.get("financials"): + company = db.query(entities.Company).get(company_id) + if company is None: + raise KeyError(f"The company with the id {company_id} could not be found.") + logger.debug(f"No financial data found for {company.name} in the year {year}.") + return + db.add( + entities.AnnualFinanceStatement( + company_id=company_id, + date=date(year, 1, 1), + **report.get("financials", {}), # TODO can we have a date? + ), + ) + for auditor in report.get("auditors", ""): + pass + _ = auditor + # person_id = get_person_id(person.get("name")) # how to create a person relation? + # company relation? + + +def add_annual_financial_reports(companies: list[dict], db: Session) -> None: + """Adds all the yearly results to the sql db. + + Args: + companies: The companies datadump from the MongoDB. + db: A session to connect to an SQL db via SQLAlchemy. + """ + total: int = sum(len(company.get("yearly_results", "")) for company in companies) + with tqdm( + total=total, + desc="Company connections added", + ) as pbar: + for company in companies: + yearly_results: dict[str, dict] = company.get("yearly_results", {}) + if not yearly_results: + continue + try: + company_id: int = get_company_id( + company["name"], + company["location"]["zip_code"], + company["location"]["city"], + db=db, + ) + except Exception: + logger.exception("The company could not be identified.") + pbar.update(len(yearly_results)) + db.rollback() + raise + for year, report in yearly_results.items(): + if not report: + continue + try: + year_int = int(year) + except ValueError: + logger.warning( + f"The company {company['name']} has a yearly result with an invalid year of \"{year}\".", + ) + continue + add_annual_report(company_id, year_int, report, db=db) + pbar.update() + db.commit() + logger.info("Company connections added.") + + +def transfer_data(db: Session | None = None) -> None: """This functions transfers all the data from a production environment to a staging environment.""" - if db is None: - db = get_session(JsonFileConfigProvider("./secrets.json")) logger.remove() logger.add( sys.stdout, level="INFO", catch=True, - format="{time:YYYY-MM-DD HH:mm:ss} {level} {message}", ) logger.add("data-transfer.log", level="INFO", retention=5) - reset_all_tables(db) mongo_connector = MongoConnector( JsonFileConfigProvider("./secrets.json").get_mongo_connection_string() ) @@ -354,8 +429,14 @@ def transfer_data(db: Session | None) -> None: companies: list[dict[str, Any]] = mongo_company.get_all() # type: ignore del mongo_company + if db is None: + db = get_session(JsonFileConfigProvider("./secrets.json")) + + reset_all_tables(db) + add_companies(companies, db) add_relationships(companies, db) + add_annual_financial_reports(companies, db) db.close() diff --git a/src/aki_prj23_transparenzregister/utils/sql/entities.py b/src/aki_prj23_transparenzregister/utils/sql/entities.py index fefdf83..a1f05e6 100644 --- a/src/aki_prj23_transparenzregister/utils/sql/entities.py +++ b/src/aki_prj23_transparenzregister/utils/sql/entities.py @@ -69,16 +69,25 @@ class AnnualFinanceStatement(Base): id = sa.Column(sa.Integer, primary_key=True) company_id = sa.Column(sa.Integer, sa.ForeignKey("company.id")) - date = sa.Column(sa.DateTime(timezone=True), nullable=False) - total_volume = sa.Column(sa.Float) - ebit = sa.Column(sa.Float) - ebitda = sa.Column(sa.Float) - ebit_margin = sa.Column(sa.Float) - total_balance = sa.Column(sa.Float) - equity = sa.Column(sa.Float) - debt = sa.Column(sa.Float) - return_on_equity = sa.Column(sa.Float) - capital_turnover_rate = sa.Column(sa.Float) + date = sa.Column(sa.Date, nullable=False) + total_volume = sa.Column(sa.Float, default="NaN") + ebit = sa.Column(sa.Float, default="NaN") + ebitda = sa.Column(sa.Float, default="NaN") + ebit_margin = sa.Column(sa.Float, default="NaN") + total_balance = sa.Column(sa.Float, default="NaN") + equity = sa.Column(sa.Float, default="NaN") + debt = sa.Column(sa.Float, default="NaN") + return_on_equity = sa.Column(sa.Float, default="NaN") + capital_turnover_rate = sa.Column(sa.Float, default="NaN") + current_liabilities = sa.Column(sa.Float, default="NaN") + dividends = sa.Column(sa.Float, default="NaN") + net_income = sa.Column(sa.Float, default="NaN") + assets = sa.Column(sa.Float, default="NaN") + long_term_debt = sa.Column(sa.Float, default="NaN") + short_term_debt = sa.Column(sa.Float, default="NaN") + revenue = sa.Column(sa.Float, default="NaN") + cash_flow = sa.Column(sa.Float, default="NaN") + current_assets = sa.Column(sa.Float, default="NaN") # assets vs current assets # company: Mapped[Company] = relationship(Company) diff --git a/tests/utils/data_transfer_test.py b/tests/utils/data_transfer_test.py index 351cf83..275aa7f 100644 --- a/tests/utils/data_transfer_test.py +++ b/tests/utils/data_transfer_test.py @@ -13,6 +13,7 @@ from sqlalchemy.engine import Engine from sqlalchemy.orm import Session from aki_prj23_transparenzregister.utils import data_transfer +from aki_prj23_transparenzregister.utils.sql import entities @pytest.mark.parametrize( @@ -326,7 +327,7 @@ def test_add_company_broken_date( @pytest.mark.parametrize("seed", list(range(5))) @pytest.mark.parametrize("overwrite", ["", None, " "]) def test_add_company_broken_district_court( - seed: int, overwrite: str | None, full_db: Session, mocker: MockerFixture + seed: int, overwrite: str | None, full_db: Session ) -> None: """Test a broken district court entry.""" company = company_generator(seed) @@ -595,7 +596,6 @@ def test_add_relationships_none(empty_relations: list, full_db: Session) -> None data_transfer.add_relationships([], full_db) -@pytest.mark.working_on() @pytest.mark.parametrize( "documents", [ @@ -742,3 +742,265 @@ def test_relationships(documents: list[dict[str, Any]], full_db: Session) -> Non } ), ) + + +@pytest.mark.parametrize( + "companies", + [ + [], + [{}], + [ + { + "_id": {"$oid": "649f16a2ecc"}, + "id": { + "hr_number": "HRB 123", + "district_court": { + "name": "Amtsgericht Dortmund", + "city": "Dortmund", + }, + }, + "location": { + "city": "TV City", + "zip_code": "12345", + "street": "Sesamstr.", + "house_number": "1", + }, + "name": "Some Company GmbH", + "last_update": "2023-05-04", + "relationships": [], + "yearly_results": {2023: {}, "2023": {}}, + }, + ], + ], +) +def test_add_annual_financial_reports_no_call( + companies: list[dict], full_db: Session, mocker: MockerFixture +) -> None: + """Testing if financial reports are added correctly to the db.""" + spy_warning = mocker.spy(data_transfer.logger, "warning") + info_warning = mocker.spy(data_transfer.logger, "info") + mocker.patch("aki_prj23_transparenzregister.utils.data_transfer.add_annual_report") + data_transfer.add_annual_financial_reports(companies, full_db) + + input_args = mocker.call.args + input_kwargs = mocker.call.kwargs + assert len(input_args) == len(input_kwargs) + spy_warning.assert_not_called() + info_warning.assert_called_once() + + +@pytest.mark.parametrize( + "companies", + [ + [ + { + "_id": {"$oid": "649f16a2ecc"}, + "id": { + "hr_number": "HRB 123", + "district_court": { + "name": "Amtsgericht Dortmund", + "city": "Dortmund", + }, + }, + "location": { + "city": "TV City", + "zip_code": "12345", + "street": "Sesamstr.", + "house_number": "1", + }, + "name": "Some Company GmbH", + "last_update": "2023-05-04", + "relationships": [], + "yearly_results": {"i am not an int": {"auditor": {}}}, + } + ], + ], +) +def test_add_annual_financial_reports_defect_year( + companies: list[dict], full_db: Session, mocker: MockerFixture +) -> None: + """Testing if financial reports are added correctly to the db.""" + spy_warning = mocker.spy(data_transfer.logger, "warning") + info_warning = mocker.spy(data_transfer.logger, "info") + mocker.patch("aki_prj23_transparenzregister.utils.data_transfer.add_annual_report") + data_transfer.add_annual_financial_reports(companies, full_db) + + input_args = mocker.call.args + input_kwargs = mocker.call.kwargs + assert len(input_args) == len(input_kwargs) + spy_warning.assert_called_once() + info_warning.assert_called_once() + + +def test_add_annual_financial_reports(full_db: Session, mocker: MockerFixture) -> None: + """Testing if financial reports are added correctly to the db.""" + companies = [ + { + "_id": {"$oid": "649f16a2ecc"}, + "id": { + "hr_number": "HRB 123", + "district_court": { + "name": "Amtsgericht Dortmund", + "city": "Dortmund", + }, + }, + "location": { + "city": "TV City", + "zip_code": "12345", + "street": "Sesamstr.", + "house_number": "1", + }, + "name": "Some Company GmbH", + "last_update": "2023-05-04", + "relationships": [], + "yearly_results": { + 2023: {"some-text1": {}}, + "cast-me-to-int": {"some-text4": {}}, + 2025: {"some-text2": {}}, + "cast-me-too": {"some-text5": {}}, + 2024: {"some-text3": {}}, + }, + } + ] + + spy_warning = mocker.spy(data_transfer.logger, "warning") + info_warning = mocker.spy(data_transfer.logger, "info") + mocked = mocker.patch( + "aki_prj23_transparenzregister.utils.data_transfer.add_annual_report" + ) + data_transfer.add_annual_financial_reports(companies, full_db) + + spy_warning.assert_has_calls([]) + for input_args in mocked.call_args_list: + assert input_args.args[0] == 1 + assert isinstance(input_args.kwargs["db"], Session) + assert len(input_args.kwargs) == 1 + + for year, input_args in zip([2023, 2025, 2024], mocked.call_args_list, strict=True): + assert year == input_args.args[1] + + report: dict + for report, input_args in zip( + [{"some-text1": {}}, {"some-text2": {}}, {"some-text3": {}}], + mocked.call_args_list, + strict=True, + ): + assert report == input_args.args[2] + + for input_args in mocked.call_args_list: + assert isinstance(input_args.kwargs["db"], Session) + + info_warning.assert_called_once() + + +@pytest.mark.parametrize("year", list(range(2000, 2025, 5))) +@pytest.mark.parametrize("company_id", [1, 2, 3]) +@pytest.mark.parametrize( + "empty_report", + [{}, {"auditors": []}, {"financials": []}, {"auditors": [], "financials": []}], +) +def test_add_annual_report_empty( + year: int, + company_id: int, + empty_report: dict, + full_db: Session, + mocker: MockerFixture, +) -> None: + """Testing if the correct warning is thrown when the financial and auditor records are empty.""" + df_prior = pd.read_sql_table( + entities.AnnualFinanceStatement.__tablename__, full_db.bind # type: ignore + ) + spy_warning = mocker.spy(data_transfer.logger, "debug") + + data_transfer.add_annual_report(company_id, year, empty_report, full_db) + full_db.commit() + spy_warning.assert_called_once() + pd.testing.assert_frame_equal( + df_prior, + pd.read_sql_table(entities.AnnualFinanceStatement.__tablename__, full_db.bind), # type: ignore + ) + + +@pytest.mark.parametrize("year", [2015, 2023, 2024]) +@pytest.mark.parametrize("company_id", [7, 8, 9]) +@pytest.mark.parametrize( + "empty_report", + [{}, {"auditors": []}, {"financials": []}, {"auditors": [], "financials": []}], +) +def test_add_annual_report_to_unknown_company( + year: int, company_id: int, empty_report: dict, full_db: Session +) -> None: + """Tests if an error is thrown when the company id isn't registered in the db.""" + with pytest.raises( + KeyError, match="The company with the id .* could not be found." + ): + data_transfer.add_annual_report(company_id, year, empty_report, full_db) + + +@pytest.mark.parametrize("company_id", [1, 2, 3]) +@pytest.mark.parametrize("year", [2023, 2025, 2020]) +@pytest.mark.parametrize("short_term_debt", [2023.2, 2025.5, 2020.5, float("NaN")]) +def test_add_annual_report( + short_term_debt: float, company_id: int, year: int, full_db: Session +) -> None: + """Tests the addition of annual financial records.""" + data_transfer.add_annual_report( + company_id, + year, + { + "financials": { + "ebit": 123, + "ebitda": 235, + "short_term_debt": short_term_debt, + }, + "auditors": {}, + }, + db=full_db, + ) + full_db.commit() + df_prior = pd.read_sql_table( + entities.AnnualFinanceStatement.__tablename__, full_db.bind # type: ignore + ) + pd.testing.assert_frame_equal( + pd.DataFrame( + [ + { + "id": 1, + "company_id": company_id, + "date": pd.to_datetime(date(year, 1, 1)), + "total_volume": float("NaN"), + "ebit": 123.0, + "ebitda": 235.0, + "ebit_margin": float("NaN"), + "total_balance": float("NaN"), + "equity": float("NaN"), + "debt": float("NaN"), + "return_on_equity": float("NaN"), + "capital_turnover_rate": float("NaN"), + "current_liabilities": float("NaN"), + "dividends": float("NaN"), + "net_income": float("NaN"), + "assets": float("NaN"), + "long_term_debt": float("NaN"), + "short_term_debt": short_term_debt, + "revenue": float("NaN"), + "cash_flow": float("NaN"), + "current_assets": float("NaN"), + } + ] + ), + df_prior, + ) + + +def test_add_annual_report_financial_key_error(full_db: Session) -> None: + """Tests if an error is thrown financial data is tried to be added with an unknown financial record type.""" + with pytest.raises( + TypeError, match="is an invalid keyword argument for AnnualFinanceStatement" + ): + data_transfer.add_annual_report( + 2, + 2023, + {"financials": {"something-strange": 123.12}, "auditors": {}}, + db=full_db, + ) From 06fef5361dafd7cc956a351476e70ed4dcfdcb63 Mon Sep 17 00:00:00 2001 From: Philipp Horstenkamp Date: Sun, 17 Sep 2023 13:47:20 +0200 Subject: [PATCH 07/11] Lightend black in the pipline building (#128) Pipline should now run now 20 -40s faster. --- .github/workflows/lint-actions.yaml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/lint-actions.yaml b/.github/workflows/lint-actions.yaml index 21cdbb3..7583d71 100644 --- a/.github/workflows/lint-actions.yaml +++ b/.github/workflows/lint-actions.yaml @@ -15,14 +15,8 @@ jobs: python-version: '3.11' - name: Check out Git repository uses: actions/checkout@v3 - - name: Install and configure Poetry - uses: snok/install-poetry@v1 - with: - version: 1.4.2 - virtualenvs-create: false - virtualenvs-path: ~/local/share/virtualenvs - - run: poetry install --without develop,doc,test - - name: Run linters + - run: pip install black + - name: Run black run: | black src tests @@ -43,7 +37,7 @@ jobs: virtualenvs-create: false virtualenvs-path: ~/local/share/virtualenvs - run: poetry install --without develop,doc - - name: Run linters + - name: Run mypy run: | mypy src tests From 80f077ee7afe01b31a9a6b826accca83829b3494 Mon Sep 17 00:00:00 2001 From: Philipp Horstenkamp Date: Sun, 17 Sep 2023 13:50:53 +0200 Subject: [PATCH 08/11] Version ups (#117) Rutine version ups for - python-depdencies - pre-commits - piplines --- .github/workflows/lint-actions.yaml | 2 +- .pre-commit-config.yaml | 4 +- poetry.lock | 584 ++++++++++++++-------------- pyproject.toml | 10 +- 4 files changed, 302 insertions(+), 298 deletions(-) diff --git a/.github/workflows/lint-actions.yaml b/.github/workflows/lint-actions.yaml index 7583d71..ffbed26 100644 --- a/.github/workflows/lint-actions.yaml +++ b/.github/workflows/lint-actions.yaml @@ -47,7 +47,7 @@ jobs: - uses: actions/checkout@v3 - uses: chartboost/ruff-action@v1 with: - version: 0.0.287 + version: 0.0.289 python-requirements: name: Check Python Requirements diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cc41585..f4f2f57 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,13 +26,13 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.0.287 + rev: v0.0.289 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black - rev: 23.9.0 + rev: 23.9.1 hooks: - id: black args: [--config=pyproject.toml] diff --git a/poetry.lock b/poetry.lock index 095c857..ce01744 100644 --- a/poetry.lock +++ b/poetry.lock @@ -774,14 +774,14 @@ files = [ [[package]] name = "cyclonedx-python-lib" -version = "4.2.1" +version = "4.2.2" description = "A library for producing CycloneDX SBOM (Software Bill of Materials) files." category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "cyclonedx_python_lib-4.2.1-py3-none-any.whl", hash = "sha256:b6b3818d48ed932545d3c5c819cbfe9a1fe452fba363388d2005ba9b054c81cb"}, - {file = "cyclonedx_python_lib-4.2.1.tar.gz", hash = "sha256:adcb074d00e5171754fc2f04269987cdf11342d2dbce2b9eeee7ebb218b1ed94"}, + {file = "cyclonedx_python_lib-4.2.2-py3-none-any.whl", hash = "sha256:fabc09bedc1e5aa2244d16bb72faaf88d2ff918c9a5f5c1a9026f75d1f896015"}, + {file = "cyclonedx_python_lib-4.2.2.tar.gz", hash = "sha256:d8fd40a94ab9130e9d1292ecebd35bd081f8a2d589c5259eaf7ec5caa5e95a43"}, ] [package.dependencies] @@ -1143,34 +1143,30 @@ urllib3 = ">=1.25.3" [[package]] name = "debugpy" -version = "1.7.0" +version = "1.8.0" description = "An implementation of the Debug Adapter Protocol for Python" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "debugpy-1.7.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:17ad9a681aca1704c55b9a5edcb495fa8f599e4655c9872b7f9cf3dc25890d48"}, - {file = "debugpy-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1285920a3f9a75f5d1acf59ab1b9da9ae6eb9a05884cd7674f95170c9cafa4de"}, - {file = "debugpy-1.7.0-cp310-cp310-win32.whl", hash = "sha256:a6f43a681c5025db1f1c0568069d1d1bad306a02e7c36144912b26d9c90e4724"}, - {file = "debugpy-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:9e9571d831ad3c75b5fb6f3efcb71c471cf2a74ba84af6ac1c79ce00683bed4b"}, - {file = "debugpy-1.7.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:538765a41198aa88cc089295b39c7322dd598f9ef1d52eaae12145c63bf9430a"}, - {file = "debugpy-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7e8cf91f8f3f9b5fad844dd88427b85d398bda1e2a0cd65d5a21312fcbc0c6f"}, - {file = "debugpy-1.7.0-cp311-cp311-win32.whl", hash = "sha256:18a69f8e142a716310dd0af6d7db08992aed99e2606108732efde101e7c65e2a"}, - {file = "debugpy-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7515a5ba5ee9bfe956685909c5f28734c1cecd4ee813523363acfe3ca824883a"}, - {file = "debugpy-1.7.0-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:bc8da67ade39d9e75608cdb8601d07e63a4e85966e0572c981f14e2cf42bcdef"}, - {file = "debugpy-1.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5036e918c6ba8fc4c4f1fd0207d81db634431a02f0dc2ba51b12fd793c8c9de"}, - {file = "debugpy-1.7.0-cp37-cp37m-win32.whl", hash = "sha256:d5be95b3946a4d7b388e45068c7b75036ac5a610f41014aee6cafcd5506423ad"}, - {file = "debugpy-1.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:0e90314a078d4e3f009520c8387aba8f74c3034645daa7a332a3d1bb81335756"}, - {file = "debugpy-1.7.0-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:1565fd904f9571c430adca597771255cff4f92171486fced6f765dcbdfc8ec8d"}, - {file = "debugpy-1.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6516f36a2e95b3be27f171f12b641e443863f4ad5255d0fdcea6ae0be29bb912"}, - {file = "debugpy-1.7.0-cp38-cp38-win32.whl", hash = "sha256:2b0e489613bc066051439df04c56777ec184b957d6810cb65f235083aef7a0dc"}, - {file = "debugpy-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:7bf0b4bbd841b2397b6a8de15da9227f1164f6d43ceee971c50194eaed930a9d"}, - {file = "debugpy-1.7.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:ad22e1095b9977af432465c1e09132ba176e18df3834b1efcab1a449346b350b"}, - {file = "debugpy-1.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f625e427f21423e5874139db529e18cb2966bdfcc1cb87a195538c5b34d163d1"}, - {file = "debugpy-1.7.0-cp39-cp39-win32.whl", hash = "sha256:18bca8429d6632e2d3435055416d2d88f0309cc39709f4f6355c8d412cc61f24"}, - {file = "debugpy-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:dc8a12ac8b97ef3d6973c6679a093138c7c9b03eb685f0e253269a195f651559"}, - {file = "debugpy-1.7.0-py2.py3-none-any.whl", hash = "sha256:f6de2e6f24f62969e0f0ef682d78c98161c4dca29e9fb05df4d2989005005502"}, - {file = "debugpy-1.7.0.zip", hash = "sha256:676911c710e85567b17172db934a71319ed9d995104610ce23fd74a07f66e6f6"}, + {file = "debugpy-1.8.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:7fb95ca78f7ac43393cd0e0f2b6deda438ec7c5e47fa5d38553340897d2fbdfb"}, + {file = "debugpy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef9ab7df0b9a42ed9c878afd3eaaff471fce3fa73df96022e1f5c9f8f8c87ada"}, + {file = "debugpy-1.8.0-cp310-cp310-win32.whl", hash = "sha256:a8b7a2fd27cd9f3553ac112f356ad4ca93338feadd8910277aff71ab24d8775f"}, + {file = "debugpy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5d9de202f5d42e62f932507ee8b21e30d49aae7e46d5b1dd5c908db1d7068637"}, + {file = "debugpy-1.8.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:ef54404365fae8d45cf450d0544ee40cefbcb9cb85ea7afe89a963c27028261e"}, + {file = "debugpy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60009b132c91951354f54363f8ebdf7457aeb150e84abba5ae251b8e9f29a8a6"}, + {file = "debugpy-1.8.0-cp311-cp311-win32.whl", hash = "sha256:8cd0197141eb9e8a4566794550cfdcdb8b3db0818bdf8c49a8e8f8053e56e38b"}, + {file = "debugpy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a64093656c4c64dc6a438e11d59369875d200bd5abb8f9b26c1f5f723622e153"}, + {file = "debugpy-1.8.0-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:b05a6b503ed520ad58c8dc682749113d2fd9f41ffd45daec16e558ca884008cd"}, + {file = "debugpy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c6fb41c98ec51dd010d7ed650accfd07a87fe5e93eca9d5f584d0578f28f35f"}, + {file = "debugpy-1.8.0-cp38-cp38-win32.whl", hash = "sha256:46ab6780159eeabb43c1495d9c84cf85d62975e48b6ec21ee10c95767c0590aa"}, + {file = "debugpy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:bdc5ef99d14b9c0fcb35351b4fbfc06ac0ee576aeab6b2511702e5a648a2e595"}, + {file = "debugpy-1.8.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:61eab4a4c8b6125d41a34bad4e5fe3d2cc145caecd63c3fe953be4cc53e65bf8"}, + {file = "debugpy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:125b9a637e013f9faac0a3d6a82bd17c8b5d2c875fb6b7e2772c5aba6d082332"}, + {file = "debugpy-1.8.0-cp39-cp39-win32.whl", hash = "sha256:57161629133113c97b387382045649a2b985a348f0c9366e22217c87b68b73c6"}, + {file = "debugpy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:e3412f9faa9ade82aa64a50b602544efcba848c91384e9f93497a458767e6926"}, + {file = "debugpy-1.8.0-py2.py3-none-any.whl", hash = "sha256:9c9b0ac1ce2a42888199df1a1906e45e6f3c9555497643a85e0bf2406e3ffbc4"}, + {file = "debugpy-1.8.0.zip", hash = "sha256:12af2c55b419521e33d5fb21bd022df0b5eb267c3e178f1d374a63a2a6bdccd0"}, ] [[package]] @@ -1369,19 +1365,20 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc [[package]] name = "filelock" -version = "3.12.3" +version = "3.12.4" description = "A platform independent file lock." category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.12.3-py3-none-any.whl", hash = "sha256:f067e40ccc40f2b48395a80fcbd4728262fab54e232e090a4063ab804179efeb"}, - {file = "filelock-3.12.3.tar.gz", hash = "sha256:0ecc1dd2ec4672a10c8550a8182f1bd0c0a5088470ecd5a125e45f49472fac3d"}, + {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, + {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, ] [package.extras] docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +typing = ["typing-extensions (>=4.7.1)"] [[package]] name = "flask" @@ -1666,14 +1663,14 @@ pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_ve [[package]] name = "identify" -version = "2.5.27" +version = "2.5.28" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.27-py2.py3-none-any.whl", hash = "sha256:fdb527b2dfe24602809b2201e033c2a113d7bdf716db3ca8e3243f735dcecaba"}, - {file = "identify-2.5.27.tar.gz", hash = "sha256:287b75b04a0e22d727bc9a41f0d4f3c1bcada97490fa6eabb5b28f0e9097e733"}, + {file = "identify-2.5.28-py2.py3-none-any.whl", hash = "sha256:87816de144bf46d161bd5b3e8f5596b16cade3b80be537087334b26bc5c177f3"}, + {file = "identify-2.5.28.tar.gz", hash = "sha256:94bb59643083ebd60dc996d043497479ee554381fbc5307763915cda49b0e78f"}, ] [package.extras] @@ -1802,22 +1799,22 @@ files = [ [[package]] name = "ipywidgets" -version = "8.1.0" +version = "8.1.1" description = "Jupyter interactive widgets" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "ipywidgets-8.1.0-py3-none-any.whl", hash = "sha256:6c8396cc7b8c95dfb4e9ab0054f48c002f045e7e5d7ae523f559d64e525a98ab"}, - {file = "ipywidgets-8.1.0.tar.gz", hash = "sha256:ce97dd90525b3066fd00094690964e7eac14cf9b7745d35565b5eeac20cce687"}, + {file = "ipywidgets-8.1.1-py3-none-any.whl", hash = "sha256:2b88d728656aea3bbfd05d32c747cfd0078f9d7e159cf982433b58ad717eed7f"}, + {file = "ipywidgets-8.1.1.tar.gz", hash = "sha256:40211efb556adec6fa450ccc2a77d59ca44a060f4f9f136833df59c9f538e6e8"}, ] [package.dependencies] comm = ">=0.1.3" ipython = ">=6.1.0" -jupyterlab-widgets = ">=3.0.7,<3.1.0" +jupyterlab-widgets = ">=3.0.9,<3.1.0" traitlets = ">=4.3.1" -widgetsnbextension = ">=4.0.7,<4.1.0" +widgetsnbextension = ">=4.0.9,<4.1.0" [package.extras] test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] @@ -2149,14 +2146,14 @@ test = ["coverage", "jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-cov", [[package]] name = "jupyterlab" -version = "4.0.5" +version = "4.0.6" description = "JupyterLab computational environment" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "jupyterlab-4.0.5-py3-none-any.whl", hash = "sha256:13b3a326e7b95d72746fe20dbe80ee1e71165d6905e01ceaf1320eb809cb1b47"}, - {file = "jupyterlab-4.0.5.tar.gz", hash = "sha256:de49deb75f9b9aec478ed04754cbefe9c5d22fd796a5783cdc65e212983d3611"}, + {file = "jupyterlab-4.0.6-py3-none-any.whl", hash = "sha256:7d9dacad1e3f30fe4d6d4efc97fda25fbb5012012b8f27cc03a2283abcdee708"}, + {file = "jupyterlab-4.0.6.tar.gz", hash = "sha256:6c43ae5a6a1fd2fdfafcb3454004958bde6da76331abb44cffc6f9e436b19ba1"}, ] [package.dependencies] @@ -2173,8 +2170,8 @@ tornado = ">=6.2.0" traitlets = "*" [package.extras] -dev = ["black[jupyter] (==23.3.0)", "build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.0.271)"] -docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-tornasync", "sphinx (>=1.8)", "sphinx-copybutton"] +dev = ["black[jupyter] (==23.7.0)", "build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.0.286)"] +docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-tornasync", "sphinx (>=1.8,<7.2.0)", "sphinx-copybutton"] docs-screenshots = ["altair (==5.0.1)", "ipython (==8.14.0)", "ipywidgets (==8.0.6)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.0.post0)", "matplotlib (==3.7.1)", "nbconvert (>=7.0.0)", "pandas (==2.0.2)", "scipy (==1.10.1)", "vega-datasets (==0.9.0)"] test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"] @@ -2192,40 +2189,40 @@ files = [ [[package]] name = "jupyterlab-server" -version = "2.24.0" +version = "2.25.0" description = "A set of server components for JupyterLab and JupyterLab like applications." category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "jupyterlab_server-2.24.0-py3-none-any.whl", hash = "sha256:5f077e142bb8dc9b843d960f940c513581bceca3793a0d80f9c67d9522c4e876"}, - {file = "jupyterlab_server-2.24.0.tar.gz", hash = "sha256:4e6f99e0a5579bbbc32e449c4dbb039561d4f1a7827d5733273ed56738f21f07"}, + {file = "jupyterlab_server-2.25.0-py3-none-any.whl", hash = "sha256:c9f67a98b295c5dee87f41551b0558374e45d449f3edca153dd722140630dcb2"}, + {file = "jupyterlab_server-2.25.0.tar.gz", hash = "sha256:77c2f1f282d610f95e496e20d5bf1d2a7706826dfb7b18f3378ae2870d272fb7"}, ] [package.dependencies] babel = ">=2.10" jinja2 = ">=3.0.3" json5 = ">=0.9.0" -jsonschema = ">=4.17.3" +jsonschema = ">=4.18.0" jupyter-server = ">=1.21,<3" packaging = ">=21.3" -requests = ">=2.28" +requests = ">=2.31" [package.extras] docs = ["autodoc-traits", "jinja2 (<3.2.0)", "mistune (<4)", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi (>0.8)"] -openapi = ["openapi-core (>=0.16.1,<0.17.0)", "ruamel-yaml"] -test = ["hatch", "ipykernel", "jupyterlab-server[openapi]", "openapi-spec-validator (>=0.5.1,<0.7.0)", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"] +openapi = ["openapi-core (>=0.18.0,<0.19.0)", "ruamel-yaml"] +test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-validator (>=0.6.0,<0.7.0)", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "ruamel-yaml", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"] [[package]] name = "jupyterlab-widgets" -version = "3.0.8" +version = "3.0.9" description = "Jupyter interactive widgets for JupyterLab" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "jupyterlab_widgets-3.0.8-py3-none-any.whl", hash = "sha256:4715912d6ceab839c9db35953c764b3214ebbc9161c809f6e0510168845dfdf5"}, - {file = "jupyterlab_widgets-3.0.8.tar.gz", hash = "sha256:d428ab97b8d87cc7c54cbf37644d6e0f0e662f23876e05fa460a73ec3257252a"}, + {file = "jupyterlab_widgets-3.0.9-py3-none-any.whl", hash = "sha256:3cf5bdf5b897bf3bccf1c11873aa4afd776d7430200f765e0686bd352487b58d"}, + {file = "jupyterlab_widgets-3.0.9.tar.gz", hash = "sha256:6005a4e974c7beee84060fdfba341a3218495046de8ae3ec64888e5fe19fdb4c"}, ] [[package]] @@ -2608,53 +2605,40 @@ files = [ [[package]] name = "matplotlib" -version = "3.7.2" +version = "3.8.0" description = "Python plotting package" category = "main" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "matplotlib-3.7.2-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:2699f7e73a76d4c110f4f25be9d2496d6ab4f17345307738557d345f099e07de"}, - {file = "matplotlib-3.7.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a8035ba590658bae7562786c9cc6ea1a84aa49d3afab157e414c9e2ea74f496d"}, - {file = "matplotlib-3.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f8e4a49493add46ad4a8c92f63e19d548b2b6ebbed75c6b4c7f46f57d36cdd1"}, - {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71667eb2ccca4c3537d9414b1bc00554cb7f91527c17ee4ec38027201f8f1603"}, - {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:152ee0b569a37630d8628534c628456b28686e085d51394da6b71ef84c4da201"}, - {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070f8dddd1f5939e60aacb8fa08f19551f4b0140fab16a3669d5cd6e9cb28fc8"}, - {file = "matplotlib-3.7.2-cp310-cp310-win32.whl", hash = "sha256:fdbb46fad4fb47443b5b8ac76904b2e7a66556844f33370861b4788db0f8816a"}, - {file = "matplotlib-3.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:23fb1750934e5f0128f9423db27c474aa32534cec21f7b2153262b066a581fd1"}, - {file = "matplotlib-3.7.2-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:30e1409b857aa8a747c5d4f85f63a79e479835f8dffc52992ac1f3f25837b544"}, - {file = "matplotlib-3.7.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:50e0a55ec74bf2d7a0ebf50ac580a209582c2dd0f7ab51bc270f1b4a0027454e"}, - {file = "matplotlib-3.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ac60daa1dc83e8821eed155796b0f7888b6b916cf61d620a4ddd8200ac70cd64"}, - {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305e3da477dc8607336ba10bac96986d6308d614706cae2efe7d3ffa60465b24"}, - {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c308b255efb9b06b23874236ec0f10f026673ad6515f602027cc8ac7805352d"}, - {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60c521e21031632aa0d87ca5ba0c1c05f3daacadb34c093585a0be6780f698e4"}, - {file = "matplotlib-3.7.2-cp311-cp311-win32.whl", hash = "sha256:26bede320d77e469fdf1bde212de0ec889169b04f7f1179b8930d66f82b30cbc"}, - {file = "matplotlib-3.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:af4860132c8c05261a5f5f8467f1b269bf1c7c23902d75f2be57c4a7f2394b3e"}, - {file = "matplotlib-3.7.2-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:a1733b8e84e7e40a9853e505fe68cc54339f97273bdfe6f3ed980095f769ddc7"}, - {file = "matplotlib-3.7.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d9881356dc48e58910c53af82b57183879129fa30492be69058c5b0d9fddf391"}, - {file = "matplotlib-3.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f081c03f413f59390a80b3e351cc2b2ea0205839714dbc364519bcf51f4b56ca"}, - {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1cd120fca3407a225168238b790bd5c528f0fafde6172b140a2f3ab7a4ea63e9"}, - {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c1590b90aa7bd741b54c62b78de05d4186271e34e2377e0289d943b3522273"}, - {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d2ff3c984b8a569bc1383cd468fc06b70d7b59d5c2854ca39f1436ae8394117"}, - {file = "matplotlib-3.7.2-cp38-cp38-win32.whl", hash = "sha256:5dea00b62d28654b71ca92463656d80646675628d0828e08a5f3b57e12869e13"}, - {file = "matplotlib-3.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:0f506a1776ee94f9e131af1ac6efa6e5bc7cb606a3e389b0ccb6e657f60bb676"}, - {file = "matplotlib-3.7.2-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:6515e878f91894c2e4340d81f0911857998ccaf04dbc1bba781e3d89cbf70608"}, - {file = "matplotlib-3.7.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:71f7a8c6b124e904db550f5b9fe483d28b896d4135e45c4ea381ad3b8a0e3256"}, - {file = "matplotlib-3.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12f01b92ecd518e0697da4d97d163b2b3aa55eb3eb4e2c98235b3396d7dad55f"}, - {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7e28d6396563955f7af437894a36bf2b279462239a41028323e04b85179058b"}, - {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbcf59334ff645e6a67cd5f78b4b2cdb76384cdf587fa0d2dc85f634a72e1a3e"}, - {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:318c89edde72ff95d8df67d82aca03861240512994a597a435a1011ba18dbc7f"}, - {file = "matplotlib-3.7.2-cp39-cp39-win32.whl", hash = "sha256:ce55289d5659b5b12b3db4dc9b7075b70cef5631e56530f14b2945e8836f2d20"}, - {file = "matplotlib-3.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:2ecb5be2b2815431c81dc115667e33da0f5a1bcf6143980d180d09a717c4a12e"}, - {file = "matplotlib-3.7.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fdcd28360dbb6203fb5219b1a5658df226ac9bebc2542a9e8f457de959d713d0"}, - {file = "matplotlib-3.7.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c3cca3e842b11b55b52c6fb8bd6a4088693829acbfcdb3e815fa9b7d5c92c1b"}, - {file = "matplotlib-3.7.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebf577c7a6744e9e1bd3fee45fc74a02710b214f94e2bde344912d85e0c9af7c"}, - {file = "matplotlib-3.7.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:936bba394682049919dda062d33435b3be211dc3dcaa011e09634f060ec878b2"}, - {file = "matplotlib-3.7.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bc221ffbc2150458b1cd71cdd9ddd5bb37962b036e41b8be258280b5b01da1dd"}, - {file = "matplotlib-3.7.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35d74ebdb3f71f112b36c2629cf32323adfbf42679e2751252acd468f5001c07"}, - {file = "matplotlib-3.7.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717157e61b3a71d3d26ad4e1770dc85156c9af435659a25ee6407dc866cb258d"}, - {file = "matplotlib-3.7.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:20f844d6be031948148ba49605c8b96dfe7d3711d1b63592830d650622458c11"}, - {file = "matplotlib-3.7.2.tar.gz", hash = "sha256:a8cdb91dddb04436bd2f098b8fdf4b81352e68cf4d2c6756fcc414791076569b"}, + {file = "matplotlib-3.8.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c4940bad88a932ddc69734274f6fb047207e008389489f2b6f77d9ca485f0e7a"}, + {file = "matplotlib-3.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a33bd3045c7452ca1fa65676d88ba940867880e13e2546abb143035fa9072a9d"}, + {file = "matplotlib-3.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea6886e93401c22e534bbfd39201ce8931b75502895cfb115cbdbbe2d31f287"}, + {file = "matplotlib-3.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d670b9348e712ec176de225d425f150dc8e37b13010d85233c539b547da0be39"}, + {file = "matplotlib-3.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7b37b74f00c4cb6af908cb9a00779d97d294e89fd2145ad43f0cdc23f635760c"}, + {file = "matplotlib-3.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:0e723f5b96f3cd4aad99103dc93e9e3cdc4f18afdcc76951f4857b46f8e39d2d"}, + {file = "matplotlib-3.8.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5dc945a9cb2deb7d197ba23eb4c210e591d52d77bf0ba27c35fc82dec9fa78d4"}, + {file = "matplotlib-3.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8b5a1bf27d078453aa7b5b27f52580e16360d02df6d3dc9504f3d2ce11f6309"}, + {file = "matplotlib-3.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f25ffb6ad972cdffa7df8e5be4b1e3cadd2f8d43fc72085feb1518006178394"}, + {file = "matplotlib-3.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee482731c8c17d86d9ddb5194d38621f9b0f0d53c99006275a12523ab021732"}, + {file = "matplotlib-3.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36eafe2128772195b373e1242df28d1b7ec6c04c15b090b8d9e335d55a323900"}, + {file = "matplotlib-3.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:061ee58facb3580cd2d046a6d227fb77e9295599c5ec6ad069f06b5821ad1cfc"}, + {file = "matplotlib-3.8.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3cc3776836d0f4f22654a7f2d2ec2004618d5cf86b7185318381f73b80fd8a2d"}, + {file = "matplotlib-3.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6c49a2bd6981264bddcb8c317b6bd25febcece9e2ebfcbc34e7f4c0c867c09dc"}, + {file = "matplotlib-3.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ed11654fc83cd6cfdf6170b453e437674a050a452133a064d47f2f1371f8d3"}, + {file = "matplotlib-3.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dae97fdd6996b3a25da8ee43e3fc734fff502f396801063c6b76c20b56683196"}, + {file = "matplotlib-3.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:87df75f528020a6299f76a1d986c0ed4406e3b2bd44bc5e306e46bca7d45e53e"}, + {file = "matplotlib-3.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:90d74a95fe055f73a6cd737beecc1b81c26f2893b7a3751d52b53ff06ca53f36"}, + {file = "matplotlib-3.8.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c3499c312f5def8f362a2bf761d04fa2d452b333f3a9a3f58805273719bf20d9"}, + {file = "matplotlib-3.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31e793c8bd4ea268cc5d3a695c27b30650ec35238626961d73085d5e94b6ab68"}, + {file = "matplotlib-3.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d5ee602ef517a89d1f2c508ca189cfc395dd0b4a08284fb1b97a78eec354644"}, + {file = "matplotlib-3.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5de39dc61ca35342cf409e031f70f18219f2c48380d3886c1cf5ad9f17898e06"}, + {file = "matplotlib-3.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dd386c80a98b5f51571b9484bf6c6976de383cd2a8cd972b6a9562d85c6d2087"}, + {file = "matplotlib-3.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:f691b4ef47c7384d0936b2e8ebdeb5d526c81d004ad9403dfb9d4c76b9979a93"}, + {file = "matplotlib-3.8.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0b11f354aae62a2aa53ec5bb09946f5f06fc41793e351a04ff60223ea9162955"}, + {file = "matplotlib-3.8.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f54b9fb87ca5acbcdd0f286021bedc162e1425fa5555ebf3b3dfc167b955ad9"}, + {file = "matplotlib-3.8.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:60a6e04dfd77c0d3bcfee61c3cd335fff1b917c2f303b32524cd1235e194ef99"}, + {file = "matplotlib-3.8.0.tar.gz", hash = "sha256:df8505e1c19d5c2c26aff3497a7cbd3ccfc2e97043d1e4db3e76afa399164b69"}, ] [package.dependencies] @@ -2662,11 +2646,12 @@ contourpy = ">=1.0.1" cycler = ">=0.10" fonttools = ">=4.22.0" kiwisolver = ">=1.0.1" -numpy = ">=1.20" +numpy = ">=1.21,<2" packaging = ">=20.0" pillow = ">=6.2.0" -pyparsing = ">=2.3.1,<3.1" +pyparsing = ">=2.3.1" python-dateutil = ">=2.7" +setuptools_scm = ">=7" [[package]] name = "matplotlib-inline" @@ -3380,68 +3365,66 @@ files = [ [[package]] name = "pillow" -version = "10.0.0" +version = "10.0.1" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "Pillow-10.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891"}, - {file = "Pillow-10.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf"}, - {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3"}, - {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992"}, - {file = "Pillow-10.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de"}, - {file = "Pillow-10.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485"}, - {file = "Pillow-10.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629"}, - {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538"}, - {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d"}, - {file = "Pillow-10.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f"}, - {file = "Pillow-10.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37"}, - {file = "Pillow-10.0.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883"}, - {file = "Pillow-10.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff"}, - {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551"}, - {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5"}, - {file = "Pillow-10.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199"}, - {file = "Pillow-10.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca"}, - {file = "Pillow-10.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3"}, - {file = "Pillow-10.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51"}, - {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86"}, - {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7"}, - {file = "Pillow-10.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0"}, - {file = "Pillow-10.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa"}, - {file = "Pillow-10.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba"}, - {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3"}, - {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017"}, - {file = "Pillow-10.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3"}, - {file = "Pillow-10.0.0.tar.gz", hash = "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396"}, + {file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"}, + {file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff"}, + {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf"}, + {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd"}, + {file = "Pillow-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0"}, + {file = "Pillow-10.0.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1"}, + {file = "Pillow-10.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2"}, + {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b"}, + {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1"}, + {file = "Pillow-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088"}, + {file = "Pillow-10.0.1-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b"}, + {file = "Pillow-10.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91"}, + {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4"}, + {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08"}, + {file = "Pillow-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08"}, + {file = "Pillow-10.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a"}, + {file = "Pillow-10.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7"}, + {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a"}, + {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7"}, + {file = "Pillow-10.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3"}, + {file = "Pillow-10.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849"}, + {file = "Pillow-10.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145"}, + {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2"}, + {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf"}, + {file = "Pillow-10.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d"}, + {file = "Pillow-10.0.1.tar.gz", hash = "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d"}, ] [package.extras] @@ -3560,14 +3543,14 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co [[package]] name = "plotly" -version = "5.16.1" +version = "5.17.0" description = "An open-source, interactive data visualization library for Python" category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "plotly-5.16.1-py2.py3-none-any.whl", hash = "sha256:19cc34f339acd4e624177806c14df22f388f23fb70658b03aad959a0e650a0dc"}, - {file = "plotly-5.16.1.tar.gz", hash = "sha256:295ac25edeb18c893abb71dcadcea075b78fd6fdf07cee4217a4e1009667925b"}, + {file = "plotly-5.17.0-py2.py3-none-any.whl", hash = "sha256:7c84cdf11da162423da957bb093287134f2d6f170eb9a74f1459f825892247c3"}, + {file = "plotly-5.17.0.tar.gz", hash = "sha256:290d796bf7bab87aad184fe24b86096234c4c95dcca6ecbca02d02bdf17d3d97"}, ] [package.dependencies] @@ -4057,14 +4040,14 @@ zstd = ["zstandard"] [[package]] name = "pyparsing" -version = "3.0.9" +version = "3.1.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "main" optional = false python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, ] [package.extras] @@ -4692,136 +4675,136 @@ notebook = ">=6.0" [[package]] name = "rpds-py" -version = "0.10.2" +version = "0.10.3" description = "Python bindings to Rust's persistent data structures (rpds)" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.10.2-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:9f00d54b18dd837f1431d66b076737deb7c29ce3ebb8412ceaf44d5e1954ac0c"}, - {file = "rpds_py-0.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f4d561f4728f825e3b793a53064b606ca0b6fc264f67d09e54af452aafc5b82"}, - {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:013d6c784150d10236a74b4094a79d96a256b814457e388fc5a4ba9efe24c402"}, - {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd1142d22fdb183a0fff66d79134bf644401437fed874f81066d314c67ee193c"}, - {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a0536ed2b9297c75104e1a3da330828ba1b2639fa53b38d396f98bf7e3c68df"}, - {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:41bd430b7b63aa802c02964e331ac0b177148fef5f807d2c90d05ce71a52b4d4"}, - {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e8474f7233fe1949ce4e03bea698a600c2d5d6b51dab6d6e6336dbe69acf23e"}, - {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d9d7efaad48b859053b90dedd69bc92f2095084251e732e4c57ac9726bcb1e64"}, - {file = "rpds_py-0.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5612b0b1de8d5114520094bd5fc3d04eb8af6f3e10d48ef05b7c8e77c1fd9545"}, - {file = "rpds_py-0.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5d5eaf988951f6ecb6854ca3300b87123599c711183c83da7ce39717a7cbdbce"}, - {file = "rpds_py-0.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:75c8766734ac0053e1d683567e65e85306c4ec62631b0591caeb287ac8f72e08"}, - {file = "rpds_py-0.10.2-cp310-none-win32.whl", hash = "sha256:8de9b88f0cbac73cfed34220d13c57849e62a7099a714b929142425e926d223a"}, - {file = "rpds_py-0.10.2-cp310-none-win_amd64.whl", hash = "sha256:2275f1a022e2383da5d2d101fe11ccdcbae799148c4b83260a4b9309fa3e1fc2"}, - {file = "rpds_py-0.10.2-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dd91a7d7a9ce7f4983097c91ce211f3e5569cc21caa16f2692298a07e396f82b"}, - {file = "rpds_py-0.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e82b4a70cc67094f3f3fd77579702f48fcf1de7bdc67d79b8f1e24d089a6162c"}, - {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e281b71922208e00886e4b7ffbfcf27874486364f177418ab676f102130e7ec9"}, - {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3eb1a0d2b6d232d1bcdfc3fcc5f7b004ab3fbd9203011a3172f051d4527c0b6"}, - {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02945ae38fd78efc40900f509890de84cfd5ffe2cd2939eeb3a8800dc68b87cb"}, - {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccfb77f6dc8abffa6f1c7e3975ed9070a41ce5fcc11154d2bead8c1baa940f09"}, - {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af52078719209bef33e38131486fd784832dd8d1dc9b85f00a44f6e7437dd021"}, - {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56ba7c1100ed079527f2b995bf5486a2e557e6d5b733c52e8947476338815b69"}, - {file = "rpds_py-0.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:899b03a3be785a7e1ff84b237da71f0efa2f021512f147dd34ffdf7aa82cb678"}, - {file = "rpds_py-0.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22e6de18f00583f06928cc8d0993104ecc62f7c6da6478db2255de89a30e45d1"}, - {file = "rpds_py-0.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:edd74b760a6bb950397e7a7bd2f38e6700f6525062650b1d77c6d851b82f02c2"}, - {file = "rpds_py-0.10.2-cp311-none-win32.whl", hash = "sha256:18909093944727e068ebfc92e2e6ed1c4fa44135507c1c0555213ce211c53214"}, - {file = "rpds_py-0.10.2-cp311-none-win_amd64.whl", hash = "sha256:9568764e72d85cf7855ca78b48e07ed1be47bf230e2cea8dabda3c95f660b0ff"}, - {file = "rpds_py-0.10.2-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:0fc625059b83695fbb4fc8b7a8b66fa94ff9c7b78c84fb9986cd53ff88a28d80"}, - {file = "rpds_py-0.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c86231c66e4f422e7c13ea6200bb4048b3016c8bfd11b4fd0dabd04d2c8e3501"}, - {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56777c57246e048908b550af9b81b0ec9cf804fd47cb7502ccd93238bd6025c2"}, - {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4cb372e22e9c879bd9a9cc9b20b7c1fbf30a605ac953da45ecec05d8a6e1c77"}, - {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa3b3a43dabc4cc57a7800f526cbe03f71c69121e21b863fdf497b59b462b163"}, - {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d222086daa55421d599609b32d0ebe544e57654c4a0a1490c54a7ebaa67561"}, - {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:529aab727f54a937085184e7436e1d0e19975cf10115eda12d37a683e4ee5342"}, - {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e9b1531d6a898bdf086acb75c41265c7ec4331267d7619148d407efc72bd24"}, - {file = "rpds_py-0.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c2772bb95062e3f9774140205cd65d8997e39620715486cf5f843cf4ad8f744c"}, - {file = "rpds_py-0.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ba1b28e44f611f3f2b436bd8290050a61db4b59a8e24be4465f44897936b3824"}, - {file = "rpds_py-0.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5aba767e64b494483ad60c4873bec78d16205a21f8247c99749bd990d9c846c2"}, - {file = "rpds_py-0.10.2-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:e1954f4b239d1a92081647eecfd51cbfd08ea16eb743b8af1cd0113258feea14"}, - {file = "rpds_py-0.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:de4a2fd524993578fe093044f291b4b24aab134390030b3b9b5f87fd41ab7e75"}, - {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e69737bd56006a86fd5a78b2b85447580a6138c930a75eb9ef39fe03d90782b1"}, - {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f40abbcc0a7d9a8a80870af839d317e6932533f98682aabd977add6c53beeb23"}, - {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29ec8507664f94cc08457d98cfc41c3cdbddfa8952438e644177a29b04937876"}, - {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcde80aefe7054fad6277762fb7e9d35c72ea479a485ae1bb14629c640987b30"}, - {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a65de5c02884760a14a58304fb6303f9ddfc582e630f385daea871e1bdb18686"}, - {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e92e5817eb6bfed23aa5e45bfe30647b83602bdd6f9e25d63524d4e6258458b0"}, - {file = "rpds_py-0.10.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2c8fc6c841ada60a86d29c9ebe2e8757c47eda6553f3596c560e59ca6e9b6fa1"}, - {file = "rpds_py-0.10.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:8557c807388e6617161fe51b1a4747ea8d1133f2d2ad8e79583439abebe58fbd"}, - {file = "rpds_py-0.10.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:00e97d43a36811b78fa9ad9d3329bf34f76a31e891a7031a2ac01450c9b168ab"}, - {file = "rpds_py-0.10.2-cp38-none-win32.whl", hash = "sha256:1ed3d5385d14be894e12a9033be989e012214a9811e7194849c94032ad69682a"}, - {file = "rpds_py-0.10.2-cp38-none-win_amd64.whl", hash = "sha256:02b4a2e28eb24dac4ef43dda4f6a6f7766e355179b143f7d0c76a1c5488a307b"}, - {file = "rpds_py-0.10.2-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:2a55631b93e47956fbc97d69ba2054a8c6a4016f9a3064ec4e031f5f1030cb90"}, - {file = "rpds_py-0.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2ffbf1b38c88d0466de542e91b08225d51782282512f8e2b11715126c41fda48"}, - {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213f9ef5c02ec2f883c1075d25a873149daadbaea50d18d622e9db55ec9849c2"}, - {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b00150a9a3fd0a8efaa90bc2696c105b04039d50763dd1c95a34c88c5966cb57"}, - {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ab0f7aabdbce4a202e013083eeab71afdb85efa405dc4a06fea98cde81204675"}, - {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cd0c9fb5d40887500b4ed818770c68ab4fa6e0395d286f9704be6751b1b7d98"}, - {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8578fc6c8bdd0201327503720fa581000b4bd3934abbf07e2628d1ad3de157d"}, - {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d27d08056fcd61ff47a0cd8407eff4d3e816c82cb6b9c6f0ce9a0ad49225f81"}, - {file = "rpds_py-0.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c8f6526df47953b07c45b95c4d1da6b9a0861c0e5da0271db96bb1d807825412"}, - {file = "rpds_py-0.10.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177c033e467a66a054dd3a9534167234a3d0b2e41445807b13b626e01da25d92"}, - {file = "rpds_py-0.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c74cbee9e532dc34371127f7686d6953e5153a1f22beab7f953d95ee4a0fe09"}, - {file = "rpds_py-0.10.2-cp39-none-win32.whl", hash = "sha256:05a1382905026bdd560f806c8c7c16e0f3e3fb359ba8868203ca6e5799884968"}, - {file = "rpds_py-0.10.2-cp39-none-win_amd64.whl", hash = "sha256:3fd503c27e7b7034128e30847ecdb4bff4ca5e60f29ad022a9f66ae8940d54ac"}, - {file = "rpds_py-0.10.2-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4a96147791e49e84207dd1530109aa0e9eeaf1c8b7a59f150047fc0fcdf9bb64"}, - {file = "rpds_py-0.10.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:203eb1532d51591d32e8dfafd60b5d31347ea7278c8da02b4b550287f6abe28b"}, - {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2f416cdfe92f5fbb77177f5f3f7830059d1582db05f2c7119bf80069d1ab69b"}, - {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2660000e1a113869c86eb5cc07f3343467490f3cd9d0299f81da9ddae7137b7"}, - {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1adb04e4b4e41bf30aaa77eeb169c1b9ba9e5010e2e6ce8d6c17e1446edc9b68"}, - {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bca97521ee786087f0c5ef318fef3eef0266a9c3deff88205523cf353af7394"}, - {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4969592e3cdeefa4cbb15a26cec102cbd4a1d6e5b695fac9fa026e19741138c8"}, - {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df61f818edf7c8626bfa392f825860fb670b5f8336e238eb0ec7e2a5689cdded"}, - {file = "rpds_py-0.10.2-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:b589d93a60e78fe55d5bc76ee8c2bf945dbdbb7cd16044c53e0307604e448de1"}, - {file = "rpds_py-0.10.2-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:73da69e1f612c3e682e34dcb971272d90d6f27b2c99acff444ca455a89978574"}, - {file = "rpds_py-0.10.2-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:89438e8885a186c69fe31f7ef98bb2bf29688c466c3caf9060f404c0be89ae80"}, - {file = "rpds_py-0.10.2-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c4ecc4e9a5d73a816cae36ee6b5d8b7a0c72013cae1e101406e832887c3dc2d8"}, - {file = "rpds_py-0.10.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:907b214da5d2fcff0b6ddb83de1333890ca92abaf4bbf8d9c61dc1b95c87fd6e"}, - {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb44644371eaa29a3aba7b69b1862d0d56f073bb7585baa32e4271a71a91ee82"}, - {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:80c3cf46511653f94dfe07c7c79ab105c4164d6e1dfcb35b7214fb9af53eaef4"}, - {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaba0613c759ebf95988a84f766ca6b7432d55ce399194f95dde588ad1be0878"}, - {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0527c97dcd8bb983822ee31d3760187083fd3ba18ac4dd22cf5347c89d5628f4"}, - {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cdfd649011ce2d90cb0dd304c5aba1190fac0c266d19a9e2b96b81cfd150a09"}, - {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:75eea40355a8690459c7291ce6c8ce39c27bd223675c7da6619f510c728feb97"}, - {file = "rpds_py-0.10.2-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1b804cfad04f862d6a84af9d1ad941b06f671878f0f7ecad6c92007d423de6"}, - {file = "rpds_py-0.10.2-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:bf77f9017fcfa1232f98598a637406e6c33982ccba8a5922339575c3e2b90ea5"}, - {file = "rpds_py-0.10.2-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:46c4c550bf59ce05d6bff2c98053822549aaf9fbaf81103edea325e03350bca1"}, - {file = "rpds_py-0.10.2-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:46af4a742b90c7460e94214f923452c2c1d050a9da1d2b8d4c70cbc045e692b7"}, - {file = "rpds_py-0.10.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:2a86d246a160d98d820ee7d02dc18c923c228de095be362e57b9fd8970b2c4a1"}, - {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae141c9017f8f473a6ee07a9425da021816a9f8c0683c2e5442f0ccf56b0fc62"}, - {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1147bc3d0dd1e549d991110d0a09557ec9f925dbc1ca62871fcdab2ec9d716b"}, - {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fce7a8ee8d0f682c953c0188735d823f0fcb62779bf92cd6ba473a8e730e26ad"}, - {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c7f9d70f99e1fbcbf57c75328b80e1c0a7f6cad43e75efa90a97221be5efe15"}, - {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b309908b6ff5ffbf6394818cb73b5a2a74073acee2c57fe8719046389aeff0d"}, - {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ff1f585a0fdc1415bd733b804f33d386064a308672249b14828130dd43e7c31"}, - {file = "rpds_py-0.10.2-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:0188b580c490bccb031e9b67e9e8c695a3c44ac5e06218b152361eca847317c3"}, - {file = "rpds_py-0.10.2-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:abe081453166e206e3a8c6d8ace57214c17b6d9477d7601ac14a365344dbc1f4"}, - {file = "rpds_py-0.10.2-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9118de88c16947eaf5b92f749e65b0501ea69e7c2be7bd6aefc12551622360e1"}, - {file = "rpds_py-0.10.2.tar.gz", hash = "sha256:289073f68452b96e70990085324be7223944c7409973d13ddfe0eea1c1b5663b"}, + {file = "rpds_py-0.10.3-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:485747ee62da83366a44fbba963c5fe017860ad408ccd6cd99aa66ea80d32b2e"}, + {file = "rpds_py-0.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c55f9821f88e8bee4b7a72c82cfb5ecd22b6aad04033334f33c329b29bfa4da0"}, + {file = "rpds_py-0.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3b52a67ac66a3a64a7e710ba629f62d1e26ca0504c29ee8cbd99b97df7079a8"}, + {file = "rpds_py-0.10.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3aed39db2f0ace76faa94f465d4234aac72e2f32b009f15da6492a561b3bbebd"}, + {file = "rpds_py-0.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:271c360fdc464fe6a75f13ea0c08ddf71a321f4c55fc20a3fe62ea3ef09df7d9"}, + {file = "rpds_py-0.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef5fddfb264e89c435be4adb3953cef5d2936fdeb4463b4161a6ba2f22e7b740"}, + {file = "rpds_py-0.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a771417c9c06c56c9d53d11a5b084d1de75de82978e23c544270ab25e7c066ff"}, + {file = "rpds_py-0.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:52b5cbc0469328e58180021138207e6ec91d7ca2e037d3549cc9e34e2187330a"}, + {file = "rpds_py-0.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6ac3fefb0d168c7c6cab24fdfc80ec62cd2b4dfd9e65b84bdceb1cb01d385c33"}, + {file = "rpds_py-0.10.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8d54bbdf5d56e2c8cf81a1857250f3ea132de77af543d0ba5dce667183b61fec"}, + {file = "rpds_py-0.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cd2163f42868865597d89399a01aa33b7594ce8e2c4a28503127c81a2f17784e"}, + {file = "rpds_py-0.10.3-cp310-none-win32.whl", hash = "sha256:ea93163472db26ac6043e8f7f93a05d9b59e0505c760da2a3cd22c7dd7111391"}, + {file = "rpds_py-0.10.3-cp310-none-win_amd64.whl", hash = "sha256:7cd020b1fb41e3ab7716d4d2c3972d4588fdfbab9bfbbb64acc7078eccef8860"}, + {file = "rpds_py-0.10.3-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:1d9b5ee46dcb498fa3e46d4dfabcb531e1f2e76b477e0d99ef114f17bbd38453"}, + {file = "rpds_py-0.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:563646d74a4b4456d0cf3b714ca522e725243c603e8254ad85c3b59b7c0c4bf0"}, + {file = "rpds_py-0.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e626b864725680cd3904414d72e7b0bd81c0e5b2b53a5b30b4273034253bb41f"}, + {file = "rpds_py-0.10.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485301ee56ce87a51ccb182a4b180d852c5cb2b3cb3a82f7d4714b4141119d8c"}, + {file = "rpds_py-0.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42f712b4668831c0cd85e0a5b5a308700fe068e37dcd24c0062904c4e372b093"}, + {file = "rpds_py-0.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c9141af27a4e5819d74d67d227d5047a20fa3c7d4d9df43037a955b4c748ec5"}, + {file = "rpds_py-0.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef750a20de1b65657a1425f77c525b0183eac63fe7b8f5ac0dd16f3668d3e64f"}, + {file = "rpds_py-0.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e1a0ffc39f51aa5f5c22114a8f1906b3c17eba68c5babb86c5f77d8b1bba14d1"}, + {file = "rpds_py-0.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f4c179a7aeae10ddf44c6bac87938134c1379c49c884529f090f9bf05566c836"}, + {file = "rpds_py-0.10.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:176287bb998fd1e9846a9b666e240e58f8d3373e3bf87e7642f15af5405187b8"}, + {file = "rpds_py-0.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6446002739ca29249f0beaaf067fcbc2b5aab4bc7ee8fb941bd194947ce19aff"}, + {file = "rpds_py-0.10.3-cp311-none-win32.whl", hash = "sha256:c7aed97f2e676561416c927b063802c8a6285e9b55e1b83213dfd99a8f4f9e48"}, + {file = "rpds_py-0.10.3-cp311-none-win_amd64.whl", hash = "sha256:8bd01ff4032abaed03f2db702fa9a61078bee37add0bd884a6190b05e63b028c"}, + {file = "rpds_py-0.10.3-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:4cf0855a842c5b5c391dd32ca273b09e86abf8367572073bd1edfc52bc44446b"}, + {file = "rpds_py-0.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69b857a7d8bd4f5d6e0db4086da8c46309a26e8cefdfc778c0c5cc17d4b11e08"}, + {file = "rpds_py-0.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:975382d9aa90dc59253d6a83a5ca72e07f4ada3ae3d6c0575ced513db322b8ec"}, + {file = "rpds_py-0.10.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:35fbd23c1c8732cde7a94abe7fb071ec173c2f58c0bd0d7e5b669fdfc80a2c7b"}, + {file = "rpds_py-0.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:106af1653007cc569d5fbb5f08c6648a49fe4de74c2df814e234e282ebc06957"}, + {file = "rpds_py-0.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce5e7504db95b76fc89055c7f41e367eaadef5b1d059e27e1d6eabf2b55ca314"}, + {file = "rpds_py-0.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aca759ada6b1967fcfd4336dcf460d02a8a23e6abe06e90ea7881e5c22c4de6"}, + {file = "rpds_py-0.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b5d4bdd697195f3876d134101c40c7d06d46c6ab25159ed5cbd44105c715278a"}, + {file = "rpds_py-0.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a657250807b6efd19b28f5922520ae002a54cb43c2401e6f3d0230c352564d25"}, + {file = "rpds_py-0.10.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:177c9dd834cdf4dc39c27436ade6fdf9fe81484758885f2d616d5d03c0a83bd2"}, + {file = "rpds_py-0.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e22491d25f97199fc3581ad8dd8ce198d8c8fdb8dae80dea3512e1ce6d5fa99f"}, + {file = "rpds_py-0.10.3-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:2f3e1867dd574014253b4b8f01ba443b9c914e61d45f3674e452a915d6e929a3"}, + {file = "rpds_py-0.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c22211c165166de6683de8136229721f3d5c8606cc2c3d1562da9a3a5058049c"}, + {file = "rpds_py-0.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40bc802a696887b14c002edd43c18082cb7b6f9ee8b838239b03b56574d97f71"}, + {file = "rpds_py-0.10.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e271dd97c7bb8eefda5cca38cd0b0373a1fea50f71e8071376b46968582af9b"}, + {file = "rpds_py-0.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:95cde244e7195b2c07ec9b73fa4c5026d4a27233451485caa1cd0c1b55f26dbd"}, + {file = "rpds_py-0.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08a80cf4884920863623a9ee9a285ee04cef57ebedc1cc87b3e3e0f24c8acfe5"}, + {file = "rpds_py-0.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763ad59e105fca09705d9f9b29ecffb95ecdc3b0363be3bb56081b2c6de7977a"}, + {file = "rpds_py-0.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:187700668c018a7e76e89424b7c1042f317c8df9161f00c0c903c82b0a8cac5c"}, + {file = "rpds_py-0.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5267cfda873ad62591b9332fd9472d2409f7cf02a34a9c9cb367e2c0255994bf"}, + {file = "rpds_py-0.10.3-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:2ed83d53a8c5902ec48b90b2ac045e28e1698c0bea9441af9409fc844dc79496"}, + {file = "rpds_py-0.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:255f1a10ae39b52122cce26ce0781f7a616f502feecce9e616976f6a87992d6b"}, + {file = "rpds_py-0.10.3-cp38-none-win32.whl", hash = "sha256:a019a344312d0b1f429c00d49c3be62fa273d4a1094e1b224f403716b6d03be1"}, + {file = "rpds_py-0.10.3-cp38-none-win_amd64.whl", hash = "sha256:efb9ece97e696bb56e31166a9dd7919f8f0c6b31967b454718c6509f29ef6fee"}, + {file = "rpds_py-0.10.3-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:570cc326e78ff23dec7f41487aa9c3dffd02e5ee9ab43a8f6ccc3df8f9327623"}, + {file = "rpds_py-0.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cff7351c251c7546407827b6a37bcef6416304fc54d12d44dbfecbb717064717"}, + {file = "rpds_py-0.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:177914f81f66c86c012311f8c7f46887ec375cfcfd2a2f28233a3053ac93a569"}, + {file = "rpds_py-0.10.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:448a66b8266de0b581246ca7cd6a73b8d98d15100fb7165974535fa3b577340e"}, + {file = "rpds_py-0.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bbac1953c17252f9cc675bb19372444aadf0179b5df575ac4b56faaec9f6294"}, + {file = "rpds_py-0.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dd9d9d9e898b9d30683bdd2b6c1849449158647d1049a125879cb397ee9cd12"}, + {file = "rpds_py-0.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8c71ea77536149e36c4c784f6d420ffd20bea041e3ba21ed021cb40ce58e2c9"}, + {file = "rpds_py-0.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16a472300bc6c83fe4c2072cc22b3972f90d718d56f241adabc7ae509f53f154"}, + {file = "rpds_py-0.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9255e7165083de7c1d605e818025e8860636348f34a79d84ec533546064f07e"}, + {file = "rpds_py-0.10.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:53d7a3cd46cdc1689296348cb05ffd4f4280035770aee0c8ead3bbd4d6529acc"}, + {file = "rpds_py-0.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22da15b902f9f8e267020d1c8bcfc4831ca646fecb60254f7bc71763569f56b1"}, + {file = "rpds_py-0.10.3-cp39-none-win32.whl", hash = "sha256:850c272e0e0d1a5c5d73b1b7871b0a7c2446b304cec55ccdb3eaac0d792bb065"}, + {file = "rpds_py-0.10.3-cp39-none-win_amd64.whl", hash = "sha256:de61e424062173b4f70eec07e12469edde7e17fa180019a2a0d75c13a5c5dc57"}, + {file = "rpds_py-0.10.3-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:af247fd4f12cca4129c1b82090244ea5a9d5bb089e9a82feb5a2f7c6a9fe181d"}, + {file = "rpds_py-0.10.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3ad59efe24a4d54c2742929001f2d02803aafc15d6d781c21379e3f7f66ec842"}, + {file = "rpds_py-0.10.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642ed0a209ced4be3a46f8cb094f2d76f1f479e2a1ceca6de6346a096cd3409d"}, + {file = "rpds_py-0.10.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37d0c59548ae56fae01c14998918d04ee0d5d3277363c10208eef8c4e2b68ed6"}, + {file = "rpds_py-0.10.3-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad6ed9e70ddfb34d849b761fb243be58c735be6a9265b9060d6ddb77751e3e8"}, + {file = "rpds_py-0.10.3-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8f94fdd756ba1f79f988855d948ae0bad9ddf44df296770d9a58c774cfbcca72"}, + {file = "rpds_py-0.10.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77076bdc8776a2b029e1e6ffbe6d7056e35f56f5e80d9dc0bad26ad4a024a762"}, + {file = "rpds_py-0.10.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:87d9b206b1bd7a0523375dc2020a6ce88bca5330682ae2fe25e86fd5d45cea9c"}, + {file = "rpds_py-0.10.3-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8efaeb08ede95066da3a3e3c420fcc0a21693fcd0c4396d0585b019613d28515"}, + {file = "rpds_py-0.10.3-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:a4d9bfda3f84fc563868fe25ca160c8ff0e69bc4443c5647f960d59400ce6557"}, + {file = "rpds_py-0.10.3-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:d27aa6bbc1f33be920bb7adbb95581452cdf23005d5611b29a12bb6a3468cc95"}, + {file = "rpds_py-0.10.3-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ed8313809571a5463fd7db43aaca68ecb43ca7a58f5b23b6e6c6c5d02bdc7882"}, + {file = "rpds_py-0.10.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:e10e6a1ed2b8661201e79dff5531f8ad4cdd83548a0f81c95cf79b3184b20c33"}, + {file = "rpds_py-0.10.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:015de2ce2af1586ff5dc873e804434185199a15f7d96920ce67e50604592cae9"}, + {file = "rpds_py-0.10.3-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae87137951bb3dc08c7d8bfb8988d8c119f3230731b08a71146e84aaa919a7a9"}, + {file = "rpds_py-0.10.3-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0bb4f48bd0dd18eebe826395e6a48b7331291078a879295bae4e5d053be50d4c"}, + {file = "rpds_py-0.10.3-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09362f86ec201288d5687d1dc476b07bf39c08478cde837cb710b302864e7ec9"}, + {file = "rpds_py-0.10.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821392559d37759caa67d622d0d2994c7a3f2fb29274948ac799d496d92bca73"}, + {file = "rpds_py-0.10.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7170cbde4070dc3c77dec82abf86f3b210633d4f89550fa0ad2d4b549a05572a"}, + {file = "rpds_py-0.10.3-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:5de11c041486681ce854c814844f4ce3282b6ea1656faae19208ebe09d31c5b8"}, + {file = "rpds_py-0.10.3-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:4ed172d0c79f156c1b954e99c03bc2e3033c17efce8dd1a7c781bc4d5793dfac"}, + {file = "rpds_py-0.10.3-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:11fdd1192240dda8d6c5d18a06146e9045cb7e3ba7c06de6973000ff035df7c6"}, + {file = "rpds_py-0.10.3-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:f602881d80ee4228a2355c68da6b296a296cd22bbb91e5418d54577bbf17fa7c"}, + {file = "rpds_py-0.10.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:691d50c99a937709ac4c4cd570d959a006bd6a6d970a484c84cc99543d4a5bbb"}, + {file = "rpds_py-0.10.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24cd91a03543a0f8d09cb18d1cb27df80a84b5553d2bd94cba5979ef6af5c6e7"}, + {file = "rpds_py-0.10.3-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc2200e79d75b5238c8d69f6a30f8284290c777039d331e7340b6c17cad24a5a"}, + {file = "rpds_py-0.10.3-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea65b59882d5fa8c74a23f8960db579e5e341534934f43f3b18ec1839b893e41"}, + {file = "rpds_py-0.10.3-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:829e91f3a8574888b73e7a3feb3b1af698e717513597e23136ff4eba0bc8387a"}, + {file = "rpds_py-0.10.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eab75a8569a095f2ad470b342f2751d9902f7944704f0571c8af46bede438475"}, + {file = "rpds_py-0.10.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:061c3ff1f51ecec256e916cf71cc01f9975af8fb3af9b94d3c0cc8702cfea637"}, + {file = "rpds_py-0.10.3-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:39d05e65f23a0fe897b6ac395f2a8d48c56ac0f583f5d663e0afec1da89b95da"}, + {file = "rpds_py-0.10.3-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:4eca20917a06d2fca7628ef3c8b94a8c358f6b43f1a621c9815243462dcccf97"}, + {file = "rpds_py-0.10.3-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e8d0f0eca087630d58b8c662085529781fd5dc80f0a54eda42d5c9029f812599"}, + {file = "rpds_py-0.10.3.tar.gz", hash = "sha256:fcc1ebb7561a3e24a6588f7c6ded15d80aec22c66a070c757559b57b17ffd1cb"}, ] [[package]] name = "ruff" -version = "0.0.287" +version = "0.0.289" description = "An extremely fast Python linter, written in Rust." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.287-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:1e0f9ee4c3191444eefeda97d7084721d9b8e29017f67997a20c153457f2eafd"}, - {file = "ruff-0.0.287-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:e9843e5704d4fb44e1a8161b0d31c1a38819723f0942639dfeb53d553be9bfb5"}, - {file = "ruff-0.0.287-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca1ed11d759a29695aed2bfc7f914b39bcadfe2ef08d98ff69c873f639ad3a8"}, - {file = "ruff-0.0.287-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1cf4d5ad3073af10f186ea22ce24bc5a8afa46151f6896f35c586e40148ba20b"}, - {file = "ruff-0.0.287-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d9d58bcb29afd72d2afe67120afcc7d240efc69a235853813ad556443dc922"}, - {file = "ruff-0.0.287-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:06ac5df7dd3ba8bf83bba1490a72f97f1b9b21c7cbcba8406a09de1a83f36083"}, - {file = "ruff-0.0.287-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2bfb478e1146a60aa740ab9ebe448b1f9e3c0dfb54be3cc58713310eef059c30"}, - {file = "ruff-0.0.287-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00d579a011949108c4b4fa04c4f1ee066dab536a9ba94114e8e580c96be2aeb4"}, - {file = "ruff-0.0.287-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a810a79b8029cc92d06c36ea1f10be5298d2323d9024e1d21aedbf0a1a13e5"}, - {file = "ruff-0.0.287-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:150007028ad4976ce9a7704f635ead6d0e767f73354ce0137e3e44f3a6c0963b"}, - {file = "ruff-0.0.287-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a24a280db71b0fa2e0de0312b4aecb8e6d08081d1b0b3c641846a9af8e35b4a7"}, - {file = "ruff-0.0.287-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2918cb7885fa1611d542de1530bea3fbd63762da793751cc8c8d6e4ba234c3d8"}, - {file = "ruff-0.0.287-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:33d7b251afb60bec02a64572b0fd56594b1923ee77585bee1e7e1daf675e7ae7"}, - {file = "ruff-0.0.287-py3-none-win32.whl", hash = "sha256:022f8bed2dcb5e5429339b7c326155e968a06c42825912481e10be15dafb424b"}, - {file = "ruff-0.0.287-py3-none-win_amd64.whl", hash = "sha256:26bd0041d135a883bd6ab3e0b29c42470781fb504cf514e4c17e970e33411d90"}, - {file = "ruff-0.0.287-py3-none-win_arm64.whl", hash = "sha256:44bceb3310ac04f0e59d4851e6227f7b1404f753997c7859192e41dbee9f5c8d"}, - {file = "ruff-0.0.287.tar.gz", hash = "sha256:02dc4f5bf53ef136e459d467f3ce3e04844d509bc46c025a05b018feb37bbc39"}, + {file = "ruff-0.0.289-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:c9a89d748e90c840bac9c37afe90cf13a5bfd460ca02ea93dad9d7bee3af03b4"}, + {file = "ruff-0.0.289-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:7f7396c6ea01ba332a6ad9d47642bac25d16bd2076aaa595b001f58b2f32ff05"}, + {file = "ruff-0.0.289-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7180de86c8ecd39624dec1699136f941c07e723201b4ce979bec9e7c67b40ad2"}, + {file = "ruff-0.0.289-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:73f37c65508203dd01a539926375a10243769c20d4fcab3fa6359cd3fbfc54b7"}, + {file = "ruff-0.0.289-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c14abcd7563b5c80be2dd809eeab20e4aa716bf849860b60a22d87ddf19eb88"}, + {file = "ruff-0.0.289-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:91b6d63b6b46d4707916472c91baa87aa0592e73f62a80ff55efdf6c0668cfd6"}, + {file = "ruff-0.0.289-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6479b8c4be3c36046c6c92054762b276fa0fddb03f6b9a310fbbf4c4951267fd"}, + {file = "ruff-0.0.289-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5424318c254bcb091cb67e140ec9b9f7122074e100b06236f252923fb41e767"}, + {file = "ruff-0.0.289-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4daa90865796aedcedf0d8897fdd4cd09bf0ddd3504529a4ccf211edcaff3c7d"}, + {file = "ruff-0.0.289-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8057e8ab0016c13b9419bad119e854f881e687bd96bc5e2d52c8baac0f278a44"}, + {file = "ruff-0.0.289-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7eebfab2e6a6991908ff1bf82f2dc1e5095fc7e316848e62124526837b445f4d"}, + {file = "ruff-0.0.289-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ebc7af550018001a7fb39ca22cdce20e1a0de4388ea4a007eb5c822f6188c297"}, + {file = "ruff-0.0.289-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6e4e6eccb753efe760ba354fc8e9f783f6bba71aa9f592756f5bd0d78db898ed"}, + {file = "ruff-0.0.289-py3-none-win32.whl", hash = "sha256:bbb3044f931c09cf17dbe5b339896eece0d6ac10c9a86e172540fcdb1974f2b7"}, + {file = "ruff-0.0.289-py3-none-win_amd64.whl", hash = "sha256:6d043c5456b792be2615a52f16056c3cf6c40506ce1f2d6f9d3083cfcb9eeab6"}, + {file = "ruff-0.0.289-py3-none-win_arm64.whl", hash = "sha256:04a720bcca5e987426bb14ad8b9c6f55e259ea774da1cbeafe71569744cfd20a"}, + {file = "ruff-0.0.289.tar.gz", hash = "sha256:2513f853b0fc42f0339b7ab0d2751b63ce7a50a0032d2689b54b2931b3b866d7"}, ] [[package]] @@ -4894,14 +4877,14 @@ win32 = ["pywin32"] [[package]] name = "setuptools" -version = "68.2.1" +version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-68.2.1-py3-none-any.whl", hash = "sha256:eff96148eb336377ab11beee0c73ed84f1709a40c0b870298b0d058828761bae"}, - {file = "setuptools-68.2.1.tar.gz", hash = "sha256:56ee14884fd8d0cd015411f4a13f40b4356775a0aefd9ebc1d3bfb9a1acb32f1"}, + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] [package.extras] @@ -4909,6 +4892,27 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +[[package]] +name = "setuptools-scm" +version = "7.1.0" +description = "the blessed package to manage your versions by scm tags" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools_scm-7.1.0-py3-none-any.whl", hash = "sha256:73988b6d848709e2af142aa48c986ea29592bbcfca5375678064708205253d8e"}, + {file = "setuptools_scm-7.1.0.tar.gz", hash = "sha256:6c508345a771aad7d56ebff0e70628bf2b0ec7573762be9960214730de278f27"}, +] + +[package.dependencies] +packaging = ">=20.0" +setuptools = "*" +typing-extensions = "*" + +[package.extras] +test = ["pytest (>=6.2)", "virtualenv (>20)"] +toml = ["setuptools (>=42)"] + [[package]] name = "shapely" version = "1.8.5.post1" @@ -5550,19 +5554,19 @@ telegram = ["requests"] [[package]] name = "traitlets" -version = "5.9.0" +version = "5.10.0" description = "Traitlets Python configuration system" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, - {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, + {file = "traitlets-5.10.0-py3-none-any.whl", hash = "sha256:417745a96681fbb358e723d5346a547521f36e9bd0d50ba7ab368fff5d67aa54"}, + {file = "traitlets-5.10.0.tar.gz", hash = "sha256:f584ea209240466e66e91f3c81aa7d004ba4cf794990b0c775938a1544217cd1"}, ] [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.5.1)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] [[package]] name = "trio" @@ -5867,14 +5871,14 @@ watchdog = ["watchdog"] [[package]] name = "widgetsnbextension" -version = "4.0.8" +version = "4.0.9" description = "Jupyter interactive widgets for Jupyter Notebook" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "widgetsnbextension-4.0.8-py3-none-any.whl", hash = "sha256:2e37f0ce9da11651056280c7efe96f2db052fe8fc269508e3724f5cbd6c93018"}, - {file = "widgetsnbextension-4.0.8.tar.gz", hash = "sha256:9ec291ba87c2dfad42c3d5b6f68713fa18be1acd7476569516b2431682315c17"}, + {file = "widgetsnbextension-4.0.9-py3-none-any.whl", hash = "sha256:91452ca8445beb805792f206e560c1769284267a30ceb1cec9f5bcc887d15175"}, + {file = "widgetsnbextension-4.0.9.tar.gz", hash = "sha256:3c1f5e46dc1166dfd40a42d685e6a51396fd34ff878742a3e47c6f0cc4a2a385"}, ] [[package]] @@ -5913,4 +5917,4 @@ ingest = ["selenium"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "8bb7730a3c0b9881f7d4cdadff6d1edadfec8e4244be0c530f072bd8ea17dc15" +content-hash = "07b56bcce6ab9ba9c417c9da908bcabeac7d10be0eb51883868f15482d495d70" diff --git a/pyproject.toml b/pyproject.toml index cb0c2be..fc09c87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,8 +55,8 @@ tqdm = "^4.66.1" ingest = ["selenium"] [tool.poetry.group.develop.dependencies] -black = {extras = ["jupyter"], version = "^23.9.0"} -jupyterlab = "^4.0.5" +black = {extras = ["jupyter"], version = "^23.9.1"} +jupyterlab = "^4.0.6" nbconvert = "^7.8.0" openpyxl = "^3.1.2" pre-commit = "^3.4.0" @@ -75,13 +75,13 @@ sphinxcontrib-mermaid = "^0.9.2" sphinxcontrib-napoleon = "^0.7" [tool.poetry.group.lint.dependencies] -black = "^23.9.0" +black = "^23.9.1" loguru-mypy = "^0.0.4" mypy = "^1.5.1" -pandas-stubs = "^2.0.1.230501" +pandas-stubs = "^2.0.3.230814" pip-audit = "^2.6.1" pip-licenses = "^4.3.2" -ruff = "^0.0.287" +ruff = "^0.0.289" types-cachetools = "^5.3.0.6" types-pyOpenSSL = "*" types-requests = "^2.31.0.2" From 487b2f42d1474dad765b39944e20499cea296fa3 Mon Sep 17 00:00:00 2001 From: KM-R <129882581+KM-R@users.noreply.github.com> Date: Tue, 19 Sep 2023 23:45:10 +0200 Subject: [PATCH 09/11] update data based on selected company (#122) Added UI elements to select a company and update shown data depending on chosen company --------- Co-authored-by: Philipp Horstenkamp --- .github/workflows/lint-actions.yaml | 2 +- .gitignore | 3 + .pre-commit-config.yaml | 2 +- .vscode/settings.json | 3 - poetry.lock | 465 ++++++------------ pyproject.toml | 4 +- .../ui/assets/company_stats.css | 69 +++ .../ui/assets/header.css | 58 +++ .../ui/assets/tabs.css | 23 + .../ui/company_finance_dash.py | 422 +++------------- .../ui/ui_elements.py | 331 +++++++++++++ .../utils/data_transfer.py | 2 +- tests/conftest.py | 64 ++- tests/ui/ui_elements_test.py | 118 +++++ tests/utils/data_transfer_test.py | 66 +-- 15 files changed, 900 insertions(+), 732 deletions(-) delete mode 100644 .vscode/settings.json create mode 100644 src/aki_prj23_transparenzregister/ui/assets/company_stats.css create mode 100644 src/aki_prj23_transparenzregister/ui/assets/header.css create mode 100644 src/aki_prj23_transparenzregister/ui/assets/tabs.css create mode 100644 src/aki_prj23_transparenzregister/ui/ui_elements.py create mode 100644 tests/ui/ui_elements_test.py diff --git a/.github/workflows/lint-actions.yaml b/.github/workflows/lint-actions.yaml index ffbed26..81cf648 100644 --- a/.github/workflows/lint-actions.yaml +++ b/.github/workflows/lint-actions.yaml @@ -47,7 +47,7 @@ jobs: - uses: actions/checkout@v3 - uses: chartboost/ruff-action@v1 with: - version: 0.0.289 + version: 0.0.290 python-requirements: name: Check Python Requirements diff --git a/.gitignore b/.gitignore index a5206be..d5f8fdc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ *secrets.json *secrets_prod.json +# Settings +.vscode/ + # Snyk .dccache diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f4f2f57..922d607 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.0.289 + rev: v0.0.290 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 7e6882b..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "files.eol": "\n" -} diff --git a/poetry.lock b/poetry.lock index ce01744..bd2f1c0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -16,7 +15,6 @@ files = [ name = "ansi2html" version = "1.8.0" description = "" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -32,7 +30,6 @@ test = ["pytest", "pytest-cov"] name = "anyio" version = "4.0.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -53,7 +50,6 @@ trio = ["trio (>=0.22)"] name = "appnope" version = "0.1.3" description = "Disable App Nap on macOS >= 10.9" -category = "dev" optional = false python-versions = "*" files = [ @@ -65,7 +61,6 @@ files = [ name = "argon2-cffi" version = "23.1.0" description = "Argon2 for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -86,7 +81,6 @@ typing = ["mypy"] name = "argon2-cffi-bindings" version = "21.2.0" description = "Low-level CFFI bindings for Argon2" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -124,7 +118,6 @@ tests = ["pytest"] name = "arrow" version = "1.2.3" description = "Better dates & times for Python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -139,7 +132,6 @@ python-dateutil = ">=2.7.0" name = "asttokens" version = "2.4.0" description = "Annotate AST trees with source code positions" -category = "dev" optional = false python-versions = "*" files = [ @@ -157,7 +149,6 @@ test = ["astroid", "pytest"] name = "async-lru" version = "2.0.4" description = "Simple LRU cache for asyncio" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -169,7 +160,6 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -188,7 +178,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "babel" version = "2.12.1" description = "Internationalization utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -200,7 +189,6 @@ files = [ name = "backcall" version = "0.2.0" description = "Specifications for callback functions passed in to an API" -category = "dev" optional = false python-versions = "*" files = [ @@ -212,7 +200,6 @@ files = [ name = "beautifulsoup4" version = "4.12.2" description = "Screen-scraping library" -category = "main" optional = false python-versions = ">=3.6.0" files = [ @@ -231,7 +218,6 @@ lxml = ["lxml"] name = "black" version = "23.9.1" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -278,7 +264,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "bleach" version = "6.0.0" description = "An easy safelist-based HTML-sanitizing tool." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -297,7 +282,6 @@ css = ["tinycss2 (>=1.1.0,<1.2)"] name = "boolean-py" version = "4.0" description = "Define boolean algebras, create and parse boolean expressions and create custom boolean DSL." -category = "dev" optional = false python-versions = "*" files = [ @@ -309,7 +293,6 @@ files = [ name = "cachecontrol" version = "0.13.1" description = "httplib2 caching for requests" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -331,7 +314,6 @@ redis = ["redis (>=2.10.5)"] name = "cachetools" version = "5.3.1" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -343,7 +325,6 @@ files = [ name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -355,7 +336,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -432,7 +412,6 @@ pycparser = "*" name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -444,7 +423,6 @@ files = [ name = "charset-normalizer" version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -529,7 +507,6 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -544,7 +521,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -556,7 +532,6 @@ files = [ name = "coloredlogs" version = "15.0.1" description = "Colored terminal output for Python's logging module" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -574,7 +549,6 @@ cron = ["capturer (>=2.4)"] name = "comm" version = "0.1.4" description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -594,7 +568,6 @@ typing = ["mypy (>=0.990)"] name = "contourpy" version = "1.1.0" description = "Python library for calculating contours of 2D quadrilateral grids" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -649,11 +622,81 @@ mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.2.0)", "types-Pill test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] test-no-images = ["pytest", "pytest-cov", "wurlitzer"] +[[package]] +name = "contourpy" +version = "1.1.1" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.8" +files = [ + {file = "contourpy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:46e24f5412c948d81736509377e255f6040e94216bf1a9b5ea1eaa9d29f6ec1b"}, + {file = "contourpy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e48694d6a9c5a26ee85b10130c77a011a4fedf50a7279fa0bdaf44bafb4299d"}, + {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a66045af6cf00e19d02191ab578a50cb93b2028c3eefed999793698e9ea768ae"}, + {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ebf42695f75ee1a952f98ce9775c873e4971732a87334b099dde90b6af6a916"}, + {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6aec19457617ef468ff091669cca01fa7ea557b12b59a7908b9474bb9674cf0"}, + {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:462c59914dc6d81e0b11f37e560b8a7c2dbab6aca4f38be31519d442d6cde1a1"}, + {file = "contourpy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6d0a8efc258659edc5299f9ef32d8d81de8b53b45d67bf4bfa3067f31366764d"}, + {file = "contourpy-1.1.1-cp310-cp310-win32.whl", hash = "sha256:d6ab42f223e58b7dac1bb0af32194a7b9311065583cc75ff59dcf301afd8a431"}, + {file = "contourpy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:549174b0713d49871c6dee90a4b499d3f12f5e5f69641cd23c50a4542e2ca1eb"}, + {file = "contourpy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:407d864db716a067cc696d61fa1ef6637fedf03606e8417fe2aeed20a061e6b2"}, + {file = "contourpy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe80c017973e6a4c367e037cb31601044dd55e6bfacd57370674867d15a899b"}, + {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e30aaf2b8a2bac57eb7e1650df1b3a4130e8d0c66fc2f861039d507a11760e1b"}, + {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3de23ca4f381c3770dee6d10ead6fff524d540c0f662e763ad1530bde5112532"}, + {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:566f0e41df06dfef2431defcfaa155f0acfa1ca4acbf8fd80895b1e7e2ada40e"}, + {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04c2f0adaf255bf756cf08ebef1be132d3c7a06fe6f9877d55640c5e60c72c5"}, + {file = "contourpy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0c188ae66b772d9d61d43c6030500344c13e3f73a00d1dc241da896f379bb62"}, + {file = "contourpy-1.1.1-cp311-cp311-win32.whl", hash = "sha256:0683e1ae20dc038075d92e0e0148f09ffcefab120e57f6b4c9c0f477ec171f33"}, + {file = "contourpy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:8636cd2fc5da0fb102a2504fa2c4bea3cbc149533b345d72cdf0e7a924decc45"}, + {file = "contourpy-1.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:560f1d68a33e89c62da5da4077ba98137a5e4d3a271b29f2f195d0fba2adcb6a"}, + {file = "contourpy-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24216552104ae8f3b34120ef84825400b16eb6133af2e27a190fdc13529f023e"}, + {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56de98a2fb23025882a18b60c7f0ea2d2d70bbbcfcf878f9067234b1c4818442"}, + {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:07d6f11dfaf80a84c97f1a5ba50d129d9303c5b4206f776e94037332e298dda8"}, + {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1eaac5257a8f8a047248d60e8f9315c6cff58f7803971170d952555ef6344a7"}, + {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19557fa407e70f20bfaba7d55b4d97b14f9480856c4fb65812e8a05fe1c6f9bf"}, + {file = "contourpy-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:081f3c0880712e40effc5f4c3b08feca6d064cb8cfbb372ca548105b86fd6c3d"}, + {file = "contourpy-1.1.1-cp312-cp312-win32.whl", hash = "sha256:059c3d2a94b930f4dafe8105bcdc1b21de99b30b51b5bce74c753686de858cb6"}, + {file = "contourpy-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f44d78b61740e4e8c71db1cf1fd56d9050a4747681c59ec1094750a658ceb970"}, + {file = "contourpy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70e5a10f8093d228bb2b552beeb318b8928b8a94763ef03b858ef3612b29395d"}, + {file = "contourpy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8394e652925a18ef0091115e3cc191fef350ab6dc3cc417f06da66bf98071ae9"}, + {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5bd5680f844c3ff0008523a71949a3ff5e4953eb7701b28760805bc9bcff217"}, + {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66544f853bfa85c0d07a68f6c648b2ec81dafd30f272565c37ab47a33b220684"}, + {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0c02b75acfea5cab07585d25069207e478d12309557f90a61b5a3b4f77f46ce"}, + {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41339b24471c58dc1499e56783fedc1afa4bb018bcd035cfb0ee2ad2a7501ef8"}, + {file = "contourpy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f29fb0b3f1217dfe9362ec55440d0743fe868497359f2cf93293f4b2701b8251"}, + {file = "contourpy-1.1.1-cp38-cp38-win32.whl", hash = "sha256:f9dc7f933975367251c1b34da882c4f0e0b2e24bb35dc906d2f598a40b72bfc7"}, + {file = "contourpy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:498e53573e8b94b1caeb9e62d7c2d053c263ebb6aa259c81050766beb50ff8d9"}, + {file = "contourpy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ba42e3810999a0ddd0439e6e5dbf6d034055cdc72b7c5c839f37a7c274cb4eba"}, + {file = "contourpy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c06e4c6e234fcc65435223c7b2a90f286b7f1b2733058bdf1345d218cc59e34"}, + {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6fab080484e419528e98624fb5c4282148b847e3602dc8dbe0cb0669469887"}, + {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93df44ab351119d14cd1e6b52a5063d3336f0754b72736cc63db59307dabb718"}, + {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eafbef886566dc1047d7b3d4b14db0d5b7deb99638d8e1be4e23a7c7ac59ff0f"}, + {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efe0fab26d598e1ec07d72cf03eaeeba8e42b4ecf6b9ccb5a356fde60ff08b85"}, + {file = "contourpy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f08e469821a5e4751c97fcd34bcb586bc243c39c2e39321822060ba902eac49e"}, + {file = "contourpy-1.1.1-cp39-cp39-win32.whl", hash = "sha256:bfc8a5e9238232a45ebc5cb3bfee71f1167064c8d382cadd6076f0d51cff1da0"}, + {file = "contourpy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:c84fdf3da00c2827d634de4fcf17e3e067490c4aea82833625c4c8e6cdea0887"}, + {file = "contourpy-1.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:229a25f68046c5cf8067d6d6351c8b99e40da11b04d8416bf8d2b1d75922521e"}, + {file = "contourpy-1.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a10dab5ea1bd4401c9483450b5b0ba5416be799bbd50fc7a6cc5e2a15e03e8a3"}, + {file = "contourpy-1.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4f9147051cb8fdb29a51dc2482d792b3b23e50f8f57e3720ca2e3d438b7adf23"}, + {file = "contourpy-1.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a75cc163a5f4531a256f2c523bd80db509a49fc23721b36dd1ef2f60ff41c3cb"}, + {file = "contourpy-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b53d5769aa1f2d4ea407c65f2d1d08002952fac1d9e9d307aa2e1023554a163"}, + {file = "contourpy-1.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11b836b7dbfb74e049c302bbf74b4b8f6cb9d0b6ca1bf86cfa8ba144aedadd9c"}, + {file = "contourpy-1.1.1.tar.gz", hash = "sha256:96ba37c2e24b7212a77da85004c38e7c4d155d3e72a45eeaf22c1f03f607e8ab"}, +] + +[package.dependencies] +numpy = {version = ">=1.16,<2.0", markers = "python_version <= \"3.11\""} + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.4.1)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "wurlitzer"] + [[package]] name = "coverage" version = "7.3.1" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -716,35 +759,34 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.3" +version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, - {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, - {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, - {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, + {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, + {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, + {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, ] [package.dependencies] @@ -764,7 +806,6 @@ test-randomorder = ["pytest-randomly"] name = "cycler" version = "0.11.0" description = "Composable style cycles" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -776,7 +817,6 @@ files = [ name = "cyclonedx-python-lib" version = "4.2.2" description = "A library for producing CycloneDX SBOM (Software Bill of Materials) files." -category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -794,7 +834,6 @@ sortedcontainers = ">=2.4.0,<3.0.0" name = "dash" version = "2.13.0" description = "A Python framework for building reactive web-apps. Developed by Plotly." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -828,7 +867,6 @@ testing = ["beautifulsoup4 (>=4.8.2)", "cryptography (<3.4)", "dash-testing-stub name = "dash-bootstrap-components" version = "1.5.0" description = "Bootstrap themed components for use in Plotly Dash" -category = "main" optional = false python-versions = ">=3.7, <4" files = [ @@ -846,7 +884,6 @@ pandas = ["numpy", "pandas"] name = "dash-core-components" version = "2.0.0" description = "Core component suite for Dash" -category = "main" optional = false python-versions = "*" files = [ @@ -858,7 +895,6 @@ files = [ name = "dash-html-components" version = "2.0.0" description = "Vanilla HTML components for Dash" -category = "main" optional = false python-versions = "*" files = [ @@ -870,7 +906,6 @@ files = [ name = "dash-table" version = "5.0.0" description = "Dash table" -category = "main" optional = false python-versions = "*" files = [ @@ -882,7 +917,6 @@ files = [ name = "dateparser" version = "1.1.8" description = "Date parsing library designed to parse dates from HTML pages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -905,7 +939,6 @@ langdetect = ["langdetect"] name = "de-autobahn" version = "1.0.4" description = "Autobahn App API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -921,7 +954,6 @@ urllib3 = ">=1.25.3" name = "de-bundesrat" version = "0.1.0" description = "Bundesrat: Live Informationen" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -937,7 +969,6 @@ urllib3 = ">=1.25.3" name = "de-bundestag" version = "0.1.0" description = "Bundestag: Live Informationen" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -953,7 +984,6 @@ urllib3 = ">=1.25.3" name = "de-dwd" version = "1.0.1" description = "Deutscher Wetterdienst: API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -969,7 +999,6 @@ urllib3 = ">=1.25.3" name = "de-interpol" version = "0.1.0" description = "Interpol: Interpol Red Notices API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -985,7 +1014,6 @@ urllib3 = ">=1.25.3" name = "de-jobsuche" version = "0.1.0" description = "Arbeitsagentur Jobsuche API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1001,7 +1029,6 @@ urllib3 = ">=1.25.3" name = "de-ladestationen" version = "1.0.5" description = "Bundesnetzagentur: Ladesäulenregister" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1017,7 +1044,6 @@ urllib3 = ">=1.25.3" name = "de-mudab" version = "0.1.0" description = "Umweltbundesamt: Meeresumweltdatenbank (MUDAB)" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1033,7 +1059,6 @@ urllib3 = ">=1.25.3" name = "de-nina" version = "1.1.0" description = "Bundesamt für Bevölkerungsschutz: NINA API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1049,7 +1074,6 @@ urllib3 = ">=1.25.3" name = "de-polizei-brandenburg" version = "0.1.0" description = "Polizei Brandenburg: App" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1065,7 +1089,6 @@ urllib3 = ">=1.25.3" name = "de-risikogebiete" version = "0.1.0" description = "Robert Koch Institut: Corona Risikogebiete API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1081,7 +1104,6 @@ urllib3 = ">=1.25.3" name = "de-smard" version = "0.1.0" description = "Bundesnetzagentur Strommarktdaten" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1097,7 +1119,6 @@ urllib3 = ">=1.25.3" name = "de-strahlenschutz" version = "1.0.0" description = "ODL-Info API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1113,7 +1134,6 @@ urllib3 = ">=1.25.3" name = "de-travelwarning" version = "0.1.0" description = "Auswärtiges Amt: Reisewarnungen OpenData Schnittstelle" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1129,7 +1149,6 @@ urllib3 = ">=1.25.3" name = "de-zoll" version = "0.1.0" description = "Einfuhrzoll API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1145,7 +1164,6 @@ urllib3 = ">=1.25.3" name = "debugpy" version = "1.8.0" description = "An implementation of the Debug Adapter Protocol for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1173,7 +1191,6 @@ files = [ name = "decorator" version = "5.1.1" description = "Decorators for Humans" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1185,7 +1202,6 @@ files = [ name = "defusedxml" version = "0.7.1" description = "XML bomb protection for Python stdlib modules" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1197,7 +1213,6 @@ files = [ name = "deutschland" version = "0.3.1" description = "" -category = "main" optional = false python-versions = "^3.11" files = [] @@ -1266,7 +1281,6 @@ resolved_reference = "507901b95cffa8dede08b69adab23dd8eda92d87" name = "distlib" version = "0.3.7" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -1278,7 +1292,6 @@ files = [ name = "dnspython" version = "2.4.2" description = "DNS toolkit" -category = "main" optional = false python-versions = ">=3.8,<4.0" files = [ @@ -1298,7 +1311,6 @@ wmi = ["wmi (>=1.5.1,<2.0.0)"] name = "docutils" version = "0.18.1" description = "Docutils -- Python Documentation Utilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1310,7 +1322,6 @@ files = [ name = "et-xmlfile" version = "1.1.0" description = "An implementation of lxml.xmlfile for the standard library" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1322,7 +1333,6 @@ files = [ name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1337,7 +1347,6 @@ test = ["pytest (>=6)"] name = "executing" version = "1.2.0" description = "Get the currently executing AST node of a frame, and other information" -category = "dev" optional = false python-versions = "*" files = [ @@ -1352,7 +1361,6 @@ tests = ["asttokens", "littleutils", "pytest", "rich"] name = "fastjsonschema" version = "2.18.0" description = "Fastest Python implementation of JSON schema" -category = "dev" optional = false python-versions = "*" files = [ @@ -1367,7 +1375,6 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc name = "filelock" version = "3.12.4" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1384,7 +1391,6 @@ typing = ["typing-extensions (>=4.7.1)"] name = "flask" version = "2.2.5" description = "A simple framework for building complex web applications." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1406,7 +1412,6 @@ dotenv = ["python-dotenv"] name = "flatbuffers" version = "23.5.26" description = "The FlatBuffers serialization format for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -1418,7 +1423,6 @@ files = [ name = "fonttools" version = "4.42.1" description = "Tools to manipulate font files" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1476,7 +1480,6 @@ woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] name = "fqdn" version = "1.5.1" description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" -category = "dev" optional = false python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" files = [ @@ -1488,7 +1491,6 @@ files = [ name = "future" version = "0.18.3" description = "Clean single-source support for Python 3 and 2" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1499,7 +1501,6 @@ files = [ name = "gql" version = "2.0.0" description = "GraphQL client for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -1521,7 +1522,6 @@ test = ["coveralls (==2.0.0)", "mock (==4.0.2)", "pytest (==5.4.2)", "pytest-asy name = "graphql-core" version = "2.3.2" description = "GraphQL implementation for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -1542,7 +1542,6 @@ test = ["coveralls (==1.11.1)", "cython (==0.29.17)", "gevent (==1.5.0)", "pyann name = "greenlet" version = "2.0.2" description = "Lightweight in-process concurrent programming" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -1616,7 +1615,6 @@ test = ["objgraph", "psutil"] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1628,7 +1626,6 @@ files = [ name = "html5lib" version = "1.1" description = "HTML parser based on the WHATWG HTML specification" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1650,7 +1647,6 @@ lxml = ["lxml"] name = "humanfriendly" version = "10.0" description = "Human friendly output for text interfaces using Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1663,14 +1659,13 @@ pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_ve [[package]] name = "identify" -version = "2.5.28" +version = "2.5.29" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.28-py2.py3-none-any.whl", hash = "sha256:87816de144bf46d161bd5b3e8f5596b16cade3b80be537087334b26bc5c177f3"}, - {file = "identify-2.5.28.tar.gz", hash = "sha256:94bb59643083ebd60dc996d043497479ee554381fbc5307763915cda49b0e78f"}, + {file = "identify-2.5.29-py2.py3-none-any.whl", hash = "sha256:24437fbf6f4d3fe6efd0eb9d67e24dd9106db99af5ceb27996a5f7895f24bf1b"}, + {file = "identify-2.5.29.tar.gz", hash = "sha256:d43d52b86b15918c137e3a74fff5224f60385cd0e9c38e99d07c257f02f151a5"}, ] [package.extras] @@ -1680,7 +1675,6 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1692,7 +1686,6 @@ files = [ name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1704,7 +1697,6 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1716,7 +1708,6 @@ files = [ name = "ipykernel" version = "6.25.2" description = "IPython Kernel for Jupyter" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1730,7 +1721,7 @@ comm = ">=0.1.1" debugpy = ">=1.6.5" ipython = ">=7.23.1" jupyter-client = ">=6.1.12" -jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" matplotlib-inline = ">=0.1" nest-asyncio = "*" packaging = "*" @@ -1750,7 +1741,6 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio" name = "ipython" version = "8.15.0" description = "IPython: Productive Interactive Computing" -category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -1789,7 +1779,6 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pa name = "ipython-genutils" version = "0.2.0" description = "Vestigial utilities from IPython" -category = "dev" optional = false python-versions = "*" files = [ @@ -1801,7 +1790,6 @@ files = [ name = "ipywidgets" version = "8.1.1" description = "Jupyter interactive widgets" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1823,7 +1811,6 @@ test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] name = "isoduration" version = "20.11.0" description = "Operations with ISO 8601 durations" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1838,7 +1825,6 @@ arrow = ">=0.15.0" name = "itsdangerous" version = "2.1.2" description = "Safely pass data to untrusted environments and back." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1850,7 +1836,6 @@ files = [ name = "jedi" version = "0.19.0" description = "An autocompletion tool for Python that can be used for text editors." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1870,7 +1855,6 @@ testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1888,7 +1872,6 @@ i18n = ["Babel (>=2.7)"] name = "json5" version = "0.9.14" description = "A Python implementation of the JSON5 data format." -category = "dev" optional = false python-versions = "*" files = [ @@ -1903,7 +1886,6 @@ dev = ["hypothesis"] name = "jsonpointer" version = "2.4" description = "Identify specific nodes in a JSON document (RFC 6901)" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" files = [ @@ -1915,7 +1897,6 @@ files = [ name = "jsonschema" version = "4.19.0" description = "An implementation of JSON Schema validation for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1945,7 +1926,6 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- name = "jsonschema-specifications" version = "2023.7.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1960,7 +1940,6 @@ referencing = ">=0.28.0" name = "jupyter" version = "1.0.0" description = "Jupyter metapackage. Install all the Jupyter components in one go." -category = "dev" optional = false python-versions = "*" files = [ @@ -1981,7 +1960,6 @@ qtconsole = "*" name = "jupyter-client" version = "8.3.1" description = "Jupyter protocol implementation and client libraries" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1990,7 +1968,7 @@ files = [ ] [package.dependencies] -jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" python-dateutil = ">=2.8.2" pyzmq = ">=23.0" tornado = ">=6.2" @@ -2004,7 +1982,6 @@ test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pyt name = "jupyter-console" version = "6.6.3" description = "Jupyter terminal console" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2016,7 +1993,7 @@ files = [ ipykernel = ">=6.14" ipython = "*" jupyter-client = ">=7.0.0" -jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" prompt-toolkit = ">=3.0.30" pygments = "*" pyzmq = ">=17" @@ -2029,7 +2006,6 @@ test = ["flaky", "pexpect", "pytest"] name = "jupyter-core" version = "5.3.1" description = "Jupyter core package. A base package on which Jupyter projects rely." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2050,7 +2026,6 @@ test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] name = "jupyter-events" version = "0.7.0" description = "Jupyter Event System library" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2076,7 +2051,6 @@ test = ["click", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "p name = "jupyter-lsp" version = "2.2.0" description = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2091,7 +2065,6 @@ jupyter-server = ">=1.1.2" name = "jupyter-server" version = "2.7.3" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2104,7 +2077,7 @@ anyio = ">=3.1.0" argon2-cffi = "*" jinja2 = "*" jupyter-client = ">=7.4.4" -jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" jupyter-events = ">=0.6.0" jupyter-server-terminals = "*" nbconvert = ">=6.4.4" @@ -2128,7 +2101,6 @@ test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-sc name = "jupyter-server-terminals" version = "0.4.4" description = "A Jupyter Server Extension Providing Terminals." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2148,7 +2120,6 @@ test = ["coverage", "jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-cov", name = "jupyterlab" version = "4.0.6" description = "JupyterLab computational environment" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2179,7 +2150,6 @@ test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-cons name = "jupyterlab-pygments" version = "0.2.2" description = "Pygments theme using JupyterLab CSS variables" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2191,7 +2161,6 @@ files = [ name = "jupyterlab-server" version = "2.25.0" description = "A set of server components for JupyterLab and JupyterLab like applications." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2217,7 +2186,6 @@ test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-v name = "jupyterlab-widgets" version = "3.0.9" description = "Jupyter interactive widgets for JupyterLab" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2229,7 +2197,6 @@ files = [ name = "kiwisolver" version = "1.4.5" description = "A fast implementation of the Cassowary constraint solver" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2343,7 +2310,6 @@ files = [ name = "license-expression" version = "30.1.1" description = "license-expression is a comprehensive utility library to parse, compare, simplify and normalize license expressions (such as SPDX license expressions) using boolean logic." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2362,7 +2328,6 @@ testing = ["black", "isort", "pytest (>=6,!=7.0.0)", "pytest-xdist (>=2)", "twin name = "loguru" version = "0.7.2" description = "Python logging made (stupidly) simple" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2381,7 +2346,6 @@ dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptio name = "loguru-mypy" version = "0.0.4" description = "" -category = "dev" optional = false python-versions = ">=3.6.0" files = [ @@ -2396,7 +2360,6 @@ typing-extensions = "*" name = "lxml" version = "4.9.3" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ @@ -2504,7 +2467,6 @@ source = ["Cython (>=0.29.35)"] name = "mapbox-vector-tile" version = "1.2.1" description = "Mapbox Vector Tile" -category = "main" optional = false python-versions = "*" files = [ @@ -2522,7 +2484,6 @@ shapely = "*" name = "markdown-it-py" version = "2.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2547,7 +2508,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2607,7 +2567,6 @@ files = [ name = "matplotlib" version = "3.8.0" description = "Python plotting package" -category = "main" optional = false python-versions = ">=3.9" files = [ @@ -2657,7 +2616,6 @@ setuptools_scm = ">=7" name = "matplotlib-inline" version = "0.1.6" description = "Inline Matplotlib backend for Jupyter" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2672,7 +2630,6 @@ traitlets = "*" name = "mdit-py-plugins" version = "0.3.5" description = "Collection of plugins for markdown-it-py" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2692,7 +2649,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2704,7 +2660,6 @@ files = [ name = "mistune" version = "3.0.1" description = "A sane and fast Markdown parser with useful plugins and renderers" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2716,7 +2671,6 @@ files = [ name = "more-itertools" version = "8.14.0" description = "More routines for operating on iterables, beyond itertools" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2728,7 +2682,6 @@ files = [ name = "mpmath" version = "1.3.0" description = "Python library for arbitrary-precision floating-point arithmetic" -category = "main" optional = false python-versions = "*" files = [ @@ -2746,7 +2699,6 @@ tests = ["pytest (>=4.6)"] name = "msgpack" version = "1.0.5" description = "MessagePack serializer" -category = "dev" optional = false python-versions = "*" files = [ @@ -2819,7 +2771,6 @@ files = [ name = "mypy" version = "1.5.1" description = "Optional static typing for Python" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2865,7 +2816,6 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2877,7 +2827,6 @@ files = [ name = "myst-parser" version = "1.0.0" description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2904,7 +2853,6 @@ testing-docutils = ["pygments", "pytest (>=7,<8)", "pytest-param-files (>=0.3.4, name = "nbclient" version = "0.8.0" description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -2914,7 +2862,7 @@ files = [ [package.dependencies] jupyter-client = ">=6.1.12" -jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" nbformat = ">=5.1" traitlets = ">=5.4" @@ -2927,7 +2875,6 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>= name = "nbconvert" version = "7.8.0" description = "Converting Jupyter Notebooks" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2965,7 +2912,6 @@ webpdf = ["playwright"] name = "nbformat" version = "5.9.2" description = "The Jupyter Notebook format" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2987,7 +2933,6 @@ test = ["pep440", "pre-commit", "pytest", "testpath"] name = "nbsphinx" version = "0.9.3" description = "Jupyter Notebook Tools for Sphinx" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3005,21 +2950,19 @@ traitlets = ">=5" [[package]] name = "nest-asyncio" -version = "1.5.7" +version = "1.5.8" description = "Patch asyncio to allow nested event loops" -category = "main" optional = false python-versions = ">=3.5" files = [ - {file = "nest_asyncio-1.5.7-py3-none-any.whl", hash = "sha256:5301c82941b550b3123a1ea772ba9a1c80bad3a182be8c1a5ae6ad3be57a9657"}, - {file = "nest_asyncio-1.5.7.tar.gz", hash = "sha256:6a80f7b98f24d9083ed24608977c09dd608d83f91cccc24c9d2cba6d10e01c10"}, + {file = "nest_asyncio-1.5.8-py3-none-any.whl", hash = "sha256:accda7a339a70599cb08f9dd09a67e0c2ef8d8d6f4c07f96ab203f2ae254e48d"}, + {file = "nest_asyncio-1.5.8.tar.gz", hash = "sha256:25aa2ca0d2a5b5531956b9e273b45cf664cae2b145101d73b86b199978d48fdb"}, ] [[package]] name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -3034,7 +2977,6 @@ setuptools = "*" name = "notebook" version = "7.0.3" description = "Jupyter Notebook - A web-based notebook environment for interactive computing" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3058,7 +3000,6 @@ test = ["importlib-resources (>=5.0)", "ipykernel", "jupyter-server[test] (>=2.4 name = "notebook-shim" version = "0.2.3" description = "A shim layer for notebook traits and config" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3076,7 +3017,6 @@ test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync" name = "numpy" version = "1.25.2" description = "Fundamental package for array computing in Python" -category = "main" optional = false python-versions = ">=3.9" files = [ @@ -3111,7 +3051,6 @@ files = [ name = "onnxruntime" version = "1.15.1" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" -category = "main" optional = false python-versions = "*" files = [ @@ -3153,7 +3092,6 @@ sympy = "*" name = "openpyxl" version = "3.1.2" description = "A Python library to read/write Excel 2010 xlsx/xlsm files" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3168,7 +3106,6 @@ et-xmlfile = "*" name = "outcome" version = "1.2.0" description = "Capture the outcome of Python function calls." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3183,7 +3120,6 @@ attrs = ">=19.2.0" name = "overrides" version = "7.4.0" description = "A decorator to automatically detect mismatch when overriding a method." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3195,7 +3131,6 @@ files = [ name = "packageurl-python" version = "0.11.2" description = "A purl aka. Package URL parser and builder" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3213,7 +3148,6 @@ test = ["pytest"] name = "packaging" version = "23.1" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3225,7 +3159,6 @@ files = [ name = "pandas" version = "2.1.0" description = "Powerful data structures for data analysis, time series, and statistics" -category = "main" optional = false python-versions = ">=3.9" files = [ @@ -3284,7 +3217,6 @@ xml = ["lxml (>=4.8.0)"] name = "pandas-stubs" version = "2.0.3.230814" description = "Type annotations for pandas" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3300,7 +3232,6 @@ types-pytz = ">=2022.1.1" name = "pandocfilters" version = "1.5.0" description = "Utilities for writing pandoc filters in python" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3312,7 +3243,6 @@ files = [ name = "parso" version = "0.8.3" description = "A Python Parser" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3328,7 +3258,6 @@ testing = ["docopt", "pytest (<6.0.0)"] name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3340,7 +3269,6 @@ files = [ name = "pexpect" version = "4.8.0" description = "Pexpect allows easy control of interactive console applications." -category = "dev" optional = false python-versions = "*" files = [ @@ -3355,7 +3283,6 @@ ptyprocess = ">=0.5" name = "pickleshare" version = "0.7.5" description = "Tiny 'shelve'-like database with concurrency support" -category = "dev" optional = false python-versions = "*" files = [ @@ -3367,7 +3294,6 @@ files = [ name = "pillow" version = "10.0.1" description = "Python Imaging Library (Fork)" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3435,7 +3361,6 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "pip" version = "23.2.1" description = "The PyPA recommended tool for installing Python packages." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3447,7 +3372,6 @@ files = [ name = "pip-api" version = "0.0.30" description = "An unofficial, importable pip API" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3462,7 +3386,6 @@ pip = "*" name = "pip-audit" version = "2.6.1" description = "A tool for scanning Python environments for known vulnerabilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3491,7 +3414,6 @@ test = ["coverage[toml]", "pretend", "pytest", "pytest-cov"] name = "pip-licenses" version = "4.3.2" description = "Dump the software license list of Python packages installed with pip." -category = "dev" optional = false python-versions = "~=3.8" files = [ @@ -3509,7 +3431,6 @@ test = ["docutils", "mypy", "pytest-cov", "pytest-pycodestyle", "pytest-runner"] name = "pip-requirements-parser" version = "32.0.1" description = "pip requirements parser - a mostly correct pip requirements parsing library because it uses pip's own code." -category = "dev" optional = false python-versions = ">=3.6.0" files = [ @@ -3529,7 +3450,6 @@ testing = ["aboutcode-toolkit (>=6.0.0)", "black", "pytest (>=6,!=7.0.0)", "pyte name = "platformdirs" version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3545,7 +3465,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "plotly" version = "5.17.0" description = "An open-source, interactive data visualization library for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3561,7 +3480,6 @@ tenacity = ">=6.2.0" name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3577,7 +3495,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pockets" version = "0.9.1" description = "A collection of helpful Python tools!" -category = "dev" optional = false python-versions = "*" files = [ @@ -3592,7 +3509,6 @@ six = ">=1.5.2" name = "pprintpp" version = "0.4.0" description = "A drop-in replacement for pprint that's actually pretty" -category = "dev" optional = false python-versions = "*" files = [ @@ -3604,7 +3520,6 @@ files = [ name = "pre-commit" version = "3.4.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3623,7 +3538,6 @@ virtualenv = ">=20.10.0" name = "prettytable" version = "3.9.0" description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3641,7 +3555,6 @@ tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"] name = "prometheus-client" version = "0.17.1" description = "Python client for the Prometheus monitoring system." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3656,7 +3569,6 @@ twisted = ["twisted"] name = "promise" version = "2.3" description = "Promises/A+ implementation for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -3673,7 +3585,6 @@ test = ["coveralls", "futures", "mock", "pytest (>=2.7.3)", "pytest-benchmark", name = "prompt-toolkit" version = "3.0.39" description = "Library for building powerful interactive command lines in Python" -category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -3688,7 +3599,6 @@ wcwidth = "*" name = "protobuf" version = "3.20.3" description = "Protocol Buffers" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3720,7 +3630,6 @@ files = [ name = "psutil" version = "5.9.5" description = "Cross-platform lib for process and system monitoring in Python." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3747,7 +3656,6 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] name = "psycopg2-binary" version = "2.9.7" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3817,7 +3725,6 @@ files = [ name = "ptyprocess" version = "0.7.0" description = "Run a subprocess in a pseudo terminal" -category = "dev" optional = false python-versions = "*" files = [ @@ -3829,7 +3736,6 @@ files = [ name = "pure-eval" version = "0.2.2" description = "Safely evaluate AST nodes without side effects" -category = "dev" optional = false python-versions = "*" files = [ @@ -3844,7 +3750,6 @@ tests = ["pytest"] name = "py-serializable" version = "0.11.1" description = "Library for serializing and deserializing Python Objects to and from JSON and XML." -category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -3859,7 +3764,6 @@ defusedxml = ">=0.7.1,<0.8.0" name = "pyclipper" version = "1.3.0.post5" description = "Cython wrapper for the C++ translation of the Angus Johnson's Clipper library (ver. 6.4.2)" -category = "main" optional = false python-versions = "*" files = [ @@ -3913,7 +3817,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3925,7 +3828,6 @@ files = [ name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3940,7 +3842,6 @@ plugins = ["importlib-metadata"] name = "pymongo" version = "4.5.0" description = "Python driver for MongoDB " -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4042,7 +3943,6 @@ zstd = ["zstandard"] name = "pyparsing" version = "3.1.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" optional = false python-versions = ">=3.6.8" files = [ @@ -4057,7 +3957,6 @@ diagrams = ["jinja2", "railroad-diagrams"] name = "pyreadline3" version = "3.4.1" description = "A python implementation of GNU readline." -category = "main" optional = false python-versions = "*" files = [ @@ -4069,7 +3968,6 @@ files = [ name = "pysocks" version = "1.7.1" description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -4082,7 +3980,6 @@ files = [ name = "pytest" version = "7.4.2" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4103,7 +4000,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-clarity" version = "1.0.1" description = "A plugin providing an alternative, colourful diff output for failing assertions." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -4119,7 +4015,6 @@ rich = ">=8.0.0" name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4138,7 +4033,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-mock" version = "3.11.1" description = "Thin-wrapper around the mock package for easier use with pytest" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4156,7 +4050,6 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "pytest-repeat" version = "0.9.1" description = "pytest plugin for repeating tests" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -4171,7 +4064,6 @@ pytest = ">=3.6" name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -4186,7 +4078,6 @@ six = ">=1.5" name = "python-dotenv" version = "1.0.0" description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4201,7 +4092,6 @@ cli = ["click (>=5.0)"] name = "python-json-logger" version = "2.0.7" description = "A python library adding a json log formatter" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -4213,7 +4103,6 @@ files = [ name = "pytz" version = "2023.3.post1" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -4225,7 +4114,6 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" -category = "dev" optional = false python-versions = "*" files = [ @@ -4249,7 +4137,6 @@ files = [ name = "pywinpty" version = "2.0.11" description = "Pseudo terminal support for Windows from Python." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4264,7 +4151,6 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -4314,7 +4200,6 @@ files = [ name = "pyzmq" version = "25.1.1" description = "Python bindings for 0MQ" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -4420,7 +4305,6 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} name = "qtconsole" version = "5.4.4" description = "Jupyter Qt console" -category = "dev" optional = false python-versions = ">= 3.7" files = [ @@ -4447,7 +4331,6 @@ test = ["flaky", "pytest", "pytest-qt"] name = "qtpy" version = "2.4.0" description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4465,7 +4348,6 @@ test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] name = "referencing" version = "0.30.2" description = "JSON Referencing + Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4481,7 +4363,6 @@ rpds-py = ">=0.7.0" name = "regex" version = "2023.8.8" description = "Alternative regular expression module, to replace re." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -4579,7 +4460,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4601,7 +4481,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "retrying" version = "1.3.4" description = "Retrying" -category = "main" optional = false python-versions = "*" files = [ @@ -4616,7 +4495,6 @@ six = ">=1.7.0" name = "rfc3339-validator" version = "0.1.4" description = "A pure python RFC3339 validator" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -4631,7 +4509,6 @@ six = "*" name = "rfc3986-validator" version = "0.1.1" description = "Pure python rfc3986 validator" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -4641,14 +4518,13 @@ files = [ [[package]] name = "rich" -version = "13.5.2" +version = "13.5.3" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "dev" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.5.2-py3-none-any.whl", hash = "sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808"}, - {file = "rich-13.5.2.tar.gz", hash = "sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39"}, + {file = "rich-13.5.3-py3-none-any.whl", hash = "sha256:9257b468badc3d347e146a4faa268ff229039d4c2d176ab0cffb4c4fbc73d5d9"}, + {file = "rich-13.5.3.tar.gz", hash = "sha256:87b43e0543149efa1253f485cd845bb7ee54df16c9617b8a893650ab84b4acb6"}, ] [package.dependencies] @@ -4662,7 +4538,6 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "rise" version = "5.7.1" description = "Reveal.js - Jupyter/IPython Slideshow Extension" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" files = [ @@ -4677,7 +4552,6 @@ notebook = ">=6.0" name = "rpds-py" version = "0.10.3" description = "Python bindings to Rust's persistent data structures (rpds)" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4782,36 +4656,34 @@ files = [ [[package]] name = "ruff" -version = "0.0.289" +version = "0.0.290" description = "An extremely fast Python linter, written in Rust." -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.289-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:c9a89d748e90c840bac9c37afe90cf13a5bfd460ca02ea93dad9d7bee3af03b4"}, - {file = "ruff-0.0.289-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:7f7396c6ea01ba332a6ad9d47642bac25d16bd2076aaa595b001f58b2f32ff05"}, - {file = "ruff-0.0.289-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7180de86c8ecd39624dec1699136f941c07e723201b4ce979bec9e7c67b40ad2"}, - {file = "ruff-0.0.289-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:73f37c65508203dd01a539926375a10243769c20d4fcab3fa6359cd3fbfc54b7"}, - {file = "ruff-0.0.289-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c14abcd7563b5c80be2dd809eeab20e4aa716bf849860b60a22d87ddf19eb88"}, - {file = "ruff-0.0.289-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:91b6d63b6b46d4707916472c91baa87aa0592e73f62a80ff55efdf6c0668cfd6"}, - {file = "ruff-0.0.289-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6479b8c4be3c36046c6c92054762b276fa0fddb03f6b9a310fbbf4c4951267fd"}, - {file = "ruff-0.0.289-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5424318c254bcb091cb67e140ec9b9f7122074e100b06236f252923fb41e767"}, - {file = "ruff-0.0.289-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4daa90865796aedcedf0d8897fdd4cd09bf0ddd3504529a4ccf211edcaff3c7d"}, - {file = "ruff-0.0.289-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8057e8ab0016c13b9419bad119e854f881e687bd96bc5e2d52c8baac0f278a44"}, - {file = "ruff-0.0.289-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7eebfab2e6a6991908ff1bf82f2dc1e5095fc7e316848e62124526837b445f4d"}, - {file = "ruff-0.0.289-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ebc7af550018001a7fb39ca22cdce20e1a0de4388ea4a007eb5c822f6188c297"}, - {file = "ruff-0.0.289-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6e4e6eccb753efe760ba354fc8e9f783f6bba71aa9f592756f5bd0d78db898ed"}, - {file = "ruff-0.0.289-py3-none-win32.whl", hash = "sha256:bbb3044f931c09cf17dbe5b339896eece0d6ac10c9a86e172540fcdb1974f2b7"}, - {file = "ruff-0.0.289-py3-none-win_amd64.whl", hash = "sha256:6d043c5456b792be2615a52f16056c3cf6c40506ce1f2d6f9d3083cfcb9eeab6"}, - {file = "ruff-0.0.289-py3-none-win_arm64.whl", hash = "sha256:04a720bcca5e987426bb14ad8b9c6f55e259ea774da1cbeafe71569744cfd20a"}, - {file = "ruff-0.0.289.tar.gz", hash = "sha256:2513f853b0fc42f0339b7ab0d2751b63ce7a50a0032d2689b54b2931b3b866d7"}, + {file = "ruff-0.0.290-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:0e2b09ac4213b11a3520221083866a5816616f3ae9da123037b8ab275066fbac"}, + {file = "ruff-0.0.290-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:4ca6285aa77b3d966be32c9a3cd531655b3d4a0171e1f9bf26d66d0372186767"}, + {file = "ruff-0.0.290-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35e3550d1d9f2157b0fcc77670f7bb59154f223bff281766e61bdd1dd854e0c5"}, + {file = "ruff-0.0.290-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d748c8bd97874f5751aed73e8dde379ce32d16338123d07c18b25c9a2796574a"}, + {file = "ruff-0.0.290-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:982af5ec67cecd099e2ef5e238650407fb40d56304910102d054c109f390bf3c"}, + {file = "ruff-0.0.290-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bbd37352cea4ee007c48a44c9bc45a21f7ba70a57edfe46842e346651e2b995a"}, + {file = "ruff-0.0.290-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d9be6351b7889462912e0b8185a260c0219c35dfd920fb490c7f256f1d8313e"}, + {file = "ruff-0.0.290-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75cdc7fe32dcf33b7cec306707552dda54632ac29402775b9e212a3c16aad5e6"}, + {file = "ruff-0.0.290-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb07f37f7aecdbbc91d759c0c09870ce0fb3eed4025eebedf9c4b98c69abd527"}, + {file = "ruff-0.0.290-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2ab41bc0ba359d3f715fc7b705bdeef19c0461351306b70a4e247f836b9350ed"}, + {file = "ruff-0.0.290-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:150bf8050214cea5b990945b66433bf9a5e0cef395c9bc0f50569e7de7540c86"}, + {file = "ruff-0.0.290-py3-none-musllinux_1_2_i686.whl", hash = "sha256:75386ebc15fe5467248c039f5bf6a0cfe7bfc619ffbb8cd62406cd8811815fca"}, + {file = "ruff-0.0.290-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ac93eadf07bc4ab4c48d8bb4e427bf0f58f3a9c578862eb85d99d704669f5da0"}, + {file = "ruff-0.0.290-py3-none-win32.whl", hash = "sha256:461fbd1fb9ca806d4e3d5c745a30e185f7cf3ca77293cdc17abb2f2a990ad3f7"}, + {file = "ruff-0.0.290-py3-none-win_amd64.whl", hash = "sha256:f1f49f5ec967fd5778813780b12a5650ab0ebcb9ddcca28d642c689b36920796"}, + {file = "ruff-0.0.290-py3-none-win_arm64.whl", hash = "sha256:ae5a92dfbdf1f0c689433c223f8dac0782c2b2584bd502dfdbc76475669f1ba1"}, + {file = "ruff-0.0.290.tar.gz", hash = "sha256:949fecbc5467bb11b8db810a7fa53c7e02633856ee6bd1302b2f43adcd71b88d"}, ] [[package]] name = "rx" version = "1.6.3" description = "Reactive Extensions (Rx) for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -4822,7 +4694,6 @@ files = [ name = "seaborn" version = "0.12.2" description = "Statistical data visualization" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4844,7 +4715,6 @@ stats = ["scipy (>=1.3)", "statsmodels (>=0.10)"] name = "selenium" version = "4.12.0" description = "" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4862,7 +4732,6 @@ urllib3 = {version = ">=1.26,<3", extras = ["socks"]} name = "send2trash" version = "1.8.2" description = "Send file to trash natively under Mac OS X, Windows and Linux" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -4879,7 +4748,6 @@ win32 = ["pywin32"] name = "setuptools" version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4896,7 +4764,6 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar name = "setuptools-scm" version = "7.1.0" description = "the blessed package to manage your versions by scm tags" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4917,7 +4784,6 @@ toml = ["setuptools (>=42)"] name = "shapely" version = "1.8.5.post1" description = "Geometric objects, predicates, and operations" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -4974,7 +4840,6 @@ vectorized = ["numpy"] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -4986,7 +4851,6 @@ files = [ name = "slugify" version = "0.0.1" description = "A generic slugifier." -category = "main" optional = false python-versions = "*" files = [ @@ -4997,7 +4861,6 @@ files = [ name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -5009,7 +4872,6 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" optional = false python-versions = "*" files = [ @@ -5021,7 +4883,6 @@ files = [ name = "sortedcontainers" version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" -category = "main" optional = false python-versions = "*" files = [ @@ -5033,7 +4894,6 @@ files = [ name = "soupsieve" version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -5045,7 +4905,6 @@ files = [ name = "sphinx" version = "6.2.1" description = "Python documentation generator" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -5080,7 +4939,6 @@ test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] name = "sphinx-autodoc-typehints" version = "1.23.0" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -5100,7 +4958,6 @@ type-comment = ["typed-ast (>=1.5.4)"] name = "sphinx-copybutton" version = "0.5.2" description = "Add a copy button to each of your code cells." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -5119,7 +4976,6 @@ rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] name = "sphinx-notfound-page" version = "1.0.0" description = "Sphinx extension to build a 404 page with absolute URLs" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -5138,7 +4994,6 @@ test = ["tox"] name = "sphinx-rtd-theme" version = "1.3.0" description = "Read the Docs theme for Sphinx" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -5158,7 +5013,6 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] name = "sphinxcontrib-applehelp" version = "1.0.7" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -5177,7 +5031,6 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.5" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" -category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -5196,7 +5049,6 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.4" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -5215,7 +5067,6 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jquery" version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" -category = "dev" optional = false python-versions = ">=2.7" files = [ @@ -5230,7 +5081,6 @@ Sphinx = ">=1.8" name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -5245,7 +5095,6 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-mermaid" version = "0.9.2" description = "Mermaid diagrams in yours Sphinx powered docs" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -5257,7 +5106,6 @@ files = [ name = "sphinxcontrib-napoleon" version = "0.7" description = "Sphinx \"napoleon\" extension." -category = "dev" optional = false python-versions = "*" files = [ @@ -5273,7 +5121,6 @@ six = ">=1.5.2" name = "sphinxcontrib-qthelp" version = "1.0.6" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" -category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -5292,7 +5139,6 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.9" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" -category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -5311,7 +5157,6 @@ test = ["pytest"] name = "sqlalchemy" version = "1.4.49" description = "Database Abstraction Library" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -5356,7 +5201,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and platform_machine == \"aarch64\" or python_version >= \"3\" and platform_machine == \"ppc64le\" or python_version >= \"3\" and platform_machine == \"x86_64\" or python_version >= \"3\" and platform_machine == \"amd64\" or python_version >= \"3\" and platform_machine == \"AMD64\" or python_version >= \"3\" and platform_machine == \"win32\" or python_version >= \"3\" and platform_machine == \"WIN32\""} +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} mypy = {version = ">=0.910", optional = true, markers = "python_version >= \"3\" and extra == \"mypy\""} sqlalchemy2-stubs = {version = "*", optional = true, markers = "extra == \"mypy\""} @@ -5385,7 +5230,6 @@ sqlcipher = ["sqlcipher3-binary"] name = "sqlalchemy2-stubs" version = "0.0.2a35" description = "Typing Stubs for SQLAlchemy 1.4" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -5400,7 +5244,6 @@ typing-extensions = ">=3.7.4" name = "stack-data" version = "0.6.2" description = "Extract data from python stack frames and tracebacks for informative displays" -category = "dev" optional = false python-versions = "*" files = [ @@ -5420,7 +5263,6 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] name = "sympy" version = "1.12" description = "Computer algebra system (CAS) in Python" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -5435,7 +5277,6 @@ mpmath = ">=0.19" name = "tenacity" version = "8.2.3" description = "Retry code until it succeeds" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -5450,7 +5291,6 @@ doc = ["reno", "sphinx", "tornado (>=4.5)"] name = "terminado" version = "0.17.1" description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -5471,7 +5311,6 @@ test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] name = "tinycss2" version = "1.2.1" description = "A tiny CSS parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -5490,7 +5329,6 @@ test = ["flake8", "isort", "pytest"] name = "tokenize-rt" version = "5.2.0" description = "A wrapper around the stdlib `tokenize` which roundtrips." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -5502,7 +5340,6 @@ files = [ name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -5514,7 +5351,6 @@ files = [ name = "tornado" version = "6.3.3" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -category = "dev" optional = false python-versions = ">= 3.8" files = [ @@ -5535,7 +5371,6 @@ files = [ name = "tqdm" version = "4.66.1" description = "Fast, Extensible Progress Meter" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -5556,7 +5391,6 @@ telegram = ["requests"] name = "traitlets" version = "5.10.0" description = "Traitlets Python configuration system" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -5572,7 +5406,6 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.5.1)", "pre-commit", "pytest (>=7.0, name = "trio" version = "0.22.2" description = "A friendly Python library for async concurrency and I/O" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -5592,7 +5425,6 @@ sortedcontainers = "*" name = "trio-websocket" version = "0.10.4" description = "WebSocket library for Trio" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -5609,7 +5441,6 @@ wsproto = ">=0.14" name = "types-cachetools" version = "5.3.0.6" description = "Typing stubs for cachetools" -category = "dev" optional = false python-versions = "*" files = [ @@ -5621,7 +5452,6 @@ files = [ name = "types-pyopenssl" version = "23.2.0.2" description = "Typing stubs for pyOpenSSL" -category = "dev" optional = false python-versions = "*" files = [ @@ -5634,21 +5464,19 @@ cryptography = ">=35.0.0" [[package]] name = "types-pytz" -version = "2023.3.0.1" +version = "2023.3.1.0" description = "Typing stubs for pytz" -category = "dev" optional = false python-versions = "*" files = [ - {file = "types-pytz-2023.3.0.1.tar.gz", hash = "sha256:1a7b8d4aac70981cfa24478a41eadfcd96a087c986d6f150d77e3ceb3c2bdfab"}, - {file = "types_pytz-2023.3.0.1-py3-none-any.whl", hash = "sha256:65152e872137926bb67a8fe6cc9cfd794365df86650c5d5fdc7b167b0f38892e"}, + {file = "types-pytz-2023.3.1.0.tar.gz", hash = "sha256:8e7d2198cba44a72df7628887c90f68a568e1445f14db64631af50c3cab8c090"}, + {file = "types_pytz-2023.3.1.0-py3-none-any.whl", hash = "sha256:a660a38ed86d45970603e4f3b4877c7ba947668386a896fb5d9589c17e7b8407"}, ] [[package]] name = "types-requests" version = "2.31.0.2" description = "Typing stubs for requests" -category = "dev" optional = false python-versions = "*" files = [ @@ -5663,7 +5491,6 @@ types-urllib3 = "*" name = "types-setuptools" version = "68.2.0.0" description = "Typing stubs for setuptools" -category = "dev" optional = false python-versions = "*" files = [ @@ -5675,7 +5502,6 @@ files = [ name = "types-tqdm" version = "4.66.0.2" description = "Typing stubs for tqdm" -category = "dev" optional = false python-versions = "*" files = [ @@ -5687,7 +5513,6 @@ files = [ name = "types-urllib3" version = "1.26.25.14" description = "Typing stubs for urllib3" -category = "dev" optional = false python-versions = "*" files = [ @@ -5697,21 +5522,19 @@ files = [ [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -5723,7 +5546,6 @@ files = [ name = "tzlocal" version = "5.0.1" description = "tzinfo object for the local timezone" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -5741,7 +5563,6 @@ devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pyte name = "uri-template" version = "1.3.0" description = "RFC 6570 URI Template Processor" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -5756,7 +5577,6 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake name = "urllib3" version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -5777,7 +5597,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "virtualenv" version = "20.24.5" description = "Virtual Python Environment builder" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -5798,7 +5617,6 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess name = "wcwidth" version = "0.2.6" description = "Measures the displayed width of unicode strings in a terminal" -category = "dev" optional = false python-versions = "*" files = [ @@ -5810,7 +5628,6 @@ files = [ name = "webcolors" version = "1.13" description = "A library for working with the color formats defined by HTML and CSS." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -5826,7 +5643,6 @@ tests = ["pytest", "pytest-cov"] name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" -category = "dev" optional = false python-versions = "*" files = [ @@ -5838,7 +5654,6 @@ files = [ name = "websocket-client" version = "1.6.3" description = "WebSocket client for Python with low level API options" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -5855,7 +5670,6 @@ test = ["websockets"] name = "werkzeug" version = "2.2.3" description = "The comprehensive WSGI web application library." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -5873,7 +5687,6 @@ watchdog = ["watchdog"] name = "widgetsnbextension" version = "4.0.9" description = "Jupyter interactive widgets for Jupyter Notebook" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -5885,7 +5698,6 @@ files = [ name = "win32-setctime" version = "1.1.0" description = "A small Python utility to set file creation time on Windows" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -5900,7 +5712,6 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] name = "wsproto" version = "1.2.0" description = "WebSockets state-machine based protocol implementation" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -5917,4 +5728,4 @@ ingest = ["selenium"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "07b56bcce6ab9ba9c417c9da908bcabeac7d10be0eb51883868f15482d495d70" +content-hash = "31c5eadf695c95382e7cc8796fa4b978d19a6fb3eea46393e81f7ecd5ffdb406" diff --git a/pyproject.toml b/pyproject.toml index fc09c87..2bac0ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ requires = ["poetry-core"] target-version = ["py311"] [tool.coverage.report] -exclude_also = ["if __name__ == .__main__.:"] +exclude_also = ["if __name__ == .__main__.:", "if not isinstance(engine, Engine):"] [tool.coverage.run] branch = true @@ -81,7 +81,7 @@ mypy = "^1.5.1" pandas-stubs = "^2.0.3.230814" pip-audit = "^2.6.1" pip-licenses = "^4.3.2" -ruff = "^0.0.289" +ruff = "^0.0.290" types-cachetools = "^5.3.0.6" types-pyOpenSSL = "*" types-requests = "^2.31.0.2" diff --git a/src/aki_prj23_transparenzregister/ui/assets/company_stats.css b/src/aki_prj23_transparenzregister/ui/assets/company_stats.css new file mode 100644 index 0000000..a51b778 --- /dev/null +++ b/src/aki_prj23_transparenzregister/ui/assets/company_stats.css @@ -0,0 +1,69 @@ +.company-header { + float: left; + background-color: var(--paynes-gray); + border: 1px solid; + width: 100%; +} + +.company-header .company-header-title { + color: white; + text-align: left; + margin: 0; + vertical-align: middle; + padding: 20px; +} + +.stats-wrapper { + float: left; + width: 100%; + background-color: white; + margin-top: 20px; + margin-right: 2%; +} + +.stats-wrapper .widget-large { + background-color: var(--ash-gray); + border: 1px solid; + border-radius: 10px; + width: 45%; + min-width: 600px; + display: inline-block; + vertical-align: middle; + margin-left: 2%; + overflow: hidden; +} + +.stats-wrapper .widget-large .widget-title { + color: var(--raisin-black); + text-align: center; + margin: 0; + padding-bottom: 10px; + padding-top: 10px; +} + +.stats-wrapper .widget-small { + background-color: var(--ash-gray); + border: 1px solid; + border-radius: 10px; + width: 15%; + min-width: 200px; + height: 150px; + display: inline-block; + vertical-align: middle; + margin-top: 10px; + margin-bottom: 10px; + margin-left: 2%; +} + +.stats-wrapper .widget-small .widget-title { + color: var(--raisin-black); + text-align: center; + margin: 0; + padding-bottom: 10px; + padding-top: 10px; +} + +.stats-wrapper .widget-small .widget-content { + color: var(--raisin-black); + text-align: center; +} diff --git a/src/aki_prj23_transparenzregister/ui/assets/header.css b/src/aki_prj23_transparenzregister/ui/assets/header.css new file mode 100644 index 0000000..9a3690e --- /dev/null +++ b/src/aki_prj23_transparenzregister/ui/assets/header.css @@ -0,0 +1,58 @@ +:root { + --light: #edefef; + --lavender-blush: #f3e8ee; + --ash-gray: #bacdb0; + --cambridge-blue: #729b79; + --paynes-gray: #475b63; + --raisin-black: #2e2c2f; +} + +.header-wrapper { + float:left; + background-color: var(--raisin-black); + border: 1px solid; + width: 100%; + overflow: visible; + min-height: 77px; +} + +.header-wrapper .header-title { + float: left; + text-align: left; + margin: 0; + padding-left: 15px; + padding-top: 20px; + padding-bottom: 20px; + vertical-align: middle; +} + +.header-wrapper .header-title .bi-house-door-fill { + color: white; + font-size: x-large; + display: inline-block; + vertical-align: middle; + padding-right: 15px; +} + +.header-wrapper .header-title .header-title-text { + color: white; + text-align: left; + margin: 0; + display: inline-block; + vertical-align: middle; +} + +.header-wrapper .header-search { + float: right; + width: 400px; + margin: 0; + padding-right: 10px; + padding-bottom: 20px; + padding-top: 20px; + vertical-align: middle; + overflow: visible !important; +} + +.header-wrapper .header-search .header-search-dropdown { + overflow: visible; +} diff --git a/src/aki_prj23_transparenzregister/ui/assets/tabs.css b/src/aki_prj23_transparenzregister/ui/assets/tabs.css new file mode 100644 index 0000000..2ddb1cb --- /dev/null +++ b/src/aki_prj23_transparenzregister/ui/assets/tabs.css @@ -0,0 +1,23 @@ +.tabs { + float: left; + margin-top: 20px; + border: 1px solid; + width: 100%; +} + +.tabs .tab-style { + border-bottom: 1px solid #d6d6d6 !important; + padding: 8px !important; + background-color: white !important; + color: var(--paynes-gray) !important; + font-weight: bold !important; +} + +.tabs .selected-tab-style { + border-bottom: 1px solid #d6d6d6 !important; + border-top: 1px solid #d6d6d6 !important; + padding: 8px !important; + color: white !important; + background-color: var(--paynes-gray) !important; + font-weight: bold !important; +} diff --git a/src/aki_prj23_transparenzregister/ui/company_finance_dash.py b/src/aki_prj23_transparenzregister/ui/company_finance_dash.py index ab6c2fa..f81a78f 100644 --- a/src/aki_prj23_transparenzregister/ui/company_finance_dash.py +++ b/src/aki_prj23_transparenzregister/ui/company_finance_dash.py @@ -1,390 +1,78 @@ """Dash.""" import dash_bootstrap_components as dbc -import pandas as pd -import plotly.graph_objs as go -from dash import Dash, Input, Output, callback, dash_table, dcc, html -from dash.exceptions import PreventUpdate -from sqlalchemy.engine import Engine +from dash import Dash, Input, Output, callback, html from aki_prj23_transparenzregister.config.config_providers import JsonFileConfigProvider -from aki_prj23_transparenzregister.utils.sql import connector, entities +from aki_prj23_transparenzregister.ui.ui_elements import ( + create_company_header, + create_company_stats, + create_header, + create_tabs, + get_company_data, + get_finance_data, +) +from aki_prj23_transparenzregister.utils.sql import connector if __name__ == "__main__": session = connector.get_session(JsonFileConfigProvider("./secrets.json")) - query_finance = session.query( - entities.AnnualFinanceStatement, entities.Company.name, entities.Company.id - ).join(entities.Company) - - query_company = session.query(entities.Company, entities.DistrictCourt.name).join( - entities.DistrictCourt - ) - engine = session.bind - if not isinstance(engine, Engine): - raise TypeError - - finance_df: pd.DataFrame = pd.read_sql(str(query_finance), engine) - company_df: pd.DataFrame = pd.read_sql(str(query_company), engine) - - select_company_df = company_df[["company_id", "company_name"]] - select_company_dropdown = select_company_df.to_dict("records") - options = [ - {"label": i["company_name"], "value": i["company_id"]} - for i in select_company_dropdown - ] - - colors = { - "light": "#edefef", - "lavender-blush": "#f3e8ee", - "ash-gray": "#bacdb0", - "cambridge-blue": "#729b79", - "paynes-gray": "#475b63", - "raisin-black": "#2e2c2f", - } - - def financials_figure( - finance_df: pd.DataFrame, company: str, metric: str - ) -> go.Figure: - """Creates plotly line chart for a specific company and a metric.""" - finance_df = finance_df.loc[finance_df["company_name"] == company] - # create figure - fig_line = go.Figure() - # add trace for company 1 - fig_line.add_trace( - go.Scatter( - x=finance_df["annual_finance_statement_date"], - y=finance_df[metric], - name=company, - line_color=colors["raisin-black"], - marker_color=colors["raisin-black"], - ) - ) - # set title and labels - fig_line.update_layout( - title=metric, - xaxis_title="Jahr", - yaxis_title="in Mio.€", - plot_bgcolor=colors["light"], - ) - return fig_line - - tab_style = { - "borderBottom": "1px solid #d6d6d6", - "padding": "6px", - "backgroundColor": "white", - "color": colors["paynes-gray"], - "fontWeight": "bold", - } - - tab_selected_style = { - "borderTop": "1px solid #d6d6d6", - "borderBottom": "1px solid #d6d6d6", - "padding": "6px", - "backgroundColor": colors["paynes-gray"], - "color": "white", - "fontWeight": "bold", - } - - # TBD: get data from database instead of mock data - company = 1 # selected company id - selected_company = company_df.loc[company_df["company_id"] == company] - - turnover = 123456 - stock = "1,23" - company_data = { - "col1": ["Unternehmen", "Straße", "Stadt"], - "col2": [ - selected_company["company_name"][0], - selected_company["company_street"][0], - str( - selected_company["company_zip_code"][0] - + " " - + selected_company["company_city"][0] - ), - ], - "col3": ["Branche", "Amtsgericht", "Gründungsjahr"], - "col4": [ - selected_company["company_sector"][0], - selected_company["district_court_name"][0], - "xxx", - ], - } - df_company_data = pd.DataFrame(data=company_data) + company_df = get_company_data(session) + finance_df = get_finance_data(session) + options = company_df["company_name"].to_dict() app = Dash( __name__, external_stylesheets=[dbc.icons.BOOTSTRAP] ) # use dbc for icons - - kennzahlen_layout = html.Div( - [ - dcc.Graph( - figure=financials_figure( - finance_df, str(company), "annual_finance_statement_ebit" - ) - ) - ] - ) + app.title = "Company Finance Data" app.layout = html.Div( - [ - # title header of page - html.Div( - style={ - "backgroundColor": colors["raisin-black"], - "border": "1px solid", - }, - children=[ - html.I( - className="bi bi-house-door-fill", - style={ - "fontSize": 24, - "paddingLeft": "10px", - "color": "white", - "display": "inline-block", - "verticalAlign": "middle", - }, - ), - html.H1( - children="Transparenzregister für Kapitalgesellschaften", - style={ - "color": "white", - "textAlign": "left", - "margin": "0", - "paddingLeft": "10px", - "paddingBottom": "20px", - "paddingTop": "20px", - "display": "inline-block", - "verticalAlign": "middle", - }, - ), - html.Div( - dcc.Dropdown( - id="select_company", - placeholder="Suche nach Unternehmen oder Person", - ), - style={ - "float": "right", - "width": "30%", - "margin": "0", - "paddingRight": "10px", - "paddingBottom": "20px", - "paddingTop": "20px", - "display": "inline-block", - "verticalAlign": "middle", - }, - ), - ], - ), - # header company name - html.Div( - style={"backgroundColor": colors["paynes-gray"], "border": "1px solid"}, - children=[ - html.H1( - children=selected_company["company_name"][0], - style={ - "color": "white", - "fontSize": 30, - "textAlign": "left", - "margin": "0", - "paddingLeft": "20px", - "paddingBottom": "20px", - "paddingTop": "20px", - }, - ) - ], - ), - html.Div(style={"height": "20px"}), - html.Div(style={"width": "2%", "display": "inline-block"}), - # table basic company information - html.Div( - style={ - "backgroundColor": colors["ash-gray"], - "border": "1px solid", - "border-radius": 10, - "width": "45%", - "height": "150px", - "display": "inline-block", - "vertical-align": "top", - }, - children=[ - html.H5( - children="Stammdaten", - style={ - "color": colors["raisin-black"], - "fontSize": 16, - "textAlign": "center", - "margin": "0", - "paddingBottom": "10px", - "paddingTop": "10px", - }, - ), - dash_table.DataTable( - df_company_data.to_dict("records"), - [{"name": i, "id": i} for i in df_company_data.columns], - style_table={ - "width": "80%", - "overflowX": "auto", - "marginLeft": "auto", - "marginRight": "auto", - "paddingBottom": "20px", - "color": colors["raisin-black"], - }, - # hide header of table - css=[ - { - "selector": "tr:first-child", - "rule": "display: none", - }, - ], - style_cell={"textAlign": "center"}, - style_cell_conditional=[ - {"if": {"column_id": c}, "fontWeight": "bold"} - for c in ["col1", "col3"] - ], - ), - ], - ), - html.Div(style={"width": "2%", "display": "inline-block"}), - html.Div( - style={ - "backgroundColor": colors["ash-gray"], - "border": "1px solid", - "border-radius": 10, - "width": "15%", - "height": "150px", - "display": "inline-block", - "vertical-align": "top", - }, - children=[ - html.H5( - children="Stimmung", - style={ - "color": colors["raisin-black"], - "fontSize": 16, - "textAlign": "center", - "margin": "0", - "paddingBottom": "10px", - "paddingTop": "10px", - }, - ), - ], - ), - html.Div(style={"width": "2%", "display": "inline-block"}), - html.Div( - style={ - "backgroundColor": colors["ash-gray"], - "border": "1px solid", - "border-radius": 10, - "width": "15%", - "height": "150px", - "display": "inline-block", - "vertical-align": "top", - }, - children=[ - html.H5( - children="Aktienkurs", - style={ - "color": colors["raisin-black"], - "fontSize": 16, - "textAlign": "center", - "margin": "0", - "paddingBottom": "10px", - "paddingTop": "10px", - }, - ), - html.H1( - children=stock, - style={ - "color": colors["raisin-black"], - "textAlign": "center", - }, - ), - ], - ), - html.Div(style={"width": "2%", "display": "inline-block"}), - html.Div( - style={ - "backgroundColor": colors["ash-gray"], - "border": "1px solid", - "border-radius": 10, - "width": "15%", - "height": "150px", - "display": "inline-block", - "vertical-align": "top", - }, - children=[ - html.H5( - children="Umsatz", - style={ - "color": colors["raisin-black"], - "fontSize": 16, - "textAlign": "center", - "margin": "0", - "paddingBottom": "10px", - "paddingTop": "10px", - }, - ), - html.H1( - children=turnover, - style={ - "color": colors["raisin-black"], - "textAlign": "center", - }, - ), - ], - ), - html.Div(style={"width": "2%", "display": "inline-block"}), - # ]), - html.Div( - style={ - "marginTop": "20px", - "border": "1px solid", - }, - children=[ - dcc.Tabs( - id="tabs", - value="tab-1", - children=[ - dcc.Tab( - label="Kennzahlen", - value="tab-1", - style=tab_style, - selected_style=tab_selected_style, - children=[kennzahlen_layout], - ), - dcc.Tab( - label="Beteiligte Personen", - value="tab-2", - style=tab_style, - selected_style=tab_selected_style, - ), - dcc.Tab( - label="Stimmung", - value="tab-3", - style=tab_style, - selected_style=tab_selected_style, - ), - dcc.Tab( - label="Verflechtungen", - value="tab-4", - style=tab_style, - selected_style=tab_selected_style, - ), - ], - ), - html.Div(id="tabs-example-content-1"), - ], - ), - ] + className="page_content", + children=[ + create_header(options), + html.Div(id="id-company-header"), + ], ) @callback( Output("select_company", "options"), Input("select_company", "search_value") ) def update_options(search_value: str) -> list: - """Update page based on selected company.""" + """Update dropdown options based on user input. + + Args: + search_value: The input string in the dropdown field entered by the user. + + Returns: + The available companies matching the input. + """ if not search_value: - raise PreventUpdate - return [o for o in options if search_value in o["label"]] + return [{"label": o, "value": key} for key, o in options.items()] + return [ + {"label": o, "value": key} + for key, o in options.items() + if search_value.upper() in o.upper() + ] + + @callback(Output("id-company-header", "children"), Input("select_company", "value")) + def update_output(value_chosen: int) -> html: + """Update page based on chosen company. + + Args: + value_chosen: Id of the selected company. + + Returns: + The html divs of the company page. + """ + label = options.get(value_chosen) + if not label: + return "" + selected_company = str(label) + selected_company_stats = company_df.loc[value_chosen] + selected_finance_df = finance_df.loc[finance_df["company_id"] == value_chosen] + return ( + create_company_header(selected_company), + create_company_stats(selected_company_stats), + create_tabs(selected_finance_df), + ) app.run_server(debug=True) diff --git a/src/aki_prj23_transparenzregister/ui/ui_elements.py b/src/aki_prj23_transparenzregister/ui/ui_elements.py new file mode 100644 index 0000000..ca06b1a --- /dev/null +++ b/src/aki_prj23_transparenzregister/ui/ui_elements.py @@ -0,0 +1,331 @@ +"""Dash elements.""" + +import pandas as pd +import plotly.graph_objs as go +from dash import dash_table, dcc, html +from sqlalchemy.engine import Engine +from sqlalchemy.orm import Session + +from aki_prj23_transparenzregister.utils.sql import entities + +COLORS = { + "light": "#edefef", + "lavender-blush": "#f3e8ee", + "ash-gray": "#bacdb0", + "cambridge-blue": "#729b79", + "paynes-gray": "#475b63", + "raisin-black": "#2e2c2f", +} + + +def get_company_data(session: Session) -> pd.DataFrame: + """Creates a session to the database and get's all available company data. + + Args: + session: A session connecting to the database. + + Returns: + A dataframe containing all available company data including the corresponding district court. + """ + query_company = session.query(entities.Company, entities.DistrictCourt.name).join( + entities.DistrictCourt + ) + engine = session.bind + if not isinstance(engine, Engine): + raise TypeError + + return pd.read_sql(str(query_company), engine, index_col="company_id") + + +def get_finance_data(session: Session) -> pd.DataFrame: + """Creates a session to the database and get's all available company data. + + Args: + session: A session connecting to the database. + + Returns: + A dataframe containing all financial data of all companies. + """ + query_finance = session.query( + entities.AnnualFinanceStatement, entities.Company.name, entities.Company.id + ).join(entities.Company) + + engine = session.bind + if not isinstance(engine, Engine): + raise TypeError + + return pd.read_sql(str(query_finance), engine) + + +def create_header(options: dict) -> html: + """Creates header for dashboard. + + Args: + options: A dictionary with company names and ids for the dropdown. + + Returns: + The html div to create the page's header including the name of the page and the search for companies. + """ + return html.Div( + className="header-wrapper", + children=[ + html.Div( + className="header-title", + children=[ + html.I( + className="bi-house-door-fill", + ), + html.H1( + className="header-title-text", + children="Transparenzregister für Kapitalgesellschaften", + ), + ], + ), + html.Div( + className="header-search", + children=[ + html.Div( + className="header-search-dropdown", + children=[ + dcc.Dropdown( + id="select_company", + options=[ + {"label": o, "value": key} + for key, o in options.items() + ], + placeholder="Suche nach Unternehmen oder Person", + ), + ], + ), + ], + ), + ], + ) + + +def create_company_header(selected_company_name: str) -> html: + """Create company header based on selected company. + + Args: + selected_company_name: The company name that has been chosen in the dropdown. + + Returns: + The html div to create the company header. + """ + return html.Div( + className="company-header", + children=[ + html.H1( + className="company-header-title", + id="id-company-header-title", + children=selected_company_name, + ), + ], + ) + + +def create_company_stats(selected_company_data: pd.Series) -> html: + """Create company stats. + + Args: + selected_company_data: A series containing all company information of the selected company. + + Returns: + The html div to create the company stats table and the three small widgets. + """ + company_data = { + "col1": ["Unternehmen", "Straße", "Stadt"], + "col2": [ + selected_company_data["company_name"], + selected_company_data["company_street"], + str( + selected_company_data["company_zip_code"] + + " " + + selected_company_data["company_city"] + ), + ], + "col3": ["Branche", "Amtsgericht", "Gründungsjahr"], + "col4": [ + selected_company_data["company_sector"], + selected_company_data["district_court_name"], + "xxx", + ], + } + df_company_data = pd.DataFrame(data=company_data) + return html.Div( + className="stats-wrapper", + children=[ + html.Div( + className="widget-large", + children=[ + html.H3( + className="widget-title", + children="Stammdaten", + ), + dash_table.DataTable( + df_company_data.to_dict("records"), + [{"name": i, "id": i} for i in df_company_data.columns], + style_table={ + "width": "90%", + "marginLeft": "auto", + "marginRight": "auto", + "paddingBottom": "20px", + "color": COLORS["raisin-black"], + }, + # hide header of table + css=[ + { + "selector": "tr:first-child", + "rule": "display: none", + }, + ], + style_cell={"textAlign": "center"}, + style_cell_conditional=[ + {"if": {"column_id": c}, "fontWeight": "bold"} + for c in ["col1", "col3"] + ], + style_data={ + "whiteSpace": "normal", + "height": "auto", + }, + ), + ], + ), + html.Div( + className="widget-small", + children=[ + html.H3( + className="widget-title", + children="Stimmung", + ), + ], + ), + html.Div( + className="widget-small", + children=[ + html.H3( + className="widget-title", + children="Aktienkurs", + ), + html.H1( + className="widget-content", + children="123", + ), + ], + ), + html.Div( + className="widget-small", + children=[ + html.H3( + className="widget-title", + children="Umsatz", + ), + html.H1( + className="widget-content", + children="1234", + ), + ], + ), + ], + ) + + +def create_tabs(selected_finance_df: pd.DataFrame) -> html: + """Create tabs for more company information. + + Args: + selected_company_id: Id of the chosen company in the dropdown. + selected_finance_df: A dataframe containing all available finance information of the companies. + + Returns: + The html div to create the tabs of the company page. + """ + return html.Div( + className="tabs", + children=[ + dcc.Tabs( + id="tabs", + value="tab-1", + children=[ + dcc.Tab( + label="Kennzahlen", + value="tab-1", + className="tab-style", + selected_className="selected-tab-style", + children=[kennzahlen_layout(selected_finance_df)], + ), + dcc.Tab( + label="Beteiligte Personen", + value="tab-2", + className="tab-style", + selected_className="selected-tab-style", + ), + dcc.Tab( + label="Stimmung", + value="tab-3", + className="tab-style", + selected_className="selected-tab-style", + ), + dcc.Tab( + label="Verflechtungen", + value="tab-4", + className="tab-style", + selected_className="selected-tab-style", + ), + ], + ), + html.Div(id="tabs-example-content-1"), + ], + ) + + +def kennzahlen_layout(selected_finance_df: pd.DataFrame) -> html: + """Create metrics tab. + + Args: + selected_company_id: Id of the chosen company in the dropdown. + selected_finance_df: A dataframe containing all available finance information of the companies. + + Returns: + The html div to create the metrics tab of the company page. + """ + return html.Div( + [ + dcc.Graph( + figure=financials_figure( + selected_finance_df, "annual_finance_statement_ebit" + ) + ) + ] + ) + + +def financials_figure(selected_finance_df: pd.DataFrame, metric: str) -> go.Figure: + """Creates plotly line chart for a specific company and a metric. + + Args: + selected_finance_df: A dataframe containing all finance information of the selected company. + metric: The metric that should be visualized. + + Returns: + A plotly figure showing the available metric data of the company. + """ + # create figure + fig_line = go.Figure() + # add trace for company 1 + fig_line.add_trace( + go.Scatter( + x=selected_finance_df["annual_finance_statement_date"], + y=selected_finance_df[metric], + line_color=COLORS["raisin-black"], + marker_color=COLORS["raisin-black"], + ) + ) + # set title and labels + fig_line.update_layout( + title=metric, + xaxis_title="Jahr", + yaxis_title="in Mio.€", + plot_bgcolor=COLORS["light"], + ) + return fig_line diff --git a/src/aki_prj23_transparenzregister/utils/data_transfer.py b/src/aki_prj23_transparenzregister/utils/data_transfer.py index 038a0ab..f28712c 100644 --- a/src/aki_prj23_transparenzregister/utils/data_transfer.py +++ b/src/aki_prj23_transparenzregister/utils/data_transfer.py @@ -124,7 +124,7 @@ def get_district_court_id(name: str, city: str | None, db: Session) -> int: @cached(cache=LRUCache(maxsize=2000), key=lambda name, surname, date_of_birth, db: hash((name, surname, date_of_birth))) # type: ignore def get_person_id( - name: str, surname: str, date_of_birth: date | str, db: Session + name: str, surname: str, date_of_birth: date | str | None, db: Session ) -> int: """Identifies the id of and court. diff --git a/tests/conftest.py b/tests/conftest.py index b8da9d9..e3b4397 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ import datetime import os from collections.abc import Generator from inspect import getmembers, isfunction +from typing import Any import pytest from sqlalchemy.engine import Engine @@ -49,7 +50,60 @@ def empty_db() -> Generator[Session, None, None]: @pytest.fixture() -def full_db(empty_db: Session) -> Session: +def finance_statements() -> list[dict[str, Any]]: + """Creates a list of finance statements.""" + return [ + { + "id": 1, + "company_id": 1, + "date": datetime.date.fromisoformat("2023-01-01"), + "total_volume": 1000.0, + "ebit": 1000.0, + "ebitda": 1000.0, + "ebit_margin": 1000.0, + "total_balance": 1000.0, + "equity": 1000.0, + "debt": 1000.0, + "return_on_equity": 1000.0, + "capital_turnover_rate": 1000.0, + "current_liabilities": 1000.0, + "dividends": float("NaN"), + "net_income": float("NaN"), + "assets": 1000.0, + "long_term_debt": 1000.0, + "short_term_debt": 1000.0, + "revenue": 1000.0, + "cash_flow": 1000.0, + "current_assets": 1000.0, + }, + { + "id": 2, + "company_id": 1, + "date": datetime.date.fromisoformat("2022-01-01"), + "total_volume": 1100.0, + "ebit": 1100.0, + "ebitda": 1100.0, + "ebit_margin": 1100.0, + "total_balance": 1100.0, + "equity": 1100.0, + "debt": 1100.0, + "return_on_equity": 1100.0, + "capital_turnover_rate": 1100.0, + "current_liabilities": 1100.0, + "dividends": float("NaN"), + "net_income": float("NaN"), + "assets": 1100.0, + "long_term_debt": 1100.0, + "short_term_debt": 1100.0, + "revenue": 1100.0, + "cash_flow": 1100.0, + "current_assets": 1100.0, + }, + ] + + +@pytest.fixture() +def full_db(empty_db: Session, finance_statements: list[dict[str, Any]]) -> Session: """Fills a db with some test data.""" empty_db.add_all( [ @@ -112,5 +166,13 @@ def full_db(empty_db: Session) -> Session: ] ) empty_db.commit() + + empty_db.add_all( + [ + entities.AnnualFinanceStatement(**finance_statement) + for finance_statement in finance_statements + ] + ) + empty_db.commit() # print(pd.read_sql_table("company", empty_db.bind).to_string()) return empty_db diff --git a/tests/ui/ui_elements_test.py b/tests/ui/ui_elements_test.py new file mode 100644 index 0000000..21650df --- /dev/null +++ b/tests/ui/ui_elements_test.py @@ -0,0 +1,118 @@ +"""Tests for ui elements.""" + +import pandas as pd +from sqlalchemy.orm import Session + +from aki_prj23_transparenzregister.ui import ui_elements + + +def test_import() -> None: + """Checks if an import co ui_elements can be made.""" + assert ui_elements is not None + + +def test_get_company_data(full_db: Session) -> None: + """Checks if data from the company and district court tables can be accessed.""" + company_df = ui_elements.get_company_data(full_db) + + test_data = pd.DataFrame( + { + "company_id": {0: 1, 1: 2, 2: 3}, + "company_hr": {0: "HRB 123", 1: "HRB 123", 2: "HRB 12"}, + "company_court_id": {0: 2, 1: 1, 2: 2}, + "company_name": { + 0: "Some Company GmbH", + 1: "Other Company GmbH", + 2: "Third Company GmbH", + }, + "company_street": {0: "Sesamstr.", 1: "Sesamstr.", 2: None}, + "company_zip_code": {0: "12345", 1: "12345", 2: None}, + "company_city": {0: "TV City", 1: "TV City", 2: None}, + "company_last_update": { + 0: "2023-01-01", + 1: "2023-01-01", + 2: "2023-01-01", + }, + "company_sector": {0: None, 1: None, 2: None}, + "district_court_name": { + 0: "Amtsgericht Dortmund", + 1: "Amtsgericht Bochum", + 2: "Amtsgericht Dortmund", + }, + } + ) + test_data = test_data.set_index("company_id") + pd.testing.assert_frame_equal(company_df, test_data) + + +def test_get_finance_data(full_db: Session) -> None: + """Checks if data from the company and finance tables can be accessed.""" + finance_df = ui_elements.get_finance_data(full_db) + test_data = pd.DataFrame( + { + "annual_finance_statement_id": {0: 1, 1: 2}, + "annual_finance_statement_company_id": {0: 1, 1: 1}, + "annual_finance_statement_date": {0: "2023-01-01", 1: "2022-01-01"}, + "annual_finance_statement_total_volume": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_ebit": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_ebitda": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_ebit_margin": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_total_balance": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_equity": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_debt": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_return_on_equity": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_capital_turnover_rate": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_current_liabilities": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_dividends": {0: None, 1: None}, + "annual_finance_statement_net_income": {0: None, 1: None}, + "annual_finance_statement_assets": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_long_term_debt": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_short_term_debt": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_revenue": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_cash_flow": {0: 1000.0, 1: 1100.0}, + "annual_finance_statement_current_assets": {0: 1000.0, 1: 1100.0}, + "company_name": {0: "Some Company GmbH", 1: "Some Company GmbH"}, + "company_id": {0: 1, 1: 1}, + } + ) + pd.testing.assert_frame_equal(finance_df, test_data) + + +def test_create_header() -> None: + """Checks if the header can be created.""" + options = {1: "a", 2: "b"} + ui_elements.create_header(options) + + +def test_create_company_header() -> None: + """Checks if the company header can be created.""" + selected_company = "Test GmbH" + ui_elements.create_company_header(selected_company) + + +def test_create_company_stats(full_db: Session) -> None: + """Checks if the company widgets can be created.""" + company_df = ui_elements.get_company_data(full_db) + value_chosen = 1 + selected_company_stats = company_df.loc[value_chosen] + ui_elements.create_company_stats(selected_company_stats) + + +def test_create_tabs(full_db: Session) -> None: + """Checks if the tabs of the company page can be created.""" + selected_company_id = 1 + finance_df = ui_elements.get_finance_data(full_db) + selected_finance_df = finance_df.loc[ + finance_df["company_id"] == selected_company_id + ] + ui_elements.create_tabs(selected_finance_df) + + +def test_kennzahlen_layout(full_db: Session) -> None: + """Checks if the financial metric layout of the company page can be created.""" + selected_company_id = 1 + finance_df = ui_elements.get_finance_data(full_db) + selected_finance_df = finance_df.loc[ + finance_df["company_id"] == selected_company_id + ] + ui_elements.kennzahlen_layout(selected_finance_df) diff --git a/tests/utils/data_transfer_test.py b/tests/utils/data_transfer_test.py index 275aa7f..b92bb28 100644 --- a/tests/utils/data_transfer_test.py +++ b/tests/utils/data_transfer_test.py @@ -168,7 +168,7 @@ def test_get_person_id_value_check( data_transfer.get_person_id( firstname, surname, - date.fromisoformat(date_str) if date_str else None, # type: ignore + date.fromisoformat(date_str) if date_str else None, full_db, ) @@ -941,7 +941,11 @@ def test_add_annual_report_to_unknown_company( @pytest.mark.parametrize("year", [2023, 2025, 2020]) @pytest.mark.parametrize("short_term_debt", [2023.2, 2025.5, 2020.5, float("NaN")]) def test_add_annual_report( - short_term_debt: float, company_id: int, year: int, full_db: Session + short_term_debt: float, + company_id: int, + year: int, + finance_statements: list[dict[str, Any]], + full_db: Session, ) -> None: """Tests the addition of annual financial records.""" data_transfer.add_annual_report( @@ -961,34 +965,38 @@ def test_add_annual_report( df_prior = pd.read_sql_table( entities.AnnualFinanceStatement.__tablename__, full_db.bind # type: ignore ) + expected_results = pd.DataFrame( + finance_statements + + [ + { + "id": 3, + "company_id": company_id, + "date": pd.to_datetime(date(year, 1, 1)), + "total_volume": float("NaN"), + "ebit": 123.0, + "ebitda": 235.0, + "ebit_margin": float("NaN"), + "total_balance": float("NaN"), + "equity": float("NaN"), + "debt": float("NaN"), + "return_on_equity": float("NaN"), + "capital_turnover_rate": float("NaN"), + "current_liabilities": float("NaN"), + "dividends": float("NaN"), + "net_income": float("NaN"), + "assets": float("NaN"), + "long_term_debt": float("NaN"), + "short_term_debt": short_term_debt, + "revenue": float("NaN"), + "cash_flow": float("NaN"), + "current_assets": float("NaN"), + } + ] + ) + + expected_results["date"] = pd.to_datetime(expected_results["date"]) pd.testing.assert_frame_equal( - pd.DataFrame( - [ - { - "id": 1, - "company_id": company_id, - "date": pd.to_datetime(date(year, 1, 1)), - "total_volume": float("NaN"), - "ebit": 123.0, - "ebitda": 235.0, - "ebit_margin": float("NaN"), - "total_balance": float("NaN"), - "equity": float("NaN"), - "debt": float("NaN"), - "return_on_equity": float("NaN"), - "capital_turnover_rate": float("NaN"), - "current_liabilities": float("NaN"), - "dividends": float("NaN"), - "net_income": float("NaN"), - "assets": float("NaN"), - "long_term_debt": float("NaN"), - "short_term_debt": short_term_debt, - "revenue": float("NaN"), - "cash_flow": float("NaN"), - "current_assets": float("NaN"), - } - ] - ), + expected_results, df_prior, ) From 495cd90d8528427ad5d4ee048da5b549cc8e5f13 Mon Sep 17 00:00:00 2001 From: Philipp Horstenkamp Date: Thu, 21 Sep 2023 18:09:16 +0200 Subject: [PATCH 10/11] Reduced required code coverage to 70% (#140) --- .github/workflows/test-and-build-action.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-and-build-action.yaml b/.github/workflows/test-and-build-action.yaml index 758f932..9653c3b 100644 --- a/.github/workflows/test-and-build-action.yaml +++ b/.github/workflows/test-and-build-action.yaml @@ -58,9 +58,9 @@ jobs: with: coverageFile: coverage.xml token: ${{ secrets.GITHUB_TOKEN }} - thresholdAll: 0.8 - thresholdNew: 0.8 - thresholdModified: 0.8 + thresholdAll: 0.7 + thresholdNew: 0.7 + thresholdModified: 0.7 coverage_report: runs-on: ubuntu-latest From 286636dfa4b56b0de862b6ae4013823b8508f7b6 Mon Sep 17 00:00:00 2001 From: Philipp Horstenkamp Date: Sat, 23 Sep 2023 09:22:14 +0200 Subject: [PATCH 11/11] Reduced the execution of tests and lint actions on pull requests (#143) This change will change the pipline to limit duplicat execution of tests and lint actions in pull requests to save computing time. --- .github/workflows/documentation.yaml | 1 + .github/workflows/lint-actions.yaml | 1 + .github/workflows/test-and-build-action.yaml | 3 +++ .gitignore | 1 + 4 files changed, 6 insertions(+) diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml index eb7652b..3fcb11b 100644 --- a/.github/workflows/documentation.yaml +++ b/.github/workflows/documentation.yaml @@ -5,6 +5,7 @@ on: branches: - main pull_request: + types: [opened, reopened, synchronize] jobs: doc-build: diff --git a/.github/workflows/lint-actions.yaml b/.github/workflows/lint-actions.yaml index 81cf648..4563a11 100644 --- a/.github/workflows/lint-actions.yaml +++ b/.github/workflows/lint-actions.yaml @@ -3,6 +3,7 @@ name: Python-Lint on: push: pull_request: + types: [reopened, opened] jobs: Black: diff --git a/.github/workflows/test-and-build-action.yaml b/.github/workflows/test-and-build-action.yaml index 9653c3b..b69a149 100644 --- a/.github/workflows/test-and-build-action.yaml +++ b/.github/workflows/test-and-build-action.yaml @@ -2,6 +2,7 @@ name: Test & Build on: pull_request: + types: [reopened, opened, synchronize] push: jobs: @@ -63,6 +64,7 @@ jobs: thresholdModified: 0.7 coverage_report: + if: ${{ github.event_name == 'push' }} runs-on: ubuntu-latest needs: test steps: @@ -99,6 +101,7 @@ jobs: build: runs-on: ubuntu-latest needs: test + if: ${{ github.event_name == 'push' }} steps: - name: Setup Python uses: actions/setup-python@v4 diff --git a/.gitignore b/.gitignore index d5f8fdc..f536711 100644 --- a/.gitignore +++ b/.gitignore @@ -221,3 +221,4 @@ replay_pid* /lbr-audit.md /.ruff_cache/ /Jupyter/test.ipynb +/secrets*.json