diff --git a/.bandit b/.bandit deleted file mode 100644 index 49ef14846..000000000 --- a/.bandit +++ /dev/null @@ -1 +0,0 @@ -skips: ["B101", "B608"] diff --git a/.coveragerc b/.coveragerc index 5d869e0a9..d0e976fdf 100644 --- a/.coveragerc +++ b/.coveragerc @@ -6,9 +6,7 @@ source = ims [paths] source= src/ims - .tox/*/lib/python*/site-packages/ims - .tox/*/Lib/site-packages/ims - .tox/pypy*/site-packages/ims + .tox/py*/**/site-packages [report] exclude_lines = diff --git a/.docker/sample.env b/.docker/sample.env new file mode 100644 index 000000000..91b69260a --- /dev/null +++ b/.docker/sample.env @@ -0,0 +1,9 @@ +# DAEMON_GROUP_ID=1420 # Match your docker group ID for permissions +# IMS_SERVER_PORT=8080 +# DOCKER_RANGERS_NETWORK=rangers +# IMS_DB_IMAGE=mariadb:10.5.24 +# IMS_DB_HOST_NAME=ranger_ims_database +# IMS_DB_DATABASE=ims +# IMS_DB_PASSWORD=7B33108D-4CD4-41B5-A244-B16F97038860 +# IMS_DB_ROOT_PASSWORD=% +# IMS_DB_ROOT_PASSWORD=ims-root diff --git a/.dockerignore b/.dockerignore index 4b6cf2f88..9415b08cc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ +/.docker /.hypothesis /.tox /build/ diff --git a/.flake8 b/.flake8 index 81d8ba6a0..1849d36a0 100644 --- a/.flake8 +++ b/.flake8 @@ -76,6 +76,3 @@ extend-ignore = # End of list (allows last item to end with trailing ',') EOL - -# flake8-import-order: local module name space -application-import-names = ims diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f65493c7a..270ae38c5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,7 +9,7 @@ updates: allow: - dependency-type: "all" schedule: - interval: weekly + interval: "weekly" open-pull-requests-limit: 10 labels: - "Task" @@ -17,13 +17,13 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: weekly + interval: "weekly" labels: - "Task" - package-ecosystem: "docker" directory: "/" schedule: - interval: weekly + interval: "weekly" labels: - "Task" diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 817deee3f..054be53ac 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -12,14 +12,8 @@ jobs: runs-on: ubuntu-latest - if: ${{ github.actor == 'dependabot[bot]' }} + if: ${{ contains('["dependabot[bot]", "pre-commit-ci[bot]"]', github.actor) }} steps: - # - name: Get Dependabot metadata - # id: dependabot-metadata - # uses: dependabot/fetch-metadata@v1.6.0 - # with: - # github-token: "${{ secrets.GITHUB_TOKEN }}" - - name: Approve the PR run: gh pr review --approve "${{ github.event.pull_request.html_url }}" env: diff --git a/.github/workflows/autoupdate.yaml b/.github/workflows/autoupdate.yaml deleted file mode 100644 index 9edbe0f3c..000000000 --- a/.github/workflows/autoupdate.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: Update branch for PRs that are passing and approved for merging - -on: - schedule: - - cron: "0 4 * * *" # Daily at 4am - -jobs: - update-branches: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Update branches - uses: brainly/action-autoupdate-branch@2.1.1 - id: autoUpdateBranch - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" - requiredPassedChecks: 1 - requiredApprovals: 1 - update-limits: 2 diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 25573ec68..c438a6444 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Harden CI - uses: step-security/harden-runner@v2.6.1 + uses: step-security/harden-runner@v2.10.1 with: disable-sudo: true disable-file-monitoring: true @@ -30,6 +30,7 @@ jobs: api.github.com:443 files.pythonhosted.org:443 github.com:443 + objects.githubusercontent.com:443 pypi.org:443 - name: Checkout source code @@ -38,23 +39,23 @@ jobs: - name: Install Python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version-file: ".python-version" - - name: System Python Information - uses: twisted/python-info-action@v1 + - name: Install uv + uses: astral-sh/setup-uv@v3 - name: Set up Tox environment run: | - pip install tox + uv tool install tox --with tox-uv tox run -e lint --notest - - name: Tox Python Information - uses: twisted/python-info-action@v1 - with: - python-path: .tox/lint/*/python - - name: Run Linters run: tox run -e lint + env: + # no-commit-to-branch always fails in CI, because this pre-commit + # is only designed for running locally (i.e. when committing to a + # local master branch) + SKIP: no-commit-to-branch mypy: @@ -67,7 +68,7 @@ jobs: steps: - name: Harden CI - uses: step-security/harden-runner@v2.6.1 + uses: step-security/harden-runner@v2.10.1 with: disable-sudo: true disable-file-monitoring: true @@ -76,6 +77,7 @@ jobs: api.github.com:443 files.pythonhosted.org:443 github.com:443 + objects.githubusercontent.com:443 pypi.org:443 - name: Checkout source code @@ -84,164 +86,20 @@ jobs: - name: Install Python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version-file: ".python-version" - - name: System Python Information - uses: twisted/python-info-action@v1 + - name: Install uv + uses: astral-sh/setup-uv@v3 - name: Set up Tox environment run: | - pip install tox - tox run -e mypy --notest - - - name: Tox Python Information - uses: twisted/python-info-action@v1 - with: - python-path: .tox/mypy/*/python + uv tool install tox --with tox-uv + tox run -e lint --notest - name: Run Mypy run: tox run -e mypy - bandit: - - name: Bandit (security static analyzer) - - runs-on: ubuntu-latest - timeout-minutes: 5 - - steps: - - - name: Harden CI - uses: step-security/harden-runner@v2.6.1 - with: - disable-sudo: true - disable-file-monitoring: true - egress-policy: block - allowed-endpoints: > - api.github.com:443 - files.pythonhosted.org:443 - github.com:443 - pypi.org:443 - - - name: Checkout source code - uses: actions/checkout@v4 - - - name: Install Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: System Python Information - uses: twisted/python-info-action@v1 - - - name: Set up Tox environment - run: | - pip install tox - tox run -e bandit --notest - - - name: Tox Python Information - uses: twisted/python-info-action@v1 - with: - python-path: .tox/bandit/*/python - - - name: Run Bandit - run: tox run -e bandit - - - safety: - - name: Safety (dependency security checker) - - runs-on: ubuntu-latest - timeout-minutes: 5 - - steps: - - - name: Harden CI - uses: step-security/harden-runner@v2.6.1 - with: - disable-sudo: true - disable-file-monitoring: true - egress-policy: block - allowed-endpoints: > - api.github.com:443 - files.pythonhosted.org:443 - github.com:443 - pypi.org:443 - pyup.io:443 - - - name: Checkout source code - uses: actions/checkout@v4 - - - name: Install Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: System Python Information - uses: twisted/python-info-action@v1 - - - name: Set up Tox environment - run: | - pip install tox - tox run -e safety --notest - - - name: Tox Python Information - uses: twisted/python-info-action@v1 - with: - python-path: .tox/safety/*/python - - - name: Run Safety - run: tox run -e safety - - - docs: - - name: Build documentation - - runs-on: ubuntu-latest - timeout-minutes: 5 - - steps: - - - name: Harden CI - uses: step-security/harden-runner@v2.6.1 - with: - disable-sudo: true - disable-file-monitoring: true - egress-policy: block - allowed-endpoints: > - api.github.com:443 - files.pythonhosted.org:443 - github.com:443 - pypi.org:443 - - - name: Checkout source code - uses: actions/checkout@v4 - - - name: Install Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: System Python Information - uses: twisted/python-info-action@v1 - - - name: Set up Tox environment - run: | - pip install tox - tox run -e docs --notest - - - name: Tox Python Information - uses: twisted/python-info-action@v1 - with: - python-path: .tox/docs/*/python - - - name: Build documentation - run: tox run -e docs - - packaging: name: Packaging @@ -251,7 +109,7 @@ jobs: steps: - name: Harden CI - uses: step-security/harden-runner@v2.6.1 + uses: step-security/harden-runner@v2.10.1 with: disable-sudo: true disable-file-monitoring: true @@ -260,6 +118,7 @@ jobs: api.github.com:443 files.pythonhosted.org:443 github.com:443 + objects.githubusercontent.com:443 pypi.org:443 - name: Checkout source code @@ -268,20 +127,15 @@ jobs: - name: Install Python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version-file: ".python-version" - - name: System Python Information - uses: twisted/python-info-action@v1 + - name: Install uv + uses: astral-sh/setup-uv@v3 - name: Set up Tox environment run: | - pip install tox - tox run -e packaging --notest - - - name: Tox Python Information - uses: twisted/python-info-action@v1 - with: - python-path: .tox/packaging/*/python + uv tool install tox --with tox-uv + tox run -e lint --notest - name: Check packaging run: tox run -e packaging @@ -290,6 +144,8 @@ jobs: unit: name: "Py:${{ matrix.python-version }} - ${{ matrix.os }}" + # Available versions: + # https://github.com/actions/python-versions/blob/main/versions-manifest.json runs-on: ${{ matrix.os }} timeout-minutes: 30 @@ -297,13 +153,13 @@ jobs: strategy: matrix: os: ["ubuntu-latest"] - python-version: ["3.11"] # Versions to test with coverage + python-version: ["3.12"] # Versions to test with coverage tox-prefix: ["coverage"] optional: [false] include: - # Test Python 3.12 beta but allow it to fail + # Test Python beta but allow it to fail - os: "ubuntu-latest" - python-version: "3.12.0-beta.4" + python-version: "3.13.0" optional: true tox-prefix: "test" @@ -326,13 +182,15 @@ jobs: steps: - name: Harden CI - uses: step-security/harden-runner@v2.6.1 + uses: step-security/harden-runner@v2.10.1 with: disable-sudo: true disable-file-monitoring: true egress-policy: block allowed-endpoints: > + api.codecov.io:443 api.github.com:443 + cli.codecov.io:443 codecov.io:443 files.pythonhosted.org:443 github.com:443 @@ -351,9 +209,6 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: System Python Information - uses: twisted/python-info-action@v1 - - name: Translate Python version to Tox environment shell: python run: | @@ -371,15 +226,13 @@ jobs: f = p.open(mode="a") f.write(f"TOX_ENV={env}\n") + - name: Install uv + uses: astral-sh/setup-uv@v3 + - name: Set up Tox environment run: | - pip install tox - tox run -e ${TOX_ENV} --notest - - - name: Tox Python Information - uses: twisted/python-info-action@v1 - with: - python-path: .tox/${TOX_ENV}/*/python + uv tool install tox --with tox-uv + tox run -e lint --notest - name: Run unit tests run: tox run -e ${TOX_ENV} @@ -402,10 +255,10 @@ jobs: - uses: "actions/setup-python@v5" if: ${{ matrix.tox-prefix == 'coverage' }} with: - python-version: "3.11" + python-version-file: ".python-version" - name: "Upload coverage to Codecov" - uses: "codecov/codecov-action@v3.1.4" + uses: "codecov/codecov-action@v4" if: ${{ matrix.tox-prefix == 'coverage' }} with: token: ${{ secrets.CODECOV_TOKEN }} @@ -428,7 +281,7 @@ jobs: steps: - name: Harden CI - uses: step-security/harden-runner@v2.6.1 + uses: step-security/harden-runner@v2.10.1 with: disable-sudo: true egress-policy: block @@ -470,7 +323,7 @@ jobs: steps: - name: Harden CI - uses: step-security/harden-runner@v2.6.1 + uses: step-security/harden-runner@v2.10.1 with: disable-sudo: true egress-policy: block @@ -511,7 +364,7 @@ jobs: # steps: - # - uses: step-security/harden-runner@v2.6.1 + # - uses: step-security/harden-runner@v2.10.1 # with: # egress-policy: audit @@ -541,9 +394,19 @@ jobs: steps: - name: Harden CI - uses: step-security/harden-runner@v2.6.1 + uses: step-security/harden-runner@v2.10.1 with: - egress-policy: audit + disable-sudo: true + egress-policy: block + allowed-endpoints: > + 655216687927.dkr.ecr.us-west-2.amazonaws.com:443 + api.ecr.us-west-2.amazonaws.com:443 + ecs.us-west-2.amazonaws.com:443 + email-smtp.us-west-2.amazonaws.com:465 + files.pythonhosted.org:443 + github.com:443 + pypi.org:443 + raw.githubusercontent.com:443 - name: Checkout source code uses: actions/checkout@v4 @@ -559,7 +422,7 @@ jobs: - name: Install Python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version-file: ".python-version" - name: Deploy to staging run: ./bin/deploy staging diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d692d6ef8..2b15e39b3 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,9 +15,17 @@ jobs: steps: - name: Harden CI - uses: step-security/harden-runner@v2.6.1 + uses: step-security/harden-runner@v2.10.1 with: - egress-policy: audit + disable-sudo: true + egress-policy: block + allowed-endpoints: > + ecs.us-west-2.amazonaws.com:443 + files.pythonhosted.org:443 + github.com:443 + pypi.org:443 + raw.githubusercontent.com:443 + sts.us-west-2.amazonaws.com:443 - name: Check user if: ${{ ! contains('["wsanchez", "mikeburg", "plapsley"]', github.actor) }} @@ -36,7 +44,7 @@ jobs: - name: Install Python uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - name: System Python Information uses: twisted/python-info-action@v1 @@ -70,9 +78,17 @@ jobs: steps: - name: Harden CI - uses: step-security/harden-runner@v2.6.1 + uses: step-security/harden-runner@v2.10.1 with: - egress-policy: audit + disable-sudo: true + egress-policy: block + allowed-endpoints: > + ecs.us-west-2.amazonaws.com:443 + files.pythonhosted.org:443 + github.com:443 + pypi.org:443 + raw.githubusercontent.com:443 + sts.us-west-2.amazonaws.com:443 - name: Check user if: ${{ ! contains('["wsanchez", "mikeburg", "plapsley"]', github.actor) }} @@ -91,7 +107,7 @@ jobs: - name: Install Python uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - name: System Python Information uses: twisted/python-info-action@v1 diff --git a/.gitignore b/.gitignore index bf788de6b..1707830bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ +__pycache__/ +!/.docker/sample.env +*.env /.docker_environment -/.hypothesis -/.tox +/.docker/* +/.hypothesis/ +/.tox/ +/.venv/ /build/ /cached/ /data/ @@ -8,4 +13,4 @@ /htmlcov/ /htmldocs/ /src/*.egg-info/ -__pycache__/ +/wheels/ diff --git a/.isort.cfg b/.isort.cfg index 69e81144f..29769dd9f 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -10,4 +10,4 @@ lines_after_imports = 2 line_length = 80 # Default puts 1st party before 3rd party, this reverses that -sections = FUTURE,STDLIB,THIRDPARTY,SECONDPARTY,FIRSTPARTY,LOCALFOLDER +sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ca50a3dae..d3f8df08e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,19 +5,19 @@ ci: repos: - repo: https://github.com/asottile/pyupgrade - rev: "v3.15.0" + rev: "v3.19.0" hooks: - id: pyupgrade - args: ["--py310-plus"] + args: ["--py312-plus"] - repo: https://github.com/psf/black - rev: "23.12.1" + rev: "24.10.0" hooks: - id: black - args: ["--target-version", "py311"] + args: ["--target-version", "py312"] - repo: https://github.com/PyCQA/autoflake - rev: "v2.2.1" + rev: "v2.3.1" hooks: - id: autoflake args: @@ -28,16 +28,16 @@ repos: - --remove-unused-variables - repo: https://github.com/PyCQA/flake8 - rev: "7.0.0" + rev: "7.1.1" hooks: - id: flake8 additional_dependencies: - - flake8-bugbear==24.1.16 + - flake8-bugbear==24.8.19 - flake8-docstrings==1.7.0 - flake8-mutable==1.2.0 - flake8-pep3101==2.1.0 - - pep8-naming==0.13.3 - - pycodestyle==2.11.1 + - pep8-naming==0.14.1 + - pycodestyle==2.12.1 - pydocstyle==6.3.0 - pyflakes==3.2.0 @@ -46,12 +46,12 @@ repos: hooks: - id: yesqa additional_dependencies: - - flake8-bugbear==24.1.16 + - flake8-bugbear==24.8.19 - flake8-docstrings==1.7.0 - flake8-mutable==1.2.0 - flake8-pep3101==2.1.0 - - pep8-naming==0.13.3 - - pycodestyle==2.11.1 + - pep8-naming==0.14.1 + - pycodestyle==2.12.1 - pydocstyle==6.3.0 - pyflakes==3.2.0 @@ -62,7 +62,7 @@ repos: args: ["--filter-files"] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: "v4.5.0" + rev: "v5.0.0" hooks: - id: check-ast - id: check-builtin-literals @@ -82,12 +82,14 @@ repos: - id: detect-private-key - id: end-of-file-fixer - id: fix-byte-order-marker + - id: end-of-file-fixer - id: forbid-new-submodules - id: mixed-line-ending - id: name-tests-test args: - --pytest-test-first - - id: requirements-txt-fixer + - id: no-commit-to-branch + args: ["--branch", "master"] - id: trailing-whitespace - repo: https://github.com/pre-commit/pygrep-hooks @@ -103,6 +105,6 @@ repos: - id: text-unicode-replacement-char - repo: https://github.com/mgedmin/check-manifest - rev: "0.49" + rev: "0.50" hooks: - id: check-manifest diff --git a/.python-version b/.python-version new file mode 100644 index 000000000..e4fba2183 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..3a9cefc37 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,45 @@ +# Changelog + +This is the changelog for ranger-ims-server. This is intended to summarize changes over time, +for example to inform the Operator team each event of any differences to expect. + +This file must use the [Common Changelog format](https://common-changelog.org/), with the variation +that we use months rather than version numbers. We don't include dependency version upgrades in the +changelog, as those would pollute this too much. + + + +## 2024-10 + +### Changed + +- Resolved IMS's longstanding 6-open-tab limitation ([#1320](https://github.com/burningmantech/ranger-ims-server/issues/1320)) ([#1322](https://github.com/burningmantech/ranger-ims-server/pull/1322)) +- Changed login screen to encourage users to log in by email address, rather than by Ranger handle ([#1293](https://github.com/burningmantech/ranger-ims-server/pull/1293)) +- Used text and datalist for "Add Ranger" on incident page, rather than a select field ([#1292](https://github.com/burningmantech/ranger-ims-server/pull/1292)) +- Removed email address from the personnel API endpoint ([#1317](https://github.com/burningmantech/ranger-ims-server/pull/1317)) + +### Added + +- Added groupings to the "add incident report" dropdown, which emphasize which incident reports are or are not attached to any other incident. This also simplified the sort order for that list ([#1321](https://github.com/burningmantech/ranger-ims-server/pull/1321)) + +### Fixed + +- Stopped using hardcoded 1-hour limit on IMS sessions; used timeout from JWT instead ([#1301](https://github.com/burningmantech/ranger-ims-server/pull/1301)) +- Got rid of the browser popup alerts that occurred frequently on JavaScript errors. Instead, error messages will now be written to a text field near the top of each page ([#1335](https://github.com/burningmantech/ranger-ims-server/pull/1335)) + +## 2024-01 + +### Added + +- Added "Changes you made may not be saved" browser popup when a user might otherwise lose data on incident entries and incident report entries ([#1088](https://github.com/burningmantech/ranger-ims-server/pull/1088)) + +### Fixed + +- Do case-insensitive sorting of Ranger handles on incident page ([#1089](https://github.com/burningmantech/ranger-ims-server/pull/1089)) diff --git a/Dockerfile b/Dockerfile index e129576ad..39a6e601f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # This stage builds the build container. # ----------------------------------------------------------------------------- -FROM python:3.11.0-alpine3.16 as build +FROM python:3.12.3-alpine3.18 AS build # Install compiler toolchain and libraries. RUN apk add --no-cache build-base libffi-dev libressl-dev @@ -18,24 +18,27 @@ WORKDIR "${IMS_SOURCE_DIR}" COPY ./COPYRIGHT.txt ./ COPY ./LICENSE.txt ./ +COPY ./MANIFEST.in ./ COPY ./pyproject.toml ./ COPY ./README.rst ./ -COPY ./requirements/ ./requirements/ -COPY ./setup.py ./ COPY ./src/ ./src/ +COPY ./uv.lock ./ # Install the application WORKDIR /tmp RUN install -o daemon -g daemon -d "${IMS_INSTALL_DIR}" -RUN python -m venv "${IMS_INSTALL_DIR}" -RUN "${IMS_INSTALL_DIR}/bin/pip" --no-cache-dir install --upgrade pip -RUN "${IMS_INSTALL_DIR}/bin/pip" --no-cache-dir install "${IMS_SOURCE_DIR}" +RUN pip install --no-cache-dir --upgrade pip uv +RUN uv venv "${IMS_INSTALL_DIR}" +RUN uv pip --no-progress install \ + --python="${IMS_INSTALL_DIR}/bin/python" \ + --no-cache --exact --compile-bytecode \ + "${IMS_SOURCE_DIR}" # ----------------------------------------------------------------------------- # This stage builds the application container. # ----------------------------------------------------------------------------- -FROM python:3.11.0-alpine3.16 as application +FROM python:3.12.3-alpine3.18 AS application # Paths ARG IMS_INSTALL_DIR="/opt/ims" @@ -53,7 +56,7 @@ RUN apk add --no-cache libressl # Allow Python to bind to privileged port numbers RUN apk add --no-cache libcap -RUN setcap "cap_net_bind_service=+ep" /usr/local/bin/python3.11 +RUN setcap "cap_net_bind_service=+ep" /usr/local/bin/python3.12 # Create server root and make that our working directory RUN install -o daemon -g daemon -d "${IMS_SERVER_ROOT}" diff --git a/MANIFEST.in b/MANIFEST.in index 0fa4b316e..65317dbd8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,10 +1,10 @@ -include .bandit -include .codecov.yml include .coveragerc +include .docker/sample.env include .dockerignore include .flake8 include .isort.cfg include .pre-commit-config.yaml +include .python-version include bin/.common.sh include bin/build include bin/deploy @@ -14,17 +14,30 @@ include bin/mysql-run include bin/run include bin/shell include bin/test_docker +include CHANGELOG.md +include codecov.yml include codecov.yml include conf/directory-sample.yaml +include conf/imsd-docker-compose-sample.conf include conf/imsd-sample.conf include COPYRIGHT.txt +include docker-compose.yml include Dockerfile +include docs/*.py +include docs/*.rst include LICENSE.txt include mypy.ini include pyproject.toml include README.rst -include requirements/requirements*.txt +include src/ims/config/test/*.conf +include src/ims/directory/file/test/*.yaml +include src/ims/element/static/*.css +include src/ims/element/static/*.js +include src/ims/element/static/*.png +include src/ims/element/static/*.zip +include src/ims/store/file/*.txt +include src/ims/store/mysql/schema/*.mysql +include src/ims/store/sqlite/schema/*.sqlite include tox.ini -recursive-include docs *.py -recursive-include docs *.rst -recursive-include src *.txt +include uv.lock +recursive-include src/ims/element *.xhtml diff --git a/README.rst b/README.rst index 1b5d08054..a4cb2b1be 100644 --- a/README.rst +++ b/README.rst @@ -23,10 +23,19 @@ The server is implemented using Twisted_ Klein_ and requires Python 3.9+. Development ----------- -Running the Test Suite -~~~~~~~~~~~~~~~~~~~~~~ +This project requires uv_ and pre-commit_ for development and Tox_ and running tests. + +If you do not have uv installed, see the uv documentation for instructions. + +If you do not have pre-commit and/or tox installed, use uv. Be sure to include the tox-uv_ plugin:: + + uv tool install pre-commit + pre-commit install + uv tool install tox --with tox-uv + +The ``pre-commit install`` step installs a Git hook which runs pre-commit automatically when you run ``git commit``. +This is very useful as a measure to prevent commits that would later fail during CI builds. -This project uses Tox_ for running tests. To run all of the default test environments:: tox run @@ -34,6 +43,28 @@ To run all of the default test environments:: Running the Server ~~~~~~~~~~~~~~~~~~ +-------------------- +With docker-compose +-------------------- + +First, get the configuration set up:: + + # These files are mounted into the container + cp conf/imsd-docker-compose-sample.conf conf/imsd.conf + cp conf/directory-sample.yaml conf/directory.yaml + +If you need to override any of the environment variables set in +``docker-compose.yml``, copy ``.docker/sample.env`` to ``/.env`` and +uncomment and edit the neccessary variables. + +Start the server:: + + docker compose up + +------------------ +Outside docker +------------------ + To run the server will require some configuration, and if you try to start the server with the default configuration, you will probably see an error such as this:: 2020-03-12T09:16:55-0700 [ims.run._command.Command#info] Setting up web service at http://localhost:80/ @@ -49,16 +80,26 @@ To set up a configuration for development, start by copying the example configur To build and run the server (for development only):: - tox run -e exec + uv run ims server + + +--------------------- +Settings Permissions +--------------------- -In your browser, open http://localhost:8080/ to reach the server. Log in as any user in the ``conf/directory.yaml`` directory file. In the ``conf/imsd.conf`` sample configuration file, the users ``Hardware`` and ``Loosy`` are administrators, and in the sample directory, all users have passwords that match their handles. You'll want to log in as one of those to set up an Event. +In your browser, open http://localhost:8080/ to reach the server. Log in as any user in the ``conf/directory.yaml`` directory file. +In the ``conf/imsd-sample.conf`` sample configuration file, the users ``Hardware`` and ``Loosy`` are administrators, and in the sample directory, all users have passwords that match their handles. +You'll want to log in as one of those to set up an Event. -Use the pull-down menu at the top right corner of the page (it will show the logged in user's Ranger handle), and select ``Admin``. On the next page, navigate to the Events page and create an event called ``Test``. +Use the pull-down menu at the top right corner of the page (it will show the logged in user's Ranger handle), and select ``Admin``. +On the next page, navigate to the Events page and create an event called ``Test``. -In the box labeled ``Access for Test (writers)``, enter the string ``*``. That will give all users the ability to create and edit incidents in that event. +In the box labeled ``Access for Test (writers)``, enter the string ``*``. +That will give all users the ability to create and edit incidents in that event. You should now be able to select your new event from the ``Event`` menu at the top right, and then create new incidents within that event. + Pull Requests ~~~~~~~~~~~~~ @@ -68,8 +109,12 @@ Pull requests in GitHub will run all tests on Travis CI, and all are required to .. ------------------------------------------------------------------------- .. -.. _Twisted: https://twistedmatrix.com/ -.. _Klein: https://klein.readthedocs.io/ -.. _Tox: http://tox.readthedocs.io/ .. _Flake8: http://flake8.pycqa.org/ +.. _Klein: https://klein.readthedocs.io/ .. _Mypy: http://mypy.readthedocs.io/ +.. _pipx: https://pipx.pypa.io/stable/ +.. _pre-commit: https://pre-commit.com/ +.. _tox-uv: https://github.com/tox-dev/tox-uv +.. _Tox: http://tox.readthedocs.io/ +.. _Twisted: https://twistedmatrix.com/ +.. _uv: https://docs.astral.sh/uv/ diff --git a/conf/imsd-docker-compose-sample.conf b/conf/imsd-docker-compose-sample.conf new file mode 100644 index 000000000..dd3abc004 --- /dev/null +++ b/conf/imsd-docker-compose-sample.conf @@ -0,0 +1,56 @@ +[Core] + +# Absolute or relative to parent of parent of this file +ServerRoot = . + +DataStore = MySQL +Directory = File + +# Absolute or relative to ServerRoot +ConfigRoot = conf +DataRoot = data +CachedResources = cache + +# Bind address +Host = localhost +Port = 8080 + +Admins = Hardware, Loosy + +#MasterKey = 6C21E8C9-8B83-4EA3-93BD-6C6EFE8A712B + +#JWTSecret = DD264110-3A97-4348-9473-6D50B582550C + +RequireActive = True + + +[Store:SQLite] + +# Relative to DataRoot +File = db.sqlite + + +[Store:MySQL] + +HostName = mysql +HostPort = 3306 + +Database = ims +UserName = ims +Password = ims + + +[Directory:File] + +# Relative to ConfigRoot +File = directory.yaml + + +[Directory:ClubhouseDB] + +Hostname = dms.rangers.example.com +HostPort = 3306 + +Database = rangers +Username = ims +Password = 9F29BB2B-E775-489C-9C20-9FE3EFEE1F22 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..2f8a041bd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,48 @@ +services: + app: + build: . + container_name: ranger_ims_server + user: :${DAEMON_GROUP_ID:-1420} + environment: + IMS_DIRECTORY: "${IMS_DIRECTORY:-File}" + IMS_DIRECTORY_FILE: "${IMS_DIRECTORY_FILE:-/opt/ims/conf/directory.yaml}" + IMS_DB_HOST_NAME: "${IMS_DB_HOST_NAME:-ranger_ims_database}" + IMS_DB_DATABASE: "${IMS_DB_DATABASE:-ims}" + IMS_DB_USER_NAME: "${IMS_DB_USER_NAME:-ims}" + IMS_DB_PASSWORD: "${IMS_DB_PASSWORD:-ims}" + volumes: + - ./conf:/opt/ims/conf + - ./:/srv/ims + ports: + - ${IMS_SERVER_PORT:-8080}:8080 + depends_on: + database: + condition: service_healthy + command: + - /opt/ims/bin/ims + - "--log-file" + - "-" + - "--config" + - "/opt/ims/conf/imsd.conf" + - server + + database: + image: "${IMS_DB_IMAGE:-mariadb:10.5.24}" + container_name: ranger_ims_database + environment: + MARIADB_DATABASE: "${IMS_DB_DATABASE:-ims}" + MARIADB_USER: "${IMS_DB_USER_NAME:-ims}" + MARIADB_PASSWORD: "${IMS_DB_PASSWORD:-ims}" + MARIADB_ROOT_HOST: "${IMS_DB_ROOT_HOST:-%}" + MARIADB_ROOT_PASSWORD: "${IMS_DB_ROOT_PASSWORD:-ims-root}" + volumes: + - ./.docker/mysql/data/:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s + retries: 5 + +networks: + default: + name: "${DOCKER_RANGERS_NETWORK:-rangers}" diff --git a/pyproject.toml b/pyproject.toml index 0c4c86147..ccc0811a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,112 @@ +## +# Project settings +## + +[project] + +name = "ranger-ims-server" +dynamic = ["version"] +description = "Ranger Incident Management System" +readme = "README.rst" +license = {file = "LICENSE.txt"} + +# Requirements +requires-python = ">=3.12" +dependencies = [ + "arrow==1.3.0", + "attrs==24.2.0", + "bcrypt==4.2.0", + "cattrs==24.1.2", + "cryptography==43.0.1", + "hyperlink==21.0.0", + "jwcrypto==1.5.6", + "klein==24.8.0", + "pymysql==1.1.1", + "pyopenssl==24.2.1", + "pyyaml==6.0.2", + "service-identity==24.1.0", + "twisted==24.7.0", + "werkzeug==3.0.4", + "zope-interface==7.0.3", +] + +# Authors/maintainers +authors = [ + {name="Wilfredo Sánchez Vega", email="tool@burningman.org"}, +] +maintainers = [ + {name = "Burning Man Project, Black Rock Rangers", email = "ranger-tech-ninjas@burningman.org"}, +] + +# Search metadata +classifiers = [ + "Framework :: Twisted", + "Intended Audience :: Information Technology", + "Intended Audience :: Other Audience", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.12", + "Topic :: Office/Business", +] +keywords = ["incident management", "ranger"] + + +[project.scripts] +ims = "ims.run:Command.main" + + +[project.urls] +Homepage = "https://github.com/burningmantech/ranger-ims-server" +#Documentation = "" +Repository = "https://github.com/burningmantech/ranger-ims-server.git" +Issues = "https://github.com/burningmantech/ranger-ims-server/issues" +Changelog = "https://github.com/burningmantech/ranger-ims-server/blob/master/CHANGELOG.md" + + [build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" +## +# Black +## + [tool.black] line-length = 80 -target-version = ["py311"] +target-version = ["py312"] + + +## +# Setuptools +## + +[tool.setuptools.dynamic] +version = {attr = "ims.__version__"} + +[tool.setuptools.packages.find] +where = ["src"] + + +## +# uv +## + +[tool.uv] + +default-groups = [] +[dependency-groups] +unit = [ + "coverage==7.6.1", + "docker==7.1.0", + "hypothesis==6.112.4", + "mock==5.1.0", +] +mypy = [ + "mypy-zope==1.0.5", + "mypy==1.9.0", + "types-pymysql==1.1.0.20240524", + "types-pyyaml==6.0.12.20240917", +] diff --git a/requirements/requirements-bandit.txt b/requirements/requirements-bandit.txt deleted file mode 100644 index f142655de..000000000 --- a/requirements/requirements-bandit.txt +++ /dev/null @@ -1 +0,0 @@ -bandit==1.7.6 diff --git a/requirements/requirements-coverage.txt b/requirements/requirements-coverage.txt deleted file mode 100644 index 5e00acee1..000000000 --- a/requirements/requirements-coverage.txt +++ /dev/null @@ -1 +0,0 @@ -coverage==7.4.0 diff --git a/requirements/requirements-direct.txt b/requirements/requirements-direct.txt deleted file mode 100644 index e465299e5..000000000 --- a/requirements/requirements-direct.txt +++ /dev/null @@ -1,14 +0,0 @@ -arrow==1.3.0 -attrs==23.2.0 -bcrypt==4.1.2 -cattrs==23.2.3 -hyperlink==21.0.0 -jwcrypto==1.5.1 -klein==21.8.0 -PyMySQL==1.1.0 -pyOpenSSL==23.3.0 -PyYAML==6.0.1 -service-identity==24.1.0 -Twisted==23.10.0 -Werkzeug==2.2.3 -zope.interface==6.1 diff --git a/requirements/requirements-docs.txt b/requirements/requirements-docs.txt deleted file mode 100644 index 4e73ead78..000000000 --- a/requirements/requirements-docs.txt +++ /dev/null @@ -1,3 +0,0 @@ -furo==2023.9.10 -Sphinx==7.2.6 -sphinx-autobuild==2021.3.14 diff --git a/requirements/requirements-indirect.txt b/requirements/requirements-indirect.txt deleted file mode 100644 index 7e3ee8fbe..000000000 --- a/requirements/requirements-indirect.txt +++ /dev/null @@ -1,23 +0,0 @@ -Automat==22.10.0 -certifi==2023.11.17 -cffi==1.16.0 -charset-normalizer==3.3.2 -constantly==23.10.4 -Deprecated==1.2.14 -exceptiongroup==1.2.0 -idna==3.6 -incremental==22.10.0 -MarkupSafe==2.1.3 -packaging==23.2 -pyasn1==0.5.1 -pyasn1-modules==0.3.0 -pycparser==2.21 -pyparsing==3.1.1 -python-dateutil==2.8.2 -six==1.16.0 -sortedcontainers==2.4.0 -Tubes==0.2.1 -typing_extensions==4.9.0 -urllib3==2.1.0 -websocket-client==1.7.0 -wrapt==1.16.0 diff --git a/requirements/requirements-lint.txt b/requirements/requirements-lint.txt deleted file mode 100644 index 5cef2202d..000000000 --- a/requirements/requirements-lint.txt +++ /dev/null @@ -1 +0,0 @@ -pre-commit==3.6.0 diff --git a/requirements/requirements-mypy.txt b/requirements/requirements-mypy.txt deleted file mode 100644 index 6808e84d8..000000000 --- a/requirements/requirements-mypy.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Note: mypy-zope pins the mypy version - -mypy==1.5.1 -mypy-zope==1.0.3 - -types-PyMySQL==1.1.0.1 -types-PyYAML==6.0.12.12 diff --git a/requirements/requirements-packaging.txt b/requirements/requirements-packaging.txt deleted file mode 100644 index 5d4955a0a..000000000 --- a/requirements/requirements-packaging.txt +++ /dev/null @@ -1,3 +0,0 @@ -packaging==23.2 -readme-renderer==42.0 -twine==4.0.2 diff --git a/requirements/requirements-safety.txt b/requirements/requirements-safety.txt deleted file mode 100644 index 774b05d4b..000000000 --- a/requirements/requirements-safety.txt +++ /dev/null @@ -1 +0,0 @@ -safety==2.3.5 diff --git a/requirements/requirements-tests.txt b/requirements/requirements-tests.txt deleted file mode 100644 index b35d07fc9..000000000 --- a/requirements/requirements-tests.txt +++ /dev/null @@ -1,3 +0,0 @@ -docker==7.0.0 -hypothesis==6.91.0 -mock==5.1.0 diff --git a/setup.py b/setup.py deleted file mode 100755 index 6dc9ca245..000000000 --- a/setup.py +++ /dev/null @@ -1,175 +0,0 @@ -#!/usr/bin/env python - -## -# See the file COPYRIGHT for copyright information. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -## - -""" -Setuptools configuration -""" - -from pathlib import Path -from sys import path - -from setuptools import find_packages, setup - - -path.insert(0, "src") - -from ims import __version__ as version_string # noqa: E402 - - -# -# Options -# - -name = "ranger-ims-server" - -description = "Ranger Incident Management System" - -project_root = Path(__file__).parent - -readme_path = project_root / "README.rst" -long_description = readme_path.open().read() - -url = "https://github.com/burningmantech/ranger-ims-server" - -author = "Burning Man Project, Black Rock Rangers" - -author_email = "ranger-tech-ninjas@burningman.org" - -license = "Apache License, Version 2.0" - -platforms = ["all"] - -packages = find_packages(where="src") - -classifiers = [ - "Framework :: Twisted", - "Intended Audience :: Information Technology", - "Intended Audience :: Other Audience", - "License :: OSI Approved :: Apache Software License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Topic :: Office/Business", -] - - -# -# Entry points -# - -entry_points = { - "console_scripts": ["ims = ims.run:Command.main"], -} - - -# -# Package data -# - -package_data = dict( - ims=[ - "config/test/*.conf", - "directory/file/test/directory.yaml", - "element/*/template.xhtml", - "element/*/*/template.xhtml", - "element/static/*.css", - "element/static/*.js", - "element/static/*.png", - "store/mysql/schema/*.mysql", - "store/sqlite/schema/*.sqlite", - ], -) - - -# -# Dependencies -# - - -def read_requirements(path: Path) -> list[str]: - """ - Parse requirements file. - """ - return [ - requirement - for requirement in ( - line.split("#", 1)[0].strip() for line in path.open() - ) - if requirement - ] - - -python_requirements = ">=3.9" - -setup_requirements: list[str] = [] - -requirements_dir = project_root / "requirements" -install_requirements = read_requirements( - requirements_dir / "requirements-direct.txt" -) + read_requirements(requirements_dir / "requirements-indirect.txt") - -extras_requirements: dict[str, str] = {} - - -# -# Set up Extension modules that need to be built -# - -extensions: list[str] = [] - - -# -# Run setup -# - - -def main() -> None: - """ - Run :func:`setup`. - """ - setup( - name=name, - version=version_string, - description=description, - long_description=long_description, - long_description_content_type="text/x-rst", - url=url, - classifiers=classifiers, - author=author, - author_email=author_email, - license=license, - platforms=platforms, - packages=packages, - package_dir={"": "src"}, - package_data=package_data, - entry_points=entry_points, - data_files=[], - ext_modules=extensions, - python_requires=python_requirements, - setup_requires=setup_requirements, - install_requires=install_requirements, - extras_require=extras_requirements, - ) - - -# -# Main -# - -if __name__ == "__main__": - main() diff --git a/src/ims/__init__.py b/src/ims/__init__.py index fbd50d3e3..76dc6e89a 100644 --- a/src/ims/__init__.py +++ b/src/ims/__init__.py @@ -18,4 +18,4 @@ Incident Management System """ -__version__ = None +__version__ = "0.0.0" diff --git a/src/ims/application/_api.py b/src/ims/application/_api.py index 3ddbc9ee7..78d575d62 100644 --- a/src/ims/application/_api.py +++ b/src/ims/application/_api.py @@ -35,6 +35,7 @@ from attrs import frozen from hyperlink import URL from klein import KleinRenderable +from klein._app import KleinSynchronousRenderable from twisted.internet.defer import Deferred from twisted.internet.error import ConnectionDone from twisted.logger import Logger @@ -151,7 +152,9 @@ def bagResource(self, request: IRequest) -> KleinRenderable: return jsonBytes(request, self._bag, str(hash(self._bag))) @router.route(_unprefix(URLs.auth), methods=("POST",)) - async def authResource(self, request: IRequest) -> KleinRenderable: + async def authResource( + self, request: IRequest + ) -> KleinSynchronousRenderable: """ Authentication endpoint. """ @@ -201,7 +204,7 @@ async def authResource(self, request: IRequest) -> KleinRenderable: else: self._log.info("Issuing credentials for user {user}", user=user) credentials = await authProvider.credentialsForUser( - user, self.config.tokenLifetimeNormal + user, self.config.tokenLifetime ) return jsonBytes( request, jsonTextFromObject(credentials).encode("utf-8") @@ -216,7 +219,9 @@ async def authResource(self, request: IRequest) -> KleinRenderable: ) @router.route(_unprefix(URLs.personnel), methods=("HEAD", "GET")) - async def personnelResource(self, request: IRequest) -> KleinRenderable: + async def personnelResource( + self, request: IRequest + ) -> KleinSynchronousRenderable: """ Personnel endpoint. """ @@ -249,7 +254,9 @@ async def personnelData(self) -> tuple[Iterable[bytes], str]: ) @router.route(_unprefix(URLs.incidentTypes), methods=("HEAD", "GET")) - async def incidentTypesResource(self, request: IRequest) -> KleinRenderable: + async def incidentTypesResource( + self, request: IRequest + ) -> KleinSynchronousRenderable: """ Incident types endpoint. """ @@ -272,7 +279,7 @@ async def incidentTypesResource(self, request: IRequest) -> KleinRenderable: @router.route(_unprefix(URLs.incidentTypes), methods=("POST",)) async def editIncidentTypesResource( self, request: IRequest - ) -> KleinRenderable: + ) -> KleinSynchronousRenderable: """ Incident types editing endpoint. """ @@ -313,7 +320,9 @@ async def editIncidentTypesResource( return noContentResponse(request) @router.route(_unprefix(URLs.events), methods=("HEAD", "GET")) - async def eventsResource(self, request: IRequest) -> KleinRenderable: + async def eventsResource( + self, request: IRequest + ) -> KleinSynchronousRenderable: """ Events endpoint. """ @@ -336,7 +345,9 @@ async def eventsResource(self, request: IRequest) -> KleinRenderable: return jsonBytes(request, data, str(hash(data))) @router.route(_unprefix(URLs.events), methods=("POST",)) - async def editEventsResource(self, request: IRequest) -> KleinRenderable: + async def editEventsResource( + self, request: IRequest + ) -> KleinSynchronousRenderable: """ Events editing endpoint. """ @@ -396,7 +407,7 @@ async def listIncidentsResource( @router.route(_unprefix(URLs.incidents), methods=("POST",)) async def newIncidentResource( self, request: IRequest, event_id: str - ) -> KleinRenderable: + ) -> KleinSynchronousRenderable: """ New incident endpoint. """ @@ -438,9 +449,9 @@ async def newIncidentResource( json[IncidentJSONKey.state.value] = IncidentStateJSONValue.new.value if IncidentJSONKey.priority.value not in json: - json[ - IncidentJSONKey.priority.value - ] = IncidentPriorityJSONValue.normal.value + json[IncidentJSONKey.priority.value] = ( + IncidentPriorityJSONValue.normal.value + ) # If not provided, set JSON handles, types, entries, # incident report numbers to an empty list @@ -514,7 +525,7 @@ async def newIncidentResource( @router.route(_unprefix(URLs.incidentNumber), methods=("HEAD", "GET")) async def readIncidentResource( self, request: IRequest, event_id: str, incident_number: str - ) -> KleinRenderable: + ) -> KleinSynchronousRenderable: """ Incident endpoint. """ @@ -544,7 +555,7 @@ async def readIncidentResource( @router.route(_unprefix(URLs.incidentNumber), methods=("POST",)) async def editIncidentResource( self, request: IRequest, event_id: str, incident_number: str - ) -> KleinRenderable: + ) -> KleinSynchronousRenderable: """ Incident edit endpoint. """ @@ -705,7 +716,7 @@ def _cast(obj: Any) -> Any: @router.route(_unprefix(URLs.incidentReports), methods=("HEAD", "GET")) async def listIncidentReportsResource( self, request: IRequest, event_id: str - ) -> KleinRenderable: + ) -> KleinSynchronousRenderable: """ Incident reports endpoint. """ @@ -760,7 +771,7 @@ async def listIncidentReportsResource( @router.route(_unprefix(URLs.incidentReports), methods=("POST",)) async def newIncidentReportResource( self, request: IRequest, event_id: str - ) -> KleinRenderable: + ) -> KleinSynchronousRenderable: """ New incident report endpoint. """ @@ -873,7 +884,7 @@ async def newIncidentReportResource( @router.route(_unprefix(URLs.incidentReport), methods=("HEAD", "GET")) async def readIncidentReportResource( self, request: IRequest, event_id: str, incident_report_number: str - ) -> KleinRenderable: + ) -> KleinSynchronousRenderable: """ Incident report endpoint. """ @@ -899,7 +910,7 @@ async def readIncidentReportResource( @router.route(_unprefix(URLs.incidentReport), methods=("POST",)) async def editIncidentReportResource( self, request: IRequest, event_id: str, incident_report_number: str - ) -> KleinRenderable: + ) -> KleinSynchronousRenderable: """ Incident report edit endpoint. """ @@ -1027,7 +1038,7 @@ def _cast(obj: Any) -> Any: @router.route(_unprefix(URLs.acl), methods=("HEAD", "GET")) async def readAdminAccessResource( self, request: IRequest - ) -> KleinRenderable: + ) -> KleinSynchronousRenderable: """ Admin access control endpoint. """ @@ -1050,7 +1061,7 @@ async def readAdminAccessResource( @router.route(_unprefix(URLs.acl), methods=("POST",)) async def editAdminAccessResource( self, request: IRequest - ) -> KleinRenderable: + ) -> KleinSynchronousRenderable: """ Admin access control edit endpoint. """ @@ -1076,7 +1087,9 @@ async def editAdminAccessResource( return noContentResponse(request) @router.route(_unprefix(URLs.streets), methods=("HEAD", "GET")) - async def readStreetsResource(self, request: IRequest) -> KleinRenderable: + async def readStreetsResource( + self, request: IRequest + ) -> KleinSynchronousRenderable: """ Street list endpoint. """ @@ -1104,7 +1117,9 @@ async def authorizedEvents() -> AsyncIterable[Event]: ) @router.route(_unprefix(URLs.streets), methods=("POST",)) - async def editStreetsResource(self, request: IRequest) -> KleinRenderable: + async def editStreetsResource( + self, request: IRequest + ) -> KleinSynchronousRenderable: """ Street list edit endpoint. """ diff --git a/src/ims/application/_klein.py b/src/ims/application/_klein.py index 7ab048bbd..b2c7ac2e7 100644 --- a/src/ims/application/_klein.py +++ b/src/ims/application/_klein.py @@ -21,10 +21,11 @@ from collections.abc import Callable, Iterable, Sequence from functools import wraps -from typing import Any, Optional, cast +from typing import Any, Optional, TypeVar, cast from hyperlink import URL from klein import Klein, KleinRenderable, KleinRouteHandler +from klein._app import KleinSynchronousRenderable from twisted.logger import Logger from twisted.python.failure import Failure from twisted.web import http @@ -53,6 +54,9 @@ log = Logger() +KleinRouteHandlerT = TypeVar("KleinRouteHandlerT", bound=KleinRouteHandler) + + def renderResponse(f: KleinRouteHandler) -> KleinRouteHandler: """ Decorator to ensure that the returned response is rendered, if applicable. @@ -66,7 +70,9 @@ def wrapper( response = f(self, request, *args, **kwargs) if IRenderable.providedBy(response): - return renderElement(request, response) + return renderElement( # type: ignore[return-value] + request, response + ) return response @@ -75,7 +81,7 @@ def wrapper( def redirect( request: IRequest, location: URL, origin: str | None = None -) -> KleinRenderable: +) -> KleinSynchronousRenderable: """ Perform a redirect. """ @@ -107,7 +113,7 @@ def redirect( def noContentResponse( request: IRequest, etag: str | None = None -) -> KleinRenderable: +) -> KleinSynchronousRenderable: """ Respond with no content. """ @@ -117,7 +123,7 @@ def noContentResponse( return b"" -def textResponse(request: IRequest, message: str) -> KleinRenderable: +def textResponse(request: IRequest, message: str) -> KleinSynchronousRenderable: """ Respond with the given text. """ @@ -126,7 +132,7 @@ def textResponse(request: IRequest, message: str) -> KleinRenderable: return message.encode("utf-8") -def notFoundResponse(request: IRequest) -> KleinRenderable: +def notFoundResponse(request: IRequest) -> KleinSynchronousRenderable: """ Respond with a NOT FOUND status. """ @@ -136,7 +142,7 @@ def notFoundResponse(request: IRequest) -> KleinRenderable: return textResponse(request, "Not found") -def methodNotAllowedResponse(request: IRequest) -> KleinRenderable: +def methodNotAllowedResponse(request: IRequest) -> KleinSynchronousRenderable: """ Respond with a METHOD NOT ALLOWED status. """ @@ -149,7 +155,7 @@ def methodNotAllowedResponse(request: IRequest) -> KleinRenderable: return textResponse(request, "HTTP method not allowed") -def forbiddenResponse(request: IRequest) -> KleinRenderable: +def forbiddenResponse(request: IRequest) -> KleinSynchronousRenderable: """ Respond with a FORBIDDEN status. """ @@ -163,7 +169,7 @@ def forbiddenResponse(request: IRequest) -> KleinRenderable: return textResponse(request, "Permission denied") -def notAuthenticatedResponse(request: IRequest) -> KleinRenderable: +def notAuthenticatedResponse(request: IRequest) -> KleinSynchronousRenderable: """ Respond with a UNAUTHORIZED status. """ @@ -179,7 +185,7 @@ def notAuthenticatedResponse(request: IRequest) -> KleinRenderable: def badRequestResponse( request: IRequest, message: str | None = None -) -> KleinRenderable: +) -> KleinSynchronousRenderable: """ Respond with a BAD REQUEST status. """ @@ -199,7 +205,7 @@ def badRequestResponse( def invalidJSONResponse( request: IRequest, error: Exception | None = None -) -> KleinRenderable: +) -> KleinSynchronousRenderable: """ Respond with a BAD REQUEST status for invalid JSON request data. """ @@ -208,7 +214,7 @@ def invalidJSONResponse( def invalidQueryResponse( request: IRequest, arg: str, value: str | None = None -) -> KleinRenderable: +) -> KleinSynchronousRenderable: """ Respond with a BAD REQUEST status due to an invalid query. """ @@ -220,7 +226,9 @@ def invalidQueryResponse( return badRequestResponse(request, f"Invalid query: {arg}={value}") -def badGatewayResponse(request: IRequest, message: str) -> KleinRenderable: +def badGatewayResponse( + request: IRequest, message: str +) -> KleinSynchronousRenderable: """ Respond with a BAD GATEWAY status. """ @@ -232,7 +240,7 @@ def badGatewayResponse(request: IRequest, message: str) -> KleinRenderable: def internalErrorResponse( request: IRequest, message: str | None = None -) -> KleinRenderable: +) -> KleinSynchronousRenderable: """ Respond with an INTERNAL SERVER ERROR status. """ @@ -327,7 +335,7 @@ def __init__(self) -> None: def route( self, url: str | URL, *args: Any, **kwargs: Any - ) -> Callable[[KleinRouteHandler], KleinRouteHandler]: + ) -> Callable[[KleinRouteHandlerT], KleinRouteHandlerT]: """ See :meth:`Klein.route`. """ @@ -336,7 +344,7 @@ def route( if isinstance(url, URL): url = url.asText() - def decorator(f: KleinRouteHandler) -> KleinRouteHandler: + def decorator(f: KleinRouteHandlerT) -> KleinRouteHandlerT: @superRoute(url, *args, **kwargs) @wraps(f) def wrapper( @@ -354,7 +362,7 @@ def wrapper( return f(app, request, *args, **kwargs) - return cast(KleinRouteHandler, wrapper) + return wrapper # type: ignore[return-value] return decorator @@ -368,7 +376,8 @@ def requestRedirectError( Redirect. """ assert failure.value is not None - url = URL.fromText(failure.value.args[0]) + assert isinstance(failure.value, RequestRedirect) + url = URL.fromText(failure.value.new_url) return redirect(request, url) @self.handle_errors(NotFound) diff --git a/src/ims/config/_config.py b/src/ims/config/_config.py index f7f6e6112..42aba37b8 100644 --- a/src/ims/config/_config.py +++ b/src/ims/config/_config.py @@ -388,26 +388,16 @@ def fromConfigFile(cls, configFile: Path | None) -> "Configuration": masterKey = parser.valueFromConfig("MASTER_KEY", "Core", "MasterKey") - tokenLifetimeNormal = TimeDelta( + tokenLifetime = TimeDelta( seconds=int( parser.valueFromConfig( - "TOKEN_LIFETIME_NORMAL", + "TOKEN_LIFETIME", "Core", "TokenLifetime", str(1 * 60 * 60), ) ) ) - tokenLifetimeExtended = TimeDelta( - seconds=int( - parser.valueFromConfig( - "TOKEN_LIFETIME_EXTENDED", - "Core", - "TokenLifetime", - str(30 * 24 * 60 * 60), - ) - ) - ) # # Persist some objects @@ -430,8 +420,7 @@ def fromConfigFile(cls, configFile: Path | None) -> "Configuration": requireActive=requireActive, serverRoot=serverRoot, storeFactory=storeFactory, - tokenLifetimeExtended=tokenLifetimeExtended, - tokenLifetimeNormal=tokenLifetimeNormal, + tokenLifetime=tokenLifetime, ) cachedResourcesRoot: Path @@ -449,8 +438,7 @@ def fromConfigFile(cls, configFile: Path | None) -> "Configuration": port: int requireActive: bool serverRoot: Path - tokenLifetimeExtended: TimeDelta - tokenLifetimeNormal: TimeDelta + tokenLifetime: TimeDelta _storeFactory: Callable[[], IMSDataStore] diff --git a/src/ims/ext/sqlite.py b/src/ims/ext/sqlite.py index 1bb1fe9f0..720ea3c2a 100644 --- a/src/ims/ext/sqlite.py +++ b/src/ims/ext/sqlite.py @@ -103,10 +103,7 @@ def cursor( # type: ignore[override] """ See :meth:`sqlite3.Cursor.cursor`. """ - return cast( - "Cursor", - super().cursor(factory=factory), # type: ignore[call-overload] - ) + return super().cursor(factory=factory) def executeAndPrint( self, sql: str, parameters: Parameters | None = None diff --git a/src/ims/ext/test/test_klein.py b/src/ims/ext/test/test_klein.py index 5850b89d4..1f546b8ab 100644 --- a/src/ims/ext/test/test_klein.py +++ b/src/ims/ext/test/test_klein.py @@ -49,6 +49,7 @@ def test_static_etag(self) -> None: app.root(request) etags = request.responseHeaders.getRawHeaders("etag") + assert etags is not None self.assertTrue(len(etags) == 1, etags) etag = etags[0] self.assertTrue(etag) @@ -63,6 +64,7 @@ def test_static_cacheControl(self) -> None: app.root(request) etags = request.responseHeaders.getRawHeaders("cache-control") + assert etags is not None self.assertTrue(len(etags) == 1, etags) etag = etags[0] self.assertTrue(etag) diff --git a/src/ims/model/_ranger.py b/src/ims/model/_ranger.py index 325a5fa72..4e72b6045 100644 --- a/src/ims/model/_ranger.py +++ b/src/ims/model/_ranger.py @@ -73,7 +73,9 @@ class Ranger(ReplaceMixIn): handle: str name: str status: RangerStatus - email: frozenset[str] = field(converter=freezeStrings) + email: frozenset[str] = field( + converter=freezeStrings, default=frozenset[str]() + ) enabled: bool directoryID: str | None password: str | None = field( diff --git a/src/ims/model/json/_json.py b/src/ims/model/json/_json.py index cd89ec426..56a7ca1c7 100644 --- a/src/ims/model/json/_json.py +++ b/src/ims/model/json/_json.py @@ -36,7 +36,7 @@ log = Logger() -JSON = Union[Mapping[str, Any], Iterable, int, str, float, bool, None] +JSON = Union[Mapping[str, Any], Iterable[Any], int, str, float, bool, None] class JSONCodecError(Exception): diff --git a/src/ims/model/json/_ranger.py b/src/ims/model/json/_ranger.py index cb6518963..850052b11 100644 --- a/src/ims/model/json/_ranger.py +++ b/src/ims/model/json/_ranger.py @@ -42,9 +42,10 @@ class RangerJSONKey(Enum): handle = "handle" name = "name" status = "status" - email = "email" + # email is intentionally not serialized, since no web client needs it enabled = "enabled" directoryID = "directory_id" + # password is intentionally not serialized, since no web client needs it class RangerJSONType(Enum): @@ -55,9 +56,10 @@ class RangerJSONType(Enum): handle = str name = str # type: ignore[assignment] status = RangerStatus - email = set[str] + # email is intentionally not serialized, since no web client needs it enabled = bool directoryID = Optional[str] + # password is intentionally not serialized, since no web client needs it def serializeRanger(ranger: Ranger) -> dict[str, Any]: diff --git a/src/ims/model/json/test/json.py b/src/ims/model/json/test/json.py index 2853b8d5c..e4352e3a3 100644 --- a/src/ims/model/json/test/json.py +++ b/src/ims/model/json/test/json.py @@ -186,9 +186,10 @@ def jsonFromRanger(ranger: Ranger) -> dict[str, Any]: handle=ranger.handle, name=ranger.name, status=jsonFromRangerStatus(ranger.status), - directory_id=ranger.directoryID, - email=jsonSerialize([e for e in ranger.email]), + # email is intentionally not serialized enabled=ranger.enabled, + directory_id=ranger.directoryID, + # password is intentionally not serialized ) diff --git a/src/ims/model/json/test/test_ranger.py b/src/ims/model/json/test/test_ranger.py index 06eff9453..043df8b45 100644 --- a/src/ims/model/json/test/test_ranger.py +++ b/src/ims/model/json/test/test_ranger.py @@ -56,5 +56,8 @@ def test_deserialize(self, ranger: Ranger) -> None: """ self.assertEqual( jsonDeserialize(jsonFromRanger(ranger), Ranger), - ranger.replace(password=None), + ranger.replace( + email=frozenset(), + password=None, + ), ) diff --git a/src/ims/run/_command.py b/src/ims/run/_command.py index d2871747c..440bfa61e 100644 --- a/src/ims/run/_command.py +++ b/src/ims/run/_command.py @@ -55,10 +55,6 @@ __all__ = () -class IMSSession(Session): - sessionTimeout = 60 * 60 * 1 # 1 hour - - @frozen(kw_only=True) class Command: """ @@ -107,6 +103,9 @@ def runServer(cls, config: Configuration, options: ServerOptions) -> None: patchCombinedLogFormatter() + class IMSSession(Session): + sessionTimeout = int(config.tokenLifetime.total_seconds()) + factory = Site(application.router.resource()) factory.sessionFactory = IMSSession diff --git a/tox.ini b/tox.ini index 38bf736cb..a7d91fd13 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,10 @@ [tox] envlist = - lint, mypy, bandit - test-py312 - coverage-py311 + lint, mypy + test-py313 + coverage-py312 coverage_report - docs packaging skip_missing_interpreters = {tty:True:False} @@ -13,12 +12,7 @@ skip_missing_interpreters = {tty:True:False} [default] -basepython = python3.11 - -deps = - {test,coverage}: -r requirements/requirements-tests.txt - - coverage: {[testenv:coverage_report]deps} +basepython = python3.12 setenv = PY_MODULE=ims @@ -37,10 +31,11 @@ description = run tests basepython = py: python - py311: python3.11 py312: python3.12 + py313: python3.13 -deps = {[default]deps} +runner = uv-venv-lock-runner +uv_sync_flags = --group=unit passenv = {test,coverage}: IMS_TEST_* @@ -87,15 +82,17 @@ description = run all linters basepython = {[default]basepython} +runner = uv-venv-runner + +deps = pre-commit==4.0.0 + usedevelop = true skip_install = True -deps = - -r requirements/requirements-lint.txt - commands = pre-commit run {posargs:--all-files} +passenv = SKIP ## # Mypy static type checking @@ -107,6 +104,9 @@ description = run Mypy (static type checker) basepython = {[default]basepython} +runner = uv-venv-lock-runner +uv_sync_flags = --group=mypy + usedevelop = true deps = @@ -117,7 +117,7 @@ commands = mypy \ --cache-dir="{toxworkdir}/mypy_cache" \ {tty:--pretty:} \ - {posargs:setup.py src} + {posargs:src} ## @@ -128,15 +128,14 @@ commands = description = generate coverage report -depends = coverage-py{311,312} +depends = coverage-py{312,313} basepython = {[default]basepython} -usedevelop = true -skip_install = True +runner = uv-venv-lock-runner +uv_sync_flags = --group=unit -deps = - -r requirements/requirements-coverage.txt +usedevelop = true setenv = {[default]setenv} @@ -149,80 +148,6 @@ commands = - coverage html -## -# Bandit security static analyzer -## - -[testenv:bandit] - -description = run Bandit (security static analyzer) - -basepython = {[default]basepython} - -usedevelop = true -skip_install = True - -deps = - -r requirements/requirements-bandit.txt - -commands = - bandit -c {toxinidir}/.bandit -r {posargs:src} - - -## -# Safety dependency security checker -## - -[testenv:safety] - -description = run Safety (dependency security checker) - -basepython = {[default]basepython} - -deps = - -r requirements/requirements-safety.txt - -commands = - safety check - - -## -# Documentation -## - -[testenv:docs] - -description = build documentation - -basepython = {[default]basepython} - -deps = - -r requirements/requirements-docs.txt - -commands = - sphinx-build \ - -b html -d "{envtmpdir}/doctrees" \ - "{toxinidir}/docs" \ - "{toxinidir}/htmldocs" - - -[testenv:docs-auto] - -description = build documentation and rebuild automatically - -basepython = {[default]basepython} - -deps = - {[testenv:docs]deps} - -commands = - sphinx-autobuild \ - -b html -d "{envtmpdir}/doctrees" \ - --host=localhost \ - "{toxinidir}/docs" \ - "{toxinidir}/htmldocs" - - ## # Packaging ## @@ -231,50 +156,19 @@ commands = description = check for potential packaging problems -depends = {test,coverage}-py{311,312} +depends = {test,coverage}-py{312,313} basepython = {[default]basepython} +runner = uv-venv-runner + skip_install = True deps = - -r requirements/requirements-packaging.txt + pip + packaging==24.1 + twine==5.1.1 commands = pip wheel --wheel-dir "{envtmpdir}/dist" --no-deps {toxinidir} twine check "{envtmpdir}/dist/"* - - -## -# Print dependencies -## - -[testenv:dependencies] - -description = print dependencies - -basepython = {[default]basepython} - -recreate = true - -deps = - pipdeptree - -commands = - python -c 'print()' - pip freeze --exclude=ranger-ims-server --exclude=pipdeptree - - python -c 'print()' - pipdeptree - - -## -# Run the service -## - -[testenv:exec] - -basepython = {[default]basepython} - -commands = - "{envbindir}/ims" --config="{toxinidir}/conf/imsd.conf" --log-file=- {posargs:server} diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000..a2e0bed95 --- /dev/null +++ b/uv.lock @@ -0,0 +1,770 @@ +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "arrow" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "types-python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419 }, +] + +[[package]] +name = "attrs" +version = "24.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 }, +] + +[[package]] +name = "automat" +version = "24.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/2d/ede4ad7fc34ab4482389fa3369d304f2fa22e50770af706678f6a332fa82/automat-24.8.1.tar.gz", hash = "sha256:b34227cf63f6325b8ad2399ede780675083e439b20c323d376373d8ee6306d88", size = 128679 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/cc/55a32a2c98022d88812b5986d2a92c4ff3ee087e83b712ebc703bba452bf/Automat-24.8.1-py3-none-any.whl", hash = "sha256:bf029a7bc3da1e2c24da2343e7598affaa9f10bf0ab63ff808566ce90551e02a", size = 42585 }, +] + +[[package]] +name = "bcrypt" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/7e/d95e7d96d4828e965891af92e43b52a4cd3395dc1c1ef4ee62748d0471d0/bcrypt-4.2.0.tar.gz", hash = "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221", size = 24294 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/81/4e8f5bc0cd947e91fb720e1737371922854da47a94bc9630454e7b2845f8/bcrypt-4.2.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb", size = 471568 }, + { url = "https://files.pythonhosted.org/packages/05/d2/1be1e16aedec04bcf8d0156e01b987d16a2063d38e64c3f28030a3427d61/bcrypt-4.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00", size = 277372 }, + { url = "https://files.pythonhosted.org/packages/e3/96/7a654027638ad9b7589effb6db77eb63eba64319dfeaf9c0f4ca953e5f76/bcrypt-4.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d", size = 273488 }, + { url = "https://files.pythonhosted.org/packages/46/54/dc7b58abeb4a3d95bab653405935e27ba32f21b812d8ff38f271fb6f7f55/bcrypt-4.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291", size = 277759 }, + { url = "https://files.pythonhosted.org/packages/ac/be/da233c5f11fce3f8adec05e8e532b299b64833cc962f49331cdd0e614fa9/bcrypt-4.2.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328", size = 273796 }, + { url = "https://files.pythonhosted.org/packages/b0/b8/8b4add88d55a263cf1c6b8cf66c735280954a04223fcd2880120cc767ac3/bcrypt-4.2.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7", size = 311082 }, + { url = "https://files.pythonhosted.org/packages/7b/76/2aa660679abbdc7f8ee961552e4bb6415a81b303e55e9374533f22770203/bcrypt-4.2.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399", size = 305912 }, + { url = "https://files.pythonhosted.org/packages/00/03/2af7c45034aba6002d4f2b728c1a385676b4eab7d764410e34fd768009f2/bcrypt-4.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060", size = 325185 }, + { url = "https://files.pythonhosted.org/packages/dc/5d/6843443ce4ab3af40bddb6c7c085ed4a8418b3396f7a17e60e6d9888416c/bcrypt-4.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7", size = 335188 }, + { url = "https://files.pythonhosted.org/packages/cb/4c/ff8ca83d816052fba36def1d24e97d9a85739b9bbf428c0d0ecd296a07c8/bcrypt-4.2.0-cp37-abi3-win32.whl", hash = "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458", size = 156481 }, + { url = "https://files.pythonhosted.org/packages/65/f1/e09626c88a56cda488810fb29d5035f1662873777ed337880856b9d204ae/bcrypt-4.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5", size = 151336 }, + { url = "https://files.pythonhosted.org/packages/96/86/8c6a84daed4dd878fbab094400c9174c43d9b838ace077a2f8ee8bc3ae12/bcrypt-4.2.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841", size = 472414 }, + { url = "https://files.pythonhosted.org/packages/f6/05/e394515f4e23c17662e5aeb4d1859b11dc651be01a3bd03c2e919a155901/bcrypt-4.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68", size = 277599 }, + { url = "https://files.pythonhosted.org/packages/4b/3b/ad784eac415937c53da48983756105d267b91e56aa53ba8a1b2014b8d930/bcrypt-4.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe", size = 273491 }, + { url = "https://files.pythonhosted.org/packages/cc/14/b9ff8e0218bee95e517b70e91130effb4511e8827ac1ab00b4e30943a3f6/bcrypt-4.2.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2", size = 277934 }, + { url = "https://files.pythonhosted.org/packages/3e/d0/31938bb697600a04864246acde4918c4190a938f891fd11883eaaf41327a/bcrypt-4.2.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c", size = 273804 }, + { url = "https://files.pythonhosted.org/packages/e7/c3/dae866739989e3f04ae304e1201932571708cb292a28b2f1b93283e2dcd8/bcrypt-4.2.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae", size = 311275 }, + { url = "https://files.pythonhosted.org/packages/5d/2c/019bc2c63c6125ddf0483ee7d914a405860327767d437913942b476e9c9b/bcrypt-4.2.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d", size = 306355 }, + { url = "https://files.pythonhosted.org/packages/75/fe/9e137727f122bbe29771d56afbf4e0dbc85968caa8957806f86404a5bfe1/bcrypt-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e", size = 325381 }, + { url = "https://files.pythonhosted.org/packages/1a/d4/586b9c18a327561ea4cd336ff4586cca1a7aa0f5ee04e23a8a8bb9ca64f1/bcrypt-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8", size = 335685 }, + { url = "https://files.pythonhosted.org/packages/24/55/1a7127faf4576138bb278b91e9c75307490178979d69c8e6e273f74b974f/bcrypt-4.2.0-cp39-abi3-win32.whl", hash = "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34", size = 155857 }, + { url = "https://files.pythonhosted.org/packages/1c/2a/c74052e54162ec639266d91539cca7cbf3d1d3b8b36afbfeaee0ea6a1702/bcrypt-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9", size = 151717 }, +] + +[[package]] +name = "cattrs" +version = "24.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/65/af6d57da2cb32c076319b7489ae0958f746949d407109e3ccf4d115f147c/cattrs-24.1.2.tar.gz", hash = "sha256:8028cfe1ff5382df59dd36474a86e02d817b06eaf8af84555441bac915d2ef85", size = 426462 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/d5/867e75361fc45f6de75fe277dd085627a9db5ebb511a87f27dc1396b5351/cattrs-24.1.2-py3-none-any.whl", hash = "sha256:67c7495b760168d931a10233f979b28dc04daf853b30752246f4f8471c6d68d0", size = 66446 }, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/0b/4b7a70987abf9b8196845806198975b6aab4ce016632f817ad758a5aa056/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", size = 194445 }, + { url = "https://files.pythonhosted.org/packages/50/89/354cc56cf4dd2449715bc9a0f54f3aef3dc700d2d62d1fa5bbea53b13426/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", size = 125275 }, + { url = "https://files.pythonhosted.org/packages/fa/44/b730e2a2580110ced837ac083d8ad222343c96bb6b66e9e4e706e4d0b6df/charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", size = 119020 }, + { url = "https://files.pythonhosted.org/packages/9d/e4/9263b8240ed9472a2ae7ddc3e516e71ef46617fe40eaa51221ccd4ad9a27/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", size = 139128 }, + { url = "https://files.pythonhosted.org/packages/6b/e3/9f73e779315a54334240353eaea75854a9a690f3f580e4bd85d977cb2204/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", size = 149277 }, + { url = "https://files.pythonhosted.org/packages/1a/cf/f1f50c2f295312edb8a548d3fa56a5c923b146cd3f24114d5adb7e7be558/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", size = 142174 }, + { url = "https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", size = 143838 }, + { url = "https://files.pythonhosted.org/packages/a4/01/2117ff2b1dfc61695daf2babe4a874bca328489afa85952440b59819e9d7/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", size = 146149 }, + { url = "https://files.pythonhosted.org/packages/f6/9b/93a332b8d25b347f6839ca0a61b7f0287b0930216994e8bf67a75d050255/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", size = 140043 }, + { url = "https://files.pythonhosted.org/packages/ab/f6/7ac4a01adcdecbc7a7587767c776d53d369b8b971382b91211489535acf0/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", size = 148229 }, + { url = "https://files.pythonhosted.org/packages/9d/be/5708ad18161dee7dc6a0f7e6cf3a88ea6279c3e8484844c0590e50e803ef/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", size = 151556 }, + { url = "https://files.pythonhosted.org/packages/5a/bb/3d8bc22bacb9eb89785e83e6723f9888265f3a0de3b9ce724d66bd49884e/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", size = 149772 }, + { url = "https://files.pythonhosted.org/packages/f7/fa/d3fc622de05a86f30beea5fc4e9ac46aead4731e73fd9055496732bcc0a4/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", size = 144800 }, + { url = "https://files.pythonhosted.org/packages/9a/65/bdb9bc496d7d190d725e96816e20e2ae3a6fa42a5cac99c3c3d6ff884118/charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", size = 94836 }, + { url = "https://files.pythonhosted.org/packages/3e/67/7b72b69d25b89c0b3cea583ee372c43aa24df15f0e0f8d3982c57804984b/charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", size = 102187 }, + { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 }, + { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 }, + { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 }, + { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 }, + { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 }, + { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 }, + { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 }, + { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 }, + { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 }, + { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 }, + { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 }, + { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 }, + { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 }, + { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, +] + +[[package]] +name = "constantly" +version = "23.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/6f/cb2a94494ff74aa9528a36c5b1422756330a75a8367bf20bd63171fc324d/constantly-23.10.4.tar.gz", hash = "sha256:aa92b70a33e2ac0bb33cd745eb61776594dc48764b06c35e0efd050b7f1c7cbd", size = 13300 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/40/c199d095151addf69efdb4b9ca3a4f20f70e20508d6222bffb9b76f58573/constantly-23.10.4-py3-none-any.whl", hash = "sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9", size = 13547 }, +] + +[[package]] +name = "coverage" +version = "7.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983 }, + { url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221 }, + { url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342 }, + { url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371 }, + { url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/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", size = 239455 }, + { url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924 }, + { url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252 }, + { url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897 }, + { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606 }, + { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373 }, + { url = "https://files.pythonhosted.org/packages/8c/f9/9aa4dfb751cb01c949c990d136a0f92027fbcc5781c6e921df1cb1563f20/coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", size = 207007 }, + { url = "https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", size = 207269 }, + { url = "https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", size = 239886 }, + { url = "https://files.pythonhosted.org/packages/7b/b7/35760a67c168e29f454928f51f970342d23cf75a2bb0323e0f07334c85f3/coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", size = 237037 }, + { url = "https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/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", size = 239038 }, + { url = "https://files.pythonhosted.org/packages/6e/bd/110689ff5752b67924efd5e2aedf5190cbbe245fc81b8dec1abaffba619d/coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", size = 238690 }, + { url = "https://files.pythonhosted.org/packages/d3/a8/08d7b38e6ff8df52331c83130d0ab92d9c9a8b5462f9e99c9f051a4ae206/coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", size = 236765 }, + { url = "https://files.pythonhosted.org/packages/d6/6a/9cf96839d3147d55ae713eb2d877f4d777e7dc5ba2bce227167d0118dfe8/coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", size = 238611 }, + { url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671 }, + { url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368 }, + { url = "https://files.pythonhosted.org/packages/9c/15/08913be1c59d7562a3e39fce20661a98c0a3f59d5754312899acc6cb8a2d/coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", size = 207758 }, + { url = "https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", size = 208035 }, + { url = "https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", size = 250839 }, + { url = "https://files.pythonhosted.org/packages/7c/1e/c2967cb7991b112ba3766df0d9c21de46b476d103e32bb401b1b2adf3380/coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", size = 246569 }, + { url = "https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/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", size = 248927 }, + { url = "https://files.pythonhosted.org/packages/c8/fa/13a6f56d72b429f56ef612eb3bc5ce1b75b7ee12864b3bd12526ab794847/coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", size = 248401 }, + { url = "https://files.pythonhosted.org/packages/75/06/0429c652aa0fb761fc60e8c6b291338c9173c6aa0f4e40e1902345b42830/coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", size = 246301 }, + { url = "https://files.pythonhosted.org/packages/52/76/1766bb8b803a88f93c3a2d07e30ffa359467810e5cbc68e375ebe6906efb/coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", size = 247598 }, + { url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307 }, + { url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453 }, +] + +[[package]] +name = "cryptography" +version = "43.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/ba/0664727028b37e249e73879348cc46d45c5c1a2a2e81e8166462953c5755/cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d", size = 686927 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/28/b92c98a04ba762f8cdeb54eba5c4c84e63cac037a7c5e70117d337b15ad6/cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d", size = 6223222 }, + { url = "https://files.pythonhosted.org/packages/33/13/1193774705783ba364121aa2a60132fa31a668b8ababd5edfa1662354ccd/cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062", size = 3794751 }, + { url = "https://files.pythonhosted.org/packages/5e/4b/39bb3c4c8cfb3e94e736b8d8859ce5c81536e91a1033b1d26770c4249000/cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962", size = 3981827 }, + { url = "https://files.pythonhosted.org/packages/ce/dc/1471d4d56608e1013237af334b8a4c35d53895694fbb73882d1c4fd3f55e/cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277", size = 3780034 }, + { url = "https://files.pythonhosted.org/packages/ad/43/7a9920135b0d5437cc2f8f529fa757431eb6a7736ddfadfdee1cc5890800/cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", size = 3993407 }, + { url = "https://files.pythonhosted.org/packages/cc/42/9ab8467af6c0b76f3d9b8f01d1cf25b9c9f3f2151f4acfab888d21c55a72/cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042", size = 3886457 }, + { url = "https://files.pythonhosted.org/packages/a4/65/430509e31700286ec02868a2457d2111d03ccefc20349d24e58d171ae0a7/cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494", size = 4081499 }, + { url = "https://files.pythonhosted.org/packages/bb/18/a04b6467e6e09df8c73b91dcee8878f4a438a43a3603dc3cd6f8003b92d8/cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2", size = 2616504 }, + { url = "https://files.pythonhosted.org/packages/cc/73/0eacbdc437202edcbdc07f3576ed8fb8b0ab79d27bf2c5d822d758a72faa/cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d", size = 3067456 }, + { url = "https://files.pythonhosted.org/packages/8a/b6/bc54b371f02cffd35ff8dc6baba88304d7cf8e83632566b4b42e00383e03/cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d", size = 6225263 }, + { url = "https://files.pythonhosted.org/packages/00/0e/8217e348a1fa417ec4c78cd3cdf24154f5e76fd7597343a35bd403650dfd/cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806", size = 3794368 }, + { url = "https://files.pythonhosted.org/packages/3d/ed/38b6be7254d8f7251fde8054af597ee8afa14f911da67a9410a45f602fc3/cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85", size = 3981750 }, + { url = "https://files.pythonhosted.org/packages/64/f3/b7946c3887cf7436f002f4cbb1e6aec77b8d299b86be48eeadfefb937c4b/cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c", size = 3778925 }, + { url = "https://files.pythonhosted.org/packages/ac/7e/ebda4dd4ae098a0990753efbb4b50954f1d03003846b943ea85070782da7/cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1", size = 3993152 }, + { url = "https://files.pythonhosted.org/packages/43/f6/feebbd78a3e341e3913846a3bb2c29d0b09b1b3af1573c6baabc2533e147/cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa", size = 3886392 }, + { url = "https://files.pythonhosted.org/packages/bd/4c/ab0b9407d5247576290b4fd8abd06b7f51bd414f04eef0f2800675512d61/cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", size = 4082606 }, + { url = "https://files.pythonhosted.org/packages/05/36/e532a671998d6fcfdb9122da16434347a58a6bae9465e527e450e0bc60a5/cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", size = 2617948 }, + { url = "https://files.pythonhosted.org/packages/b3/c6/c09cee6968add5ff868525c3815e5dccc0e3c6e89eec58dc9135d3c40e88/cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", size = 3070445 }, +] + +[[package]] +name = "docker" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774 }, +] + +[[package]] +name = "hyperlink" +version = "21.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/51/1947bd81d75af87e3bb9e34593a4cf118115a8feb451ce7a69044ef1412e/hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b", size = 140743 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/aa/8caf6a0a3e62863cbb9dab27135660acba46903b703e224f14f447e57934/hyperlink-21.0.0-py2.py3-none-any.whl", hash = "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4", size = 74638 }, +] + +[[package]] +name = "hypothesis" +version = "6.112.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/cb/ee8c37f20797ded08e147e712883b776da89f15abfc58072a08aea19bbf3/hypothesis-6.112.4.tar.gz", hash = "sha256:8fe64e4a6d0862e209e3c36b42037aee9665cb839d619d9281be45345ab7d856", size = 407975 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/78/ae621a006c8ead1bbca2bf17b50b8fc4d8a4eb6bfe2c3b2845ccf9095e1f/hypothesis-6.112.4-py3-none-any.whl", hash = "sha256:6d3e3038968925069d1a7e7ebfa2ed0b65b22eff6800d1e88b687b3c6d2f57b5", size = 468836 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "incremental" +version = "24.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/87/156b374ff6578062965afe30cc57627d35234369b3336cf244b240c8d8e6/incremental-24.7.2.tar.gz", hash = "sha256:fb4f1d47ee60efe87d4f6f0ebb5f70b9760db2b2574c59c8e8912be4ebd464c9", size = 28157 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/38/221e5b2ae676a3938c2c1919131410c342b6efc2baffeda395dd66eeca8f/incremental-24.7.2-py3-none-any.whl", hash = "sha256:8cb2c3431530bec48ad70513931a760f446ad6c25e8333ca5d95e24b0ed7b8fe", size = 20516 }, +] + +[[package]] +name = "jwcrypto" +version = "1.5.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/db/870e5d5fb311b0bcf049630b5ba3abca2d339fd5e13ba175b4c13b456d08/jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039", size = 87168 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/58/4a1880ea64032185e9ae9f63940c9327c6952d5584ea544a8f66972f2fda/jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789", size = 92520 }, +] + +[[package]] +name = "klein" +version = "24.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "hyperlink" }, + { name = "incremental" }, + { name = "tubes" }, + { name = "twisted" }, + { name = "werkzeug" }, + { name = "zope-interface" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/55/70e08b134db00f2e9b5c00f6df6e4f1c13b46041b15451e8f3c28bd74908/klein-24.8.0.tar.gz", hash = "sha256:1eef4f583c4f8ef4fc57d8b57753fe6e8935501eb36497bb574064e80e3d0b15", size = 104368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/cc/a5b41def4b6bfa81aa0a1bb71360cb63d9d7158d63c9368666411c100eb8/klein-24.8.0-py2.py3-none-any.whl", hash = "sha256:90e15b74f8c842a1519ea0b4e473e97fb8265c046d8b900815008c7614c59173", size = 93819 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "mock" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/ab/41d09a46985ead5839d8be987acda54b5bb93f713b3969cc0be4f81c455b/mock-5.1.0.tar.gz", hash = "sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d", size = 80232 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/20/471f41173930550f279ccb65596a5ac19b9ac974a8d93679bcd3e0c31498/mock-5.1.0-py3-none-any.whl", hash = "sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744", size = 30938 }, +] + +[[package]] +name = "mypy" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/1e/a587a862c766a755a58b62d8c00aed11b74a15dc415c1bf5da7b607b0efd/mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974", size = 2995901 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/96/40f0f605b1d4e2ad1fb11d21988ce3a3e205886c0fcbd35c9789a214de9a/mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd", size = 10725390 }, + { url = "https://files.pythonhosted.org/packages/d7/d2/072e40384b53051106b4fcf03537fb88e2a6ad0757d2ab7f6c8c2f188a69/mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6", size = 9731292 }, + { url = "https://files.pythonhosted.org/packages/85/a5/b7dc7eb69eda899fd07e71403b51b598a1f4df0f452d1da5844374082bcd/mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185", size = 12455450 }, + { url = "https://files.pythonhosted.org/packages/1c/1b/3e962a201d2f0f57c9fa1990e0dd6076f4f2f94954ab56e4a701ec3cc070/mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913", size = 12530368 }, + { url = "https://files.pythonhosted.org/packages/72/1f/8b214b69d08cc5e4bd8c3769ac55a43318f3529362ea55e5957774b69924/mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6", size = 9319112 }, + { url = "https://files.pythonhosted.org/packages/60/db/0ba2eaedca52bf5276275e8489951c26206030b3d31bf06f00875ae75d5d/mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e", size = 2555887 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "mypy-zope" +version = "1.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy" }, + { name = "zope-interface" }, + { name = "zope-schema" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/8f/4be355338ff798e7cb6afbc01993bd952e834b3718e47eba626d4822b331/mypy_zope-1.0.5.tar.gz", hash = "sha256:2440406d49c0e1199c1cd819c92a2c4957de65579c6abc8a081c927f4bdc8d49", size = 33936 } + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135 }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/67/6afbf0d507f73c32d21084a79946bfcfca5fbc62a72057e9c23797a737c9/pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c", size = 310028 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/89/bc88a6711935ba795a679ea6ebee07e128050d6382eaa35a0a47c8032bdc/pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd", size = 181537 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pymysql" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/ce59b5e5ed4ce8512f879ff1fa5ab699d211ae2495f1adaa5fbba2a1eada/pymysql-1.1.1.tar.gz", hash = "sha256:e127611aaf2b417403c60bf4dc570124aeb4a57f5f37b8e95ae399a42f904cd0", size = 47678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/94/e4181a1f6286f545507528c78016e00065ea913276888db2262507693ce5/PyMySQL-1.1.1-py3-none-any.whl", hash = "sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c", size = 44972 }, +] + +[[package]] +name = "pyopenssl" +version = "24.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/70/ff56a63248562e77c0c8ee4aefc3224258f1856977e0c1472672b62dadb8/pyopenssl-24.2.1.tar.gz", hash = "sha256:4247f0dbe3748d560dcbb2ff3ea01af0f9a1a001ef5f7c4c647956ed8cbf0e95", size = 184323 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/dd/e0aa7ebef5168c75b772eda64978c597a9129b46be17779054652a7999e4/pyOpenSSL-24.2.1-py3-none-any.whl", hash = "sha256:967d5719b12b243588573f39b0c677637145c7a1ffedcd495a487e58177fbb8d", size = 58390 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "pywin32" +version = "308" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729 }, + { url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015 }, + { url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033 }, + { url = "https://files.pythonhosted.org/packages/a9/a4/aa562d8935e3df5e49c161b427a3a2efad2ed4e9cf81c3de636f1fdddfd0/pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed", size = 5938579 }, + { url = "https://files.pythonhosted.org/packages/c7/50/b0efb8bb66210da67a53ab95fd7a98826a97ee21f1d22949863e6d588b22/pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4", size = 6542056 }, + { url = "https://files.pythonhosted.org/packages/26/df/2b63e3e4f2df0224f8aaf6d131f54fe4e8c96400eb9df563e2aae2e1a1f9/pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd", size = 7974986 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "ranger-ims-server" +version = "0.0.0" +source = { editable = "." } +dependencies = [ + { name = "arrow" }, + { name = "attrs" }, + { name = "bcrypt" }, + { name = "cattrs" }, + { name = "cryptography" }, + { name = "hyperlink" }, + { name = "jwcrypto" }, + { name = "klein" }, + { name = "pymysql" }, + { name = "pyopenssl" }, + { name = "pyyaml" }, + { name = "service-identity" }, + { name = "twisted" }, + { name = "werkzeug" }, + { name = "zope-interface" }, +] + +[package.dev-dependencies] +mypy = [ + { name = "mypy" }, + { name = "mypy-zope" }, + { name = "types-pymysql" }, + { name = "types-pyyaml" }, +] +unit = [ + { name = "coverage" }, + { name = "docker" }, + { name = "hypothesis" }, + { name = "mock" }, +] + +[package.metadata] +requires-dist = [ + { name = "arrow", specifier = "==1.3.0" }, + { name = "attrs", specifier = "==24.2.0" }, + { name = "bcrypt", specifier = "==4.2.0" }, + { name = "cattrs", specifier = "==24.1.2" }, + { name = "cryptography", specifier = "==43.0.1" }, + { name = "hyperlink", specifier = "==21.0.0" }, + { name = "jwcrypto", specifier = "==1.5.6" }, + { name = "klein", specifier = "==24.8.0" }, + { name = "pymysql", specifier = "==1.1.1" }, + { name = "pyopenssl", specifier = "==24.2.1" }, + { name = "pyyaml", specifier = "==6.0.2" }, + { name = "service-identity", specifier = "==24.1.0" }, + { name = "twisted", specifier = "==24.7.0" }, + { name = "werkzeug", specifier = "==3.0.4" }, + { name = "zope-interface", specifier = "==7.0.3" }, +] + +[package.metadata.requires-dev] +mypy = [ + { name = "mypy", specifier = "==1.9.0" }, + { name = "mypy-zope", specifier = "==1.0.5" }, + { name = "types-pymysql", specifier = "==1.1.0.20240524" }, + { name = "types-pyyaml", specifier = "==6.0.12.20240917" }, +] +unit = [ + { name = "coverage", specifier = "==7.6.1" }, + { name = "docker", specifier = "==7.1.0" }, + { name = "hypothesis", specifier = "==6.112.4" }, + { name = "mock", specifier = "==5.1.0" }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "service-identity" +version = "24.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cryptography" }, + { name = "pyasn1" }, + { name = "pyasn1-modules" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/d2/2ac20fd05f1b6fce31986536da4caeac51ed2e1bb25d4a7d73ca4eccdfab/service_identity-24.1.0.tar.gz", hash = "sha256:6829c9d62fb832c2e1c435629b0a8c476e1929881f28bee4d20bc24161009221", size = 40183 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/92/44669afe6354a7bed9968013862118c401690d8b5a805bab75ac1764845f/service_identity-24.1.0-py3-none-any.whl", hash = "sha256:a28caf8130c8a5c1c7a6f5293faaf239bbfb7751e4862436920ee6f2616f568a", size = 12037 }, +] + +[[package]] +name = "setuptools" +version = "75.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/37/b31be7e4b9f13b59cde9dcaeff112d401d49e0dc5b37ed4a9fc8fb12f409/setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec", size = 1350308 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/2d/90165d51ecd38f9a02c6832198c13a4e48652485e2ccf863ebb942c531b6/setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8", size = 1249825 }, +] + +[[package]] +name = "six" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575 }, +] + +[[package]] +name = "tubes" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, + { name = "twisted" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/99/7ffc9be5b3917cc91f388e5f74def363534d6537610bb1baec1a966b8cac/Tubes-0.2.1.tar.gz", hash = "sha256:59b9197f2fa6f7fc6bc3281de55797adc729bb75c9c613b4f6d6c41599f0d78b", size = 47699 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/7a/0a4a6ab6a74ed4cc8c025d749006fb6aa1d83995b6ab602a5ce0019148a4/Tubes-0.2.1-py2.py3-none-any.whl", hash = "sha256:28f60dc0be0555a953595f8f1a96a81e96ea6833df7677d77fbe70782605e5b5", size = 59495 }, +] + +[[package]] +name = "twisted" +version = "24.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "automat" }, + { name = "constantly" }, + { name = "hyperlink" }, + { name = "incremental" }, + { name = "typing-extensions" }, + { name = "zope-interface" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/bf/f30eb89bcd14a21a36b4cd3d96658432d4c590af3c24bbe08ea77fa7bbbb/twisted-24.7.0.tar.gz", hash = "sha256:5a60147f044187a127ec7da96d170d49bcce50c6fd36f594e60f4587eff4d394", size = 3516844 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d2/7b3e869b983fbf29d770fc2893f8df7c1739c6ff03a2b926b4fc43e4263e/twisted-24.7.0-py3-none-any.whl", hash = "sha256:734832ef98108136e222b5230075b1079dad8a3fc5637319615619a7725b0c81", size = 3181556 }, +] + +[[package]] +name = "types-pymysql" +version = "1.1.0.20240524" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/a3/a24412f339ba8b58ad4e436e8ad9e9520add29b25a07c8a4001d84414c16/types-PyMySQL-1.1.0.20240524.tar.gz", hash = "sha256:93058fef2077c407e29bdcd1a7dfbbf06a59324a5440df30dd002f572199ac17", size = 14119 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/15/3c2a0596d077088447e05dfaa07d66e630afd897ee4ee8131b1e5329418e/types_PyMySQL-1.1.0.20240524-py3-none-any.whl", hash = "sha256:8be5be228bf6376f9055ec03bec0dfa6f1a84163f9a89305db446f0b31f87be3", size = 14387 }, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20241003" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/f8/f6ee4c803a7beccffee21bb29a71573b39f7037c224843eff53e5308c16e/types-python-dateutil-2.9.0.20241003.tar.gz", hash = "sha256:58cb85449b2a56d6684e41aeefb4c4280631246a0da1a719bdbe6f3fb0317446", size = 9210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/d6/ba5f61958f358028f2e2ba1b8e225b8e263053bd57d3a79e2d2db64c807b/types_python_dateutil-2.9.0.20241003-py3-none-any.whl", hash = "sha256:250e1d8e80e7bbc3a6c99b907762711d1a1cdd00e978ad39cb5940f6f0a87f3d", size = 9693 }, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20240917" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/7d/a95df0a11f95c8f48d7683f03e4aed1a2c0fc73e9de15cca4d38034bea1a/types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587", size = 12381 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/2c/c1d81d680997d24b0542aa336f0a65bd7835e5224b7670f33a7d617da379/types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570", size = 15264 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, +] + +[[package]] +name = "werkzeug" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/e2/6dbcaab07560909ff8f654d3a2e5a60552d937c909455211b1b36d7101dc/werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306", size = 803966 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/84/997bbf7c2bf2dc3f09565c6d0b4959fefe5355c18c4096cfd26d83e0785b/werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c", size = 227554 }, +] + +[[package]] +name = "zope-event" +version = "5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/c2/427f1867bb96555d1d34342f1dd97f8c420966ab564d58d18469a1db8736/zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd", size = 17350 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/42/f8dbc2b9ad59e927940325a22d6d3931d630c3644dae7e2369ef5d9ba230/zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", size = 6824 }, +] + +[[package]] +name = "zope-interface" +version = "7.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c8/83/7de03efae7fc9a4ec64301d86e29a324f32fe395022e3a5b1a79e376668e/zope.interface-7.0.3.tar.gz", hash = "sha256:cd2690d4b08ec9eaf47a85914fe513062b20da78d10d6d789a792c0b20307fb1", size = 252504 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/be/6640eb57c4b84a471d691082d0207434d1524e428fba1231c335a4cad446/zope.interface-7.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3fcdc76d0cde1c09c37b7c6b0f8beba2d857d8417b055d4f47df9c34ec518bdd", size = 208567 }, + { url = "https://files.pythonhosted.org/packages/2d/45/a891ee78ba5ef5b5437394f8c2c56c094ed1ab41a80ef7afe50191dce3d2/zope.interface-7.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3d4b91821305c8d8f6e6207639abcbdaf186db682e521af7855d0bea3047c8ca", size = 208972 }, + { url = "https://files.pythonhosted.org/packages/14/44/d12683e823ced271ae2ca3976f16066634911e02540a9559b09444a4b2d3/zope.interface-7.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35062d93bc49bd9b191331c897a96155ffdad10744ab812485b6bad5b588d7e4", size = 266389 }, + { url = "https://files.pythonhosted.org/packages/db/35/c83308ac84552c2242d5d59488dbea9a91c64765e117a71c566ddf896e31/zope.interface-7.0.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c96b3e6b0d4f6ddfec4e947130ec30bd2c7b19db6aa633777e46c8eecf1d6afd", size = 261112 }, + { url = "https://files.pythonhosted.org/packages/3d/ed/0ac414f9373d742d2eb2f436b595ed281031780a405621a4d906096092ea/zope.interface-7.0.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e0c151a6c204f3830237c59ee4770cc346868a7a1af6925e5e38650141a7f05", size = 267044 }, + { url = "https://files.pythonhosted.org/packages/38/92/e9fe2a8cb53cffc73f923da84e50e0ee3a8d38a64bef6965428d5b5c4910/zope.interface-7.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:3de1d553ce72868b77a7e9d598c9bff6d3816ad2b4cc81c04f9d8914603814f3", size = 212064 }, + { url = "https://files.pythonhosted.org/packages/2b/6f/059521297028f3037f2b19a711be845983151acbdeda1031749a91d07048/zope.interface-7.0.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab985c566a99cc5f73bc2741d93f1ed24a2cc9da3890144d37b9582965aff996", size = 266369 }, + { url = "https://files.pythonhosted.org/packages/ce/bb/51ab7785b2ad3123d5eb85b548f98fe2c0809c6bd452e677b1aca71c3c79/zope.interface-7.0.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d976fa7b5faf5396eb18ce6c132c98e05504b52b60784e3401f4ef0b2e66709b", size = 261119 }, + { url = "https://files.pythonhosted.org/packages/be/56/6a57ef0699b857b33a407162f29eade4062596870d335f53e914bb98fd0e/zope.interface-7.0.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a207c6b2c58def5011768140861a73f5240f4f39800625072ba84e76c9da0b", size = 267059 }, +] + +[[package]] +name = "zope-schema" +version = "7.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, + { name = "zope-event" }, + { name = "zope-interface" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/67/91585500260386df145b78532de588b41470d416bc6fa1cdc1b0f7a34e68/zope.schema-7.0.1.tar.gz", hash = "sha256:ead4dbcb03354d4e410c9a3b904451eb44d90254751b1cbdedf4a61aede9fbb9", size = 108737 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/97/517e3f5f4258bcc6491c806474a26e1a799bb99ec44ed95d22010caabc25/zope.schema-7.0.1-py3-none-any.whl", hash = "sha256:cf006c678793b00e0075ad54d55281c8785ea21e5bc1f5ec0584787719c2aab2", size = 85870 }, +]