Merge branch 'fields' into 'develop'
Some checks failed
audit / audit (push) Has been cancelled
docs / docs (push) Has been cancelled
lint / lint (push) Has been cancelled
test-coverage / test-coverage (push) Has been cancelled
test-docstrings / test-docstrings (push) Has been cancelled
tests / test-3.10 (push) Has been cancelled
tests / test-3.11 (push) Has been cancelled
tests / test-3.12 (push) Has been cancelled
tests / test-3.13 (push) Has been cancelled
tests / test-3.9 (push) Has been cancelled
Some checks failed
audit / audit (push) Has been cancelled
docs / docs (push) Has been cancelled
lint / lint (push) Has been cancelled
test-coverage / test-coverage (push) Has been cancelled
test-docstrings / test-docstrings (push) Has been cancelled
tests / test-3.10 (push) Has been cancelled
tests / test-3.11 (push) Has been cancelled
tests / test-3.12 (push) Has been cancelled
tests / test-3.13 (push) Has been cancelled
tests / test-3.9 (push) Has been cancelled
This commit is contained in:
commit
b2f6155872
36 changed files with 2088 additions and 568 deletions
2
.github/workflows/audit.yml
vendored
2
.github/workflows/audit.yml
vendored
|
@ -8,7 +8,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.12
|
||||
python-version: 3.13
|
||||
architecture: x64
|
||||
|
||||
- run: python --version
|
||||
|
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
|
@ -8,7 +8,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.12
|
||||
python-version: 3.13
|
||||
architecture: x64
|
||||
|
||||
- run: python --version
|
||||
|
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
@ -8,7 +8,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.12
|
||||
python-version: 3.13
|
||||
architecture: x64
|
||||
|
||||
- run: python --version
|
||||
|
|
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
|
@ -16,6 +16,7 @@ jobs:
|
|||
3.10
|
||||
3.11
|
||||
3.12
|
||||
3.13
|
||||
architecture: x64
|
||||
|
||||
- run: python --version
|
||||
|
|
1
.github/workflows/test_coverage.yml
vendored
1
.github/workflows/test_coverage.yml
vendored
|
@ -14,6 +14,7 @@ jobs:
|
|||
3.10
|
||||
3.11
|
||||
3.12
|
||||
3.13
|
||||
architecture: x64
|
||||
|
||||
- run: python --version
|
||||
|
|
2
.github/workflows/test_docstrings.yml
vendored
2
.github/workflows/test_docstrings.yml
vendored
|
@ -8,7 +8,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.12
|
||||
python-version: 3.13
|
||||
architecture: x64
|
||||
|
||||
- run: python --version
|
||||
|
|
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
@ -5,7 +5,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
name: test-${{ matrix.python-version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
|
@ -3,7 +3,7 @@ version: 2
|
|||
build:
|
||||
os: ubuntu-24.04
|
||||
tools:
|
||||
python: "3.12"
|
||||
python: "3.13"
|
||||
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
|
|
@ -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.12](https://devguide.python.org/versions/)).
|
||||
(i.e., [3.13](https://devguide.python.org/versions/)).
|
||||
|
||||
|
||||
#### Code Formatting & Linting
|
||||
|
|
33
noxfile.py
33
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.12"
|
||||
MAIN_PYTHON = "3.13"
|
||||
|
||||
DOCS_SRC, DOCS_BUILD = ("docs/", ".cache/docs/")
|
||||
TESTS_LOCATION = "tests/"
|
||||
|
@ -205,8 +205,10 @@ 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]",
|
||||
|
@ -229,11 +231,15 @@ 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:
|
||||
|
@ -249,6 +255,15 @@ 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",
|
||||
)
|
||||
if session.env.get("_smoke_tests_only")
|
||||
else ()
|
||||
),
|
||||
TEST_RANDOM_SEED,
|
||||
TESTS_LOCATION,
|
||||
)
|
||||
|
||||
|
@ -285,6 +300,7 @@ 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",
|
||||
|
@ -293,6 +309,7 @@ def test_coverage_run(session: nox.Session) -> None:
|
|||
"run",
|
||||
"-m",
|
||||
"pytest",
|
||||
TEST_RANDOM_SEED,
|
||||
TESTS_LOCATION,
|
||||
)
|
||||
|
||||
|
@ -331,6 +348,18 @@ 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"
|
||||
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.
|
||||
|
@ -432,7 +461,7 @@ 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.12", "3.11"):
|
||||
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 =}"
|
||||
|
|
731
poetry.lock
generated
731
poetry.lock
generated
|
@ -70,13 +70,13 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
|
|||
|
||||
[[package]]
|
||||
name = "bandit"
|
||||
version = "1.7.9"
|
||||
version = "1.7.10"
|
||||
description = "Security oriented static analyser for python code."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"},
|
||||
{file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"},
|
||||
{file = "bandit-1.7.10-py3-none-any.whl", hash = "sha256:665721d7bebbb4485a339c55161ac0eedde27d51e638000d91c8c2d68343ad02"},
|
||||
{file = "bandit-1.7.10.tar.gz", hash = "sha256:59ed5caf5d92b6ada4bf65bc6437feea4a9da1093384445fed4d472acc6cff7b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -94,33 +94,33 @@ yaml = ["PyYAML"]
|
|||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "24.8.0"
|
||||
version = "24.10.0"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{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"},
|
||||
{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"},
|
||||
]
|
||||
|
||||
[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.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
|
||||
d = ["aiohttp (>=3.10)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
|
@ -162,101 +162,116 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.3.2"
|
||||
version = "3.4.0"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "charset-normalizer-3.3.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"},
|
||||
{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"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -286,83 +301,73 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.6.1"
|
||||
version = "7.6.3"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{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"},
|
||||
{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"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -373,13 +378,13 @@ toml = ["tomli"]
|
|||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.3.8"
|
||||
version = "0.3.9"
|
||||
description = "Distribution utilities"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
|
||||
{file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
|
||||
{file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"},
|
||||
{file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -704,6 +709,38 @@ 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"
|
||||
|
@ -834,71 +871,72 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
|
|||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.5"
|
||||
version = "3.0.1"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{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"},
|
||||
{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"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -925,38 +963,43 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.11.2"
|
||||
version = "1.12.0"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{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"},
|
||||
{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"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1101,13 +1144,13 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "pydoclint"
|
||||
version = "0.5.7"
|
||||
version = "0.5.9"
|
||||
description = "A Python docstring linter that checks arguments, returns, yields, and raises sections"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydoclint-0.5.7-py2.py3-none-any.whl", hash = "sha256:22561b8f876b7bd3d4966a3b7fe27bfdbfa41fa50d0719f13b08844a39e2717c"},
|
||||
{file = "pydoclint-0.5.7.tar.gz", hash = "sha256:d2938efc95233205822bebe5da5c8e1c4796da09988a2a73c2d6b63cef4577a6"},
|
||||
{file = "pydoclint-0.5.9-py2.py3-none-any.whl", hash = "sha256:089327003cef6fe5605cbaa9887859ea5229ce0c9abb52775ffd57513094c1ae"},
|
||||
{file = "pydoclint-0.5.9.tar.gz", hash = "sha256:e200f964a5d9fbbb2ff1078bd7cb5433a0564d2482b6a1ba1be848f66bc4924f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1201,6 +1244,35 @@ 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"
|
||||
|
@ -1286,47 +1358,48 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
|||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "13.8.1"
|
||||
version = "13.9.2"
|
||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"},
|
||||
{file = "rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a"},
|
||||
{file = "rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1"},
|
||||
{file = "rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c"},
|
||||
]
|
||||
|
||||
[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.5"
|
||||
version = "0.6.9"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.6.5-py3-none-linux_armv6l.whl", hash = "sha256:7e4e308f16e07c95fc7753fc1aaac690a323b2bb9f4ec5e844a97bb7fbebd748"},
|
||||
{file = "ruff-0.6.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:932cd69eefe4daf8c7d92bd6689f7e8182571cb934ea720af218929da7bd7d69"},
|
||||
{file = "ruff-0.6.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a8d42d11fff8d3143ff4da41742a98f8f233bf8890e9fe23077826818f8d680"},
|
||||
{file = "ruff-0.6.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a50af6e828ee692fb10ff2dfe53f05caecf077f4210fae9677e06a808275754f"},
|
||||
{file = "ruff-0.6.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:794ada3400a0d0b89e3015f1a7e01f4c97320ac665b7bc3ade24b50b54cb2972"},
|
||||
{file = "ruff-0.6.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:381413ec47f71ce1d1c614f7779d88886f406f1fd53d289c77e4e533dc6ea200"},
|
||||
{file = "ruff-0.6.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:52e75a82bbc9b42e63c08d22ad0ac525117e72aee9729a069d7c4f235fc4d276"},
|
||||
{file = "ruff-0.6.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09c72a833fd3551135ceddcba5ebdb68ff89225d30758027280968c9acdc7810"},
|
||||
{file = "ruff-0.6.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800c50371bdcb99b3c1551d5691e14d16d6f07063a518770254227f7f6e8c178"},
|
||||
{file = "ruff-0.6.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e25ddd9cd63ba1f3bd51c1f09903904a6adf8429df34f17d728a8fa11174253"},
|
||||
{file = "ruff-0.6.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7291e64d7129f24d1b0c947ec3ec4c0076e958d1475c61202497c6aced35dd19"},
|
||||
{file = "ruff-0.6.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9ad7dfbd138d09d9a7e6931e6a7e797651ce29becd688be8a0d4d5f8177b4b0c"},
|
||||
{file = "ruff-0.6.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:005256d977021790cc52aa23d78f06bb5090dc0bfbd42de46d49c201533982ae"},
|
||||
{file = "ruff-0.6.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:482c1e6bfeb615eafc5899127b805d28e387bd87db38b2c0c41d271f5e58d8cc"},
|
||||
{file = "ruff-0.6.5-py3-none-win32.whl", hash = "sha256:cf4d3fa53644137f6a4a27a2b397381d16454a1566ae5335855c187fbf67e4f5"},
|
||||
{file = "ruff-0.6.5-py3-none-win_amd64.whl", hash = "sha256:3e42a57b58e3612051a636bc1ac4e6b838679530235520e8f095f7c44f706ff9"},
|
||||
{file = "ruff-0.6.5-py3-none-win_arm64.whl", hash = "sha256:51935067740773afdf97493ba9b8231279e9beef0f2a8079188c4776c25688e0"},
|
||||
{file = "ruff-0.6.5.tar.gz", hash = "sha256:4d32d87fab433c0cf285c3683dd4dae63be05fd7a1d65b3f5bf7cdd05a6b96fb"},
|
||||
{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"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1409,13 +1482,13 @@ test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools
|
|||
|
||||
[[package]]
|
||||
name = "sphinx"
|
||||
version = "8.0.2"
|
||||
version = "8.1.3"
|
||||
description = "Python documentation generator"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "sphinx-8.0.2-py3-none-any.whl", hash = "sha256:56173572ae6c1b9a38911786e206a110c9749116745873feae4f9ce88e59391d"},
|
||||
{file = "sphinx-8.0.2.tar.gz", hash = "sha256:0cce1ddcc4fd3532cf1dd283bc7d886758362c5c1de6598696579ce96d8ffa5b"},
|
||||
{file = "sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2"},
|
||||
{file = "sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1429,17 +1502,17 @@ packaging = ">=23.0"
|
|||
Pygments = ">=2.17"
|
||||
requests = ">=2.30.0"
|
||||
snowballstemmer = ">=2.2"
|
||||
sphinxcontrib-applehelp = "*"
|
||||
sphinxcontrib-devhelp = "*"
|
||||
sphinxcontrib-htmlhelp = ">=2.0.0"
|
||||
sphinxcontrib-jsmath = "*"
|
||||
sphinxcontrib-qthelp = "*"
|
||||
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-serializinghtml = ">=1.1.9"
|
||||
tomli = {version = ">=2", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinxcontrib-websupport"]
|
||||
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)"]
|
||||
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)"]
|
||||
test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"]
|
||||
|
||||
[[package]]
|
||||
|
@ -1571,13 +1644,13 @@ pbr = ">=2.0.0"
|
|||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
version = "2.0.2"
|
||||
description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
{file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"},
|
||||
{file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1610,13 +1683,13 @@ zstd = ["zstandard (>=0.18.0)"]
|
|||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.26.5"
|
||||
version = "20.26.6"
|
||||
description = "Virtual Python Environment builder"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "virtualenv-20.26.5-py3-none-any.whl", hash = "sha256:4f3ac17b81fba3ce3bd6f4ead2749a72da5929c01774948e243db9ba41df4ff6"},
|
||||
{file = "virtualenv-20.26.5.tar.gz", hash = "sha256:ce489cac131aa58f4b25e321d6d186171f78e6cb13fafbf32a840cee67733ff4"},
|
||||
{file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"},
|
||||
{file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1681,4 +1754,4 @@ type = ["pytest-mypy"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "e9e490a864511852844926112978e57c1421328f6231437f8f280ddfc88cecde"
|
||||
content-hash = "e0962e284e415b91fad954a9ca20d3dd45be434d23c63ce1e38da75f69e9557c"
|
||||
|
|
|
@ -16,6 +16,7 @@ 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"
|
||||
|
@ -57,8 +58,10 @@ 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" }
|
||||
|
@ -76,6 +79,8 @@ 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" }
|
||||
|
@ -171,7 +176,9 @@ 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
|
||||
|
||||
|
@ -250,6 +257,12 @@ 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
|
||||
|
@ -342,6 +355,7 @@ 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
|
||||
|
@ -355,6 +369,7 @@ 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
|
||||
|
@ -377,6 +392,8 @@ extend-ignore = [ # never check the following codes
|
|||
|
||||
]
|
||||
|
||||
allowed-confusables = ["ℂ", "ℝ", "ℚ"]
|
||||
|
||||
|
||||
[tool.ruff.lint.flake8-pytest-style] # aligned with [tool.flake8] above
|
||||
|
||||
|
|
|
@ -22,7 +22,8 @@ one
|
|||
|
||||
from importlib import metadata
|
||||
|
||||
from lalib.elements import gf2
|
||||
from lalib import elements
|
||||
from lalib import fields
|
||||
|
||||
|
||||
try:
|
||||
|
@ -40,15 +41,22 @@ else:
|
|||
del pkg_info
|
||||
|
||||
|
||||
GF2, one, zero = gf2.GF2, gf2.one, gf2.zero
|
||||
gf2, one, zero = elements.gf2, elements.one, elements.zero
|
||||
|
||||
Q, R, C, GF2 = fields.Q, fields.R, fields.C, fields.GF2
|
||||
|
||||
|
||||
del gf2
|
||||
del elements
|
||||
del fields
|
||||
del metadata
|
||||
|
||||
|
||||
__all__ = (
|
||||
"GF2",
|
||||
"gf2",
|
||||
"one",
|
||||
"zero",
|
||||
"Q",
|
||||
"R",
|
||||
"C",
|
||||
"GF2",
|
||||
)
|
||||
|
|
13
src/lalib/config.py
Normal file
13
src/lalib/config.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
"""Library-wide default settings."""
|
||||
|
||||
import math
|
||||
|
||||
|
||||
NDIGITS = 12
|
||||
|
||||
THRESHOLD = 1 / (10**NDIGITS)
|
||||
|
||||
MAX_DENOMINATOR = math.trunc(1 / THRESHOLD)
|
||||
|
||||
|
||||
del math
|
|
@ -9,26 +9,26 @@ Then, use them:
|
|||
>>> one + zero
|
||||
one
|
||||
|
||||
>>> GF2(0)
|
||||
>>> gf2(0)
|
||||
zero
|
||||
>>> GF2(42)
|
||||
>>> gf2(42)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: ...
|
||||
>>> GF2(42, strict=False)
|
||||
>>> gf2(42, strict=False)
|
||||
one
|
||||
"""
|
||||
|
||||
from lalib.elements import gf2
|
||||
from lalib.elements import galois
|
||||
|
||||
|
||||
GF2, one, zero = gf2.GF2, gf2.one, gf2.zero
|
||||
gf2, one, zero = galois.gf2, galois.one, galois.zero
|
||||
|
||||
del gf2
|
||||
del galois
|
||||
|
||||
|
||||
__all__ = (
|
||||
"GF2",
|
||||
"gf2",
|
||||
"one",
|
||||
"zero",
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
This module defines two singleton objects, `one` and `zero`,
|
||||
that follow the rules of a Galois field of two elements,
|
||||
or `GF2` for short:
|
||||
also called `GF2` in `lalib`:
|
||||
|
||||
>>> one + one
|
||||
zero
|
||||
|
@ -22,16 +22,17 @@ zero
|
|||
zero
|
||||
|
||||
Further usage explanations of `one` and `zero`
|
||||
can be found in the various docstrings of the `GF2` class.
|
||||
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
|
||||
|
||||
# When giving up support for Python 3.9, we can get rid of `Optional`
|
||||
from typing import Callable, ClassVar, Literal, Optional
|
||||
from typing import Callable, ClassVar, Literal
|
||||
|
||||
|
||||
try:
|
||||
|
@ -40,14 +41,15 @@ except ImportError: # pragma: no cover to support Python 3.9 & 3.10
|
|||
from typing_extensions import Self
|
||||
|
||||
|
||||
THRESHOLD = 1e-12
|
||||
from lalib import config
|
||||
|
||||
|
||||
def to_gf2(
|
||||
def _to_gf2(
|
||||
value: complex, # `mypy` reads `complex | float | int`
|
||||
/,
|
||||
*,
|
||||
strict: bool = True,
|
||||
threshold: float = THRESHOLD,
|
||||
threshold: float = config.THRESHOLD,
|
||||
) -> int:
|
||||
"""Cast a number as a possible Galois field value: `1` or `0`.
|
||||
|
||||
|
@ -102,15 +104,16 @@ def to_gf2(
|
|||
return 1
|
||||
|
||||
|
||||
class _GF2Meta(abc.ABCMeta):
|
||||
"""Make data type of `one` and `zero` appear to be `GF2`."""
|
||||
class GF2Meta(abc.ABCMeta):
|
||||
"""Make data type of `one` and `zero` appear to be `gf2`."""
|
||||
|
||||
def __repr__(cls) -> str:
|
||||
return "GF2"
|
||||
def __repr__(cls) -> str: # noqa: RUF100,U100
|
||||
"""Text representation for `GF2Element` and sub-classes."""
|
||||
return "gf2"
|
||||
|
||||
|
||||
@functools.total_ordering
|
||||
class GF2(metaclass=_GF2Meta):
|
||||
class GF2Element(metaclass=GF2Meta):
|
||||
"""A Galois field value: either `one` or `zero`.
|
||||
|
||||
Implements the singleton design pattern such that
|
||||
|
@ -128,7 +131,7 @@ class GF2(metaclass=_GF2Meta):
|
|||
value: object = None,
|
||||
*,
|
||||
strict: bool = True,
|
||||
threshold: float = THRESHOLD,
|
||||
threshold: float = config.THRESHOLD,
|
||||
) -> Self:
|
||||
"""See docstring for `.__init__()`."""
|
||||
if isinstance(value, cls):
|
||||
|
@ -144,7 +147,7 @@ class GF2(metaclass=_GF2Meta):
|
|||
msg = "Must create `one` and `zero` first (internal error)"
|
||||
raise RuntimeError(msg) from None
|
||||
else:
|
||||
value = to_gf2(value, strict=strict, threshold=threshold) # type: ignore[arg-type]
|
||||
value = _to_gf2(value, strict=strict, threshold=threshold) # type: ignore[arg-type]
|
||||
|
||||
try:
|
||||
return cls._instances[value]
|
||||
|
@ -156,9 +159,10 @@ class GF2(metaclass=_GF2Meta):
|
|||
def __init__(
|
||||
self,
|
||||
value: object = None,
|
||||
/,
|
||||
*,
|
||||
strict: bool = True,
|
||||
threshold: float = THRESHOLD,
|
||||
threshold: float = config.THRESHOLD,
|
||||
) -> None:
|
||||
"""Obtain one of two objects: `one` or `zero`.
|
||||
|
||||
|
@ -238,7 +242,7 @@ class GF2(metaclass=_GF2Meta):
|
|||
"""Round `self` up to the next `int`: `math.ceil(self)`."""
|
||||
return int(self)
|
||||
|
||||
def __round__(self, ndigits: Optional[int] = 0) -> int:
|
||||
def __round__(self, _ndigits: int = config.NDIGITS) -> int:
|
||||
"""Round `self` to the next `int`: `round(self)`."""
|
||||
return int(self)
|
||||
|
||||
|
@ -276,8 +280,8 @@ class GF2(metaclass=_GF2Meta):
|
|||
Either `1` or `0`.
|
||||
|
||||
Reasoning:
|
||||
- `int(one) == 1` => `GF2(1 / 1) == one`
|
||||
- `int(zero) == 0` => `GF2(0 / 1) == zero`
|
||||
- `int(one) == 1` => `gf2(1 / 1) == one`
|
||||
- `int(zero) == 0` => `gf2(0 / 1) == zero`
|
||||
|
||||
See also docstring for `.denominator`.
|
||||
"""
|
||||
|
@ -287,7 +291,7 @@ class GF2(metaclass=_GF2Meta):
|
|||
def denominator(self) -> Literal[1]:
|
||||
"""Smallest denominator when expressed as a `Rational` number.
|
||||
|
||||
Always `1` for `GF2` values.
|
||||
Always `1` for `gf2` values.
|
||||
|
||||
See also docstring for `.numerator`.
|
||||
"""
|
||||
|
@ -308,11 +312,17 @@ class GF2(metaclass=_GF2Meta):
|
|||
True
|
||||
"""
|
||||
try:
|
||||
other = GF2(other)
|
||||
other = GF2Element(other)
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
return NotImplemented
|
||||
else:
|
||||
return self is other # `one` and `zero` are singletons
|
||||
# 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`.
|
||||
|
@ -327,7 +337,7 @@ class GF2(metaclass=_GF2Meta):
|
|||
True
|
||||
"""
|
||||
try:
|
||||
other = GF2(other)
|
||||
other = GF2Element(other)
|
||||
except TypeError:
|
||||
return NotImplemented
|
||||
except ValueError:
|
||||
|
@ -358,16 +368,16 @@ class GF2(metaclass=_GF2Meta):
|
|||
def _compute(self, other: object, func: Callable) -> Self:
|
||||
"""Run arithmetic operations using `int`s.
|
||||
|
||||
The `GF2` atithmetic operations can transparently be conducted
|
||||
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
|
||||
casting of non-`gf2` values and various errors occuring
|
||||
along the way.
|
||||
"""
|
||||
try:
|
||||
other = GF2(other)
|
||||
other = GF2Element(other)
|
||||
except TypeError:
|
||||
return NotImplemented
|
||||
except ValueError:
|
||||
|
@ -391,7 +401,7 @@ class GF2(metaclass=_GF2Meta):
|
|||
def __add__(self, other: object) -> Self:
|
||||
"""Addition / Subtraction: `self + other` / `self - other`.
|
||||
|
||||
For `GF2`, addition and subtraction are identical. Besides
|
||||
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.
|
||||
|
||||
|
@ -417,7 +427,7 @@ class GF2(metaclass=_GF2Meta):
|
|||
def __mul__(self, other: object) -> Self:
|
||||
"""Multiplication: `self * other`.
|
||||
|
||||
Multiplying `GF2` values is like multiplying `int`s.
|
||||
Multiplying `gf2` values is like multiplying `int`s.
|
||||
|
||||
Example usage:
|
||||
|
||||
|
@ -435,7 +445,7 @@ class GF2(metaclass=_GF2Meta):
|
|||
def __truediv__(self, other: object) -> Self:
|
||||
"""Division: `self / other` and `self // other`.
|
||||
|
||||
Dividing `GF2` values is like dividing `int`s.
|
||||
Dividing `gf2` values is like dividing `int`s.
|
||||
|
||||
Example usage:
|
||||
|
||||
|
@ -466,7 +476,7 @@ class GF2(metaclass=_GF2Meta):
|
|||
def __mod__(self, other: object) -> Self:
|
||||
"""Modulo Division: `self % other`.
|
||||
|
||||
Modulo dividing `GF2` values is like modulo dividing `int`s.
|
||||
Modulo dividing `gf2` values is like modulo dividing `int`s.
|
||||
|
||||
Example usage:
|
||||
|
||||
|
@ -490,10 +500,10 @@ class GF2(metaclass=_GF2Meta):
|
|||
"""
|
||||
return self._compute(other, lambda s, o: o % s)
|
||||
|
||||
def __pow__(self, other: object, _modulo: Optional[object] = None) -> Self:
|
||||
def __pow__(self, other: object, _modulo: object = None) -> Self:
|
||||
"""Exponentiation: `self ** other`.
|
||||
|
||||
Powers of `GF2` values are like powers of `int`s.
|
||||
Powers of `gf2` values are like powers of `int`s.
|
||||
|
||||
Example usage:
|
||||
|
||||
|
@ -508,7 +518,7 @@ class GF2(metaclass=_GF2Meta):
|
|||
"""
|
||||
return self._compute(other, lambda s, o: s**o)
|
||||
|
||||
def __rpow__(self, other: object, _modulo: Optional[object] = None) -> Self:
|
||||
def __rpow__(self, other: object, _modulo: object = None) -> Self:
|
||||
"""(Reflected) Exponentiation: `other ** self`.
|
||||
|
||||
See docstring for `.__pow__()`.
|
||||
|
@ -516,16 +526,20 @@ class GF2(metaclass=_GF2Meta):
|
|||
return self._compute(other, lambda s, o: o**s)
|
||||
|
||||
|
||||
numbers.Rational.register(GF2)
|
||||
numbers.Rational.register(GF2Element)
|
||||
|
||||
|
||||
class GF2One(GF2):
|
||||
# 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(GF2):
|
||||
class GF2Zero(GF2Element):
|
||||
"""The Galois field value `zero`."""
|
||||
|
||||
_value = 0
|
||||
|
@ -533,3 +547,14 @@ class GF2Zero(GF2):
|
|||
|
||||
one = GF2One()
|
||||
zero = GF2Zero()
|
||||
|
||||
|
||||
# Outside this module the `GF2Element` is just "the `gf2` type"
|
||||
gf2 = GF2Element
|
||||
|
||||
|
||||
del GF2Meta
|
||||
del GF2One
|
||||
del GF2Zero
|
||||
|
||||
del config
|
32
src/lalib/fields/__init__.py
Normal file
32
src/lalib/fields/__init__.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
"""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",
|
||||
)
|
227
src/lalib/fields/base.py
Normal file
227
src/lalib/fields/base.py
Normal file
|
@ -0,0 +1,227 @@
|
|||
"""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 "<cls ...>" 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__
|
110
src/lalib/fields/complex_.py
Normal file
110
src/lalib/fields/complex_.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
"""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()
|
45
src/lalib/fields/galois.py
Normal file
45
src/lalib/fields/galois.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
"""The concrete `GaloisField2`."""
|
||||
|
||||
import random
|
||||
from typing import Any
|
||||
|
||||
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
|
||||
_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()
|
90
src/lalib/fields/rational.py
Normal file
90
src/lalib/fields/rational.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
"""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()
|
97
src/lalib/fields/real.py
Normal file
97
src/lalib/fields/real.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
"""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()
|
22
src/lalib/fields/utils.py
Normal file
22
src/lalib/fields/utils.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
"""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
|
59
tests/conftest.py
Normal file
59
tests/conftest.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
"""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
|
|
@ -1,4 +1,4 @@
|
|||
"""Test the `GF2` singeltons `one` and `zero`."""
|
||||
"""Test the `gf2` singeltons `one` and `zero`."""
|
||||
|
||||
import decimal
|
||||
import fractions
|
||||
|
@ -11,41 +11,35 @@ import sys
|
|||
|
||||
import pytest
|
||||
|
||||
from lalib.elements import gf2
|
||||
from lalib.elements import galois
|
||||
from tests import utils
|
||||
|
||||
|
||||
one, zero = (
|
||||
gf2.one,
|
||||
gf2.zero,
|
||||
gf2, one, zero = ( # official API outside of `lalib.elements.galois`
|
||||
galois.gf2,
|
||||
galois.one,
|
||||
galois.zero,
|
||||
)
|
||||
|
||||
to_gf2 = gf2.to_gf2
|
||||
|
||||
GF2, GF2One, GF2Zero = (
|
||||
gf2.GF2,
|
||||
gf2.GF2One,
|
||||
gf2.GF2Zero,
|
||||
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`
|
||||
)
|
||||
|
||||
_THRESHOLD = gf2.THRESHOLD
|
||||
|
||||
del gf2
|
||||
del galois
|
||||
|
||||
|
||||
CROSS_REFERENCE = not os.environ.get("NO_CROSS_REFERENCE")
|
||||
|
||||
|
||||
default_threshold = _THRESHOLD
|
||||
within_threshold = _THRESHOLD / 10
|
||||
not_within_threshold = _THRESHOLD * 10
|
||||
|
||||
strict_one_like_values = (
|
||||
1,
|
||||
1.0,
|
||||
1.0 + within_threshold,
|
||||
1.0 + utils.WITHIN_THRESHOLD,
|
||||
(1 + 0j),
|
||||
(1 + 0j) + complex(0, within_threshold),
|
||||
(1 + 0j) + complex(within_threshold, 0),
|
||||
(1 + 0j) + complex(0, utils.WITHIN_THRESHOLD),
|
||||
(1 + 0j) + complex(utils.WITHIN_THRESHOLD, 0),
|
||||
decimal.Decimal("1"),
|
||||
fractions.Fraction(1, 1),
|
||||
"1",
|
||||
|
@ -54,9 +48,9 @@ strict_one_like_values = (
|
|||
)
|
||||
|
||||
non_strict_one_like_values = (
|
||||
0.0 + not_within_threshold,
|
||||
1.0 + not_within_threshold,
|
||||
(1 + 0j) + complex(not_within_threshold, 0),
|
||||
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),
|
||||
|
@ -72,10 +66,10 @@ one_like_values = strict_one_like_values + non_strict_one_like_values
|
|||
zero_like_values = (
|
||||
0,
|
||||
0.0,
|
||||
0.0 + within_threshold,
|
||||
0.0 + utils.WITHIN_THRESHOLD,
|
||||
(0 + 0j),
|
||||
(0 + 0j) + complex(0, within_threshold),
|
||||
(0 + 0j) + complex(within_threshold, 0),
|
||||
(0 + 0j) + complex(0, utils.WITHIN_THRESHOLD),
|
||||
(0 + 0j) + complex(utils.WITHIN_THRESHOLD, 0),
|
||||
decimal.Decimal("0"),
|
||||
fractions.Fraction(0, 1),
|
||||
"0",
|
||||
|
@ -84,90 +78,34 @@ zero_like_values = (
|
|||
)
|
||||
|
||||
|
||||
def test_thresholds():
|
||||
"""Sanity check for the thresholds used in the tests below."""
|
||||
assert within_threshold < default_threshold < not_within_threshold
|
||||
@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 `to_gf2()` function.
|
||||
"""Test the `gf2` class's constructor.
|
||||
|
||||
`to_gf2(...)` casts numbers into either `1` or `0`.
|
||||
"""
|
||||
`gf2(value, ...)` returns either `one` or `zero`.
|
||||
|
||||
@pytest.mark.parametrize("value", strict_one_like_values)
|
||||
def test_cast_ones_strictly(self, value):
|
||||
"""`to_gf2(value, strict=True)` returns `1`."""
|
||||
result1 = to_gf2(value) # `strict=True` by default
|
||||
assert result1 == 1
|
||||
|
||||
result2 = to_gf2(value, strict=True)
|
||||
assert result2 == 1
|
||||
|
||||
@pytest.mark.parametrize("value", one_like_values)
|
||||
def test_cast_ones_not_strictly(self, value):
|
||||
"""`to_gf2(value, strict=False)` returns `1`."""
|
||||
result = to_gf2(value, strict=False)
|
||||
assert result == 1
|
||||
|
||||
@pytest.mark.parametrize("value", non_strict_one_like_values)
|
||||
def test_cannot_cast_ones_strictly(self, value):
|
||||
"""`to_gf2(value, strict=False)` returns `1`."""
|
||||
with pytest.raises(ValueError, match="`1`-like or `0`-like"):
|
||||
to_gf2(value)
|
||||
|
||||
with pytest.raises(ValueError, match="`1`-like or `0`-like"):
|
||||
to_gf2(value, strict=True)
|
||||
|
||||
@pytest.mark.parametrize("value", zero_like_values)
|
||||
def test_cast_zeros(self, value):
|
||||
"""`to_gf2(value, strict=...)` returns `0`."""
|
||||
result1 = to_gf2(value) # `strict=True` by default
|
||||
assert result1 == 0
|
||||
|
||||
result2 = to_gf2(value, strict=True)
|
||||
assert result2 == 0
|
||||
|
||||
result3 = to_gf2(value, strict=False)
|
||||
assert result3 == 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
[
|
||||
complex(1, not_within_threshold),
|
||||
complex(0, not_within_threshold),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("strict", [True, False])
|
||||
def test_cannot_cast_with_non_zero_imag_part(self, value, strict):
|
||||
"""Cannot create `1` or `0` if `.imag != 0`."""
|
||||
with pytest.raises(ValueError, match="`1`-like or `0`-like"):
|
||||
to_gf2(value, strict=strict)
|
||||
|
||||
@pytest.mark.parametrize("value", ["abc", (1,), [1]])
|
||||
@pytest.mark.parametrize("strict", [True, False])
|
||||
def test_cannot_cast_from_wrong_type(self, value, strict):
|
||||
"""Cannot create `1` or `0` from a non-numeric value."""
|
||||
with pytest.raises(TypeError):
|
||||
to_gf2(value, strict=strict)
|
||||
|
||||
@pytest.mark.parametrize("strict", [True, False])
|
||||
def test_cannot_cast_from_nan_value(self, strict):
|
||||
"""Cannot create `1` or `0` from undefined value."""
|
||||
with pytest.raises(ValueError, match="`1`-like or `0`-like"):
|
||||
to_gf2(float("NaN"), strict=strict)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cls", [GF2, GF2One, GF2Zero])
|
||||
class TestGF2ConstructorWithCastedValue:
|
||||
"""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`."""
|
||||
"""`gf2(value, strict=True)` returns `one`."""
|
||||
result1 = cls(value) # `strict=True` by default
|
||||
assert result1 is one
|
||||
|
||||
|
@ -176,13 +114,14 @@ class TestGF2ConstructorWithCastedValue:
|
|||
|
||||
@pytest.mark.parametrize("value", one_like_values)
|
||||
def test_cast_ones_not_strictly(self, cls, value):
|
||||
"""`GF2(value, strict=False)` returns `one`."""
|
||||
"""`gf2(value, strict=False)` returns `one`."""
|
||||
result = cls(value, strict=False)
|
||||
assert result 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=False)` returns `1`."""
|
||||
"""`gf2(value, strict=False)` returns `1`."""
|
||||
with pytest.raises(ValueError, match="`1`-like or `0`-like"):
|
||||
cls(value)
|
||||
|
||||
|
@ -191,7 +130,7 @@ class TestGF2ConstructorWithCastedValue:
|
|||
|
||||
@pytest.mark.parametrize("value", zero_like_values)
|
||||
def test_cast_zeros(self, cls, value):
|
||||
"""`GF2(value, strict=...)` returns `zero`."""
|
||||
"""`gf2(value, strict=...)` returns `zero`."""
|
||||
result1 = cls(value) # `strict=True` by default
|
||||
assert result1 is zero
|
||||
|
||||
|
@ -204,8 +143,8 @@ class TestGF2ConstructorWithCastedValue:
|
|||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
[
|
||||
complex(1, not_within_threshold),
|
||||
complex(0, not_within_threshold),
|
||||
complex(1, utils.NOT_WITHIN_THRESHOLD),
|
||||
complex(0, utils.NOT_WITHIN_THRESHOLD),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("strict", [True, False])
|
||||
|
@ -214,6 +153,7 @@ class TestGF2ConstructorWithCastedValue:
|
|||
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):
|
||||
|
@ -221,40 +161,44 @@ class TestGF2ConstructorWithCastedValue:
|
|||
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`
|
||||
"""`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 * not_within_threshold
|
||||
threshold = scaler * default_threshold
|
||||
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 * within_threshold
|
||||
threshold = scaler * default_threshold
|
||||
"""`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.
|
||||
"""Test the `gf2` class's constructor.
|
||||
|
||||
`GF2()` returns either `one` or `zero`.
|
||||
`gf2()` returns either `one` or `zero`.
|
||||
"""
|
||||
|
||||
def test_get_one_from_sub_class_with_no_input_value(self, strict):
|
||||
|
@ -262,9 +206,9 @@ class TestGF2ConstructorWithoutCastedValue:
|
|||
result = GF2One(strict=strict)
|
||||
assert result is one
|
||||
|
||||
@pytest.mark.parametrize("cls", [GF2, GF2Zero])
|
||||
@pytest.mark.parametrize("cls", [gf2, GF2Zero])
|
||||
def test_get_zero_with_no_input_value(self, cls, strict):
|
||||
"""`GF2()` and `GF2Zero()` return `zero`."""
|
||||
"""`gf2()` and `GF2Zero()` return `zero`."""
|
||||
result = cls(strict=strict)
|
||||
assert result is zero
|
||||
|
||||
|
@ -273,18 +217,20 @@ 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", {})
|
||||
"""`GF2One` and `GF2Zero` must be instantiated before `gf2`."""
|
||||
monkeypatch.setattr(gf2, "_instances", {})
|
||||
with pytest.raises(RuntimeError, match="internal error"):
|
||||
GF2()
|
||||
gf2()
|
||||
|
||||
@pytest.mark.parametrize("cls", [GF2, GF2One, GF2Zero])
|
||||
@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 ...
|
||||
|
@ -292,11 +238,12 @@ class TestGenericBehavior:
|
|||
the sub-classes that create `one` and `zero`.
|
||||
"""
|
||||
sub_cls = type(obj)
|
||||
assert sub_cls is not GF2
|
||||
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_",
|
||||
|
@ -311,7 +258,8 @@ class TestGenericBehavior:
|
|||
"""`one` and `zero` are officially `Numbers`s."""
|
||||
assert isinstance(obj, type_)
|
||||
|
||||
@pytest.mark.parametrize("cls", [GF2, GF2One, GF2Zero])
|
||||
@pytest.mark.overlapping_test
|
||||
@pytest.mark.parametrize("cls", [gf2, GF2One, GF2Zero])
|
||||
@pytest.mark.parametrize(
|
||||
"method",
|
||||
[
|
||||
|
@ -339,8 +287,8 @@ class TestGenericBehavior:
|
|||
value,
|
||||
):
|
||||
"""Ensure all of `numbers.Rational`'s abstact methods are implemented."""
|
||||
monkeypatch.setattr(GF2, "_instances", {})
|
||||
monkeypatch.delattr(GF2, method)
|
||||
monkeypatch.setattr(gf2, "_instances", {})
|
||||
monkeypatch.delattr(gf2, method)
|
||||
|
||||
sub_cls = type("GF2Baby", (cls, numbers.Rational), {})
|
||||
|
||||
|
@ -362,14 +310,14 @@ class TestGenericBehavior:
|
|||
@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 ...
|
||||
"""'gf2' is the text representation for all sub-classes ...
|
||||
|
||||
... which is valid code referring to the base class `GF2`.
|
||||
... which is valid code referring to the base class `gf2`.
|
||||
|
||||
`GF2()` returns `zero` if called without arguments.
|
||||
`gf2()` returns `zero` if called without arguments.
|
||||
"""
|
||||
base_cls = eval(func(type(obj))) # noqa: S307
|
||||
assert base_cls is GF2
|
||||
assert base_cls is gf2
|
||||
|
||||
new_obj = base_cls()
|
||||
assert new_obj is zero
|
||||
|
@ -378,14 +326,17 @@ class TestGenericBehavior:
|
|||
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`.
|
||||
|
@ -398,6 +349,7 @@ class TestNumericBehavior:
|
|||
"""`one` and `zero` behave like `True` and `False`."""
|
||||
assert (bool(one), bool(zero)) == (True, False)
|
||||
|
||||
@pytest.mark.overlapping_test
|
||||
@pytest.mark.parametrize("obj", [one, zero])
|
||||
def test_get_abs_value(self, obj):
|
||||
"""`abs(one)` and `abs(zero)` are `one` and `zero`."""
|
||||
|
@ -424,10 +376,12 @@ class TestNumericBehavior:
|
|||
"""`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)
|
||||
|
@ -436,11 +390,13 @@ class TestNumericBehavior:
|
|||
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"],
|
||||
[
|
||||
|
@ -459,6 +415,7 @@ class TestComparison:
|
|||
assert first == second
|
||||
assert second == first
|
||||
|
||||
@pytest.mark.overlapping_test
|
||||
@pytest.mark.parametrize(
|
||||
["first", "second"],
|
||||
[
|
||||
|
@ -476,6 +433,7 @@ class TestComparison:
|
|||
assert first != second
|
||||
assert second != first
|
||||
|
||||
@pytest.mark.overlapping_test
|
||||
@pytest.mark.parametrize(
|
||||
["first", "second"],
|
||||
[
|
||||
|
@ -499,27 +457,32 @@ class TestComparison:
|
|||
"""`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`."""
|
||||
|
@ -595,10 +558,10 @@ class TestArithmetic:
|
|||
|
||||
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)
|
||||
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)
|
||||
result4 = gf2((operator(int(abs(second)), int(abs(first))) + 2) % 2)
|
||||
assert result4 is expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -631,12 +594,13 @@ class TestArithmetic:
|
|||
|
||||
if CROSS_REFERENCE: # cast `one` and `zero` as `integer`s before doing the math
|
||||
|
||||
result3 = GF2(int(abs(first)) * int(abs(second)))
|
||||
result3 = gf2(int(abs(first)) * int(abs(second)))
|
||||
assert result3 is expected
|
||||
|
||||
result4 = GF2(int(abs(second)) * int(abs(first)))
|
||||
result4 = gf2(int(abs(second)) * int(abs(first)))
|
||||
assert result4 is expected
|
||||
|
||||
@pytest.mark.overlapping_test
|
||||
@pytest.mark.parametrize(
|
||||
"objs",
|
||||
[
|
||||
|
@ -668,9 +632,10 @@ class TestArithmetic:
|
|||
|
||||
if CROSS_REFERENCE: # cast `one` and `zero` as `integer`s before doing the math
|
||||
|
||||
result2 = GF2(operator(int(abs(first)), int(abs(second))))
|
||||
result2 = gf2(operator(int(abs(first)), int(abs(second))))
|
||||
assert result2 is expected
|
||||
|
||||
@pytest.mark.overlapping_test
|
||||
@pytest.mark.parametrize(
|
||||
"objs",
|
||||
[
|
||||
|
@ -698,7 +663,7 @@ class TestArithmetic:
|
|||
|
||||
if CROSS_REFERENCE: # cast `one` and `zero` as `integer`s before doing the math
|
||||
|
||||
result2 = GF2(int(abs(first)) % int(abs(second)))
|
||||
result2 = gf2(int(abs(first)) % int(abs(second)))
|
||||
assert result2 is expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -775,7 +740,7 @@ class TestArithmetic:
|
|||
|
||||
if CROSS_REFERENCE: # cast `one` and `zero` as `integer`s before doing the math
|
||||
|
||||
result2 = GF2(int(abs(first)) ** int(abs(second)))
|
||||
result2 = gf2(int(abs(first)) ** int(abs(second)))
|
||||
assert result2 is expected
|
||||
|
||||
@pytest.mark.parametrize("obj", [one, zero])
|
||||
|
@ -820,13 +785,20 @@ class TestArithmetic:
|
|||
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.gf2")
|
||||
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
|
1
tests/fields/__init__.py
Normal file
1
tests/fields/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
"""Tests for the `lalib.fields` sub-package."""
|
96
tests/fields/test_axioms.py
Normal file
96
tests/fields/test_axioms.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
"""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)
|
267
tests/fields/test_base.py
Normal file
267
tests/fields/test_base.py
Normal file
|
@ -0,0 +1,267 @@
|
|||
"""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}
|
100
tests/fields/test_complex.py
Normal file
100
tests/fields/test_complex.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
"""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
|
104
tests/fields/test_galois.py
Normal file
104
tests/fields/test_galois.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
"""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_complex_number_is_field_element(self, pre_value):
|
||||
"""By design, `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_complex_number_is_not_field_element(self, pre_value):
|
||||
"""By design, `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", ["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)
|
||||
|
||||
|
||||
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
|
33
tests/fields/test_rational.py
Normal file
33
tests/fields/test_rational.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
"""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)
|
75
tests/fields/utils.py
Normal file
75
tests/fields/utils.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
"""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)
|
|
@ -10,12 +10,19 @@ import pytest
|
|||
import xdoctest
|
||||
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize(
|
||||
"module",
|
||||
[
|
||||
"lalib",
|
||||
"lalib.elements",
|
||||
"lalib.elements.gf2",
|
||||
"lalib.elements.galois",
|
||||
"lalib.fields",
|
||||
"lalib.fields.base",
|
||||
"lalib.fields.complex_",
|
||||
"lalib.fields.galois",
|
||||
"lalib.fields.rational",
|
||||
"lalib.fields.real",
|
||||
],
|
||||
)
|
||||
def test_docstrings(module):
|
||||
|
|
|
@ -6,11 +6,13 @@ 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):
|
||||
|
|
|
@ -236,36 +236,7 @@ INVALID_NOT_SEMANTIC = (
|
|||
INVALID_VERSIONS = INVALID_NOT_READABLE + INVALID_NOT_SEMANTIC
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
@pytest.mark.overlapping_test
|
||||
class VersionClassification:
|
||||
"""Classifying version identifiers.
|
||||
|
||||
|
@ -292,9 +263,9 @@ class VersionClassification:
|
|||
)
|
||||
|
||||
if is_so_by_parts:
|
||||
assert parsed_version.is_devrelease is True
|
||||
assert parsed_version.is_prerelease is True
|
||||
assert parsed_version.is_postrelease is False
|
||||
assert parsed_version.is_devrelease
|
||||
assert parsed_version.is_prerelease
|
||||
assert not parsed_version.is_postrelease
|
||||
|
||||
return is_so_by_parts
|
||||
|
||||
|
@ -307,9 +278,9 @@ class VersionClassification:
|
|||
)
|
||||
|
||||
if is_so_by_parts:
|
||||
assert parsed_version.is_devrelease is False
|
||||
assert parsed_version.is_prerelease is True
|
||||
assert parsed_version.is_postrelease is False
|
||||
assert not parsed_version.is_devrelease
|
||||
assert parsed_version.is_prerelease
|
||||
assert not parsed_version.is_postrelease
|
||||
|
||||
return is_so_by_parts
|
||||
|
||||
|
@ -322,9 +293,9 @@ class VersionClassification:
|
|||
)
|
||||
|
||||
if is_so_by_parts:
|
||||
assert parsed_version.is_devrelease is False
|
||||
assert parsed_version.is_prerelease is False
|
||||
assert parsed_version.is_postrelease is False
|
||||
assert not parsed_version.is_devrelease
|
||||
assert not parsed_version.is_prerelease
|
||||
assert not parsed_version.is_postrelease
|
||||
|
||||
return is_so_by_parts
|
||||
|
||||
|
@ -337,13 +308,14 @@ class VersionClassification:
|
|||
)
|
||||
|
||||
if is_so_by_parts:
|
||||
assert parsed_version.is_devrelease is False
|
||||
assert parsed_version.is_prerelease is False
|
||||
assert parsed_version.is_postrelease is True
|
||||
assert not parsed_version.is_devrelease
|
||||
assert not parsed_version.is_prerelease
|
||||
assert parsed_version.is_postrelease
|
||||
|
||||
return is_so_by_parts
|
||||
|
||||
|
||||
@pytest.mark.overlapping_test
|
||||
class TestVersionIdentifier(VersionClassification):
|
||||
"""The versions must comply with PEP440 ...
|
||||
|
||||
|
@ -504,6 +476,7 @@ class TestVersionIdentifier(VersionClassification):
|
|||
assert parsed_version.public != unparsed_version
|
||||
|
||||
|
||||
@pytest.mark.overlapping_test
|
||||
class TestVersionIdentifierWithPattern:
|
||||
"""Test the versioning with a custom `regex` pattern."""
|
||||
|
||||
|
@ -585,3 +558,36 @@ 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
|
||||
|
|
8
tests/utils.py
Normal file
8
tests/utils.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
"""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
|
Loading…
Reference in a new issue