diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 17f5ca9..dda4fb9 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -8,7 +8,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: 3.13 + python-version: 3.12 architecture: x64 - run: python --version diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4ac03bc..da7d9d2 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -8,7 +8,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: 3.13 + python-version: 3.12 architecture: x64 - run: python --version diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ad6e324..318be47 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,7 +8,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: 3.13 + python-version: 3.12 architecture: x64 - run: python --version diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c4a35e..37db62a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,6 @@ jobs: 3.10 3.11 3.12 - 3.13 architecture: x64 - run: python --version diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml index 7c8a7a9..3a9b498 100644 --- a/.github/workflows/test_coverage.yml +++ b/.github/workflows/test_coverage.yml @@ -14,7 +14,6 @@ jobs: 3.10 3.11 3.12 - 3.13 architecture: x64 - run: python --version diff --git a/.github/workflows/test_docstrings.yml b/.github/workflows/test_docstrings.yml index 7c32390..cc55589 100644 --- a/.github/workflows/test_docstrings.yml +++ b/.github/workflows/test_docstrings.yml @@ -8,7 +8,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: 3.13 + python-version: 3.12 architecture: x64 - run: python --version diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f29c6fa..8d703e6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,7 +5,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12"] name: test-${{ matrix.python-version }} steps: - uses: actions/checkout@v4 diff --git a/.readthedocs.yml b/.readthedocs.yml index e427896..78c25f1 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -3,7 +3,7 @@ version: 2 build: os: ubuntu-24.04 tools: - python: "3.13" + python: "3.12" sphinx: configuration: docs/conf.py diff --git a/README.md b/README.md index 382d774..890a074 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ To execute all default tasks, simply invoke: `nox` This includes running the test suite for the project's main Python version - (i.e., [3.13](https://devguide.python.org/versions/)). + (i.e., [3.12](https://devguide.python.org/versions/)). #### Code Formatting & Linting diff --git a/noxfile.py b/noxfile.py index 5045946..0ce38b7 100644 --- a/noxfile.py +++ b/noxfile.py @@ -57,7 +57,7 @@ def load_supported_python_versions(*, reverse: bool = False) -> list[str]: SUPPORTED_PYTHONS = load_supported_python_versions(reverse=True) -MAIN_PYTHON = "3.13" +MAIN_PYTHON = "3.12" DOCS_SRC, DOCS_BUILD = ("docs/", ".cache/docs/") TESTS_LOCATION = "tests/" @@ -205,10 +205,8 @@ def lint(session: nox.Session) -> None: "flake8-isort", "flake8-quotes", "flake8-string-format", - "flake8-todos", "flake8-pyproject", "flake8-pytest-style", - "flake8-unused-arguments", "mypy", "pep8-naming", # flake8 plug-in "pydoclint[flake8]", @@ -231,15 +229,10 @@ TEST_DEPENDENCIES = ( "packaging", "pytest", "pytest-cov", - "pytest-randomly", - "pytest-repeat", "semver", - 'typing-extensions; python_version < "3.11"', # to support Python 3.9 & 3.10 "xdoctest", ) -TEST_RANDOM_SEED = "--randomly-seed=42" - @nox.session(python=SUPPORTED_PYTHONS) def test(session: nox.Session) -> None: @@ -255,16 +248,6 @@ def test(session: nox.Session) -> None: args = posargs or ( "--cov", "--no-cov-on-fail", - *( # If this function is run via the "test-fast" session, - ( # the following arguments are added: - "--cov-fail-under=100", - "--smoke-tests-only", - "--exitfirst", - ) - if session.env.get("_smoke_tests_only") - else () - ), - TEST_RANDOM_SEED, TESTS_LOCATION, ) @@ -301,8 +284,6 @@ def test_coverage_run(session: nox.Session) -> None: session.install(".") install_pinned(session, "coverage", *TEST_DEPENDENCIES) - session.env["N_RANDOM_DRAWS"] = "10" - session.env["NO_CROSS_REFERENCE"] = "true" session.run( "python", "-m", @@ -310,7 +291,6 @@ def test_coverage_run(session: nox.Session) -> None: "run", "-m", "pytest", - TEST_RANDOM_SEED, TESTS_LOCATION, ) @@ -349,19 +329,6 @@ def test_docstrings(session: nox.Session) -> None: session.run("xdoctest", "src/lalib") -@nox.session(name="test-fast", python=MAIN_PYTHON) -def test_fast(session: nox.Session) -> None: - """Test code with `pytest`, selected (smoke) tests only. - - The (unit) test cases are selected such that their number - is minimal but the achieved coverage remains at 100%. - """ - # See implementation notes in `pre_commit_test_hook()` below - session.env["_smoke_tests_only"] = "true" - session.env["NO_CROSS_REFERENCE"] = "true" - test(session) - - @nox.session(name="clean-cwd", python=MAIN_PYTHON, venv_backend="none") def clean_cwd(session: nox.Session) -> None: """Remove (almost) all glob patterns listed in git's ignore file. @@ -463,11 +430,6 @@ def start(session: nox.Session) -> None: session.env["PIP_CACHE_DIR"] = ".cache/pip" session.env["PIP_DISABLE_PIP_VERSION_CHECK"] = "true" - if session.python in ("3.13", "3.12", "3.11"): - session.env["PRAGMA_SUPPORT_39_N_310"] = "to support Python 3.9 & 3.10" - else: - session.env["PRAGMA_SUPPORT_39_N_310"] = f"{_magic_number =}" - def suppress_poetry_export_warning(session: nox.Session) -> None: """Temporary fix to avoid poetry's warning ... diff --git a/poetry.lock b/poetry.lock index cfec135..e172dc3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -70,13 +70,13 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "bandit" -version = "1.7.10" +version = "1.7.9" description = "Security oriented static analyser for python code." optional = false python-versions = ">=3.8" files = [ - {file = "bandit-1.7.10-py3-none-any.whl", hash = "sha256:665721d7bebbb4485a339c55161ac0eedde27d51e638000d91c8c2d68343ad02"}, - {file = "bandit-1.7.10.tar.gz", hash = "sha256:59ed5caf5d92b6ada4bf65bc6437feea4a9da1093384445fed4d472acc6cff7b"}, + {file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"}, + {file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"}, ] [package.dependencies] @@ -94,33 +94,33 @@ yaml = ["PyYAML"] [[package]] name = "black" -version = "24.10.0" +version = "24.8.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" files = [ - {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, - {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, - {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, - {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, - {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, - {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, - {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, - {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, - {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, - {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, - {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, - {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, - {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, - {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, - {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, - {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, - {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, - {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, - {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, - {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, - {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, - {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, + {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, + {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, + {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, + {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, + {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, + {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, + {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, + {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, + {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, + {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, + {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, + {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, + {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, + {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, + {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, + {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, + {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, + {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, + {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, + {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, + {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, + {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, ] [package.dependencies] @@ -134,7 +134,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.10)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -162,116 +162,101 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.4.0" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] @@ -301,73 +286,83 @@ files = [ [[package]] name = "coverage" -version = "7.6.3" +version = "7.6.1" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" files = [ - {file = "coverage-7.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6da42bbcec130b188169107ecb6ee7bd7b4c849d24c9370a0c884cf728d8e976"}, - {file = "coverage-7.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c222958f59b0ae091f4535851cbb24eb57fc0baea07ba675af718fb5302dddb2"}, - {file = "coverage-7.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab84a8b698ad5a6c365b08061920138e7a7dd9a04b6feb09ba1bfae68346ce6d"}, - {file = "coverage-7.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70a6756ce66cd6fe8486c775b30889f0dc4cb20c157aa8c35b45fd7868255c5c"}, - {file = "coverage-7.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c2e6fa98032fec8282f6b27e3f3986c6e05702828380618776ad794e938f53a"}, - {file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:921fbe13492caf6a69528f09d5d7c7d518c8d0e7b9f6701b7719715f29a71e6e"}, - {file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6d99198203f0b9cb0b5d1c0393859555bc26b548223a769baf7e321a627ed4fc"}, - {file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:87cd2e29067ea397a47e352efb13f976eb1b03e18c999270bb50589323294c6e"}, - {file = "coverage-7.6.3-cp310-cp310-win32.whl", hash = "sha256:a3328c3e64ea4ab12b85999eb0779e6139295bbf5485f69d42cf794309e3d007"}, - {file = "coverage-7.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:bca4c8abc50d38f9773c1ec80d43f3768df2e8576807d1656016b9d3eeaa96fd"}, - {file = "coverage-7.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c51ef82302386d686feea1c44dbeef744585da16fcf97deea2a8d6c1556f519b"}, - {file = "coverage-7.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0ca37993206402c6c35dc717f90d4c8f53568a8b80f0bf1a1b2b334f4d488fba"}, - {file = "coverage-7.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c77326300b839c44c3e5a8fe26c15b7e87b2f32dfd2fc9fee1d13604347c9b38"}, - {file = "coverage-7.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e484e479860e00da1f005cd19d1c5d4a813324e5951319ac3f3eefb497cc549"}, - {file = "coverage-7.6.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c6c0f4d53ef603397fc894a895b960ecd7d44c727df42a8d500031716d4e8d2"}, - {file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:37be7b5ea3ff5b7c4a9db16074dc94523b5f10dd1f3b362a827af66a55198175"}, - {file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:43b32a06c47539fe275106b376658638b418c7cfdfff0e0259fbf877e845f14b"}, - {file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee77c7bef0724165e795b6b7bf9c4c22a9b8468a6bdb9c6b4281293c6b22a90f"}, - {file = "coverage-7.6.3-cp311-cp311-win32.whl", hash = "sha256:43517e1f6b19f610a93d8227e47790722c8bf7422e46b365e0469fc3d3563d97"}, - {file = "coverage-7.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:04f2189716e85ec9192df307f7c255f90e78b6e9863a03223c3b998d24a3c6c6"}, - {file = "coverage-7.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27bd5f18d8f2879e45724b0ce74f61811639a846ff0e5c0395b7818fae87aec6"}, - {file = "coverage-7.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d546cfa78844b8b9c1c0533de1851569a13f87449897bbc95d698d1d3cb2a30f"}, - {file = "coverage-7.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9975442f2e7a5cfcf87299c26b5a45266ab0696348420049b9b94b2ad3d40234"}, - {file = "coverage-7.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:583049c63106c0555e3ae3931edab5669668bbef84c15861421b94e121878d3f"}, - {file = "coverage-7.6.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2341a78ae3a5ed454d524206a3fcb3cec408c2a0c7c2752cd78b606a2ff15af4"}, - {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a4fb91d5f72b7e06a14ff4ae5be625a81cd7e5f869d7a54578fc271d08d58ae3"}, - {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e279f3db904e3b55f520f11f983cc8dc8a4ce9b65f11692d4718ed021ec58b83"}, - {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa23ce39661a3e90eea5f99ec59b763b7d655c2cada10729ed920a38bfc2b167"}, - {file = "coverage-7.6.3-cp312-cp312-win32.whl", hash = "sha256:52ac29cc72ee7e25ace7807249638f94c9b6a862c56b1df015d2b2e388e51dbd"}, - {file = "coverage-7.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:40e8b1983080439d4802d80b951f4a93d991ef3261f69e81095a66f86cf3c3c6"}, - {file = "coverage-7.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9134032f5aa445ae591c2ba6991d10136a1f533b1d2fa8f8c21126468c5025c6"}, - {file = "coverage-7.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:99670790f21a96665a35849990b1df447993880bb6463a0a1d757897f30da929"}, - {file = "coverage-7.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc7d6b380ca76f5e817ac9eef0c3686e7834c8346bef30b041a4ad286449990"}, - {file = "coverage-7.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7b26757b22faf88fcf232f5f0e62f6e0fd9e22a8a5d0d5016888cdfe1f6c1c4"}, - {file = "coverage-7.6.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c59d6a4a4633fad297f943c03d0d2569867bd5372eb5684befdff8df8522e39"}, - {file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f263b18692f8ed52c8de7f40a0751e79015983dbd77b16906e5b310a39d3ca21"}, - {file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:79644f68a6ff23b251cae1c82b01a0b51bc40c8468ca9585c6c4b1aeee570e0b"}, - {file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:71967c35828c9ff94e8c7d405469a1fb68257f686bca7c1ed85ed34e7c2529c4"}, - {file = "coverage-7.6.3-cp313-cp313-win32.whl", hash = "sha256:e266af4da2c1a4cbc6135a570c64577fd3e6eb204607eaff99d8e9b710003c6f"}, - {file = "coverage-7.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:ea52bd218d4ba260399a8ae4bb6b577d82adfc4518b93566ce1fddd4a49d1dce"}, - {file = "coverage-7.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8d4c6ea0f498c7c79111033a290d060c517853a7bcb2f46516f591dab628ddd3"}, - {file = "coverage-7.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:331b200ad03dbaa44151d74daeb7da2cf382db424ab923574f6ecca7d3b30de3"}, - {file = "coverage-7.6.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54356a76b67cf8a3085818026bb556545ebb8353951923b88292556dfa9f812d"}, - {file = "coverage-7.6.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebec65f5068e7df2d49466aab9128510c4867e532e07cb6960075b27658dca38"}, - {file = "coverage-7.6.3-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d33a785ea8354c480515e781554d3be582a86297e41ccbea627a5c632647f2cd"}, - {file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f7ddb920106bbbbcaf2a274d56f46956bf56ecbde210d88061824a95bdd94e92"}, - {file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:70d24936ca6c15a3bbc91ee9c7fc661132c6f4c9d42a23b31b6686c05073bde5"}, - {file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c30e42ea11badb147f0d2e387115b15e2bd8205a5ad70d6ad79cf37f6ac08c91"}, - {file = "coverage-7.6.3-cp313-cp313t-win32.whl", hash = "sha256:365defc257c687ce3e7d275f39738dcd230777424117a6c76043459db131dd43"}, - {file = "coverage-7.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:23bb63ae3f4c645d2d82fa22697364b0046fbafb6261b258a58587441c5f7bd0"}, - {file = "coverage-7.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:da29ceabe3025a1e5a5aeeb331c5b1af686daab4ff0fb4f83df18b1180ea83e2"}, - {file = "coverage-7.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df8c05a0f574d480947cba11b947dc41b1265d721c3777881da2fb8d3a1ddfba"}, - {file = "coverage-7.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec1e3b40b82236d100d259854840555469fad4db64f669ab817279eb95cd535c"}, - {file = "coverage-7.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4adeb878a374126f1e5cf03b87f66279f479e01af0e9a654cf6d1509af46c40"}, - {file = "coverage-7.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43d6a66e33b1455b98fc7312b124296dad97a2e191c80320587234a77b1b736e"}, - {file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1990b1f4e2c402beb317840030bb9f1b6a363f86e14e21b4212e618acdfce7f6"}, - {file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:12f9515d875859faedb4144fd38694a761cd2a61ef9603bf887b13956d0bbfbb"}, - {file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99ded130555c021d99729fabd4ddb91a6f4cc0707df4b1daf912c7850c373b13"}, - {file = "coverage-7.6.3-cp39-cp39-win32.whl", hash = "sha256:c3a79f56dee9136084cf84a6c7c4341427ef36e05ae6415bf7d787c96ff5eaa3"}, - {file = "coverage-7.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:aac7501ae73d4a02f4b7ac8fcb9dc55342ca98ffb9ed9f2dfb8a25d53eda0e4d"}, - {file = "coverage-7.6.3-pp39.pp310-none-any.whl", hash = "sha256:b9853509b4bf57ba7b1f99b9d866c422c9c5248799ab20e652bbb8a184a38181"}, - {file = "coverage-7.6.3.tar.gz", hash = "sha256:bb7d5fe92bd0dc235f63ebe9f8c6e0884f7360f88f3411bfed1350c872ef2054"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.dependencies] @@ -378,13 +373,13 @@ toml = ["tomli"] [[package]] name = "distlib" -version = "0.3.9" +version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, - {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] [[package]] @@ -436,18 +431,18 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.16.1" +version = "3.16.0" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, - {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, + {file = "filelock-3.16.0-py3-none-any.whl", hash = "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609"}, + {file = "filelock-3.16.0.tar.gz", hash = "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.3)"] typing = ["typing-extensions (>=4.12.2)"] [[package]] @@ -709,47 +704,15 @@ files = [ [package.dependencies] flake8 = "*" -[[package]] -name = "flake8-todos" -version = "0.3.1" -description = "Python linter to check TODO comments for consistency and best practice." -optional = false -python-versions = ">=3.8" -files = [ - {file = "flake8_todos-0.3.1-py3-none-any.whl", hash = "sha256:0b4faca80bec49be7bf8e5dd46eec12f619e09cecac8a84a87775272300222b2"}, - {file = "flake8_todos-0.3.1.tar.gz", hash = "sha256:942101a08f831d7d9f7d864d86169f3bcfaf8b6d56a004c0280eb51725266b16"}, -] - -[package.dependencies] -flake8 = "*" -pycodestyle = "*" - -[package.extras] -dev = ["isort[pyproject]", "pytest"] - -[[package]] -name = "flake8-unused-arguments" -version = "0.0.13" -description = "flake8 extension to warn on unused function arguments" -optional = false -python-versions = "*" -files = [ - {file = "flake8-unused-arguments-0.0.13.tar.gz", hash = "sha256:c5894d294424bf7915e44dff0917222c454151016f96edc55b9f847bf307f3f7"}, - {file = "flake8_unused_arguments-0.0.13-py3-none-any.whl", hash = "sha256:df6a76b73a6ce67720332182a80f2f0a80783cab1ccae8175f39787cd3a74b31"}, -] - -[package.dependencies] -flake8 = ">3.0.0" - [[package]] name = "identify" -version = "2.6.1" +version = "2.6.0" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, - {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, + {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, + {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, ] [package.extras] @@ -757,18 +720,15 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.10" +version = "3.8" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, + {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, + {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, ] -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - [[package]] name = "imagesize" version = "1.4.1" @@ -782,26 +742,22 @@ files = [ [[package]] name = "importlib-metadata" -version = "8.5.0" +version = "8.4.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, - {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, + {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, + {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, ] [package.dependencies] -zipp = ">=3.20" +zipp = ">=0.5" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] -cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["pytest-mypy"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "iniconfig" @@ -871,72 +827,71 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "3.0.1" +version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.9" +python-versions = ">=3.7" files = [ - {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-win32.whl", hash = "sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-win32.whl", hash = "sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-win32.whl", hash = "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-win32.whl", hash = "sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-win32.whl", hash = "sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-win32.whl", hash = "sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b"}, - {file = "markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] @@ -963,43 +918,38 @@ files = [ [[package]] name = "mypy" -version = "1.12.0" +version = "1.11.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4397081e620dc4dc18e2f124d5e1d2c288194c2c08df6bdb1db31c38cd1fe1ed"}, - {file = "mypy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:684a9c508a283f324804fea3f0effeb7858eb03f85c4402a967d187f64562469"}, - {file = "mypy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cabe4cda2fa5eca7ac94854c6c37039324baaa428ecbf4de4567279e9810f9e"}, - {file = "mypy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:060a07b10e999ac9e7fa249ce2bdcfa9183ca2b70756f3bce9df7a92f78a3c0a"}, - {file = "mypy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:0eff042d7257f39ba4ca06641d110ca7d2ad98c9c1fb52200fe6b1c865d360ff"}, - {file = "mypy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b86de37a0da945f6d48cf110d5206c5ed514b1ca2614d7ad652d4bf099c7de7"}, - {file = "mypy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20c7c5ce0c1be0b0aea628374e6cf68b420bcc772d85c3c974f675b88e3e6e57"}, - {file = "mypy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a64ee25f05fc2d3d8474985c58042b6759100a475f8237da1f4faf7fcd7e6309"}, - {file = "mypy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:faca7ab947c9f457a08dcb8d9a8664fd438080e002b0fa3e41b0535335edcf7f"}, - {file = "mypy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:5bc81701d52cc8767005fdd2a08c19980de9ec61a25dbd2a937dfb1338a826f9"}, - {file = "mypy-1.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8462655b6694feb1c99e433ea905d46c478041a8b8f0c33f1dab00ae881b2164"}, - {file = "mypy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:923ea66d282d8af9e0f9c21ffc6653643abb95b658c3a8a32dca1eff09c06475"}, - {file = "mypy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ebf9e796521f99d61864ed89d1fb2926d9ab6a5fab421e457cd9c7e4dd65aa9"}, - {file = "mypy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e478601cc3e3fa9d6734d255a59c7a2e5c2934da4378f3dd1e3411ea8a248642"}, - {file = "mypy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:c72861b7139a4f738344faa0e150834467521a3fba42dc98264e5aa9507dd601"}, - {file = "mypy-1.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52b9e1492e47e1790360a43755fa04101a7ac72287b1a53ce817f35899ba0521"}, - {file = "mypy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48d3e37dd7d9403e38fa86c46191de72705166d40b8c9f91a3de77350daa0893"}, - {file = "mypy-1.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f106db5ccb60681b622ac768455743ee0e6a857724d648c9629a9bd2ac3f721"}, - {file = "mypy-1.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:233e11b3f73ee1f10efada2e6da0f555b2f3a5316e9d8a4a1224acc10e7181d3"}, - {file = "mypy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:4ae8959c21abcf9d73aa6c74a313c45c0b5a188752bf37dace564e29f06e9c1b"}, - {file = "mypy-1.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eafc1b7319b40ddabdc3db8d7d48e76cfc65bbeeafaa525a4e0fa6b76175467f"}, - {file = "mypy-1.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9b9ce1ad8daeb049c0b55fdb753d7414260bad8952645367e70ac91aec90e07e"}, - {file = "mypy-1.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfe012b50e1491d439172c43ccb50db66d23fab714d500b57ed52526a1020bb7"}, - {file = "mypy-1.12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c40658d4fa1ab27cb53d9e2f1066345596af2f8fe4827defc398a09c7c9519b"}, - {file = "mypy-1.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:dee78a8b9746c30c1e617ccb1307b351ded57f0de0d287ca6276378d770006c0"}, - {file = "mypy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b5df6c8a8224f6b86746bda716bbe4dbe0ce89fd67b1fa4661e11bfe38e8ec8"}, - {file = "mypy-1.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5feee5c74eb9749e91b77f60b30771563327329e29218d95bedbe1257e2fe4b0"}, - {file = "mypy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:77278e8c6ffe2abfba6db4125de55f1024de9a323be13d20e4f73b8ed3402bd1"}, - {file = "mypy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dcfb754dea911039ac12434d1950d69a2f05acd4d56f7935ed402be09fad145e"}, - {file = "mypy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:06de0498798527451ffb60f68db0d368bd2bae2bbfb5237eae616d4330cc87aa"}, - {file = "mypy-1.12.0-py3-none-any.whl", hash = "sha256:fd313226af375d52e1e36c383f39bf3836e1f192801116b31b090dfcd3ec5266"}, - {file = "mypy-1.12.0.tar.gz", hash = "sha256:65a22d87e757ccd95cbbf6f7e181e6caa87128255eb2b6be901bb71b26d8a99d"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, + {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, + {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, + {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, + {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, + {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, + {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, + {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, + {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, + {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, + {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, + {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, + {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, + {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, + {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, + {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, + {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, + {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, ] [package.dependencies] @@ -1084,13 +1034,13 @@ flake8 = ">=5.0.0" [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.3.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, + {file = "platformdirs-4.3.2-py3-none-any.whl", hash = "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617"}, + {file = "platformdirs-4.3.2.tar.gz", hash = "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c"}, ] [package.extras] @@ -1144,13 +1094,13 @@ files = [ [[package]] name = "pydoclint" -version = "0.5.9" +version = "0.5.7" description = "A Python docstring linter that checks arguments, returns, yields, and raises sections" optional = false python-versions = ">=3.8" files = [ - {file = "pydoclint-0.5.9-py2.py3-none-any.whl", hash = "sha256:089327003cef6fe5605cbaa9887859ea5229ce0c9abb52775ffd57513094c1ae"}, - {file = "pydoclint-0.5.9.tar.gz", hash = "sha256:e200f964a5d9fbbb2ff1078bd7cb5433a0564d2482b6a1ba1be848f66bc4924f"}, + {file = "pydoclint-0.5.7-py2.py3-none-any.whl", hash = "sha256:22561b8f876b7bd3d4966a3b7fe27bfdbfa41fa50d0719f13b08844a39e2717c"}, + {file = "pydoclint-0.5.7.tar.gz", hash = "sha256:d2938efc95233205822bebe5da5c8e1c4796da09988a2a73c2d6b63cef4577a6"}, ] [package.dependencies] @@ -1206,13 +1156,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" -version = "8.3.3" +version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, - {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, ] [package.dependencies] @@ -1244,35 +1194,6 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] -[[package]] -name = "pytest-randomly" -version = "3.15.0" -description = "Pytest plugin to randomly order tests and control random.seed." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest_randomly-3.15.0-py3-none-any.whl", hash = "sha256:0516f4344b29f4e9cdae8bce31c4aeebf59d0b9ef05927c33354ff3859eeeca6"}, - {file = "pytest_randomly-3.15.0.tar.gz", hash = "sha256:b908529648667ba5e54723088edd6f82252f540cc340d748d1fa985539687047"}, -] - -[package.dependencies] -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} -pytest = "*" - -[[package]] -name = "pytest-repeat" -version = "0.9.3" -description = "pytest plugin for repeating tests" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest_repeat-0.9.3-py3-none-any.whl", hash = "sha256:26ab2df18226af9d5ce441c858f273121e92ff55f5bb311d25755b8d7abdd8ed"}, - {file = "pytest_repeat-0.9.3.tar.gz", hash = "sha256:ffd3836dfcd67bb270bec648b330e20be37d2966448c4148c4092d1e8aba8185"}, -] - -[package.dependencies] -pytest = "*" - [[package]] name = "pyyaml" version = "6.0.2" @@ -1358,48 +1279,47 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.9.2" +version = "13.8.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false -python-versions = ">=3.8.0" +python-versions = ">=3.7.0" files = [ - {file = "rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1"}, - {file = "rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c"}, + {file = "rich-13.8.0-py3-none-any.whl", hash = "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc"}, + {file = "rich-13.8.0.tar.gz", hash = "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruff" -version = "0.6.9" +version = "0.6.4" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"}, - {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"}, - {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"}, - {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"}, - {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"}, - {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"}, - {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"}, + {file = "ruff-0.6.4-py3-none-linux_armv6l.whl", hash = "sha256:c4b153fc152af51855458e79e835fb6b933032921756cec9af7d0ba2aa01a258"}, + {file = "ruff-0.6.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:bedff9e4f004dad5f7f76a9d39c4ca98af526c9b1695068198b3bda8c085ef60"}, + {file = "ruff-0.6.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d02a4127a86de23002e694d7ff19f905c51e338c72d8e09b56bfb60e1681724f"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7862f42fc1a4aca1ea3ffe8a11f67819d183a5693b228f0bb3a531f5e40336fc"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eebe4ff1967c838a1a9618a5a59a3b0a00406f8d7eefee97c70411fefc353617"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:932063a03bac394866683e15710c25b8690ccdca1cf192b9a98260332ca93408"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:50e30b437cebef547bd5c3edf9ce81343e5dd7c737cb36ccb4fe83573f3d392e"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c44536df7b93a587de690e124b89bd47306fddd59398a0fb12afd6133c7b3818"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ea086601b22dc5e7693a78f3fcfc460cceabfdf3bdc36dc898792aba48fbad6"}, + {file = "ruff-0.6.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b52387d3289ccd227b62102c24714ed75fbba0b16ecc69a923a37e3b5e0aaaa"}, + {file = "ruff-0.6.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0308610470fcc82969082fc83c76c0d362f562e2f0cdab0586516f03a4e06ec6"}, + {file = "ruff-0.6.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:803b96dea21795a6c9d5bfa9e96127cc9c31a1987802ca68f35e5c95aed3fc0d"}, + {file = "ruff-0.6.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:66dbfea86b663baab8fcae56c59f190caba9398df1488164e2df53e216248baa"}, + {file = "ruff-0.6.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:34d5efad480193c046c86608dbba2bccdc1c5fd11950fb271f8086e0c763a5d1"}, + {file = "ruff-0.6.4-py3-none-win32.whl", hash = "sha256:f0f8968feea5ce3777c0d8365653d5e91c40c31a81d95824ba61d871a11b8523"}, + {file = "ruff-0.6.4-py3-none-win_amd64.whl", hash = "sha256:549daccee5227282289390b0222d0fbee0275d1db6d514550d65420053021a58"}, + {file = "ruff-0.6.4-py3-none-win_arm64.whl", hash = "sha256:ac4b75e898ed189b3708c9ab3fc70b79a433219e1e87193b4f2b77251d058d14"}, + {file = "ruff-0.6.4.tar.gz", hash = "sha256:ac3b5bfbee99973f80aa1b7cbd1c9cbce200883bdd067300c22a6cc1c7fba212"}, ] [[package]] @@ -1415,18 +1335,18 @@ files = [ [[package]] name = "setuptools" -version = "75.1.0" +version = "74.1.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2"}, - {file = "setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538"}, + {file = "setuptools-74.1.2-py3-none-any.whl", hash = "sha256:5f4c08aa4d3ebcb57a50c33b1b07e94315d7fc7230f7115e47fc99776c8ce308"}, + {file = "setuptools-74.1.2.tar.gz", hash = "sha256:95b40ed940a1c67eb70fc099094bd6e99c6ee7c23aa2306f4d2697ba7916f9c6"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] @@ -1482,13 +1402,13 @@ test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools [[package]] name = "sphinx" -version = "8.1.3" +version = "8.0.2" description = "Python documentation generator" optional = false python-versions = ">=3.10" files = [ - {file = "sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2"}, - {file = "sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927"}, + {file = "sphinx-8.0.2-py3-none-any.whl", hash = "sha256:56173572ae6c1b9a38911786e206a110c9749116745873feae4f9ce88e59391d"}, + {file = "sphinx-8.0.2.tar.gz", hash = "sha256:0cce1ddcc4fd3532cf1dd283bc7d886758362c5c1de6598696579ce96d8ffa5b"}, ] [package.dependencies] @@ -1502,17 +1422,17 @@ packaging = ">=23.0" Pygments = ">=2.17" requests = ">=2.30.0" snowballstemmer = ">=2.2" -sphinxcontrib-applehelp = ">=1.0.7" -sphinxcontrib-devhelp = ">=1.0.6" -sphinxcontrib-htmlhelp = ">=2.0.6" -sphinxcontrib-jsmath = ">=1.0.1" -sphinxcontrib-qthelp = ">=1.0.6" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = ">=1.1.9" tomli = {version = ">=2", markers = "python_version < \"3.11\""} [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=6.0)", "mypy (==1.11.1)", "pyright (==1.1.384)", "pytest (>=6.0)", "ruff (==0.6.9)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241005)", "types-requests (==2.32.0.20240914)", "types-urllib3 (==1.26.25.14)"] +lint = ["flake8 (>=6.0)", "mypy (==1.11.0)", "pytest (>=6.0)", "ruff (==0.5.5)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240520)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20240724)", "types-requests (>=2.30.0)"] test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] [[package]] @@ -1644,13 +1564,13 @@ pbr = ">=2.0.0" [[package]] name = "tomli" -version = "2.0.2" +version = "2.0.1" description = "A lil' TOML parser" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, - {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [[package]] @@ -1666,13 +1586,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.3" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -1683,13 +1603,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.6" +version = "20.26.4" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, - {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, + {file = "virtualenv-20.26.4-py3-none-any.whl", hash = "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55"}, + {file = "virtualenv-20.26.4.tar.gz", hash = "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c"}, ] [package.dependencies] @@ -1734,13 +1654,13 @@ tests-strict = ["pytest (==4.6.0)", "pytest (==6.2.5)", "pytest-cov (==3.0.0)"] [[package]] name = "zipp" -version = "3.20.2" +version = "3.20.1" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, - {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, + {file = "zipp-3.20.1-py3-none-any.whl", hash = "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064"}, + {file = "zipp-3.20.1.tar.gz", hash = "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b"}, ] [package.extras] @@ -1754,4 +1674,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "e0962e284e415b91fad954a9ca20d3dd45be434d23c63ce1e38da75f69e9557c" +content-hash = "3a34bd29eb4226a6054fe5ddba556605fcd621ceff7661334edf429d715c320f" diff --git a/pyproject.toml b/pyproject.toml index 17b995a..2b1b22e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "lalib" -version = "0.5.0.dev0" +version = "0.4.2" authors = [ "Alexander Hess ", @@ -16,7 +16,6 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", ] description = "A Python library to study linear algebra" license = "MIT" @@ -31,8 +30,6 @@ repository = "https://github.com/webartifex/lalib" python = "^3.9" -typing-extensions = [ { python = "<3.11", version = "^4.12" } ] # to support Python 3.9 & 3.10 - [tool.poetry.group.dev.dependencies] @@ -58,10 +55,8 @@ flake8-eradicate = "^1.5" flake8-isort = "^6.1" flake8-quotes = "^3.4" flake8-string-format = "^0.3" -flake8-todos = "^0.3" flake8-pyproject = "^1.2" flake8-pytest-style = "^2.0" -flake8-unused-arguments = "^0.0" mypy = "^1.11" pep8-naming = "^0.14" # flake8 plug-in pydoclint = { extras = ["flake8"], version = "^0.5" } @@ -79,13 +74,10 @@ coverage = "^7.6" packaging = "^24.1" # to test the version identifier pytest = "^8.3" pytest-cov = "^5.0" -pytest-randomly = "^3.15" -pytest-repeat = "^0.9" semver = "^3.0" # to test the version identifier tomli = [ { python = "<3.11", version = "^2.0" } ] xdoctest = { extras = ["colors"], version = "^1.2" } - [tool.poetry.urls] "Issues Tracker" = "https://github.com/webartifex/lalib/issues" @@ -109,7 +101,7 @@ remove-unused-variables = true # Source: https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html line-length = 88 -target-version = ["py313", "py312", "py311", "py310", "py39"] +target-version = ["py312", "py311", "py310", "py39"] @@ -129,16 +121,6 @@ show_missing = true skip_covered = true skip_empty = true -exclude_lines = [ - - # "pragma: no cover" - # => Intentionally commented out as we thrive for 100% test coverage - - # PyPI's "typing-extensions" are needed to make `mypy` work - "pragma: no cover ${PRAGMA_SUPPORT_39_N_310}", - -] - [tool.coverage.run] @@ -176,9 +158,7 @@ select = [ "PT", # flake8-pytest-style => enforce a consistent style with pytest "Q", # flake8-quotes => use double quotes everywhere (complying with black) "S", # flake8-bandit => common security issues - "T00", # flake8-todos => unify TODOs "T10", # flake8-debugger => no debugger usage - "U100", # flake8-unused-arguments => declared function arguments must be used # violations not covered by `ruff` below @@ -197,8 +177,6 @@ extend-ignore = [ # never check the following codes "ANN401", # allow dynamically typed expressions with `typing.Any` - "DOC301", # PEP257 => class constructor's docstring go in `.__init__()` - # Comply with black's style # Sources: https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#pycodestyle "E203", "E701", "E704", "W503", @@ -257,12 +235,6 @@ docstring-quotes = "double" inline-quotes = "double" multiline-quotes = "double" -# Plug-in: flake8-unused-arguments -# Source: https://github.com/nhoad/flake8-unused-arguments -# Make flake8-unused-arguments behave like ruff's "ARG" error code -unused-arguments-ignore-abstract-functions = true -unused-arguments-ignore-stub-functions = true - [tool.isort] # aligned with [tool.ruff.lint.isort] below @@ -355,7 +327,6 @@ select = [ # violations also covered by `flake8` above "ANN", # flake8-annotations => enforce type checking for functions - "ARG", # flake8-unused-arguments => declared function arguments must be used "B", # flake8-bugbear => bugs and design flaws "C4", # flake8-comprehensions => better comprehensions "C90", # mccabe => cyclomatic complexity @@ -369,7 +340,6 @@ select = [ "PT", # flake8-pytest-style => enforce a consistent style with pytest "Q", # flake8-quotes => use double quotes everywhere "S", # flake8-bandit => common security issues - "TD", # flake8-todos => unify TODOs "T10", # flake8-debugger => no debugger usage # violations not covered by `flake8` above @@ -392,8 +362,6 @@ extend-ignore = [ # never check the following codes ] -allowed-confusables = ["ℂ", "ℝ", "ℚ"] - [tool.ruff.lint.flake8-pytest-style] # aligned with [tool.flake8] above diff --git a/src/lalib/__init__.py b/src/lalib/__init__.py index cef3c36..26b2482 100644 --- a/src/lalib/__init__.py +++ b/src/lalib/__init__.py @@ -4,28 +4,10 @@ First, verify that your installation of `lalib` works: >>> import lalib >>> lalib.__version__ != '0.0.0' True - -`lalib` exposes its very own "words" (i.e., its public API) at the root -of the package. They can be imported all at once with: - ->>> from lalib import * - -In addition to Python's built-in numbers, `lalib` comes with a couple of -specific numeric data types, for example, `one` and `zero` representing -the two elements of the Galois field `GF2`: - ->>> one + one -zero ->>> one + zero -one """ from importlib import metadata -from lalib import domains -from lalib import elements -from lalib import fields - try: pkg_info = metadata.metadata(__name__) @@ -42,26 +24,4 @@ else: del pkg_info -Domain = domains.Domain - -gf2, one, zero = elements.gf2, elements.one, elements.zero - -Q, R, C, GF2 = fields.Q, fields.R, fields.C, fields.GF2 - - -del domains -del elements -del fields del metadata - - -__all__ = ( - "Domain", - "gf2", - "one", - "zero", - "Q", - "R", - "C", - "GF2", -) diff --git a/src/lalib/config.py b/src/lalib/config.py deleted file mode 100644 index e27ced9..0000000 --- a/src/lalib/config.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Library-wide default settings.""" - -import math - - -NDIGITS = 12 - -THRESHOLD = 1 / (10**NDIGITS) - -MAX_DENOMINATOR = math.trunc(1 / THRESHOLD) - - -del math diff --git a/src/lalib/domains.py b/src/lalib/domains.py deleted file mode 100644 index 6f0b955..0000000 --- a/src/lalib/domains.py +++ /dev/null @@ -1,144 +0,0 @@ -"""A `Domain` for discrete `Vector`s. - -This module defines a `Domain` class wrapping the built-in `frozenset`. -It is designed to model domains of discrete vectors and matrices. - -In conventional math, `Domain`s are implicitly thought of as strictly -positive natural numbers. For example, a `3`-vector over the reals -would then have a `Domain` like below: - ->>> Domain([1, 2, 3]) -Domain({1, 2, 3}) - -However, in Python we commonly start counting at `0`. Therefore, -a `3`-vector over the reals has the following `Domain` in `lalib`: - ->>> Domain([0, 1, 2]) -Domain(3) - -We call such `Domain`s "canonical", and, as a convenience, such -`Domain`s can be created by passing a `Vector`'s "length" as an -`int`eger argument to the `Domain()` constructor, for example: - ->>> Domain(5) -Domain(5) - -Domains do not need to be made of numbers. Instead, we can use, -for example, letters or words, or any other `hash`able object. - ->>> Domain(["a", "b", "c"]) -... - ->>> Domain("abc") -... - ->>> Domain(("heads", "tails")) -... - ->>> Domain({(1, 23), (4, 56), (7, 89)}) -... - ->>> Domain({"n_yes": 7, "n_no": 3, "n_total": 10}) # `.keys()` are used -... - ->>> Domain(([1, 23], [4, 56], [7, 89])) -Traceback (most recent call last): -... -TypeError: ... -""" - -from collections import abc as collections_abc - -# When giving up support for Python 3.9, we can get rid of `Union` -from typing import Union - - -try: - from typing import Self -except ImportError: # pragma: no cover to support Python 3.9 & 3.10 - from typing_extensions import Self - - -class Domain(frozenset): - """The domain for a `Vector`.""" - - @staticmethod - def __new__( - cls: type[Self], - /, - labels: Union[collections_abc.Iterable[collections_abc.Hashable], int], - ) -> Self: - """See docstring for `.__init__()`.""" - # Because `Domain` objects are immutable by design ... - if isinstance(labels, cls): - return labels - - if not isinstance(labels, collections_abc.Iterable): - try: - n_labels = int(labels) - except (TypeError, ValueError): - msg = "must provide a positive integer" - raise TypeError(msg) from None - else: - if n_labels != labels: - msg = "must provide a positive integer" - raise ValueError(msg) - - labels = range(n_labels) - - # As we require `Vector`s to have at least one entry, - if not labels: # we also enforce this constraint on the `Domain`s - msg = "must provide at least one label or a positive integer" - raise ValueError(msg) - - try: - return super().__new__(cls, labels) - except TypeError: - # Provide a nicer error message - msg = "must provide hashable labels" - raise TypeError(msg) from None - - def __init__( - self, - /, - labels: Union[collections_abc.Iterable[collections_abc.Hashable], int], - ) -> None: - """Create a new domain. - - Args: - labels: the domain labels provided by an iterable or - a strictly positive `int`eger `n` that then constructs - the labels `0`, `1`, ... up to and including `n - 1` - - Returns: - domain - - Raises: - TypeError: `labels` is not of the specified types - ValueError: - - if a collection argument contains no elements - - if an integer argument is not strictly positive - """ - - def __repr__(self) -> str: - """Text representation: `Domain(...)`. - - Designed such that `eval(repr(self)) == self`; in other words, - the text representation of a `Domain` is valid code on its own - evaluating into a (new) `Domain` with the same `labels`. - - See: https://docs.python.org/3/reference/datamodel.html#object.__repr__ - """ - if self.is_canonical: - return f"{self.__class__.__name__}({len(self)})" - return super().__repr__() - - @property # We do not use `@functools.cached_property` - # as this allows writing to the propery - def is_canonical(self) -> bool: - """If the `labels` resemble a `range(...)`.""" - try: - cached = self._is_canonical - except AttributeError: - self._is_canonical: bool = (cached := self == set(range(len(self)))) - return cached diff --git a/src/lalib/elements/__init__.py b/src/lalib/elements/__init__.py deleted file mode 100644 index 77c1f71..0000000 --- a/src/lalib/elements/__init__.py +++ /dev/null @@ -1,51 +0,0 @@ -"""Various elements of various fields. - -Import the objects like so: - ->>> from lalib.elements import * - -Then, use them: - ->>> one + zero -one ->>> one + one -zero - ->>> type(one) -gf2 - -The `gf2` type is similar to the built-in `bool`. -To cast objects as `gf2` values: - ->>> gf2(0) -zero ->>> gf2(1) -one - ->>> gf2(42) -one ->>> gf2(-42) -one - -Yet, there is also a `strict` mode where values -not equal to `1` or `0` within a `threshold` are not accepted. - ->>> gf2(42, strict=True) -Traceback (most recent call last): -... -ValueError: ... -""" - -from lalib.elements import galois - - -gf2, one, zero = galois.gf2, galois.one, galois.zero - -del galois - - -__all__ = ( - "gf2", - "one", - "zero", -) diff --git a/src/lalib/elements/galois.py b/src/lalib/elements/galois.py deleted file mode 100644 index 594d58b..0000000 --- a/src/lalib/elements/galois.py +++ /dev/null @@ -1,630 +0,0 @@ -"""A Galois field implementation with two elements. - -This module defines two singleton objects, `one` and `zero`, -that follow the rules of a Galois field of two elements, -also called `GF2` in `lalib`: - ->>> one + one -zero ->>> zero + one -one ->>> one * one -one ->>> one * zero -zero - -They mix with numbers that compare equal to either `1` or `0`, -for example: - ->>> one + 1 -zero ->>> 0 * zero -zero - -Yet, for numbers not equal to `1` or `0`, this does not work: - ->>> one + 42 -Traceback (most recent call last): -... -ValueError: ... - - -Further usage explanations of `one` and `zero` -can be found in the various docstrings of the `GF2Element` class. - -This class is also referred to as just "the `gf2` type" outside -this module. -""" - -import abc -import functools -import math -import numbers -from typing import Callable, ClassVar, Literal - - -try: - from typing import Self -except ImportError: # pragma: no cover to support Python 3.9 & 3.10 - from typing_extensions import Self - - -from lalib import config - - -def _to_gf2( - value: complex, # `mypy` reads `complex | float | int` - /, - *, - strict: bool, - threshold: float, -) -> int: - """Cast a number as a possible Galois field value: `1` or `0`. - - By default, the `value` is parsed in a `strict` mode where - `value`s equal to `1` or `0` within the specified `threshold` - return either `1` or `0` exactly. - - Args: - value: to be cast; must behave like a number; - for `complex` numbers their `.real` part is used - strict: if `True`, only accept `value`s equal to - `1` or `0` within the `threshold` as `1` or `0`; - otherwise, cast any number different from `0` as `1` - threshold: used for the equality checks to find - `1`-like and `0`-like `value`s - - Returns: - either `1` or `0` - - Raises: - TypeError: `value` does not behave like a number - ValueError: `value != 1` or `value != 0` in `strict` mode - """ - try: - value = complex(value) - except (TypeError, ValueError): - msg = "`value` must be a number" - raise TypeError(msg) from None - - if not (abs(value.imag) < threshold): - msg = "`value` must be either `1`-like or `0`-like" - raise ValueError(msg) - - value = value.real - - if math.isnan(value): - msg = "`value` must be either `1`-like or `0`-like" - raise ValueError(msg) - - if strict: - if abs(value - 1) < threshold: - return 1 - if abs(value) < threshold: - return 0 - - msg = "`value` must be either `1`-like or `0`-like" - raise ValueError(msg) - - if abs(value) < threshold and not math.isinf(value): - return 0 - - return 1 - - -class GF2Meta(abc.ABCMeta): - """Make data type of `one` and `zero` appear to be `gf2`.""" - - def __repr__(cls) -> str: # noqa: RUF100,U100 - """Text representation for `GF2Element` and sub-classes.""" - return "gf2" - - -@functools.total_ordering -class GF2Element(metaclass=GF2Meta): - """A Galois field value: either `one` or `zero`. - - Implements the singleton design pattern such that - only one instance per field value exists in the - computer's memory, i.e., there is only one `one` - and one `zero` object at all times. - """ - - _instances: ClassVar = {} - _value: int - - @staticmethod - def __new__( - cls: type[Self], - value: object = None, - /, - *, - strict: bool = False, - threshold: float = config.THRESHOLD, - ) -> Self: - """See docstring for `.__init__()`.""" - if isinstance(value, cls): - return value - - if value is None: - try: - value = cls._value - except AttributeError: - try: - return cls._instances[0] - except KeyError: - msg = "Must create `one` and `zero` first (internal error)" - raise RuntimeError(msg) from None - - value = _to_gf2(value, strict=strict, threshold=threshold) # type: ignore[arg-type] - - try: - return cls._instances[value] - except KeyError: - obj = super().__new__(cls) - cls._instances[int(obj)] = obj - return obj - - def __init__( - self, - value: object = None, - /, - *, - strict: bool = True, - threshold: float = config.THRESHOLD, - ) -> None: - """Obtain one of two objects: `one` or `zero`. - - Args: - value: to be cast; must behave like a number; - for `complex` numbers their `.real` part is used - strict: if `True`, only accept `value`s equal to - `1` or `0` within the `threshold` as `one` or `zero`; - otherwise, cast any number different from `0` as `one` - threshold: used for the equality checks to find - `1`-like and `0`-like `value`s - - Returns: - either `one` or `zero` - - Raises: - TypeError: `value` does not behave like a number - ValueError: `value != 1` or `value != 0` in `strict` mode - """ - - def __repr__(self) -> str: - """Text representation: `repr(one)` and `repr(zero)`. - - `eval(repr(self)) == self` must be `True`; in other words, - the text representation of an object must be valid code on its - own and evaluate into a (new) object with the same value. - - See: https://docs.python.org/3/reference/datamodel.html#object.__repr__ - """ - return "one" if self._value else "zero" - - __str__ = __repr__ - - def __complex__(self) -> complex: - """Cast `self` as a `complex` number: `complex(self)`.""" - return complex(self._value, 0) - - def __float__(self) -> float: - """Cast `self` as a `float`ing-point number: `float(self)`.""" - return float(self._value) - - def __int__(self) -> int: - """Cast `self` as a `int`: `int(self)`.""" - return int(self._value) - - __hash__ = __int__ - - def __bool__(self) -> bool: - """Cast `self` as a `bool`ean: `bool(self)`. - - Example usage: - - >>> bool(zero) - False - >>> if zero + one: - ... result = one - ... else: - ... result = zero - >>> result - one - """ - return bool(self._value) - - def __abs__(self) -> Self: - """Take the absolute value of `self`: `abs(self)`.""" - return self - - def __trunc__(self) -> int: - """Truncate `self` to the next `int`: `math.trunc(self)`.""" - return int(self) - - def __floor__(self) -> int: - """Round `self` down to the next `int`: `math.floor(self)`.""" - return int(self) - - def __ceil__(self) -> int: - """Round `self` up to the next `int`: `math.ceil(self)`.""" - return int(self) - - def __round__(self, _ndigits: int = config.NDIGITS) -> int: - """Round `self` to the next `int`: `round(self)`.""" - return int(self) - - @property - def real(self) -> int: - """The `.real` part of a `complex` number. - - For a non-`complex` number this is the number itself. - """ - # Return an `int` to align with `.imag` above - return int(self._value) - - @property - def imag(self) -> Literal[0]: - """The `.imag`inary part of a `complex` number. - - For a non-`complex` number this is always `0`. - """ - # `numbers.Real` returns an `int` here - # whereas `numbers.Complex` returns a `float` - # => must return an `int` to make `mypy` happy - return 0 - - def conjugate(self) -> Self: - """The conjugate of a `complex` number. - - For a non-`complex` number this is the number itself. - """ - return self - - @property - def numerator(self) -> int: - """Smallest numerator when expressed as a `Rational` number. - - Either `1` or `0`. - - Reasoning: - - `int(one) == 1` => `gf2(1 / 1) == one` - - `int(zero) == 0` => `gf2(0 / 1) == zero` - - See also docstring for `.denominator`. - """ - return int(self) - - @property - def denominator(self) -> Literal[1]: - """Smallest denominator when expressed as a `Rational` number. - - Always `1` for `gf2` values. - - See also docstring for `.numerator`. - """ - return 1 - - def __eq__(self, other: object) -> bool: - """Comparison: `self == other`. - - Example usage: - - >>> zero == one - False - >>> one == one - True - >>> one != zero - True - - >>> one == 1 - True - >>> one == 42 - False - """ - try: - other = GF2Element(other, strict=True) - except (TypeError, ValueError): - return NotImplemented - else: - # TODO(webartifex): investigate the below issue - # https://github.com/webartifex/lalib/issues/1 - # `one` and `zero` are singletons - # => yet, the following does not work with `pytest` - # in Python 3.9 & 3.10: `return self is other` - # => for now, use the following fix: - return int(self) == int(other) - - def __lt__(self, other: object) -> bool: - """Comparison: `self < other`. - - Example usage: - - >>> zero < one - True - >>> one < one - False - - >>> 0 < one - True - - The `other` object must be either `1`-like or `0`-like: - - >>> one < 42 - Traceback (most recent call last): - ... - ValueError: ... - """ - try: - other = GF2Element(other, strict=True) - except TypeError: - return NotImplemented - except ValueError: - msg = "`other` must be either `1`-like or `0`-like" - raise ValueError(msg) from None - else: - return int(self) < int(other) - - def __le__(self, other: object) -> bool: - """Comparison: `self <= other`. - - Example usage: - - >>> zero <= one - True - >>> zero <= zero - True - >>> one <= zero - False - - >>> zero <= 1 - True - - The `other` object must be either `1`-like or `0`-like: - - >>> zero <= 42 - Traceback (most recent call last): - ... - ValueError: ... - """ - # The `numbers.Rational` abstract base class requires both - # `.__lt__()` and `.__le__()` to be present alongside - # `.__eq__()` => `@functools.total_ordering` is not enough - return self == other or self < other - - def _compute(self, other: object, func: Callable) -> Self: - """Run arithmetic operations using `int`s. - - The `gf2` atithmetic operations can transparently be conducted - by converting `self` and `other` into `int`s first, and - then "do the math". - - Besides the generic arithmetic, this method also handles the - casting of non-`gf2` values and various errors occuring - along the way. - """ - try: - other = GF2Element(other, strict=True) - except TypeError: - return NotImplemented - except ValueError: - msg = "`other` must be a `1`-like or `0`-like value" - raise ValueError(msg) from None - else: - try: - return self.__class__(func(int(self), int(other))) - except ZeroDivisionError: - msg = "division by `0`-like value" - raise ZeroDivisionError(msg) from None - - def __pos__(self) -> Self: - """Make `self` positive: `+self`.""" - return self - - def __neg__(self) -> Self: - """Make `self` negative: `-self`.""" - return self - - def __add__(self, other: object) -> Self: - """Addition / Subtraction: `self + other` / `self - other`. - - For `gf2`, addition and subtraction are identical. Besides - `one + one` which cannot result in a "two", all operations - behave as one would expect from `int`s. - - Example usage: - - >>> one + one - zero - >>> one + zero - one - >>> zero - one - one - - >>> zero + 0 - zero - >>> 1 + one - zero - - The `other` object must be either `1`-like or `0`-like: - - >>> zero + 42 - Traceback (most recent call last): - ... - ValueError: ... - """ - return self._compute(other, lambda s, o: (s + o) % 2) - - __radd__ = __add__ - __sub__ = __add__ - __rsub__ = __add__ - - def __mul__(self, other: object) -> Self: - """Multiplication: `self * other`. - - Multiplying `gf2` values is like multiplying `int`s. - - Example usage: - - >>> one * one - one - >>> zero * one - zero - - >>> 0 * one - zero - - The `other` object must be either `1`-like or `0`-like: - - >>> one * 42 - Traceback (most recent call last): - ... - ValueError: ... - """ - return self._compute(other, lambda s, o: s * o) - - __rmul__ = __mul__ - - def __truediv__(self, other: object) -> Self: - """Division: `self / other` and `self // other`. - - Dividing `gf2` values is like dividing `int`s. - - Example usage: - - >>> one / one - one - >>> zero // one - zero - - >>> one / zero - Traceback (most recent call last): - ... - ZeroDivisionError: ... - - >>> 1 // one - one - - The `other` object must be either `1`-like or `0`-like: - - >>> 42 / one - Traceback (most recent call last): - ... - ValueError: ... - """ - return self._compute(other, lambda s, o: s / o) - - __floordiv__ = __truediv__ - - def __rtruediv__(self, other: object) -> Self: - """(Reflected) Division: `other / self` and `other // self`. - - See docstring for `.__truediv__()`. - """ - return self._compute(other, lambda s, o: o / s) - - __rfloordiv__ = __rtruediv__ - - def __mod__(self, other: object) -> Self: - """Modulo Division: `self % other`. - - Modulo dividing `gf2` values is like modulo dividing `int`s. - - Example usage: - - >>> one % one - zero - >>> zero % one - zero - - >>> one % zero - Traceback (most recent call last): - ... - ZeroDivisionError: ... - - >>> 1 % one - zero - - The `other` object must be either `1`-like or `0`-like: - - >>> 42 % one - Traceback (most recent call last): - ... - ValueError: ... - """ - return self._compute(other, lambda s, o: s % o) - - def __rmod__(self, other: object) -> Self: - """(Reflected) Modulo Division: `other % self`. - - See docstring for `.__mod__()`. - """ - return self._compute(other, lambda s, o: o % s) - - def __pow__(self, other: object, _modulo: object = None) -> Self: - """Exponentiation: `self ** other`. - - Powers of `gf2` values are like powers of `int`s. - - Example usage: - - >>> one ** one - one - >>> zero ** one - zero - >>> one ** zero - one - - >>> 1 ** one - one - - The `other` object must be either `1`-like or `0`-like: - - >>> 42 ** one - Traceback (most recent call last): - ... - ValueError: ... - """ - return self._compute(other, lambda s, o: s**o) - - def __rpow__(self, other: object, _modulo: object = None) -> Self: - """(Reflected) Exponentiation: `other ** self`. - - See docstring for `.__pow__()`. - """ - return self._compute(other, lambda s, o: o**s) - - -numbers.Rational.register(GF2Element) - - -# The `GF2One` and `GF2Zero` sub-classes' primary purpose -# is to give `one` and `zero` their very own `help()` message - - -class GF2One(GF2Element): - """The Galois field value `one`.""" - - _value = 1 - - -class GF2Zero(GF2Element): - """The Galois field value `zero`.""" - - _value = 0 - - -one = GF2One() -zero = GF2Zero() - - -# Outside this module the `GF2Element` is just "the `gf2` type" -gf2 = GF2Element - - -del GF2Meta -del GF2One -del GF2Zero - -del config diff --git a/src/lalib/fields/__init__.py b/src/lalib/fields/__init__.py deleted file mode 100644 index c03b9b8..0000000 --- a/src/lalib/fields/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -"""A collection of common fields used in linear algebra.""" - -from lalib.fields import base -from lalib.fields import complex_ -from lalib.fields import galois -from lalib.fields import rational -from lalib.fields import real -from lalib.fields import utils - - -Field = base.Field - -Q = rational.Q -R = real.R -C = complex_.C -GF2 = galois.GF2 - - -del base -del complex_ -del galois -del rational -del real -del utils # `import`ed and `del`eted to not be in the final namespace - - -__all__ = ( - "Q", - "R", - "C", - "GF2", -) diff --git a/src/lalib/fields/base.py b/src/lalib/fields/base.py deleted file mode 100644 index b24ccf9..0000000 --- a/src/lalib/fields/base.py +++ /dev/null @@ -1,227 +0,0 @@ -"""The abstract blueprint of a `Field`.""" - -import abc -import numbers -from typing import Any, Callable, Generic, TypeVar - - -T = TypeVar("T", bound=numbers.Real) - - -class Field(abc.ABC, Generic[T]): - """The abstract blueprint of a mathematical field.""" - - @property - @abc.abstractmethod - def _math_name(self) -> str: - """The common abbreviation used in math notation.""" - - @staticmethod - @abc.abstractmethod - def _dtype(value: Any, /) -> T: - """Data type to store the `Field` elements.""" - - def _cast_func(self, value: Any, /, **kwargs: Any) -> T: - """Function to cast `value`s as field elements.""" - return self._dtype(value, **kwargs) - - @staticmethod - def _post_cast_filter(possible_element: T, /) -> bool: # noqa: ARG004 - """Function to filter out castable `value`s. - - Called after a successfull call of the `._cast_func()`. - - For example, if one wants to avoid non-finite `Field` elements, - an overwriting `._post_cast_filter()` could return `False` for - non-finite `value`s like `float("NaN")`. - - By default, all castable `value`s may become `Field` elements. - """ - return True - - @property - @abc.abstractmethod - def _additive_identity(self) -> T: - """The field's additive identity.""" - - @property - @abc.abstractmethod - def _multiplicative_identity(self) -> T: - """The field's multiplicative identity.""" - - def cast( - self, - value: object, - /, - **cast_kwargs: Any, - ) -> T: - """Cast a (numeric) `value` as an element of the field. - - Args: - value: to be cast as the "right" data type - as defined in the concrete `Field` sub-class - **cast_kwargs: extra `kwargs` to the `._cast_func()` - - Returns: - element: of the concrete `Field` - - Raises: - ValueError: `value` is not an element of the field - """ - try: - element = self._cast_func(value, **cast_kwargs) # type: ignore[arg-type] - except (ArithmeticError, TypeError, ValueError): - msg = "`value` is not an element of the field" - raise ValueError(msg) from None - - if not self._post_cast_filter(element): - msg = "`value` is not an element of the field" - raise ValueError(msg) - - return element - - def validate( - self, - value: object, - /, - *, - silent: bool = True, - **cast_kwargs: Any, - ) -> bool: - """Check if a (numeric) `value` is an element of the `Field`. - - Wraps `.cast()`, catches the documented `ValueError`, - and returns a `bool`ean indicating field membership. - - Args: - value: see docstring for `.cast()` - silent: suppress the `ValueError` - **cast_kwargs: extra `kwargs` to `.cast()` the `value` - - Returns: - is_element - - Raises: - ValueError: `value` is not `.cast()`able - (suppressed by default) - """ - try: - self.cast(value, **cast_kwargs) - except ValueError: - if not silent: - raise - return False - - return True - - @property - def dtype(self) -> Callable[[Any], T]: - """Data type to store the `Field` elements.""" - return self._dtype - - @property - def zero(self) -> T: - """The field's additive identity.""" - return self._additive_identity - - def is_zero(self, value: T, /, **cast_kwargs: Any) -> bool: - """Check if `value` equals the `.zero`-like field element. - - This method, together with `.is_one()` below, provides a unified - way across the different `Field`s to check if a given `value` - equals the field's additive or multiplicative identity. - - Concrete `Field`s may use a different logic. For example, some - compare the absolute difference between the `value` and the - `.zero`-or-`.one`-like field element to a small `threshold`. - - Overwriting methods should - - check the `value` for field membership first, and - - accept arbitrary keyword-only arguments - that they may simply ignore - - Args: - value: see docstring for `.cast()` - **cast_kwargs: extra `kwargs` to `.cast()` the `value` - - Returns: - is_zero - - Raises: - ValueError: `value` is not `.cast()`able - """ - return self.cast(value, **cast_kwargs) == self._additive_identity - - @property - def one(self) -> T: - """The field's multiplicative identity.""" - return self._multiplicative_identity - - def is_one(self, value: T, /, **cast_kwargs: Any) -> bool: - """Check if `value` equals the `.one`-like field element. - - See docstring for `.is_zero()` above for more details. - - Args: - value: see docstring for `.cast()` - **cast_kwargs: extra `kwargs` to `.cast()` the `value` - - Returns: - is_one - - Raises: - ValueError: `value` is not `.cast()`able - """ - return self.cast(value, **cast_kwargs) == self._multiplicative_identity - - @abc.abstractmethod - def random( - self, - *, - lower: T, - upper: T, - **cast_kwargs: Any, - ) -> T: - """Draw a uniformly distributed random element from the field. - - `lower` and `upper` may come in reversed order. - Overwriting methods should sort them if necessary. - - Extra keyword arguments should be passed through to `.cast()`. - """ - - def _get_bounds( - self, - lower: T, - upper: T, - /, - **cast_kwargs: Any, - ) -> tuple[T, T]: - """Get the `lower` & `upper` bounds for `Field.random()`. - - Utility method to either - - resolve the given `lower` and `upper` bounds into a - `.cast()`ed element of the `Field`, or - - obtain their default `value`s, which are - + the `.additive_identity` for `lower`, and - + the `.multiplicative_identity` for `upper` - - Extra keyword arguments are passed through to `.cast()`. - """ - lower = lower if lower is not None else self._additive_identity - upper = upper if upper is not None else self._multiplicative_identity - - return (self.cast(lower, **cast_kwargs), self.cast(upper, **cast_kwargs)) - - def __repr__(self) -> str: - """Text representations: `repr(...)` and `str(...)`. - - The `.math_name` should be a valid name within `lalib`. - If not, use the "" convention; in other words, - the text representation is no valid code on its own. - - See: https://docs.python.org/3/reference/datamodel.html#object.__repr__ - """ - return self._math_name - - __str__ = __repr__ diff --git a/src/lalib/fields/complex_.py b/src/lalib/fields/complex_.py deleted file mode 100644 index 533f5bf..0000000 --- a/src/lalib/fields/complex_.py +++ /dev/null @@ -1,110 +0,0 @@ -"""The concrete `ComplexField`.""" - -import cmath -import random -from typing import Any - -from lalib import config -from lalib.fields import base -from lalib.fields import utils - - -class ComplexField(utils.SingletonMixin, base.Field): - """The `Field` over ℂ, the complex numbers.""" - - _math_name = "ℂ" - _dtype = complex - _post_cast_filter = cmath.isfinite - _additive_identity = 0 + 0j - _multiplicative_identity = 1 + 0j - - def random( - self, - *, - lower: complex = _additive_identity, - upper: complex = _multiplicative_identity, - ndigits: int = config.NDIGITS, - **_cast_kwargs: Any, - ) -> complex: - """Draw a uniformly distributed random element from the field. - - The `.real` and `.imag`inary parts of the `lower` and `upper` - bounds evaluated separately; i.e., the `random_element` is drawn - from a rectangle with opposing corners `lower` and `upper`. - - `lower` and `upper` may come in reversed order. - - Args: - lower: bound of the random interval - upper: bound of the random interval - ndigits: no. of significant digits to the right of the "."; - both the `.real` and the `.imag`inary parts are rounded - - Returns: - random_element - - Raises: - ValueError: `lower` and `upper` are not `.cast()`able - """ - lower, upper = self._get_bounds(lower, upper) - - random_real, random_imag = ( - # `random.uniform()` can handle `upper < lower` - round(random.uniform(lower.real, upper.real), ndigits), # noqa: S311 - round(random.uniform(lower.imag, upper.imag), ndigits), # noqa: S311 - ) - - return complex(random_real, random_imag) - - def is_zero( - self, - value: complex, - /, - *, - threshold: float = config.THRESHOLD, - **_cast_kwargs: Any, - ) -> bool: - """Check if `value` equals `0.0 + 0.0j`. - - To be precise: Check if `value` deviates by less than the - `threshold` from `0.0 + 0.0j` in absolute terms. - - Args: - value: to be compared to `0.0 + 0.0j` - threshold: for the equality check - - Returns: - is_zero - - Raises: - ValueError: `value` is not `.cast()`able - """ - return abs(self.cast(value)) < threshold - - def is_one( - self, - value: complex, - /, - *, - threshold: float = config.THRESHOLD, - **_cast_kwargs: Any, - ) -> bool: - """Check if `value` equals `1.0 + 0.0j`. - - To be precise: Check if `value` deviates by less than the - `threshold` from `1.0 + 0.0j` in absolute terms. - - Args: - value: to be compared to `1.0 + 0.0j` - threshold: for the equality check - - Returns: - is_one - - Raises: - ValueError: `value` is not `.cast()`able - """ - return abs(self.cast(value) - (1.0 + 0j)) < threshold - - -C = ComplexField() diff --git a/src/lalib/fields/galois.py b/src/lalib/fields/galois.py deleted file mode 100644 index f73fdd4..0000000 --- a/src/lalib/fields/galois.py +++ /dev/null @@ -1,58 +0,0 @@ -"""The concrete `GaloisField2`.""" - -import random -from typing import Any - -from lalib import config -from lalib.elements import galois as gf2_elements -from lalib.fields import base -from lalib.fields import utils - - -class GaloisField2(utils.SingletonMixin, base.Field): - """The Galois `Field` of 2 elements.""" - - _math_name = "GF2" - _dtype = gf2_elements.GF2Element - - def _cast_func( - self, - value: Any, - /, - *, - strict: bool = True, - threshold: float = config.THRESHOLD, - **_kwargs: Any, - ) -> gf2_elements.GF2Element: - return gf2_elements.gf2(value, strict=strict, threshold=threshold) - - _additive_identity = gf2_elements.zero - _multiplicative_identity = gf2_elements.one - - def random( - self, - lower: gf2_elements.GF2Element = gf2_elements.zero, - upper: gf2_elements.GF2Element = gf2_elements.one, - **cast_kwargs: Any, - ) -> gf2_elements.GF2Element: - """Draw a uniformly distributed random element from the field. - - Args: - lower: bound of the random interval - upper: bound of the random interval - **cast_kwargs: extra `kwargs` to `.cast()` - the `lower` and `upper` bounds - - Returns: - random_element: either `one` or `zero` - - Raises: - ValueError: `lower` and `upper` are not `.cast()`able - """ - lower, upper = self._get_bounds(lower, upper, **cast_kwargs) - - # `random.choice()` can handle `upper < lower` - return random.choice((lower, upper)) # noqa: S311 - - -GF2 = GaloisField2() diff --git a/src/lalib/fields/rational.py b/src/lalib/fields/rational.py deleted file mode 100644 index 9894c6a..0000000 --- a/src/lalib/fields/rational.py +++ /dev/null @@ -1,90 +0,0 @@ -"""The concrete `RationalField`.""" - -import fractions -import random -from typing import Any - -from lalib import config -from lalib.fields import base -from lalib.fields import utils - - -class RationalField(utils.SingletonMixin, base.Field): - """The `Field` over ℚ, the rational numbers. - - Although `Q.cast()` accepts `float`s as possible field elements, - do so only with care as `float`s are inherently imprecise numbers: - - >>> 0.1 + 0.2 - 0.30000000000000004 - - To mitigate this, `Q.cast()` cuts off the decimals, as configured - with the `MAX_DENOMINATOR` setting. So, `float`s with just a couple - of digits return the possibly desired field element. For example: - - >>> Q.cast(0.1) - Fraction(1, 10) - - Yet, with the hidden `max_denominator` argument, we can easily - see how `float`s may result in "weird" `Fraction`s. - - >>> Q.cast(0.1, max_denominator=1_000_000_000_000) - Fraction(1, 10) - >>> Q.cast(0.1, max_denominator=1_000_000_000_000_000_000) - Fraction(3602879701896397, 36028797018963968) - - It is recommended to use `str`ings instead: - - >>> Q.cast("0.1") - Fraction(1, 10) - >>> Q.cast("1/10") - Fraction(1, 10) - """ - - _math_name = "ℚ" - _dtype = fractions.Fraction - - def _cast_func( - self, - value: Any, - /, - max_denominator: int = config.MAX_DENOMINATOR, - **_kwargs: Any, - ) -> fractions.Fraction: - return fractions.Fraction(value).limit_denominator(max_denominator) - - _additive_identity = fractions.Fraction(0, 1) - _multiplicative_identity = fractions.Fraction(1, 1) - - def random( - self, - *, - lower: fractions.Fraction = _additive_identity, - upper: fractions.Fraction = _multiplicative_identity, - max_denominator: int = config.MAX_DENOMINATOR, - **_cast_kwargs: Any, - ) -> fractions.Fraction: - """Draw a uniformly distributed random element from the field. - - `lower` and `upper` may come in reversed order. - - Args: - lower: bound of the random interval - upper: bound of the random interval - max_denominator: maximum for `random_element.denominator` - - Returns: - random_element - - Raises: - ValueError: `lower` and `upper` are not `.cast()`able - """ - lower, upper = self._get_bounds(lower, upper) - - # `random.uniform()` can handle `upper < lower` - random_value = random.uniform(float(lower), float(upper)) # noqa: S311 - - return self._cast_func(random_value).limit_denominator(max_denominator) - - -Q = RationalField() diff --git a/src/lalib/fields/real.py b/src/lalib/fields/real.py deleted file mode 100644 index aaa7c26..0000000 --- a/src/lalib/fields/real.py +++ /dev/null @@ -1,97 +0,0 @@ -"""The concrete `RealField`.""" - -import math -import random -from typing import Any - -from lalib import config -from lalib.fields import base -from lalib.fields import utils - - -class RealField(utils.SingletonMixin, base.Field): - """The `Field` over ℝ, the real numbers.""" - - _math_name = "ℝ" - _dtype = float - _post_cast_filter = math.isfinite - _additive_identity = 0.0 - _multiplicative_identity = 1.0 - - def random( - self, - *, - lower: float = _additive_identity, - upper: float = _multiplicative_identity, - ndigits: int = config.NDIGITS, - **_cast_kwargs: Any, - ) -> float: - """Draw a uniformly distributed random element from the field. - - `lower` and `upper` may come in reversed order. - - Args: - lower: bound of the random interval - upper: bound of the random interval - ndigits: no. of significant digits to the right of the "." - - Returns: - random_element - - Raises: - ValueError: `lower` and `upper` are not `.cast()`able - """ - lower, upper = self._get_bounds(lower, upper) - - # `random.uniform()` can handle `upper < lower` - lower, upper = float(lower), float(upper) - rand_value = random.uniform(lower, upper) # noqa: S311 - - return round(rand_value, ndigits) - - def is_zero( - self, - value: float, - /, - *, - threshold: float = config.THRESHOLD, - **_cast_kwargs: Any, - ) -> bool: - """Check if `value` equals `0.0`. - - Args: - value: to be compared to `0.0` - threshold: for the equality check - - Returns: - is_zero (boolean) - - Raises: - ValueError: `value` is not `.cast()`able - """ - return abs(self.cast(value)) < threshold - - def is_one( - self, - value: float, - /, - *, - threshold: float = config.THRESHOLD, - **_cast_kwargs: Any, - ) -> bool: - """Check if `value` equals `1.0`. - - Args: - value: to be compared to `1.0` - threshold: for the equality check - - Returns: - is_one - - Raises: - ValueError: `value` is not `.cast()`able - """ - return abs(self.cast(value) - 1.0) < threshold - - -R = RealField() diff --git a/src/lalib/fields/utils.py b/src/lalib/fields/utils.py deleted file mode 100644 index 5bf697a..0000000 --- a/src/lalib/fields/utils.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Generic utilities for the library.""" - -from typing import Any - - -try: - from typing import Self -except ImportError: # pragma: no cover to support Python 3.9 & 3.10 - from typing_extensions import Self - - -class SingletonMixin: - """Utility class to provide singleton pattern implementation.""" - - _instance: Self - - @staticmethod - def __new__(cls: type[Self], *args: Any, **kwargs: Any) -> Self: - """Check if the `_instance` already exists.""" - if getattr(cls, "_instance", None) is None: - cls._instance = super().__new__(cls, *args, **kwargs) - return cls._instance diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 7917b02..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Configurations and utilities for all tests.""" - -import pytest - - -def pytest_addoption(parser): - """Define custom CLI options for `pytest`.""" - parser.addoption( - "--smoke-tests-only", - action="store_true", - default=False, - help="Run the minimum number of (unit) tests to achieve full coverage", - ) - - -def pytest_configure(config): - """Define custom markers explicitly.""" - config.addinivalue_line( - "markers", - "integration_test: non-unit test case; skipped during coverage reporting", - ) - config.addinivalue_line( - "markers", - "overlapping_test: test case not contributing towards higher coverage", - ) - config.addinivalue_line( - "markers", - "sanity_test: test case providing confidence in the test data", - ) - - -def pytest_collection_modifyitems(config, items): - """Pre-process the test cases programatically. - - - Add `no_cover` marker to test cases with any of these markers: - + "integration_test" - + "overlapping_test" - + "sanity_test" - - Select test cases with none of the above markers as smoke tests, - i.e., the minimum number of test cases to achieve 100% coverage - """ - smoke_tests = [] - - for item in items: - if ( - "integration_test" in item.keywords - or "overlapping_test" in item.keywords - or "sanity_test" in item.keywords - ): - item.add_marker(pytest.mark.no_cover) - - elif config.getoption("--smoke-tests-only"): - smoke_tests.append(item) - - if config.getoption("--smoke-tests-only"): - if not smoke_tests: - pytest.exit("No smoke tests found") - - items[:] = smoke_tests diff --git a/tests/elements/__init__.py b/tests/elements/__init__.py deleted file mode 100644 index e3f2918..0000000 --- a/tests/elements/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the `lalib.elements` sub-package.""" diff --git a/tests/elements/test_galois.py b/tests/elements/test_galois.py deleted file mode 100644 index bdb1e40..0000000 --- a/tests/elements/test_galois.py +++ /dev/null @@ -1,803 +0,0 @@ -"""Test the `gf2` singeltons `one` and `zero`.""" - -import decimal -import fractions -import importlib -import math -import numbers -import operator -import os -import sys - -import pytest - -from lalib.elements import galois -from tests import utils - - -gf2, one, zero = ( # official API outside of `lalib.elements.galois` - galois.gf2, - galois.one, - galois.zero, -) - -GF2Element, GF2One, GF2Zero = ( # not part of the official API - galois.GF2Element, - type(galois.one), # The `GF2One` and `GF2Zero` sub-classes - type(galois.zero), # are deleted in `lalib.elements.galois` -) - -del galois - - -CROSS_REFERENCE = not os.environ.get("NO_CROSS_REFERENCE") - - -strict_one_like_values = ( - 1, - 1.0, - 1.0 + utils.WITHIN_THRESHOLD, - (1 + 0j), - (1 + 0j) + complex(0, utils.WITHIN_THRESHOLD), - (1 + 0j) + complex(utils.WITHIN_THRESHOLD, 0), - decimal.Decimal("1"), - fractions.Fraction(1, 1), - "1", - "1.0", - "1+0j", -) - -non_strict_one_like_values = ( - 0.0 + utils.NOT_WITHIN_THRESHOLD, - 1.0 + utils.NOT_WITHIN_THRESHOLD, - (1 + 0j) + complex(utils.NOT_WITHIN_THRESHOLD, 0), - 42, - decimal.Decimal("42"), - fractions.Fraction(42, 1), - "42", - "42.0", - "42+0j", - "+inf", - "-inf", -) - -one_like_values = strict_one_like_values + non_strict_one_like_values - -zero_like_values = ( - 0, - 0.0, - 0.0 + utils.WITHIN_THRESHOLD, - (0 + 0j), - (0 + 0j) + complex(0, utils.WITHIN_THRESHOLD), - (0 + 0j) + complex(utils.WITHIN_THRESHOLD, 0), - decimal.Decimal("0"), - fractions.Fraction(0, 1), - "0", - "0.0", - "0+0j", -) - - -@pytest.mark.overlapping_test -class TestGF2SubClasses: - """Test the sub-classes behind `one` and `zero`.""" - - def test_gf2_is_an_alias(self): - """The "`gf2` type" is really just `GF2Element`.""" - assert gf2 is GF2Element - - @pytest.mark.parametrize("cls", [GF2One, GF2Zero]) - def test_sub_classes_for_gf2(self, cls): - """`GF2One` and `GF2Zero` are sub-classes of `gf2`.""" - assert issubclass(cls, gf2) - - -@pytest.mark.parametrize("cls", [gf2, GF2One, GF2Zero]) -class TestGF2Casting: - """Test the `gf2` class's constructor. - - `gf2(value, ...)` returns either `one` or `zero`. - - The sub-classes behind `one` and `zero` provide the - same functionality as `gf2` and have the sole purpose - of providing a unique `help()` message for `one` and `zero`. - """ - - @pytest.mark.parametrize("value", strict_one_like_values) - def test_cast_ones_strictly(self, cls, value): - """`gf2(value, strict=True)` returns `one`.""" - result1 = cls(value) # `strict=True` by default - assert result1 is one - - result2 = cls(value, strict=True) - assert result2 is one - - @pytest.mark.parametrize("value", one_like_values) - def test_cast_ones_not_strictly(self, cls, value): - """`gf2(value, strict=False)` returns `one`.""" - result1 = cls(value) - assert result1 is one - - result2 = cls(value, strict=False) - assert result2 is one - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("value", non_strict_one_like_values) - def test_cannot_cast_ones_strictly(self, cls, value): - """`gf2(value, strict=True)` needs strict `1`-like values.""" - with pytest.raises(ValueError, match="`1`-like or `0`-like"): - cls(value, strict=True) - - @pytest.mark.parametrize("value", zero_like_values) - def test_cast_zeros(self, cls, value): - """`gf2(value, strict=...)` returns `zero`.""" - result1 = cls(value) # `strict=True` by default - assert result1 is zero - - result2 = cls(value, strict=True) - assert result2 is zero - - result3 = cls(value, strict=False) - assert result3 is zero - - @pytest.mark.parametrize( - "value", - [ - complex(1, utils.NOT_WITHIN_THRESHOLD), - complex(0, utils.NOT_WITHIN_THRESHOLD), - ], - ) - @pytest.mark.parametrize("strict", [True, False]) - def test_cannot_cast_with_non_zero_imag_part(self, cls, value, strict): - """Cannot create `one` or `zero` if `.imag != 0`.""" - with pytest.raises(ValueError, match="`1`-like or `0`-like"): - cls(value, strict=strict) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("value", ["abc", (1,), [1]]) - @pytest.mark.parametrize("strict", [True, False]) - def test_cannot_cast_from_wrong_type(self, cls, value, strict): - """Cannot create `one` or `zero` from a non-numeric value.""" - with pytest.raises(TypeError): - cls(value, strict=strict) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("strict", [True, False]) - def test_cannot_cast_from_nan_value(self, cls, strict): - """Cannot create `one` or `zero` from undefined value.""" - with pytest.raises(ValueError, match="`1`-like or `0`-like"): - cls(float("NaN"), strict=strict) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("scaler", [1, 10, 100, 1000]) - def test_get_one_if_within_threshold(self, cls, scaler): - """`gf2()` returns `one` if `value` is larger than `threshold`.""" - # `NOT_WITHIN_THRESHOLD` is larger than the `DEFAULT_THRESHOLD` - # but still different from `1` => `strict=False` - value = scaler * utils.NOT_WITHIN_THRESHOLD - threshold = scaler * utils.DEFAULT_THRESHOLD - - result = cls(value, strict=False, threshold=threshold) - assert result is one - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("scaler", [1, 10, 100, 1000]) - @pytest.mark.parametrize("strict", [True, False]) - def test_get_zero_if_within_threshold(self, cls, scaler, strict): - """`gf2()` returns `zero` if `value` is smaller than `threshold`.""" - # `WITHIN_THRESHOLD` is smaller than the `DEFAULT_THRESHOLD` - value = scaler * utils.WITHIN_THRESHOLD - threshold = scaler * utils.DEFAULT_THRESHOLD - - result = cls(value, strict=strict, threshold=threshold) - assert result is zero - - -@pytest.mark.overlapping_test -@pytest.mark.parametrize("strict", [True, False]) -class TestGF2ConstructorWithoutCastedValue: - """Test the `gf2` class's constructor. - - `gf2()` returns either `one` or `zero`. - """ - - def test_get_one_from_sub_class_with_no_input_value(self, strict): - """`GF2One()` returns `one`.""" - result = GF2One(strict=strict) - assert result is one - - @pytest.mark.parametrize("cls", [gf2, GF2Zero]) - def test_get_zero_with_no_input_value(self, cls, strict): - """`gf2()` and `GF2Zero()` return `zero`.""" - result = cls(strict=strict) - assert result is zero - - -class TestGenericBehavior: - """Test the classes behind `one` and `zero`.""" - - def test_cannot_instantiate_base_class_alone(self, monkeypatch): - """`GF2One` and `GF2Zero` must be instantiated before `gf2`.""" - monkeypatch.setattr(gf2, "_instances", {}) - with pytest.raises(RuntimeError, match="internal error"): - gf2() - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("cls", [gf2, GF2One, GF2Zero]) - def test_create_singletons(self, cls): - """Singleton pattern: The classes always return the same instance.""" - first = cls() - second = cls() - assert first is second - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("obj", [one, zero]) - def test_sub_classes_return_objs(self, obj): - """`type(one)` and `type(zero)` return ... - - the sub-classes that create `one` and `zero`. - """ - sub_cls = type(obj) - assert sub_cls is not gf2 - - new_obj = sub_cls() - assert new_obj is obj - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("obj", [one, zero]) - @pytest.mark.parametrize( - "type_", - [ - numbers.Number, - numbers.Complex, - numbers.Real, - numbers.Rational, - ], - ) - def test_objs_are_numbers(self, obj, type_): - """`one` and `zero` are officially `Numbers`s.""" - assert isinstance(obj, type_) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("cls", [gf2, GF2One, GF2Zero]) - @pytest.mark.parametrize( - "method", - [ - "__abs__", - "__trunc__", - "__floor__", - "__ceil__", - "__round__", - "__floordiv__", - "__rfloordiv__", - "__mod__", - "__rmod__", - "__lt__", - "__le__", - "numerator", - "denominator", - ], - ) - @pytest.mark.parametrize("value", [1, 0]) - def test_classes_fulfill_rational_numbers_abc( - self, - cls, - method, - monkeypatch, - value, - ): - """Ensure all of `numbers.Rational`'s abstact methods are implemented.""" - monkeypatch.setattr(gf2, "_instances", {}) - monkeypatch.delattr(gf2, method) - - sub_cls = type("GF2Baby", (cls, numbers.Rational), {}) - - with pytest.raises(TypeError, match="instantiate abstract class"): - sub_cls(value) - - @pytest.mark.parametrize("func", [repr, str]) - @pytest.mark.parametrize("obj", [one, zero]) - def test_text_repr_for_objs(self, func, obj): - """`repr(one)` and `repr(zero)` return "one" and "zero" ... - - ... which is valid code evaluating into the objects themselves. - - `str()` does the same as `repr()`. - """ - new_obj = eval(func(obj)) # noqa: S307 - assert new_obj is obj - - @pytest.mark.parametrize("func", [repr, str]) - @pytest.mark.parametrize("obj", [one, zero]) - def test_text_repr_for_classes(self, func, obj): - """'gf2' is the text representation for all sub-classes ... - - ... which is valid code referring to the base class `gf2`. - - `gf2()` returns `zero` if called without arguments. - """ - base_cls = eval(func(type(obj))) # noqa: S307 - assert base_cls is gf2 - - new_obj = base_cls() - assert new_obj is zero - - -class TestNumericBehavior: - """Test how `one` and `zero` behave like numbers.""" - - @pytest.mark.overlapping_test - def test_make_complex(self): - """`one` and `zero` behave like `1 + 0j` and `0 + 0j`.""" - assert (complex(one), complex(zero)) == (1 + 0j, 0 + 0j) - - @pytest.mark.overlapping_test - def test_make_float(self): - """`one` and `zero` behave like `1.0` and `0.0`.""" - assert (float(one), float(zero)) == (1.0, 0.0) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("func", [int, hash]) - def test_make_int(self, func): - """`one` and `zero` behave like `1` and `0`. - - That also holds true for their hash values. - """ - assert (func(one), func(zero)) == (1, 0) - - def test_make_bool(self): - """`one` and `zero` behave like `True` and `False`.""" - assert (bool(one), bool(zero)) == (True, False) - - @pytest.mark.parametrize("obj", [one, zero]) - def test_get_abs_value(self, obj): - """`abs(one)` and `abs(zero)` are `one` and `zero`.""" - assert abs(obj) is obj - - @pytest.mark.parametrize("obj", [one, zero]) - @pytest.mark.parametrize("func", [math.trunc, math.floor, math.ceil, round]) - def test_round_obj(self, obj, func): - """`func(one)` and `func(zero)` equal `1` and `0`.""" - assert func(obj) in (1, 0) - - if CROSS_REFERENCE: - assert func(obj) == obj - - def test_real_part(self): - """`one.real` and `zero.real` are `1` and `0`.""" - assert (one.real, zero.real) == (1, 0) - - def test_imag_part(self): - """`one.imag` and `zero.imag` are `0`.""" - assert (one.imag, zero.imag) == (0, 0) - - def test_conjugate(self): - """`one.conjugate()` and `zero.conjugate()` are `1 + 0j` and `0 + 0j`.""" - assert (one.conjugate(), zero.conjugate()) == (1 + 0j, 0 + 0j) - - @pytest.mark.overlapping_test - def test_one_as_fraction(self): - """`one.numerator / one.denominator` equals `1`.""" - assert (one.numerator, one.denominator) == (1, 1) - - @pytest.mark.overlapping_test - def test_zero_as_fraction(self): - """`one.numerator / one.denominator` equals `0`.""" - assert (zero.numerator, zero.denominator) == (0, 1) - - -class TestComparison: - """Test `one` and `zero` interact with relational operators.""" - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("obj", [one, zero]) - def test_equal_to_itself(self, obj): - """`one` and `zero` are equal to themselves.""" - assert obj == obj # noqa: PLR0124 - - @pytest.mark.overlapping_test - @pytest.mark.parametrize( - ["first", "second"], - [ - (one, one), - (one, 1), - (one, 1.0), - (one, 1 + 0j), - (zero, zero), - (zero, 0), - (zero, 0.0), - (zero, 0 + 0j), - ], - ) - def test_equal_to_another(self, first, second): - """`one` and `zero` are equal to `1`-like and `0`-like numbers.""" - assert first == second - assert second == first - - @pytest.mark.overlapping_test - @pytest.mark.parametrize( - ["first", "second"], - [ - (one, zero), - (one, 0), - (one, 0.0), - (one, 0 + 0j), - (zero, 1), - (zero, 1.0), - (zero, 1 + 0j), - ], - ) - def test_not_equal_to_another_one_or_zero_like(self, first, second): - """`one` and `zero` are not equal to `0`-like and `1`-like numbers.""" - assert first != second - assert second != first - - @pytest.mark.overlapping_test - @pytest.mark.parametrize( - ["first", "second"], - [ - (one, 42), - (one, 42.0), - (one, 42 + 0j), - (one, 0 + 42j), - (zero, 42), - (zero, 42.0), - (zero, 42 + 0j), - (zero, 0 + 42j), - ], - ) - def test_not_equal_to_another_non_one_like(self, first, second): - """`one` and `zero` are not equal to non-`1`-or-`0`-like numbers.""" - assert first != second - assert second != first - - @pytest.mark.parametrize("operator", [operator.gt, operator.ge]) - def test_one_greater_than_or_equal_to_zero(self, operator): - """`one > zero` and `one >= zero`.""" - assert operator(one, zero) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("operator", [operator.lt, operator.le]) - def test_one_not_smaller_than_or_equal_to_zero(self, operator): - """`not one < zero` and `not one <= zero`.""" - assert not operator(one, zero) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("operator", [operator.lt, operator.le]) - def test_zero_smaller_than_or_equal_to_one(self, operator): - """`zero < one` and `zero <= one`.""" - assert operator(zero, one) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("operator", [operator.gt, operator.ge]) - def test_zero_not_greater_than_or_equalt_to_one(self, operator): - """`not zero > one` and `not zero >= one`.""" - assert not operator(zero, one) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("obj", [one, zero]) - def test_obj_not_strictly_greater_than_itself(self, obj): - """`obj >= obj` but not `obj > obj`.""" - assert obj >= obj # noqa: PLR0124 - assert not obj > obj # noqa: PLR0124 - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("obj", [one, zero]) - def test_obj_not_strictly_smaller_than_itself(self, obj): - """`obj <= obj` but not `obj < obj`.""" - assert obj <= obj # noqa: PLR0124 - assert not obj < obj # noqa: PLR0124 - - @pytest.mark.parametrize("obj", [one, zero]) - @pytest.mark.parametrize( - "operator", - [operator.gt, operator.ge, operator.lt, operator.le], - ) - def test_compare_to_other_operand_of_wrong_type(self, obj, operator): - """`one` and `zero` may only interact with numbers.""" - with pytest.raises(TypeError): - operator(obj, "abc") - - with pytest.raises(TypeError): - operator("abc", obj) - - @pytest.mark.parametrize("obj", [one, zero]) - @pytest.mark.parametrize( - "operator", - [operator.gt, operator.ge, operator.lt, operator.le], - ) - def test_compare_to_other_operand_of_wrong_value(self, obj, operator): - """`one` and `zero` may only interact with `1`-like or `0`-like numbers.""" - with pytest.raises(ValueError, match="`1`-like or `0`-like"): - operator(obj, 42) - - with pytest.raises(ValueError, match="`1`-like or `0`-like"): - operator(42, obj) - - -class TestArithmetic: - """Test `one` and `zero` interact with arithmetic operators.""" - - @pytest.mark.parametrize("obj", [one, zero]) - @pytest.mark.parametrize("operator", [operator.pos, operator.neg]) - def test_make_obj_positive_or_negative(self, obj, operator): - """`+one` and `+zero` equal `-one` and `-zero`.""" - assert obj is operator(obj) - - @pytest.mark.parametrize( - "objs", - [ - (one, one, zero), - (one, zero, one), - (zero, zero, zero), - (one, 1, zero), - (one, 1.0, zero), - (one, 1 + 0j, zero), - (one, 0, one), - (one, 0.0, one), - (one, 0 + 0j, one), - (zero, 1, one), - (zero, 1.0, one), - (zero, 1 + 0j, one), - (zero, 0, zero), - (zero, 0.0, zero), - (zero, 0 + 0j, zero), - ], - ) - @pytest.mark.parametrize("operator", [operator.add, operator.sub]) - def test_addition_and_subtraction(self, objs, operator): - """Adding and subtracting `one` and `zero` is identical and commutative.""" - first, second, expected = objs - - result1 = operator(first, second) - assert result1 is expected - - result2 = operator(second, first) - assert result2 is expected - - if CROSS_REFERENCE: # cast `one` and `zero` as `integer`s before doing the math - - result3 = gf2((operator(int(abs(first)), int(abs(second))) + 2) % 2) - assert result3 is expected - - result4 = gf2((operator(int(abs(second)), int(abs(first))) + 2) % 2) - assert result4 is expected - - @pytest.mark.parametrize( - ["first", "second", "expected"], - [ - (one, one, one), - (one, zero, zero), - (zero, zero, zero), - (one, 1, one), - (one, 1.0, one), - (one, 1 + 0j, one), - (one, 0, zero), - (one, 0.0, zero), - (one, 0 + 0j, zero), - (zero, 1, zero), - (zero, 1.0, zero), - (zero, 1 + 0j, zero), - (zero, 0, zero), - (zero, 0.0, zero), - (zero, 0 + 0j, zero), - ], - ) - def test_multiplication(self, first, second, expected): - """Multiplying `one` and `zero` is commutative.""" - result1 = first * second - assert result1 is expected - - result2 = second * first - assert result2 is expected - - if CROSS_REFERENCE: # cast `one` and `zero` as `integer`s before doing the math - - result3 = gf2(int(abs(first)) * int(abs(second))) - assert result3 is expected - - result4 = gf2(int(abs(second)) * int(abs(first))) - assert result4 is expected - - @pytest.mark.overlapping_test - @pytest.mark.parametrize( - "objs", - [ - # In Python 3.9 we cannot floor-divide a `complex` number - (one, one, one), - (one, 1, one), - (one, 1.0, one), - (one, 1 + 0j, one), - (1, one, one), - (1.0, one, one), - (zero, one, zero), - (zero, 1, zero), - (zero, 1.0, zero), - (zero, 1 + 0j, zero), - (0, one, zero), - (0.0, one, zero), - ], - ) - @pytest.mark.parametrize( - "operator", - [operator.truediv, operator.floordiv], - ) - def test_division_by_one(self, objs, operator): - """Division by `one`.""" - first, second, expected = objs - - result1 = operator(first, second) - assert result1 is expected - - if CROSS_REFERENCE: # cast `one` and `zero` as `integer`s before doing the math - - result2 = gf2(operator(int(abs(first)), int(abs(second)))) - assert result2 is expected - - @pytest.mark.overlapping_test - @pytest.mark.parametrize( - "objs", - [ - # In Python 3.9 we cannot modulo-divide a `complex` number - (one, one, zero), - (one, 1, zero), - (one, 1.0, zero), - (one, 1 + 0j, zero), - (1, one, zero), - (1.0, one, zero), - (zero, one, zero), - (zero, 1, zero), - (zero, 1.0, zero), - (zero, 1 + 0j, zero), - (0, one, zero), - (0.0, one, zero), - ], - ) - def test_modulo_division_by_one(self, objs): - """Division by `one`.""" - first, second, expected = objs - - result1 = first % second - assert result1 is expected - - if CROSS_REFERENCE: # cast `one` and `zero` as `integer`s before doing the math - - result2 = gf2(int(abs(first)) % int(abs(second))) - assert result2 is expected - - @pytest.mark.parametrize( - "objs", - [ - # In Python 3.9 we cannot floor-divide a `complex` number - (one, zero), - (one, 0), - (one, 0.0), - (1, zero), - (1.0, zero), - (zero, zero), - (zero, 0), - (zero, 0.0), - (0, zero), - (0.0, zero), - ], - ) - @pytest.mark.parametrize( - "operator", - [operator.truediv, operator.floordiv, operator.mod], - ) - def test_division_by_zero(self, objs, operator): - """Division by `zero` raises `ZeroDivisionError`.""" - first, second = objs - - with pytest.raises(ZeroDivisionError): - operator(first, second) - - if CROSS_REFERENCE: # cast `one` and `zero` as `integer`s before doing the math - - with pytest.raises(ZeroDivisionError): - operator(int(abs(first)), int(abs(second))) - - @pytest.mark.parametrize( - "objs", - [ - (one, one, one), - (one, 1, one), - (one, 1.0, one), - (one, 1 + 0j, one), - (1, one, one), - (1.0, one, one), - (1 + 0j, one, one), - (zero, one, zero), - (zero, 1, zero), - (zero, 1.0, zero), - (zero, 1 + 0j, zero), - (0, one, zero), - (0.0, one, zero), - (0 + 0j, one, zero), - (one, zero, one), - (one, 0, one), - (one, 0.0, one), - (one, 0 + 0j, one), - (1, zero, one), - (1.0, zero, one), - (1 + 0j, zero, one), - (zero, zero, one), - (zero, 0, one), - (zero, 0.0, one), - (zero, 0 + 0j, one), - (0, zero, one), - (0.0, zero, one), - (0 + 0j, zero, one), - ], - ) - def test_to_the_power_of(self, objs): - """Exponentiation with `one` and `zero`.""" - first, second, expected = objs - - result1 = first**second - assert result1 is expected - - if CROSS_REFERENCE: # cast `one` and `zero` as `integer`s before doing the math - - result2 = gf2(int(abs(first)) ** int(abs(second))) - assert result2 is expected - - @pytest.mark.parametrize("obj", [one, zero]) - @pytest.mark.parametrize( - "operator", - [ - operator.add, - operator.mul, - operator.truediv, - operator.floordiv, - operator.mod, - operator.pow, - ], - ) - def test_other_operand_of_wrong_type(self, obj, operator): - """`one` and `zero` may only interact with numbers.""" - # Cannot use a `str` like `"abc"` as then `%` means string formatting - with pytest.raises(TypeError): - operator(obj, ("a", "b", "c")) - - with pytest.raises(TypeError): - operator(("a", "b", "c"), obj) - - @pytest.mark.parametrize("obj", [one, zero]) - @pytest.mark.parametrize( - "operator", - [ - operator.add, - operator.mul, - operator.truediv, - operator.floordiv, - operator.mod, - operator.pow, - ], - ) - def test_other_operand_of_wrong_value(self, obj, operator): - """`one` and `zero` may only interact with `1`-like or `0`-like numbers.""" - with pytest.raises(ValueError, match="`1`-like or `0`-like"): - operator(obj, 42) - - with pytest.raises(ValueError, match="`1`-like or `0`-like"): - operator(42, obj) - - -@pytest.mark.overlapping_test -@pytest.mark.skipif( - not sys.version_info < (3, 11), - reason='"typing-extensions" are installed to support Python 3.9 & 3.10', -) -def test_can_import_typing_extensions(): - """For Python versions 3.11+ we do not need the "typing-extensions".""" - package = importlib.import_module("lalib.elements.galois") - importlib.reload(package) - - assert package.Self is not None - - -@pytest.mark.sanity_test -def test_thresholds(): - """Sanity check for the thresholds used in the tests below.""" - assert utils.WITHIN_THRESHOLD < utils.DEFAULT_THRESHOLD < utils.NOT_WITHIN_THRESHOLD diff --git a/tests/fields/__init__.py b/tests/fields/__init__.py deleted file mode 100644 index 66e8480..0000000 --- a/tests/fields/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the `lalib.fields` sub-package.""" diff --git a/tests/fields/test_axioms.py b/tests/fields/test_axioms.py deleted file mode 100644 index 680ebb6..0000000 --- a/tests/fields/test_axioms.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Ensure all `Field`s fulfill the axioms from math. - -Source: https://en.wikipedia.org/wiki/Field_(mathematics)#Classic_definition -""" - -import contextlib -import operator - -import pytest - -from tests.fields import utils - - -# None of the test cases below contributes towards higher coverage -pytestmark = pytest.mark.integration_test - - -@pytest.mark.repeat(utils.N_RANDOM_DRAWS) -@pytest.mark.parametrize("field", utils.ALL_FIELDS) -class TestAllFieldsManyTimes: - """Run the tests many times for all `field`s.""" - - @pytest.mark.parametrize("opr", [operator.add, operator.mul]) - def test_associativity(self, field, opr): - """`a + (b + c) == (a + b) + c` ... - - ... and `a * (b * c) == (a * b) * c`. - """ - a, b, c = field.random(), field.random(), field.random() - - left = opr(a, opr(b, c)) - right = opr(opr(a, b), c) - - assert left == pytest.approx(right, abs=utils.DEFAULT_THRESHOLD) - - @pytest.mark.parametrize("opr", [operator.add, operator.mul]) - def test_commutativity(self, field, opr): - """`a + b == b + a` ... - - ... and `a * b == b * a`. - """ - a, b = field.random(), field.random() - - left = opr(a, b) - right = opr(b, a) - - assert left == pytest.approx(right, abs=utils.DEFAULT_THRESHOLD) - - def test_additive_identity(self, field): - """`a + 0 == a`.""" - a = field.random() - - left = a + field.zero - right = a - - assert left == pytest.approx(right, abs=utils.DEFAULT_THRESHOLD) - - def test_multiplicative_identity(self, field): - """`a * 1 == a`.""" - a = field.random() - - left = a * field.one - right = a - - assert left == pytest.approx(right, abs=utils.DEFAULT_THRESHOLD) - - def test_additive_inverse(self, field): - """`a + (-a) == 0`.""" - a = field.random() - - left = a + (-a) - right = field.zero - - assert left == pytest.approx(right, abs=utils.DEFAULT_THRESHOLD) - - def test_multiplicative_inverse(self, field): - """`a * (1 / a) == 1`.""" - a = field.random() - - # Realistically, `ZeroDivisionError` only occurs for `GF2` - # => With a high enough `utils.N_RANDOM_DRAWS` - # this test case is also `assert`ed for `GF2` - with contextlib.suppress(ZeroDivisionError): - left = a * (field.one / a) - right = field.one - - assert left == pytest.approx(right, abs=utils.DEFAULT_THRESHOLD) - - def test_distributivity(self, field): - """`a * (b + c) == (a * b) + (a * c)`.""" - a, b, c = field.random(), field.random(), field.random() - - left = a * (b + c) - right = (a * b) + (a * c) - - assert left == pytest.approx(right, abs=utils.DEFAULT_THRESHOLD) diff --git a/tests/fields/test_base.py b/tests/fields/test_base.py deleted file mode 100644 index 80f00dc..0000000 --- a/tests/fields/test_base.py +++ /dev/null @@ -1,267 +0,0 @@ -"""Generic tests for all `lalib.fields.*.Field`s. - -The abstract base class `lalib.fields.base.Field` -defines generic behavior that all concrete `Field`s -in the `lalib.fields` sub-package must implement. -""" - -import random - -import pytest - -from lalib import fields -from tests.fields import utils - - -@pytest.mark.parametrize("field", utils.ALL_FIELDS) -class TestGenericClassBehavior: - """Generic `Field` behavior.""" - - def test_create_singletons(self, field): - """All `field`s so far are singletons.""" - cls = type(field) - new_field = cls() - - assert new_field is field - - @pytest.mark.parametrize("func", [repr, str]) - def test_text_repr(self, field, func): - """The text representations behave like Python literals.""" - new_field = eval(func(field), fields.__dict__) # noqa: S307 - - assert new_field is field - - -class TestCastAndValidateFieldElements: - """Test `Field.cast()` and `Field.validate()`. - - Every `field` must be able to tell if a given `value` is - an element of the `field`, and, if so, `.cast()` it as such. - """ - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("field", utils.NON_10_FIELDS) - @pytest.mark.parametrize("value", utils.NUMBERS) - def test_number_is_field_element(self, field, value): - """Common numbers are typically `field` elements. - - This is not true for `GF2`, which, by default, - only accepts `1`-like and `0`-like numbers. - """ - utils.is_field_element(field, value) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("field", utils.ALL_FIELDS) - @pytest.mark.parametrize("value", utils.ONES_N_ZEROS) - def test_one_and_zero_number_is_field_element(self, field, value): - """`1`-like and `0`-like numbers are always `field` elements.""" - utils.is_field_element(field, value) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("field", utils.ALL_FIELDS) - @pytest.mark.parametrize("value", ["abc", (1, 2, 3)]) - def test_non_numeric_value_is_not_field_element(self, field, value): - """Values of non-numeric data types are typically not `field` elements.""" - utils.is_not_field_element(field, value) - - @pytest.mark.parametrize("field", utils.ALL_FIELDS) - @pytest.mark.parametrize("pre_value", ["NaN", "+inf", "-inf"]) - def test_non_finite_number_is_not_field_element(self, field, pre_value): - """For now, we only allow finite numbers as `field` elements. - - Notes: - - `Q._cast_func()` cannot handle non-finite `value`s - and raises an `OverflowError` or `ValueError` - => `Field.cast()` catches these errors - and (re-)raises a `ValueError` instead - => no need to define a specific `._post_cast_filter()` - - `R._cast_func()` and `C._cast_func()` - handle non-finite `value`s without any complaints - => using a `._post_cast_filter()`, we don't allow - non-finite but castable `value`s to be `field` elements - - `GF2._cast_func()` handles non-finite `value`s - by raising a `ValueError` already - => `Field.cast()` re-raises it with an adapted message - => no need to define a specific `._post_cast_filter()` - """ - value = float(pre_value) - utils.is_not_field_element(field, value) - - -class TestDTypes: - """Test the `Field.dtype` property.""" - - @pytest.mark.parametrize("field", utils.ALL_FIELDS) - def test_field_dtype(self, field): - """`field.dtype` must be a `type`.""" - assert isinstance(field.dtype, type) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("field", utils.ALL_FIELDS) - def test_element_is_instance_of_field_dtype(self, field): - """Elements are an instance of `field.dtype`.""" - element = field.random() - - assert isinstance(element, field.dtype) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("field", utils.ALL_FIELDS) - def test_element_dtype_is_subclass_of_field_dtype(self, field): - """Elements may have a more specific `.dtype` than their `field.dtype`.""" - element = field.random() - dtype = type(element) - - assert issubclass(dtype, field.dtype) - - -class TestIsZero: - """Test `Field.zero` & `Field.is_zero()`.""" - - @pytest.mark.parametrize("field", utils.ALL_FIELDS) - @pytest.mark.parametrize("value", utils.ZEROS) - def test_is_exactly_zero(self, field, value): - """`value` is equal to `field.zero`.""" - assert field.zero == value - assert field.is_zero(value) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("field", utils.ALL_FIELDS) - def test_is_almost_zero(self, field): - """`value` is within an acceptable threshold of `field.zero`.""" - value = 0.0 + utils.WITHIN_THRESHOLD - - assert pytest.approx(field.zero, abs=utils.DEFAULT_THRESHOLD) == value - assert field.is_zero(value) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("field", utils.NON_10_FIELDS) - def test_is_slightly_not_zero(self, field): - """`value` is not within an acceptable threshold of `field.zero`.""" - value = 0.0 + utils.NOT_WITHIN_THRESHOLD - - assert pytest.approx(field.zero, abs=utils.DEFAULT_THRESHOLD) != value - assert not field.is_zero(value) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("field", utils.ALL_FIELDS) - @pytest.mark.parametrize("value", utils.ONES) - def test_is_not_zero(self, field, value): - """`value` is not equal to `field.zero`.""" - assert field.zero != value - assert not field.is_zero(value) - - -class TestIsOne: - """Test `Field.one` & `Field.is_one()`.""" - - @pytest.mark.parametrize("field", utils.ALL_FIELDS) - @pytest.mark.parametrize("value", utils.ONES) - def test_is_exactly_one(self, field, value): - """`value` is equal to `field.one`.""" - assert field.one == value - assert field.is_one(value) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("field", utils.ALL_FIELDS) - def test_is_almost_one(self, field): - """`value` is within an acceptable threshold of `field.one`.""" - value = 1.0 + utils.WITHIN_THRESHOLD - - assert pytest.approx(field.one, abs=utils.DEFAULT_THRESHOLD) == value - assert field.is_one(value) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("field", utils.NON_10_FIELDS) - def test_is_slightly_not_one(self, field): - """`value` is not within an acceptable threshold of `field.one`.""" - value = 1.0 + utils.NOT_WITHIN_THRESHOLD - - assert pytest.approx(field.one, abs=utils.DEFAULT_THRESHOLD) != value - assert not field.is_one(value) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("field", utils.ALL_FIELDS) - @pytest.mark.parametrize("value", utils.ZEROS) - def test_is_not_one(self, field, value): - """`value` is not equal to `field.one`.""" - assert field.one != value - assert not field.is_one(value) - - -@pytest.mark.repeat(utils.N_RANDOM_DRAWS) -class TestDrawRandomFieldElement: - """Test `Field.random()`.""" - - @pytest.mark.parametrize("field", utils.ALL_FIELDS) - def test_draw_element_with_default_bounds(self, field): - """Draw a random element from the `field`, ... - - ... within the `field`'s default bounds. - - Here, the default bounds come from the default arguments. - """ - element = field.random() - - assert field.validate(element) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("field", utils.ALL_FIELDS) - def test_draw_element_with_default_bounds_set_to_none(self, field): - """Draw a random element from the `field`, ... - - ... within the `field`'s default bounds. - - If no default arguments are defined in `field.random()`, - the internal `Field._get_bounds()` method provides them. - """ - element = field.random(lower=None, upper=None) - - assert field.validate(element) - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("field", utils.NON_10_FIELDS) - def test_draw_element_with_custom_bounds(self, field): - """Draw a random element from the `field` ... - - ... within the bounds passed in as arguments. - - For `GF2`, this only works in non-`strict` mode. - """ - lower = 200 * random.random() - 100 # noqa: S311 - upper = 200 * random.random() - 100 # noqa: S311 - - # `field.random()` sorts the bounds internally - # => test both directions - element1 = field.random(lower=lower, upper=upper) - element2 = field.random(lower=upper, upper=lower) - - assert field.validate(element1) - assert field.validate(element2) - - # Done implicitly in `field.random()` above - lower, upper = field.cast(lower), field.cast(upper) - - # Not all data types behind the `Field._cast_func()` - # support sorting the numbers (e.g., `complex`) - try: - swap = upper < lower - except TypeError: - pass - else: - if swap: - lower, upper = upper, lower - - assert lower <= element1 <= upper - assert lower <= element2 <= upper - - -@pytest.mark.sanity_test -def test_numbers(): - """We use `0`, `1`, `+42`, and `-42` in different data types.""" - unique_one_and_zero = {int(n) for n in utils.ONES_N_ZEROS} - unique_non_one_and_zero = {int(n) for n in utils.NON_ONES_N_ZEROS} - unique_numbers = {int(n) for n in utils.NUMBERS} - - assert unique_one_and_zero == {0, 1} - assert unique_non_one_and_zero == {+42, -42} - assert unique_numbers == {0, 1, +42, -42} diff --git a/tests/fields/test_complex.py b/tests/fields/test_complex.py deleted file mode 100644 index be9bc85..0000000 --- a/tests/fields/test_complex.py +++ /dev/null @@ -1,100 +0,0 @@ -"""Tests for the `lalib.fields.complex_.ComplexField` only.""" - -import random - -import pytest - -from lalib import fields -from tests.fields import utils - - -# None of the test cases below contributes towards higher coverage -pytestmark = pytest.mark.overlapping_test - - -C = fields.C - - -class TestCastAndValidateFieldElements: - """Test specifics for `C.cast()` and `C.validate()`.""" - - @pytest.mark.parametrize("pre_value", [1, 0, +42, -42]) - def test_complex_number_is_field_element(self, pre_value): - """`C` must be able to process `complex` numbers.""" - value = complex(pre_value, 0) - utils.is_field_element(C, value) - - @pytest.mark.parametrize("pre_value", ["NaN", "+inf", "-inf"]) - def test_non_finite_complex_number_is_not_field_element(self, pre_value): - """For now, we only allow finite numbers as field elements. - - This also holds true for `complex` numbers - with a non-finite `.real` part. - """ - value = complex(pre_value) - utils.is_not_field_element(C, value) - - -class TestIsZero: - """Test specifics for `C.zero` and `C.is_zero()`.""" - - def test_is_almost_zero(self): - """`value` is within an acceptable threshold of `C.zero`.""" - value = 0.0 + utils.WITHIN_THRESHOLD - - assert pytest.approx(C.zero, abs=utils.DEFAULT_THRESHOLD) == value - assert C.is_zero(value) - - def test_is_slightly_not_zero(self): - """`value` is not within an acceptable threshold of `C.zero`.""" - value = 0.0 + utils.NOT_WITHIN_THRESHOLD - - assert pytest.approx(C.zero, abs=utils.DEFAULT_THRESHOLD) != value - assert not C.is_zero(value) - - -class TestIsOne: - """Test specifics for `C.one` and `C.is_one()`.""" - - def test_is_almost_one(self): - """`value` is within an acceptable threshold of `C.one`.""" - value = 1.0 + utils.WITHIN_THRESHOLD - - assert pytest.approx(C.one, abs=utils.DEFAULT_THRESHOLD) == value - assert C.is_one(value) - - def test_is_slightly_not_one(self): - """`value` is not within an acceptable threshold of `C.one`.""" - value = 1.0 + utils.NOT_WITHIN_THRESHOLD - - assert pytest.approx(C.one, abs=utils.DEFAULT_THRESHOLD) != value - assert not C.is_one(value) - - -@pytest.mark.repeat(utils.N_RANDOM_DRAWS) -class TestDrawRandomFieldElement: - """Test specifics for `C.random()`.""" - - def test_draw_elements_with_custom_bounds(self): - """Draw a random element from `C` ... - - ... within the bounds passed in as arguments. - - For `C`, the bounds are interpreted in a 2D fashion. - """ - lower = complex( - 200 * random.random() - 100, # noqa: S311 - 200 * random.random() - 100, # noqa: S311 - ) - upper = complex( - 200 * random.random() - 100, # noqa: S311 - 200 * random.random() - 100, # noqa: S311 - ) - - element = C.random(lower=lower, upper=upper) - - l_r, u_r = min(lower.real, upper.real), max(lower.real, upper.real) - l_i, u_i = min(lower.imag, upper.imag), max(lower.imag, upper.imag) - - assert l_r <= element.real <= u_r - assert l_i <= element.imag <= u_i diff --git a/tests/fields/test_galois.py b/tests/fields/test_galois.py deleted file mode 100644 index d708a17..0000000 --- a/tests/fields/test_galois.py +++ /dev/null @@ -1,145 +0,0 @@ -"""Tests for the `lalib.fields.galois.GaloisField2` only.""" - -import itertools - -import pytest - -from lalib import fields -from tests.fields import utils - - -# None of the test cases below contributes towards higher coverage -pytestmark = pytest.mark.overlapping_test - - -GF2 = fields.GF2 - - -class TestCastAndValidateFieldElements: - """Test specifics for `GF2.cast()` and `GF2.validate()`.""" - - @pytest.mark.parametrize("value", utils.NUMBERS) - def test_number_is_field_element(self, value): - """Common numbers are always `GF2` elements in non-`strict` mode.""" - left = GF2.cast(value, strict=False) - right = bool(value) - - assert left == right - assert GF2.validate(value, strict=False) - - @pytest.mark.parametrize("value", utils.ONES_N_ZEROS) - def test_one_and_zero_number_is_field_element(self, value): - """`1`-like and `0`-like `value`s are `GF2` elements.""" - utils.is_field_element(GF2, value) - - @pytest.mark.parametrize("pre_value", [1, 0]) - def test_one_or_zero_like_complex_number_is_field_element(self, pre_value): - """`GF2` can process `complex` numbers.""" - value = complex(pre_value, 0) - utils.is_field_element(GF2, value) - - @pytest.mark.parametrize("pre_value", [+42, -42]) - def test_non_one_or_zero_like_complex_number_is_not_field_element(self, pre_value): - """`GF2` can process `complex` numbers ... - - ... but they must be `one`-like or `zero`-like - to become a `GF2` element. - """ - value = complex(pre_value, 0) - utils.is_not_field_element(GF2, value) - - @pytest.mark.parametrize("pre_value", [+42, -42]) - def test_non_one_or_zero_like_complex_number_is_field_element(self, pre_value): - """`GF2` can process all `complex` numbers in non-`strict` mode.""" - value = complex(pre_value, 0) - - left = GF2.cast(value, strict=False) - right = bool(value) - - assert left == right - assert GF2.validate(value, strict=False) - - @pytest.mark.parametrize("pre_value", ["NaN", "+inf", "-inf"]) - def test_non_finite_complex_number_is_not_field_element(self, pre_value): - """For now, we only allow finite numbers as field elements. - - This also holds true for `complex` numbers - with a non-finite `.real` part. - """ - value = complex(pre_value) - utils.is_not_field_element(GF2, value) - - @pytest.mark.parametrize("value", ["1", "0"]) - def test_one_or_zero_like_numeric_str_is_field_element(self, value): - """`GF2` can process `str`ings resemling `1`s and `0`s.""" - utils.is_field_element(GF2, value) - - @pytest.mark.parametrize("value", ["+42", "-42"]) - def test_non_one_or_zero_like_numeric_str_is_not_field_element(self, value): - """`GF2` can process `str`ings resembling numbers ... - - ... but they must be `1`-like or `0`-like. - """ - utils.is_not_field_element(GF2, value) - - @pytest.mark.parametrize("value", ["+42", "-42"]) - def test_non_one_or_zero_like_numeric_str_is_field_element(self, value): - """`GF2` can process `str`ings resemling any number in non-`strict` mode.""" - left = GF2.cast(value, strict=False) - right = bool(float(value)) - - assert left == right - assert GF2.validate(value, strict=False) - - @pytest.mark.parametrize("value", ["NaN", "+inf", "-inf"]) - def test_non_finite_numeric_str_is_not_field_element(self, value): - """`GF2` can process `str`ings resemling numbers ... - - ... but they must represent finite numbers. - """ - utils.is_not_field_element(GF2, value) - - -class TestIsZero: - """Test specifics for `GF2.zero` and `GF2.is_zero()`.""" - - def test_is_slightly_not_zero(self): - """`value` is not within an acceptable threshold of `GF2.zero`.""" - value = 0.0 + utils.NOT_WITHIN_THRESHOLD - - assert GF2.zero != value - - with pytest.raises(ValueError, match="not an element of the field"): - GF2.is_zero(value) - - -class TestIsOne: - """Test specifics for `GF2.one` and `GF2.is_one()`.""" - - def test_is_slightly_not_one(self): - """`value` is not within an acceptable threshold of `GF2.one`.""" - value = 1.0 + utils.NOT_WITHIN_THRESHOLD - - assert GF2.one != value - - with pytest.raises(ValueError, match="not an element of the field"): - GF2.is_one(value) - - -@pytest.mark.repeat(utils.N_RANDOM_DRAWS) -class TestDrawRandomFieldElement: - """Test specifics for `GF2.random()`.""" - - @pytest.mark.parametrize("bounds", itertools.product([0, 1], repeat=2)) - def test_draw_element_with_custom_bounds(self, bounds): - """Draw a random element from `GF2` in non-`strict` mode ... - - ... within the bounds passed in as arguments. - """ - lower, upper = bounds - element = GF2.random(lower=lower, upper=upper) - - if upper < lower: - lower, upper = upper, lower - - assert lower <= element <= upper diff --git a/tests/fields/test_rational.py b/tests/fields/test_rational.py deleted file mode 100644 index 62b909b..0000000 --- a/tests/fields/test_rational.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Tests for the `lalib.fields.rational.RationalField` only.""" - -import fractions - -import pytest - -from lalib import fields - - -# None of the test cases below contributes towards higher coverage -pytestmark = pytest.mark.overlapping_test - - -Q = fields.Q - - -class TestCastAndValidateFieldElements: - """Test specifics for `Q.cast()` and `Q.validate()`.""" - - @pytest.mark.parametrize( - "value", - ["1", "0", "1/1", "0/1", "+42", "-42", "+42/1", "-42/1"], - ) - def test_str_is_field_element(self, value): - """`fractions.Fraction()` also accepts `str`ings. - - Source: https://docs.python.org/3/library/fractions.html#fractions.Fraction - """ - left = Q.cast(value) - right = fractions.Fraction(value) - - assert left == right - assert Q.validate(value) diff --git a/tests/fields/utils.py b/tests/fields/utils.py deleted file mode 100644 index 3bb9303..0000000 --- a/tests/fields/utils.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Utilities to test the `lalib.fields` sub-package.""" - -import decimal -import fractions -import os - -import pytest - -from lalib import elements -from lalib import fields -from tests import utils as root_utils - - -ALL_FIELDS = (fields.Q, fields.R, fields.C, fields.GF2) -NON_10_FIELDS = (fields.Q, fields.R, fields.C) - -ONES = ( - 1, - 1.0, - fractions.Fraction(1, 1), - decimal.Decimal("1.0"), - elements.one, - True, -) - -ZEROS = ( - 0, - 0.0, - fractions.Fraction(0, 1), - decimal.Decimal("+0.0"), - decimal.Decimal("-0.0"), - elements.zero, - False, -) - -ONES_N_ZEROS = ONES + ZEROS - -NON_ONES_N_ZEROS = ( - +42, - +42.0, - fractions.Fraction(+42, 1), - decimal.Decimal("+42.0"), - -42, - -42.0, - fractions.Fraction(-42, 1), - decimal.Decimal("-42.0"), -) - -NUMBERS = ONES_N_ZEROS + NON_ONES_N_ZEROS - -DEFAULT_THRESHOLD = root_utils.DEFAULT_THRESHOLD -WITHIN_THRESHOLD = root_utils.WITHIN_THRESHOLD -NOT_WITHIN_THRESHOLD = root_utils.NOT_WITHIN_THRESHOLD - -N_RANDOM_DRAWS = os.environ.get("N_RANDOM_DRAWS") or 1 - - -def is_field_element(field, value): - """Utility method to avoid redundant logic in tests.""" - element = field.cast(value) - - assert element == value - assert field.validate(value) - - -def is_not_field_element(field, value): - """Utility method to avoid redundant logic in tests.""" - with pytest.raises(ValueError, match="not an element of the field"): - field.cast(value) - - assert not field.validate(value) - assert not field.validate(value, silent=True) - - with pytest.raises(ValueError, match="not an element of the field"): - field.validate(value, silent=False) diff --git a/tests/test_docstrings.py b/tests/test_docstrings.py index 2e09eca..4890a36 100644 --- a/tests/test_docstrings.py +++ b/tests/test_docstrings.py @@ -10,20 +10,10 @@ import pytest import xdoctest -@pytest.mark.integration_test @pytest.mark.parametrize( "module", [ "lalib", - "lalib.domains", - "lalib.elements", - "lalib.elements.galois", - "lalib.fields", - "lalib.fields.base", - "lalib.fields.complex_", - "lalib.fields.galois", - "lalib.fields.rational", - "lalib.fields.real", ], ) def test_docstrings(module): diff --git a/tests/test_domains.py b/tests/test_domains.py deleted file mode 100644 index ab23ec2..0000000 --- a/tests/test_domains.py +++ /dev/null @@ -1,212 +0,0 @@ -"""Tests for `lalib.domains.Domain`.""" - -import os -import random - -import pytest - -from lalib.domains import Domain - - -CROSS_REFERENCE = not os.environ.get("NO_CROSS_REFERENCE") - - -NUMERIC_LABELS = (1, 42) # always interpreted in a canonical way - -CANONICAL_ITERABLE_LABELS = tuple(range(number) for number in NUMERIC_LABELS) -NON_CANONICAL_ITERABLE_LABELS = ( - range(1, 42), - [-42, 0, +42], - "abc", - ("x", "y", "z"), -) -ITERABLE_LABELS = CANONICAL_ITERABLE_LABELS + NON_CANONICAL_ITERABLE_LABELS - -CANONICAL_MAPPING_LABELS = ( - {0: 123}, - {0: 123, 1: 456}, -) -NON_CANONICAL_MAPPING_LABELS = ( - {0: 123, 42: 456}, - {"a": 123, "b": 456}, -) -MAPPING_LABELS = CANONICAL_MAPPING_LABELS + NON_CANONICAL_MAPPING_LABELS - -CANONICAL_LABELS = ( - *NUMERIC_LABELS, - *CANONICAL_ITERABLE_LABELS, - *CANONICAL_MAPPING_LABELS, -) -NON_CANONICAL_LABELS = ( - *NON_CANONICAL_ITERABLE_LABELS, - *NON_CANONICAL_MAPPING_LABELS, -) - -ALL_LABELS = CANONICAL_LABELS + NON_CANONICAL_LABELS - - -@pytest.fixture -def domain(request): - """A `Domain` object.""" - return Domain(request.param) - - -class TestDomainInstantiation: - """Test `Domain.__new__()` with good inputs.""" - - @pytest.mark.parametrize("domain", ALL_LABELS, indirect=True) - def test_from_domain(self, domain): - """`Domain` object passed into `Domain()` is simply returned.""" - new_domain = Domain(domain) - - assert new_domain == domain - assert new_domain is domain - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("number", NUMERIC_LABELS) - def test_from_integer(self, number): - """Positive `int`eger passed into `Domain()` creates canonical `Domain`. - - This is a convenience feature. - """ - domain = Domain(number) - expected = set(range(number)) - - assert domain == expected - - if CROSS_REFERENCE: - assert domain.is_canonical - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("mapping", MAPPING_LABELS) - def test_from_mapping(self, mapping): - """Create `Domain` from various mapping objects.""" - domain = Domain(mapping) - expected = mapping.keys() - - assert domain == expected - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("iterable", ITERABLE_LABELS) - def test_from_iterable(self, iterable): - """Create `Domain` from various iterable objects.""" - domain = Domain(iterable) - expected = set(iterable) - - assert domain == expected - - @pytest.mark.overlapping_test - @pytest.mark.parametrize("number", NUMERIC_LABELS) - def test_from_iterator_yielding_canonical_labels(self, number): - """`Domain()` can consume iterators: Providing canonical `labels`.""" - - def generator_factory(): - """Yield `0`, `1`, ... `number - 1`.""" - yield from range(number) - - generator = generator_factory() - domain = Domain(generator) - expected = set(range(number)) - - assert domain == expected - - if CROSS_REFERENCE: - assert domain.is_canonical - - @pytest.mark.overlapping_test - def test_from_iterator_yielding_non_canonical_labels(self): - """`Domain()` can consume iterators: Providing non-canonical `labels`.""" - - def generator_factory(p_skipped=0.5): - """Yield `0`, `1`, ..., `100` with missing values.""" - for i in range(100): - if random.random() > p_skipped: # noqa: S311 - yield i - - generator = generator_factory() - domain = Domain(generator) - - assert domain is not None - - if CROSS_REFERENCE: - assert not domain.is_canonical - - @pytest.mark.parametrize("domain", CANONICAL_LABELS, indirect=True) - def test_from_canonical_repr(self, domain): - """`repr(domain)` is of the form "Domain(integer)".""" - new_domain = eval(repr(domain)) # noqa: S307 - - assert new_domain == domain - - @pytest.mark.parametrize("domain", NON_CANONICAL_LABELS, indirect=True) - def test_from_non_canonical_repr(self, domain): - """`repr(domain)` is of the form "Domain({label1, label2, ...})".""" - new_domain = eval(repr(domain)) # noqa: S307 - - assert new_domain == domain - - -class TestFailedDomainInstantiation: - """Test `Domain.__new__()` with bad inputs.""" - - def test_wrong_type(self): - """Cannot create `Domain` from non-numeric or non-iterable object.""" - with pytest.raises(TypeError): - Domain(object()) - - @pytest.mark.parametrize("number", [-42, 0, 4.2]) - def test_from_non_positive_integer(self, number): - """Non-positive `int`egers passed into `Domain()` do not work.""" - with pytest.raises(ValueError, match="positive integer"): - Domain(number) - - @pytest.mark.overlapping_test - def test_from_empty_mapping(self): - """Cannot create `Domain` from empty mapping objects.""" - empty_dict = {} - - with pytest.raises(ValueError, match="at least one label"): - Domain(empty_dict) - - @pytest.mark.parametrize("iterable_type", [tuple, list, set]) - def test_from_empty_iterable(self, iterable_type): - """Cannot create `Domain` from empty iterable objects.""" - empty_iterable = iterable_type() - - with pytest.raises(ValueError, match="at least one label"): - Domain(empty_iterable) - - @pytest.mark.parametrize("iterable_type", [tuple, list]) - def test_from_iterable_with_non_hashable_labels(self, iterable_type): - """Cannot create `Domain` with non-hashable `labels`.""" - bad_iterable = iterable_type(([1], [2], [3])) - - with pytest.raises(TypeError, match="hashable labels"): - Domain(bad_iterable) - - -@pytest.mark.overlapping_test -class TestCanonicalProperty: - """Test `Domain.is_canonical` property.""" - - @pytest.mark.parametrize("domain", CANONICAL_LABELS, indirect=True) - def test_is_canonical(self, domain): - """A `domain` with `labels` like `0`, `1`, ...""" - assert domain.is_canonical is True - - @pytest.mark.parametrize("domain", NON_CANONICAL_LABELS, indirect=True) - def test_is_not_canonical(self, domain): - """A `domain` with `labels` unlike `0`, `1`, ...""" - assert domain.is_canonical is False - - # `@pytest.mark.overlapping_test` can only be used - # because the one line of code in the `try`-block - # is always regarded as fully covered, - # even if an `AttributeError` is raised and excepted - @pytest.mark.parametrize("domain", ALL_LABELS, indirect=True) - def test_is_still_canonical_or_not(self, domain): - """`Domain.is_canonical` is cached.""" - result1 = domain.is_canonical - result2 = domain.is_canonical - - assert result1 is result2 diff --git a/tests/test_top_level_imports.py b/tests/test_top_level_imports.py deleted file mode 100644 index c12214a..0000000 --- a/tests/test_top_level_imports.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Test top-level imports for `lalib`.""" - -import importlib -from typing import Any - -import pytest - - -@pytest.mark.integration_test -@pytest.mark.parametrize( - "path_to_package", - [ - "lalib", - "lalib.elements", - "lalib.fields", - ], -) -def test_top_level_imports(path_to_package: str): - """Verify `from {path_to_package} import *` works.""" - package = importlib.import_module(path_to_package) - - environment: dict[str, Any] = {} - - exec("...", environment, environment) # noqa: S102 - defined_vars_before = set(environment) - - exec(f"from {path_to_package} import *", environment, environment) # noqa: S102 - defined_vars_after = set(environment) - - new_vars = defined_vars_after - defined_vars_before - - assert new_vars == set(package.__all__) diff --git a/tests/test_version.py b/tests/test_version.py index c6c6429..be02fae 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -236,7 +236,36 @@ INVALID_NOT_SEMANTIC = ( INVALID_VERSIONS = INVALID_NOT_READABLE + INVALID_NOT_SEMANTIC -@pytest.mark.overlapping_test +@pytest.mark.parametrize( + ["version1", "version2"], + zip( # loop over pairs of neighboring elements + VALID_AND_NORMALIZED_VERSIONS, + VALID_AND_NORMALIZED_VERSIONS[1:], + ), +) +def test_versions_are_strictly_ordered(version1, version2): + """`VALID_AND_NORMALIZED_VERSIONS` are ordered.""" + version1_parsed = pkg_version.Version(version1) + version2_parsed = pkg_version.Version(version2) + + assert version1_parsed < version2_parsed + + +@pytest.mark.parametrize( + ["version1", "version2"], + zip( # loop over pairs of neighboring elements + VALID_AND_NOT_NORMALIZED_VERSIONS, + VALID_AND_NOT_NORMALIZED_VERSIONS[1:], + ), +) +def test_versions_are_weakly_ordered(version1, version2): + """`VALID_AND_NOT_NORMALIZED_VERSIONS` are ordered.""" + version1_parsed = pkg_version.Version(version1) + version2_parsed = pkg_version.Version(version2) + + assert version1_parsed <= version2_parsed + + class VersionClassification: """Classifying version identifiers. @@ -263,9 +292,9 @@ class VersionClassification: ) if is_so_by_parts: - assert parsed_version.is_devrelease - assert parsed_version.is_prerelease - assert not parsed_version.is_postrelease + assert parsed_version.is_devrelease is True + assert parsed_version.is_prerelease is True + assert parsed_version.is_postrelease is False return is_so_by_parts @@ -278,9 +307,9 @@ class VersionClassification: ) if is_so_by_parts: - assert not parsed_version.is_devrelease - assert parsed_version.is_prerelease - assert not parsed_version.is_postrelease + assert parsed_version.is_devrelease is False + assert parsed_version.is_prerelease is True + assert parsed_version.is_postrelease is False return is_so_by_parts @@ -293,9 +322,9 @@ class VersionClassification: ) if is_so_by_parts: - assert not parsed_version.is_devrelease - assert not parsed_version.is_prerelease - assert not parsed_version.is_postrelease + assert parsed_version.is_devrelease is False + assert parsed_version.is_prerelease is False + assert parsed_version.is_postrelease is False return is_so_by_parts @@ -308,14 +337,13 @@ class VersionClassification: ) if is_so_by_parts: - assert not parsed_version.is_devrelease - assert not parsed_version.is_prerelease - assert parsed_version.is_postrelease + assert parsed_version.is_devrelease is False + assert parsed_version.is_prerelease is False + assert parsed_version.is_postrelease is True return is_so_by_parts -@pytest.mark.overlapping_test class TestVersionIdentifier(VersionClassification): """The versions must comply with PEP440 ... @@ -476,7 +504,6 @@ class TestVersionIdentifier(VersionClassification): assert parsed_version.public != unparsed_version -@pytest.mark.overlapping_test class TestVersionIdentifierWithPattern: """Test the versioning with a custom `regex` pattern.""" @@ -558,36 +585,3 @@ class TestUnavailablePackageMetadata: with self.hide_metadata_from_package("lalib") as lalib_pkg: assert lalib_pkg.__pkg_name__ == "unknown" assert lalib_pkg.__version__ == "unknown" - - -@pytest.mark.sanity_test -class TestSampleVersionData: - """Ensure the `VALID_*_VERSIONS` are in order.""" - - @pytest.mark.parametrize( - ["version1", "version2"], - zip( # loop over pairs of neighboring elements - VALID_AND_NORMALIZED_VERSIONS, - VALID_AND_NORMALIZED_VERSIONS[1:], - ), - ) - def test_versions_are_strictly_ordered(self, version1, version2): - """`VALID_AND_NORMALIZED_VERSIONS` are ordered.""" - version1_parsed = pkg_version.Version(version1) - version2_parsed = pkg_version.Version(version2) - - assert version1_parsed < version2_parsed - - @pytest.mark.parametrize( - ["version1", "version2"], - zip( # loop over pairs of neighboring elements - VALID_AND_NOT_NORMALIZED_VERSIONS, - VALID_AND_NOT_NORMALIZED_VERSIONS[1:], - ), - ) - def test_versions_are_weakly_ordered(self, version1, version2): - """`VALID_AND_NOT_NORMALIZED_VERSIONS` are ordered.""" - version1_parsed = pkg_version.Version(version1) - version2_parsed = pkg_version.Version(version2) - - assert version1_parsed <= version2_parsed diff --git a/tests/utils.py b/tests/utils.py deleted file mode 100644 index f12f96f..0000000 --- a/tests/utils.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Utilities to test the `lalib` package.""" - -from lalib import config - - -DEFAULT_THRESHOLD = config.THRESHOLD -WITHIN_THRESHOLD = config.THRESHOLD / 10 -NOT_WITHIN_THRESHOLD = config.THRESHOLD * 10