diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..05c25f68 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + actions: + patterns: + - "*" diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml new file mode 100644 index 00000000..17ee9b87 --- /dev/null +++ b/.github/workflows/build-deploy.yml @@ -0,0 +1,224 @@ +name: Build and Deploy CKAN (staging) + +on: + push: + branches: [master, ckan211-prod-deploy-pr] + pull_request: + # Runs the test job only (build/deploy are gated off pull_request below), + # so PRs get a visible test status check without deploying. + branches: [master, ckan211-prod-deploy-pr] + workflow_dispatch: + inputs: + image_tag: + description: "Image tag to deploy (e.g., sha-abc1234). Leave blank to build from the workflow's ref." + required: false + type: string + +concurrency: + group: deploy-staging-${{ github.ref }} + cancel-in-progress: true + +env: + ACR_NAME: adracr + IMAGE_NAME: ckan + NAMESPACE: adr-s + URL: https://dev.adr.fjelltopp.org + +jobs: + test: + name: Extension tests + # Skip on a pure redeploy (workflow_dispatch with an existing image_tag); + # that image was already tested when it was built. + if: github.event_name != 'workflow_dispatch' || inputs.image_tag == '' + runs-on: ubuntu-latest + timeout-minutes: 45 + env: + # Fresh runner DB — no need to restart the db container during testsetup. + SKIP_DB_RESTART: "True" + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + submodules: recursive + + # The dominant cost is `pipenv sync --dev` at bootstrap (CKAN + ~19 + # extensions), which lands in the bind-mounted .adxvenv. Caching it on + # Pipfile.lock makes warm runs a near no-op. .adxvenv is gitignored and + # written by root in-container, so there are no ownership issues here. + # Restore + save are split (rather than the combined cache action) so the + # venv is saved even when the test step fails — otherwise the expensive + # `pipenv sync` is repeated cold on every triage run. Saved at job end. + - name: Restore Python venv + id: venv-cache + uses: actions/cache/restore@v4 + with: + path: .adxvenv + key: adxvenv-${{ runner.os }}-${{ hashFiles('Pipfile.lock') }} + restore-keys: | + adxvenv-${{ runner.os }}- + + # Second cost: the entrypoint runs `yarn install` + build for the unaids + # React app on every boot. Cache its node_modules on the React yarn.lock. + - name: Restore unaids React node_modules + id: react-cache + uses: actions/cache/restore@v4 + with: + path: submodules/ckanext-unaids/ckanext/unaids/react/node_modules + key: react-nm-${{ runner.os }}-${{ hashFiles('submodules/ckanext-unaids/ckanext/unaids/react/yarn.lock') }} + restore-keys: | + react-nm-${{ runner.os }}- + + - name: Prepare .env + run: cp dev.env .env + + - name: Build images and start the dev stack + run: | + ./adx build + ./adx up + + - name: Wait for CKAN bootstrap + run: | + for i in $(seq 1 90); do + if docker logs ckan 2>&1 | grep -q 'CKAN bootstrapping finished, environment ready'; then + echo "CKAN ready after ${i} checks" + exit 0 + fi + echo "Waiting for CKAN bootstrap (${i}/90)…" + sleep 10 + done + echo "::error::CKAN did not finish bootstrapping in time" + docker logs ckan + exit 1 + + - name: Create test databases + run: ./adx testsetup + + - name: Run extension tests + run: | + # Run every suite (don't fail-fast) so one run shows the full picture, + # then fail at the end if any suite failed. + failed=() + for ext in unaids validation scheming dhis2harvester emailasusername; do + echo "::group::ckanext-${ext}" + if ./adx test "${ext}" --no-interaction; then + echo "ckanext-${ext}: PASS" + else + echo "ckanext-${ext}: FAIL" + failed+=("${ext}") + fi + echo "::endgroup::" + done + echo "--- summary ---" + if [ ${#failed[@]} -ne 0 ]; then + echo "::error::Failing suites: ${failed[*]}" + exit 1 + fi + echo "All extension suites passed" + + - name: Dump CKAN logs on failure + if: failure() + run: docker logs ckan || true + + - name: Save Python venv + if: always() && steps.venv-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: .adxvenv + key: ${{ steps.venv-cache.outputs.cache-primary-key }} + + - name: Save unaids React node_modules + if: always() && steps.react-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: submodules/ckanext-unaids/ckanext/unaids/react/node_modules + key: ${{ steps.react-cache.outputs.cache-primary-key }} + + build: + needs: test + # Run when tests pass (success) or were skipped (redeploy), and we're + # actually building (not a redeploy of an existing tag). + if: >- + always() + && github.event_name != 'pull_request' + && needs.test.result != 'failure' + && needs.test.result != 'cancelled' + && (github.event_name != 'workflow_dispatch' || inputs.image_tag == '') + runs-on: ubuntu-latest + outputs: + image_tag: ${{ steps.meta.outputs.version }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + submodules: recursive + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker meta + id: meta + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.6.1 + with: + images: ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }} + tags: | + type=sha,prefix=sha-,format=short + + - name: Login to ACR + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + with: + registry: ${{ env.ACR_NAME }}.azurecr.io + username: ${{ secrets.ACR_USERNAME }} + password: ${{ secrets.ACR_PASSWORD }} + + - name: Build and push + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.10.0 + with: + context: . + file: deploy/Dockerfile.prod + push: true + cache-from: type=gha + cache-to: type=gha,mode=max + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + deploy: + needs: [test, build] + # Never deploy if tests failed/cancelled. Otherwise deploy when the image + # was built (success) or already exists (build skipped on redeploy). + if: >- + always() + && github.event_name != 'pull_request' + && needs.test.result != 'failure' + && needs.test.result != 'cancelled' + && (needs.build.result == 'success' || needs.build.result == 'skipped') + runs-on: ubuntu-latest + environment: + name: staging + url: ${{ env.URL }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Resolve image tag + id: params + run: | + if [[ -n "${{ inputs.image_tag }}" ]]; then + echo "image_tag=${{ inputs.image_tag }}" >> $GITHUB_OUTPUT + else + echo "image_tag=${{ needs.build.outputs.image_tag }}" >> $GITHUB_OUTPUT + fi + + - name: Setup kubeconfig + run: | + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG_BASE64 }}" | base64 -d > ~/.kube/config + chmod 600 ~/.kube/config + + - name: Deploy to AKS + run: | + kubectl create configmap ckan-env-config \ + --from-file=env.ini=deploy/staging.ini \ + -n ${{ env.NAMESPACE }} \ + --dry-run=client -o yaml | kubectl apply -f - + + kubectl set image deployment/ckan \ + ckan=${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.params.outputs.image_tag }} \ + -n ${{ env.NAMESPACE }} + kubectl rollout status deployment/ckan -n ${{ env.NAMESPACE }} --timeout=10m diff --git a/.github/workflows/build_ckan.yml b/.github/workflows/build_ckan.yml deleted file mode 100644 index a3396678..00000000 --- a/.github/workflows/build_ckan.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Build and publish CKAN image - -on: - push: - paths: - - 'ckan/**' - - '.github/workflows/build_ckan.yml' - -jobs: - build-and-publish: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2.5.0 # Checking out the repo - - - name: Build and publish CKAN base image - uses: VaultVulp/gp-docker-action@1.5.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} # Provide GITHUB_TOKEN to login into the GitHub Packages - image-name: ckan_base - image-tag: ${{ github.ref_name }} - build-context: ckan/ - dockerfile: ckan/Dockerfile diff --git a/.github/workflows/reindex-solr.yml b/.github/workflows/reindex-solr.yml new file mode 100644 index 00000000..e95317b1 --- /dev/null +++ b/.github/workflows/reindex-solr.yml @@ -0,0 +1,38 @@ +name: Rebuild Solr Index + +on: + workflow_dispatch: + inputs: + environment: + description: "Target environment" + required: true + type: choice + options: + - staging + - production + +jobs: + reindex: + runs-on: ubuntu-latest + environment: + name: ${{ inputs.environment }} + steps: + - name: Set namespace + id: params + run: | + if [[ "${{ inputs.environment }}" == "production" ]]; then + echo "namespace=adr-p" >> $GITHUB_OUTPUT + else + echo "namespace=adr-s" >> $GITHUB_OUTPUT + fi + + - name: Setup kubeconfig + run: | + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG_BASE64 }}" | base64 -d > ~/.kube/config + chmod 600 ~/.kube/config + + - name: Rebuild Solr index + run: | + kubectl exec deployment/ckan -n ${{ steps.params.outputs.namespace }} -- \ + ckan -c /tmp/production.ini search-index rebuild -i -q diff --git a/.github/workflows/release-deploy.yml b/.github/workflows/release-deploy.yml new file mode 100644 index 00000000..9d45bf6d --- /dev/null +++ b/.github/workflows/release-deploy.yml @@ -0,0 +1,94 @@ +name: Release Deploy CKAN (production) + +# Triggered on tag push (v*). Creating a GitHub Release publishes the tag, +# so the same workflow handles both `git push --tags` and the Release UI flow. +# +# This workflow does NOT build. It promotes an existing staging image +# (tagged `sha-` by build-deploy.yml) to a `v*` tag in ACR, +# then rolls the `adr-p` deployment over to that image. + +on: + push: + tags: ["v*"] + workflow_dispatch: + inputs: + version: + description: "Version to deploy (e.g., v1.0.0). Must correspond to an existing tag." + required: true + type: string + +concurrency: + group: deploy-production-${{ github.ref }} + cancel-in-progress: true + +env: + ACR_NAME: adracr + IMAGE_NAME: ckan + NAMESPACE: adr-p + URL: https://adr-p.fjelltopp.org + +jobs: + promote-and-deploy: + runs-on: ubuntu-latest + environment: + name: production + url: ${{ env.URL }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + + - name: Resolve version and source sha + id: params + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + VERSION="${{ inputs.version }}" + git fetch origin tag "$VERSION" --no-tags + SHA=$(git rev-list -n 1 "$VERSION") + else + VERSION="${GITHUB_REF_NAME}" + SHA="${GITHUB_SHA}" + fi + SHORT_SHA=$(echo "$SHA" | cut -c1-7) + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "source_tag=sha-$SHORT_SHA" >> $GITHUB_OUTPUT + + - name: Login to ACR + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + with: + registry: ${{ env.ACR_NAME }}.azurecr.io + username: ${{ secrets.ACR_USERNAME }} + password: ${{ secrets.ACR_PASSWORD }} + + - name: Verify source image exists in ACR + run: | + docker buildx imagetools inspect \ + ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.params.outputs.source_tag }} \ + > /dev/null || { \ + echo "::error::Source image ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.params.outputs.source_tag }} not found. Push the tagged commit to master or ckan211-prod-deploy-pr first so staging CI builds the sha image."; \ + exit 1; \ + } + + - name: Promote image (sha → version) + run: | + docker buildx imagetools create \ + --tag ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.params.outputs.version }} \ + ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.params.outputs.source_tag }} + + - name: Setup kubeconfig + run: | + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG_BASE64 }}" | base64 -d > ~/.kube/config + chmod 600 ~/.kube/config + + - name: Deploy to AKS + run: | + kubectl create configmap ckan-env-config \ + --from-file=env.ini=deploy/production.ini \ + -n ${{ env.NAMESPACE }} \ + --dry-run=client -o yaml | kubectl apply -f - + + kubectl set image deployment/ckan \ + ckan=${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.params.outputs.version }} \ + -n ${{ env.NAMESPACE }} + kubectl rollout status deployment/ckan -n ${{ env.NAMESPACE }} --timeout=10m diff --git a/.gitignore b/.gitignore index a7bb2033..c4bc569c 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,6 @@ htmlcov/ .coverage .coverage.* .cache -nosetests.xml coverage.xml *.cover .hypothesis/ diff --git a/.gitmodules b/.gitmodules index 3b2a436f..6adc91a4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -59,3 +59,5 @@ [submodule "submodules/ckanext-geoview"] path = submodules/ckanext-geoview url = https://github.com/ckan/ckanext-geoview.git +[submodule "submodules/ckanext-unaids"] + url = git@github.com:/fjelltopp/ckanext-unaids.git diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 97c8d914..00000000 --- a/Jenkinsfile +++ /dev/null @@ -1,56 +0,0 @@ -def extensions = [ - "dhis2harvester", - "scheming", - "validation", - "unaids", - "emailasusername" -] - -pipeline { - agent any - triggers { - cron('H 23 * * 1-5') - } - stages { - stage('ADX setup tests') { - steps { - slackSend (color: '#FFFF00', message: "STARTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") - dir('adx_develop') { - checkout scm - } - sshagent (['local-github-ssh']) { - echo 'Get ECR login' - sh """aws ecr get-login-password --region eu-west-1 | docker login --username AWS --password-stdin 254010146609.dkr.ecr.eu-west-1.amazonaws.com""" - echo 'Starting ADX setup' - sh """cd adx_develop && ./ci_setup.sh""" - } - } - } - stage('ADX run tests') { - steps { - script { - for (test in extensions) { - stage(test) { - sh """cd adx_develop && ./ci_test.sh ${test}""" - } - } - } - } - } - - } - post { - always { - sh """cd $WORKSPACE/adx_develop &&docker-compose down --rmi all -v --remove-orphans""" - cleanWs() - } - success { - slackSend (color: '#00FF00', message: "SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") - } - - failure { - slackSend (color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") - } - - } -} diff --git a/Pipfile b/Pipfile index 7233f21b..f6ca46a9 100644 --- a/Pipfile +++ b/Pipfile @@ -89,11 +89,14 @@ jsonschema = "==3.2.0" charset-normalizer = "==3.3.2" python-dotenv = "==1.0.0" python-jose = "==3.3.0" -setuptools = ">=65.0.0" +setuptools = ">=65.0.0,<80.0.0" raven = "==6.10.0" tableschema = "==1.20.2" -frictionless = ">=5.0.0,<6.0.0" -markupsafe = ">=2.0.1" +# Pinned to match ckanext-validation/-unaids requirements.txt (the version +# those extensions are built and tested against). Newer 5.x dropped +# frictionless.Resource.__create__, which their test suites still patch. +frictionless = "==5.13.1" +markupsafe = "==2.1.5" [dev-packages] # CKAN 2.11.4 dev dependencies @@ -120,6 +123,10 @@ pytest-rerunfailures = "==15.0" pytest-split = "==0.10.0" pytest-retry = "==1.7.0" pytest-mock = "*" +mock = "*" +# Pinned to match ckanext-validation dev-requirements.txt; pyfakefs 5.0+ +# removed the CamelCase API (CreateFile) its TestFiles suite uses. +pyfakefs = "==4.6.*" coverage = "==7.7.1" junitparser = "==3.2.0" junit2html = "==31.0.2" diff --git a/Pipfile.lock b/Pipfile.lock index 77b74193..63f05471 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d8022f91758c793143e47db9f4fbb299032f3019e25750a021496945868f7172" + "sha256": "e0480c0ef39380e1673551bf3abbffc6ac41189380ea5c91507976d9e05324fb" }, "pipfile-spec": 6, "requires": { @@ -25,6 +25,14 @@ "markers": "python_version >= '3.8'", "version": "==1.13.2" }, + "annotated-doc": { + "hashes": [ + "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", + "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4" + ], + "markers": "python_version >= '3.8'", + "version": "==0.0.4" + }, "annotated-types": { "hashes": [ "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", @@ -33,6 +41,45 @@ "markers": "python_version >= '3.8'", "version": "==0.7.0" }, + "ast-serialize": { + "hashes": [ + "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", + "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", + "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", + "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", + "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", + "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", + "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", + "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", + "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", + "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", + "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", + "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", + "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", + "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", + "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", + "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", + "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", + "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", + "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", + "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", + "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", + "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", + "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", + "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", + "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", + "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", + "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", + "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", + "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", + "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", + "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", + "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", + "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6" + ], + "markers": "python_version >= '3.7'", + "version": "==0.5.0" + }, "async-timeout": { "hashes": [ "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f", @@ -44,11 +91,11 @@ }, "attrs": { "hashes": [ - "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", - "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373" + "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", + "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32" ], "markers": "python_version >= '3.9'", - "version": "==25.4.0" + "version": "==26.1.0" }, "babel": { "hashes": [ @@ -79,19 +126,19 @@ }, "boto3": { "hashes": [ - "sha256:8c2537156f5ccd72bbbfe4fc27a8a80bf3e4f80523f306417f3fb6023d13edda", - "sha256:8c78169ef47dc29863ebb11ba99134b1b418d3dfdd836419830f22552f8afe43" + "sha256:33138883e984eb1937d1553da699182c8ad2099138091e885b65c9accbccea16", + "sha256:7b62ce5c0a51428d692aa4f2adc9dc2a4a4c2989bf65a0a12834eeffa99b0b84" ], - "markers": "python_version >= '3.9'", - "version": "==1.42.42" + "markers": "python_version >= '3.10'", + "version": "==1.43.18" }, "botocore": { "hashes": [ - "sha256:1c9df5fc31e9073a9aa956271c4007d72f5d342cafca5f4154ea099bc6f83085", - "sha256:cb75639f5ba7bf73b83ac18bcd87f07b7f484f302748da974dad2801a83a1d60" + "sha256:dc8c105351b49688c667065cd5a45fc5b9db982657cefc9e3fbfb9417a55c7df", + "sha256:e2610fce16df9f89deab5f3c163430a814e6804034eb95bef8957c8db60b7dbc" ], - "markers": "python_version >= '3.9'", - "version": "==1.42.42" + "markers": "python_version >= '3.10'", + "version": "==1.43.18" }, "cached-property": { "hashes": [ @@ -103,20 +150,20 @@ }, "cachelib": { "hashes": [ - "sha256:209d8996e3c57595bee274ff97116d1d73c4980b2fd9a34c7846cd07fd2e1a48", - "sha256:8c8019e53b6302967d4e8329a504acf75e7bc46130291d30188a6e4e58162516" + "sha256:4671000b032baa8fac47ad19850f4f522785cee764b4e04c5cfe8955a18d67de", + "sha256:73fedcadd0ba818fb2bb9f3c7cd5fcc2a71e86286f1842f55f28d500faee17f1" ], "markers": "python_version >= '3.8'", - "version": "==0.13.0" + "version": "==0.14.0" }, "certifi": { "hashes": [ - "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", - "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120" + "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", + "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2026.1.4" + "version": "==2026.5.20" }, "cffi": { "hashes": [ @@ -175,11 +222,45 @@ }, "chardet": { "hashes": [ - "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", - "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970" + "sha256:0ac3bf11c645734a1701a3804e43eabd98851838192267d08c353a834ab79fea", + "sha256:1173b74051570cf08099d7429d92e4882d375ad4217f92a6e5240ccfb26f231e", + "sha256:23163921dccf3103ce59540b0443c106d2c0a0ff2e0503e05196f5e6fdea453f", + "sha256:25a862cddc6a9ac07023e808aedd297115345fbaabc2690479481ddc0f980e09", + "sha256:27cc23da03630cdecc9aa81a895aa86629c211f995cd57651f0fbc280717bf93", + "sha256:29af5999f654e8729d251f1724a62b538b1262d9292cccaefddf8a02aae1ef6a", + "sha256:365135eaf37ba65a828f8e668eb0a8c38c479dcbec724dc25f4dfd781049c357", + "sha256:3990fffcc6a6045f2234ab72752ad037e3b2d48c72037f244d42738db397eb75", + "sha256:457f619882ba66327d4d8d14c6c342269bdb1e4e1c38e8117df941d14d351b04", + "sha256:4b2799bd58e7245cfa8d4ab2e8ad1d76a5c3a5b1f32318eb6acca4c69a3e7101", + "sha256:4c3da294de1a681097848ab58bd3f2771a674f8039d2d87a5538b28856b815e9", + "sha256:4fbff1907925b0c5a1064cffb5e040cd5e338585c9c552625f30de6bc2f3107a", + "sha256:5d2879598bc220689e8ce509fe9c3f37ad2fca53a36be9c9bd91abdd91dd364f", + "sha256:626f00299ad62dfe937058a09572beed442ccc7b58f87aa667949b20fd3db235", + "sha256:6e3bd9f936e04bae89c254262af08d9e5b98f805175ba1e29d454e6cba3107b7", + "sha256:7005c88da26fd95d8abb8acbe6281d833e9a9181b03cf49b4546c4555389bd97", + "sha256:75d3c65cc16bddf40b8da1fd25ba84fca5f8070f2b14e86083653c1c85aee971", + "sha256:93c45e116dd51b66226a53ade3f9f635e870de5399b90e00ce45dcc311093bf4", + "sha256:9a4904dd5f071b7a7d7f50b4a67a86db3c902d243bf31708f1d5cde2f68239cb", + "sha256:9acd9988a93e09390f3cd231201ea7166c415eb8da1b735928990ffc05cb9fbb", + "sha256:9f3504c139a2ad544077dd2d9e412cd08b01786843d76997cd43bb6de311723c", + "sha256:a9e4486df251b8962e86ea9f139ca235aa6e0542a00f7844c9a04160afb99aa9", + "sha256:acc46d1b8b7d5783216afe15db56d1c179b9a40e5a1558bc13164c4fd20674c4", + "sha256:b95c934b9ad59e2ba8abb9be49df70d3ad1b0d95d864b9fdb7588d4fa8bd921c", + "sha256:bba8bea1b28d927b3e99e47deafe53658d34497c0a891d95ff1ba8ff6663f01c", + "sha256:bfc134b70c846c21ead8e43ada3ae1a805fff732f6922f8abcf2ff27b8f6493d", + "sha256:c0c79b13c9908ac7dfe0a74116ebc9a0f28b2319d23c32f3dfcdfbe1279c7eaf", + "sha256:c7116b0452994734ccff35e154b44240090eb0f4f74b9106292668133557c175", + "sha256:c77867f0c1cb8bd819502249fcdc500364aedb07881e11b743726fa2148e7b6e", + "sha256:cc1d4eb92a4ec1c2df3b490836ffa46922e599d34ce0bb75cf41fd2bf6303d56", + "sha256:ccc1f83ab4bcfb901cf39e0c4ba6bc6e726fc6264735f10e24ceb5cb47387578", + "sha256:cf1efeaf65a6ef2f5b9cc3a1df6f08ba2831b369ccaa4c7018eaf90aa757bb11", + "sha256:cfb54563fe5f130da17c44c6a4e2e8052ba628e5ab4eab7ef8190f736f0f8f72", + "sha256:d892d3dcd652fdef53e3d6327d39b17c0df40a899dfc919abaeb64c974497531", + "sha256:dc50f28bad067393cce0af9091052c3b8df7a23115afd8ba7b2e0947f0cef1f8", + "sha256:e1b98790c284ff813f18f7cf7de5f05ea2435a080030c7f1a8318f3a4f80b131" ], - "markers": "python_version >= '3.7'", - "version": "==5.2.0" + "markers": "python_version >= '3.10'", + "version": "==7.4.3" }, "charset-normalizer": { "hashes": [ @@ -429,11 +510,11 @@ }, "ecdsa": { "hashes": [ - "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", - "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61" + "sha256:62635b0ac1ca2e027f82122b5b81cb706edc38cd91c63dda28e4f3455a2bf930", + "sha256:840f5dc5e375c68f36c1a7a5b9caad28f95daa65185c9253c0c08dd952bb7399" ], "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==0.19.1" + "version": "==0.19.2" }, "elementpath": { "hashes": [ @@ -505,12 +586,11 @@ }, "frictionless": { "hashes": [ - "sha256:6d22dc39126fb65cf7853b6db8d409637b056c77bc709abb344bbd50ca1bdddf", - "sha256:ab4bfe55f5d831c15a16760685036fdd85c9026d2d7c633b6672b1b04f8c48c6" + "sha256:78ee7c4ea784e85ef7117f17165baf4f2728790ba9406693df8370e812726c4a", + "sha256:8e5afc7c08364d4563797944920e044a8d37970c767c0c6920ac804a05c5f1a9" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==5.16.1" + "version": "==5.13.1" }, "giftless-client": { "hashes": [ @@ -522,62 +602,88 @@ }, "greenlet": { "hashes": [ - "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", - "sha256:04bee4775f40ecefcdaa9d115ab44736cd4b9c5fba733575bfe9379419582e13", - "sha256:070472cd156f0656f86f92e954591644e158fd65aa415ffbe2d44ca77656a8f5", - "sha256:09f51496a0bfbaa9d74d36a52d2580d1ef5ed4fdfcff0a73730abfbbbe1403dd", - "sha256:1108b61b06b5224656121c3c8ee8876161c491cbe74e5c519e0634c837cf93d5", - "sha256:12184c61e5d64268a160226fb4818af4df02cfead8379d7f8b99a56c3a54ff3e", - "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336", - "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba", - "sha256:27289986f4e5b0edec7b5a91063c109f0276abb09a7e9bdab08437525977c946", - "sha256:2f080e028001c5273e0b42690eaf359aeef9cb1389da0f171ea51a5dc3c7608d", - "sha256:301860987846c24cb8964bdec0e31a96ad4a2a801b41b4ef40963c1b44f33451", - "sha256:32e4ca9777c5addcbf42ff3915d99030d8e00173a56f80001fb3875998fe410b", - "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", - "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", - "sha256:39eda9ba259cc9801da05351eaa8576e9aa83eb9411e8f0c299e05d712a210f2", - "sha256:3a300354f27dd86bae5fbf7002e6dd2b3255cd372e9242c933faf5e859b703fe", - "sha256:3e0f3878ca3a3ff63ab4ea478585942b53df66ddde327b59ecb191b19dbbd62d", - "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", - "sha256:41848f3230b58c08bb43dee542e74a2a2e34d3c59dc3076cec9151aeeedcae98", - "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149", - "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", - "sha256:4b9721549a95db96689458a1e0ae32412ca18776ed004463df3a9299c1b257ab", - "sha256:50e1457f4fed12a50e427988a07f0f9df53cf0ee8da23fab16e6732c2ec909d4", - "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", - "sha256:5fd23b9bc6d37b563211c6abbb1b3cab27db385a4449af5c32e932f93017080c", - "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", - "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", - "sha256:67ea3fc73c8cd92f42467a72b75e8f05ed51a0e9b1d15398c913416f2dafd49f", - "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", - "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", - "sha256:7932f5f57609b6a3b82cc11877709aa7a98e3308983ed93552a1c377069b20c8", - "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", - "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", - "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1", - "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975", - "sha256:80aa4d79eb5564f2e0a6144fcc744b5a37c56c4a92d60920720e99210d88db0f", - "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", - "sha256:96aff77af063b607f2489473484e39a0bbae730f2ea90c9e5606c9b73c44174a", - "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", - "sha256:b066e8b50e28b503f604fa538adc764a638b38cf8e81e025011d26e8a627fa79", - "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", - "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", - "sha256:bfb2d1763d777de5ee495c85309460f6fd8146e50ec9d0ae0183dbf6f0a829d1", - "sha256:c620051669fd04ac6b60ebc70478210119c56e2d5d5df848baec4312e260e4ca", - "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", - "sha256:cb0feb07fe6e6a74615ee62a880007d976cf739b6669cce95daa7373d4fc69c5", - "sha256:cc98b9c4e4870fa983436afa999d4eb16b12872fab7071423d5262fa7120d57a", - "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36", - "sha256:da19609432f353fed186cc1b85e9440db93d489f198b4bdf42ae19cc9d9ac9b4", - "sha256:e0093bd1a06d899892427217f0ff2a3c8f306182b8c754336d32e2d587c131b4", - "sha256:e2e7e882f83149f0a71ac822ebf156d902e7a5d22c9045e3e0d1daf59cee2cc9", - "sha256:e84b51cbebf9ae573b5fbd15df88887815e3253fc000a7d0ff95170e8f7e9729", - "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53" + "sha256:001775efe7b8e758861294c7a27c28af87f3f3f1c20468a2bc618c45b346c061", + "sha256:00929c98ec525fd9bf075875d8c5f6a983a90906cdf78a66e6de2d8e466c2a19", + "sha256:017a544f0385d441e88714160d089d6900ef46c9eff9d99b6715a5ef2d127747", + "sha256:089fff7a6ce8d9316d1f65ebc00273a56be258c1725b32b94de90a3a979557e1", + "sha256:1072b4f9edcc1e192d9283a66a3e68d6b84c561de33a83d7858beb9ba1effe10", + "sha256:10a9a1c0bfbc93d41156ffcb90c75fbc05544054faf15dcc1fdf9765f8b607f0", + "sha256:110a1ca7b49b014b097f6078272c3f4ed31af45b254de5228b79adba879f6af9", + "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64", + "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659", + "sha256:1ffdb3c0bb002c99cd8f298957e046c3dbf6006b5b7cdf11a4e19194624a0a0a", + "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541", + "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e", + "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c", + "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d", + "sha256:3c8bb982ad117d29478ef8f5533e97df21f1e2befd17a299257b0c96d1371c0b", + "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986", + "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78", + "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2", + "sha256:5028648bf2253ec4745add746129d3904121fa7fe871a76bed23c5720573ce0a", + "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc", + "sha256:51518ff74664078fc51bffcc6fc529b0df5ae58da192691cee765d45ce944a2b", + "sha256:540dae7b956209af4d70a3be35927b4055f617763771e5e84a5255bea934d2f5", + "sha256:5a56aeb7d5d9cc4b3a735efb5095bd4b4f6f0e4f93e5ca876d0e2315137b7829", + "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea", + "sha256:67821bb03e4e98664490edb787ff6af501194c29bbee0f5c1dfdcf1dc3d9d436", + "sha256:6c09df69dc1712d131332054a858a3e5cca400967fa3a672e2324fbb0971448c", + "sha256:6ebeb75c81211f5c702576cf81f315e77e23cfdb2c7c6fcb9dd143e6de35c360", + "sha256:73f78f9b9f0a5c06e5c946ba1e8e36f5114923b6be109ee618c54f079c3ea14f", + "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244", + "sha256:7715a5a2c3378ba602c3a440558261e13a820bb53a82693aacd7b7f6d964e283", + "sha256:7b5f5fae05b8ac6d176a61b60c394a8cbdc2b5b91b81793066e68745cf165e54", + "sha256:7eacb17a9d41538a2bc4912eba5ef13823c83cb69e4d141d0813debe7163187f", + "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2", + "sha256:80eb4b04dadc4e67df3fae179a32c4706a3f495bc7f22fc8a81115d5f5512188", + "sha256:88e300d136eac057b2397aa1cfd7328b4c87c7eb66a09c7bc6a1292234db474e", + "sha256:89101bfd5011e069be974903cb3a4e4523845e4ece2d62dcd8d358933c0ef249", + "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3", + "sha256:8a271fcd66c74615cda6a964fda3f304267a12e50a084472218a39bb0376f563", + "sha256:8d8a23250ea3ec7b36de8fa4b541e9e2db3ee82915cc060ab0631609ad8b28de", + "sha256:92fd6d44ac5e5a887c8a5dc4a8ba0ba908527c31c12f78c6bc7dcfe8aab279f6", + "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368", + "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26", + "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de", + "sha256:9d59e840387076a51016777a9328b3f2c427c6f9208a6e958bad251be50a648d", + "sha256:a0cbed8bb44e23c5b199f888f4e4ce096b45ad9f25ff74a7ad0213875e936bb2", + "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e", + "sha256:a203a8bd0acb0701653d3bbb26e404854a68674139ed5cbb778830f42b09bb33", + "sha256:a4764e0bfc6a4d114c865b32520805c16a990ef5f286a514413b05d5ecd6a23d", + "sha256:a57b0d05a0448eed231d59c0ceb287dde984551e54cbc51ac2d4865712838e9c", + "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd", + "sha256:a5ea42a752d47a145eae922b605cd1634665ac3d5ec1e72402d5048e8d60d207", + "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed", + "sha256:add5217d68b31130f0beca584d7fef4878327d2e31642b66618a14eef312b63b", + "sha256:b0703c2cef53e01baec47f7a3868009913ad71ec678bbecb42a6f40895e4ce62", + "sha256:b9152fca4a6466e114aaec745ae61cba739903a109754a9d4e1262f01e9259b1", + "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0", + "sha256:c3d35f87c7253b715d13d679e0783d845910144f282cb939fe1ba4ac8616269c", + "sha256:c5551170cf4f5ff5623e9af81323751979fee2c731e2287b61f73cd27257b823", + "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab", + "sha256:cc6ab7e555c8a112ad3a76e368e86e12a2754bcae1652a5602e133ec7b635523", + "sha256:cd443683db272ebaaca03af98c0b063ab30db70ea8a31a1559f35e3f7b744ccd", + "sha256:d0932b81d72f552ded9d810d00021b64d89f2195a91ce115b893f943b7a4ab3c", + "sha256:d40a890035c0058cadbdc4af7569800fd28a0e527a0fdbb7b5f9418f176846ce", + "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c", + "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07", + "sha256:dc71ff466927a201b08305acac451ebe1aedfcea002f62f1f2f2ac2ac1e6a135", + "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e", + "sha256:ded7b068c7c31c1a8657d4fd42d886b3e051ae29f88b80c5ff9d502257b0f071", + "sha256:e5cc9606aa5f4e0bde0d3bd502b44f743864c3ffa5cfa1011b1e30f5aa02366f", + "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5", + "sha256:e6cd99ea59dd5d89f0c956606571d79bfe6f68c9eb7f4a4083a41a7f1587edee", + "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e", + "sha256:ea37d5a157eb9493820d3792ac4ece28619a394391d2b9f2f78057d396ff0f0f", + "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad", + "sha256:ed8cdb691169715a9a492844a83246f090182247d1a5031dc78a403f68ba1e97", + "sha256:ef08c1567c78074b22d1a200183d52d04a14df447bf70bcbb6a3507a48e776fc", + "sha256:f16ba1efc0715b680a18b8123d90dad887c6112ae3555b4b5c32c149540c6b4e", + "sha256:fa4f98af3a528f0c3fd592a26df7f376f93329c8f4d987f6bb979057af8bf5e2", + "sha256:ffea73584b216150eab159b6d12348fb253e68757974de1e2c40d8a318ac89ed" ], "markers": "python_version >= '3.10'", - "version": "==3.3.1" + "version": "==3.5.1" }, "humanize": { "hashes": [ @@ -589,121 +695,121 @@ }, "idna": { "hashes": [ - "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", - "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + "sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c", + "sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f" ], - "markers": "python_version >= '3.8'", - "version": "==3.11" + "markers": "python_version >= '3.9'", + "version": "==3.17" }, "ijson": { "hashes": [ - "sha256:01767fcbd75a5fa5a626069787b41f04681216b798510d5f63bcf66884386368", - "sha256:043f9b7cf9cc744263a78175e769947733710d2412d25180df44b1086b23ebd5", - "sha256:04ac9ca54db20f82aeda6379b5f4f6112fdb150d09ebce04affeab98a17b4ed3", - "sha256:05807edc0bcbd222dc6ea32a2b897f0c81dc7f12c8580148bc82f6d7f5e7ec7b", - "sha256:0634b21188c67e5cf471cc1d30d193d19f521d89e2125ab1fb602aa8ae61e050", - "sha256:07f20ecd748602ac7f18c617637e53bd73ded7f3b22260bba3abe401a7fc284e", - "sha256:09127c06e5dec753feb9e4b8c5f6a23603d1cd672d098159a17e53a73b898eec", - "sha256:0b473112e72c0c506da425da3278367b6680f340ecc093084693a1e819d28435", - "sha256:0d3e82963096579d1385c06b2559570d7191e225664b7fa049617da838e1a4a4", - "sha256:103a0838061297d063bca81d724b0958b616f372bd893bbc278320152252c652", - "sha256:114ed248166ac06377e87a245a158d6b98019d2bdd3bb93995718e0bd996154f", - "sha256:11f13b73194ea2a5a8b4a2863f25b0b4624311f10db3a75747b510c4958179b0", - "sha256:1709171023ce82651b2f132575c2e6282e47f64ad67bd3260da476418d0e7895", - "sha256:17e45262a5ddef39894013fb1548ee7094e444c8389eb1a97f86708b19bea03e", - "sha256:226447e40ca9340a39ed07d68ea02ee14b52cb4fe649425b256c1f0073531c83", - "sha256:254cfb8c124af68327a0e7a49b50bbdacafd87c4690a3d62c96eb01020a685ef", - "sha256:27aa193d47ffc6bc4e45453896ad98fb089a367e8283b973f1fe5c0198b60b4e", - "sha256:2c88f0669d45d4b1aa017c9b68d378e7cd15d188dfb6f0209adc78b7f45590a7", - "sha256:2f8b9ffa2c2dfe3289da9aec4e5ab52684fa2b2da2c853c7891b360ec46fba07", - "sha256:339d49f6c5d24051c85d9226be96d2d56e633cb8b7d09dd8099de8d8b51a97e2", - "sha256:3505dff18bdeb8b171eb28af6df34857e2be80dc01e2e3b624e77215ad58897f", - "sha256:35aaa979da875fa92bea5dc5969b1541b4912b165091761785459a43f0c20946", - "sha256:35eb2760a42fd9461358b4be131287587b49ff504fc37fa3014dca6c27c343f4", - "sha256:3752dd6f51ef58a71799de745649deff293e959700f1b7f5b1989618da366f24", - "sha256:3ed19b1e4349240773a8ce4a4bfa450892d4a57949c02c515cd6be5a46b7696a", - "sha256:40007c977e230e04118b27322f25a72ae342a3d61464b2057fcd9b21eeb7427a", - "sha256:43059ae0d657b11c5ddb11d149bc400c44f9e514fb8663057e9b2ea4d8d44c1f", - "sha256:432fb60ffb952926f9438e0539011e2dfcd108f8426ee826ccc6173308c3ff2c", - "sha256:435270a4b75667305f6df3226e5224e83cd6906022d7fdcc9df05caae725f796", - "sha256:45a0b1c833ed2620eaf8da958f06ac8351c59e5e470e078400d23814670ed708", - "sha256:461acf4320219459dabe5ed90a45cb86c9ba8cc6d6db9dad0d9427d42f57794c", - "sha256:461ce4e87a21a261b60c0a68a2ad17c7dd214f0b90a0bec7e559a66b6ae3bd7e", - "sha256:47352563e8c594360bacee2e0753e97025f0861234722d02faace62b1b6d2b2a", - "sha256:4810546e66128af51fd4a0c9a640e84e8508e9c15c4f247d8a3e3253b20e1465", - "sha256:4827d9874a6a81625412c59f7ca979a84d01f7f6bfb3c6d4dc4c46d0382b14e0", - "sha256:4e39bfdc36b0b460ef15a06550a6a385c64c81f7ac205ccff39bd45147918912", - "sha256:54a0e3e05d9a0c95ecba73d9579f146cf6d5c5874116c849dba2d39a5f30380e", - "sha256:55f7f656b5986326c978cbb3a9eea9e33f3ef6ecc4535b38f1d452c731da39ab", - "sha256:56169e298c5a2e7196aaa55da78ddc2415876a74fe6304f81b1eb0d3273346f7", - "sha256:56b3089dc28c12492d92cc4896d2be585a89ecae34e25d08c1df88f21815cb50", - "sha256:57db77f4ea3eca09f519f627d9f9c76eb862b30edef5d899f031feeed94f05a1", - "sha256:5a48b9486242d1295abe7fd0fbb6308867da5ca3f69b55c77922a93c2b6847aa", - "sha256:5f0a72b1e3c0f78551670c12b2fdc1bf05f2796254d9c2055ba319bec2216020", - "sha256:61ab0b8c5bf707201dc67e02c116f4b6545c4afd7feb2264b989d242d9c4348a", - "sha256:636b6eca96c6c43c04629c6b37fad0181662eaacf9877c71c698485637f752f9", - "sha256:6458bd8e679cdff459a0a5e555b107c3bbacb1f382da3fe0f40e392871eb518d", - "sha256:659acb2843433e080c271ecedf7d19c71adde1ee5274fc7faa2fec0a793f9f1c", - "sha256:6793c29a5728e7751a7df01be58ba7da9b9690c12bf79d32094c70a908fa02b9", - "sha256:69718ed41710dfcaa7564b0af42abc05875d4f7aaa24627c808867ef32634bc7", - "sha256:7206afcb396aaef66c2b066997b4e9d9042c4b7d777f4d994e9cec6d322c2fe6", - "sha256:742c211b004ab51ccad2b301525d8a6eb2cf68a5fb82d78836f3a351eec44d4e", - "sha256:7809ec8c8f40228edaaa089f33e811dff4c5b8509702652870d3f286c9682e27", - "sha256:8311f48db6a33116db5c81682f08b6e2405501a4b4e460193ae69fec3cd1f87a", - "sha256:83fc738d81c9ea686b452996110b8a6678296c481e0546857db24785bff8da92", - "sha256:890cf6610c9554efcb9765a93e368efeb5bb6135f59ce0828d92eaefff07fde5", - "sha256:8f904a405b58a04b6ef0425f1babbc5c65feb66b0a4cc7f214d4ad7de106f77d", - "sha256:91c61a3e63e04da648737e6b4abd537df1b46fb8cdf3219b072e790bb3c1a46b", - "sha256:97f5ef3d839fc24b0ad47e8b31b4751ae72c5d83606e3ee4c92bb25965c03a4f", - "sha256:9aa02dc70bb245670a6ca7fba737b992aeeb4895360980622f7e568dbf23e41e", - "sha256:9c0886234d1fae15cf4581a430bdba03d79251c1ab3b07e30aa31b13ef28d01c", - "sha256:a07dcc1a8a1ddd76131a7c7528cbd12951c2e34eb3c3d63697b905069a2d65b1", - "sha256:a0fedf09c0f6ffa2a99e7e7fd9c5f3caf74e655c1ee015a0797383e99382ebc3", - "sha256:a2c873742e9f7e21378516217d81d6fa11d34bae860ed364832c00ab1dbf37ed", - "sha256:a39d5d36067604b26b78de70b8951c90e9272450642661fe531a8f7a6936a7fa", - "sha256:a5269af16f715855d9864937f9dd5c348ca1ac49cee6a2c7a1b7091c159e874f", - "sha256:a56b6674d7feec0401c91f86c376f4e3d8ff8129128a8ad21ca43ec0b1242f79", - "sha256:a603d7474bf35e7b3a8e49c8dabfc4751841931301adff3f3318171c4e407f32", - "sha256:ab3be841b8c430c1883b8c0775eb551f21b5500c102c7ee828afa35ddd701bdd", - "sha256:add9242f886eae844a7410b84aee2bbb8bdc83c624f227cb1fdb2d0476a96cb1", - "sha256:b005ce84e82f28b00bf777a464833465dfe3efa43a0a26c77b5ac40723e1a728", - "sha256:b200df83c901f5bfa416d069ac71077aa1608f854a4c50df1b84ced560e9c9ec", - "sha256:b2a81aee91633868f5b40280e2523f7c5392e920a5082f47c5e991e516b483f6", - "sha256:b39dbf87071f23a23c8077eea2ae7cfeeca9ff9ffec722dfc8b5f352e4dd729c", - "sha256:b55e49045f4c8031f3673f56662fd828dc9e8d65bd3b03a9420dda0d370e64ba", - "sha256:b607a500fca26101be47d2baf7cddb457b819ab60a75ce51ed1092a40da8b2f9", - "sha256:b982a3597b0439ce9c8f4cfc929d86c6ed43907908be1e8463a34dc35fe5b258", - "sha256:ba3478ff0bb49d7ba88783f491a99b6e3fa929c930ab062d2bb7837e6a38fe88", - "sha256:c117321cfa7b749cc1213f9b4c80dc958f0a206df98ec038ae4bcbbdb8463a15", - "sha256:c8dd327da225887194fe8b93f2b3c9c256353e14a6b9eefc940ed17fde38f5b8", - "sha256:ccddb2894eb7af162ba43b9475ac5825d15d568832f82eb8783036e5d2aebd42", - "sha256:cf24a48a1c3ca9d44a04feb59ccefeb9aa52bb49b9cb70ad30518c25cce74bb7", - "sha256:cf4a34c2cfe852aee75c89c05b0a4531c49dc0be27eeed221afd6fbf9c3e149c", - "sha256:d14427d366f95f21adcb97d0ed1f6d30f6fdc04d0aa1e4de839152c50c2b8d65", - "sha256:d4d4afec780881edb2a0d2dd40b1cdbe246e630022d5192f266172a0307986a7", - "sha256:da6a21b88cbf5ecbc53371283988d22c9643aa71ae2873bbeaefd2dea3b6160b", - "sha256:deda4cfcaafa72ca3fa845350045b1d0fef9364ec9f413241bb46988afbe6ee6", - "sha256:e15833dcf6f6d188fdc624a31cd0520c3ba21b6855dc304bc7c1a8aeca02d4ac", - "sha256:eb5e73028f6e63d27b3d286069fe350ed80a4ccc493b022b590fea4bb086710d", - "sha256:ec5bb1520cb212ebead7dba048bb9b70552c3440584f83b01b0abc96862e2a09", - "sha256:eeb9540f0b1a575cbb5968166706946458f98c16e7accc6f2fe71efa29864241", - "sha256:f82ca7abfb3ef3cf2194c71dad634572bcccd62a5dd466649f78fe73d492c860", - "sha256:f932969fc1fd4449ca141cf5f47ff357656a154a361f28d9ebca0badc5b02297", - "sha256:fe9c84c9b1c8798afa407be1cea1603401d99bfc7c34497e19f4f5e5ddc9b441", - "sha256:fecae19b5187d92900c73debb3a979b0b3290a53f85df1f8f3c5ba7d1e9fb9cb", - "sha256:ffb21203736b08fe27cb30df6a4f802fafb9ef7646c5ff7ef79569b63ea76c57" + "sha256:009f41443e1521847701c6d87fa3923c0b1961be3c7e7de90947c8cb92ea7c44", + "sha256:014586eec043e23c80be9a923c56c3a0920a0f1f7d17478ce7bc20ba443968ef", + "sha256:01b6dad72b7b7df225ef970d334556dfad46c696a2c6767fb5d9ed8889728bca", + "sha256:04f0fc740311388ee745ba55a12292b722d6f52000b11acbb913982ba5fbdf87", + "sha256:0574b0a841ff97495c13e9d7260fbf3d85358b061f540c52a123db9dbbaa2ed6", + "sha256:0eb402ab026ffb37a918d75af2b7260fe6cfbce13232cc83728a714dd30bd81d", + "sha256:0ec62d397447cbe4941818c53e22b054e03250ff9cdbaea75144b11bc6db44ed", + "sha256:1111a1c5ac79119c5d6e836f900c1a53844b50a18af38311baa6bb61e2645aca", + "sha256:19e30d9f00f82e64de689c0b8651b9cfed879c184b139d7e1ea5030cec401c21", + "sha256:1b2cf2c0c79313fbc607a0d90788ffb4f4614872983af4aa85c5b92533ec4da2", + "sha256:1e74aff8c681c24002b61b1822f9511d4c384f324f7dbc08c78538e01fdc9fcb", + "sha256:1ebefbe149a6106cc848a3eaf536af51a9b5ccc9082de801389f152dba6ab755", + "sha256:22a51b4f9b81f12793731cf226266d1de2112c3c04ba4a04117ad4e466897e05", + "sha256:2419f9e32e0968a876b04d8f26aeac042abd16f582810b576936bbc4c6015069", + "sha256:252dec3680a48bb82d475e36b4ae1b3a9d7eb690b951bb98a76c5fe519e30188", + "sha256:25a5a6b2045c90bb83061df27cfa43572afa43ba9408611d7bfe237c20a731a9", + "sha256:2adeecd45830bfd5580ca79a584154713aabef0b9607e16249133df5d2859813", + "sha256:2ea4b676ec98e374c1df400a47929859e4fa1239274339024df4716e802aa7e4", + "sha256:3176f23f8ebec83f374ed0c3b4e5a0c4db7ede54c005864efebbed46da123608", + "sha256:3647649f782ee06c97490b43680371186651f3f69bebe64c6083ee7615d185e5", + "sha256:3e4c1178fb50aff5f5701a30a5152ead82a14e189ce0f6102fa1b5f10b2f54ff", + "sha256:4217a1edc278660679e1197c83a1a2a2d367792bfbb2a3279577f4b59b93730d", + "sha256:45887d5e84ff0d2b138c926cebd9071830733968afe8d9d12080b3c178c7f918", + "sha256:498fd46ae2349297e43acf97cdc421e711dbd7198418677259393d2acdc62d78", + "sha256:4d4b0cd676b8c842f7648c1a783448fac5cd3b98289abd83711b3e275e143524", + "sha256:4f24b78d4ef028d17eb57ad1b16c0aed4a17bdd9badbf232dc5d9305b7e13854", + "sha256:5616311404b858d32740b7ad8b9a799c62165f5ecb85d0a8ed16c21665a90533", + "sha256:59d3f9f46deed1332ad669518b8099920512a78bda64c1f021fcd2aff2b36693", + "sha256:5a3af031e30751164c3289294f249f942535fbe7e8f35eb3ecc374247449214e", + "sha256:5b08ee08355f9f729612a8eb9bf69cc14f9310c3b2a487c6f1c3c65d85216ec4", + "sha256:5c2839fa233746d8aad3b8cd2354e441613f5df66d721d59da4a09394bd1db2b", + "sha256:63bc8121bb422f6969ced270173a3fa692c29d4ae30c860a2309941abd81012a", + "sha256:6673de9395fb9893c1c79a43becd8c8fbee0a250be6ea324bfd1487bb5e9ee4c", + "sha256:6b2dcf6349e6042d83f3f8c39ce84823cf7577eba25bac5aae5e39bbbbbe9c1c", + "sha256:6babd88e508630c6ef86c9bebaaf13bb2fb8ec1d8f8868773a03c20253f599bc", + "sha256:6bad6a1634cb7c9f3f4c7e52325283b35b565f5b6cc27d42660c6912ce883422", + "sha256:6ca0d1b6b5f8166a6248f4309497585fb8553b04bc8179a0260fad636cfdb798", + "sha256:6e249796d2090afc1c42d2458ab0dbf0072a30ffa246b5683e3f7b9dc9b1b7f9", + "sha256:7389a56b8562a19948bdf1d7bae3a2edc8c7f86fb59834dcb1c4c722818e645a", + "sha256:739a7229b1b0cc5f7e2785a6e7a5fc915e850d3fed9588d0e89a09f88a417253", + "sha256:75980237a16e5e36ad46fbdd33e3f3d817c187624974c48947df0a2bfa104b9e", + "sha256:78e9ad73e7be2dd80627504bd5cbf512348c55ce2c06e362ed7683b5220e8568", + "sha256:7a5ec7fd86d606094bba6f6f8f87494897102fa4584ef653f3005c51a784c320", + "sha256:7af0c4c8943be8b09a4e57bdc1da6001dae7b36526d4154fe5c8224738d0921f", + "sha256:7d48dc2984af02eb3c56edfb3f13b3f62f2f3e4fe36f058c8cfc75d93adf4fed", + "sha256:859eb2038f7f1b0664df4241957694cc35e6295992d71c98659b22c69b3cbc10", + "sha256:8696454245415bc617ab03b0dc3ae4c86987df5dc6a90bad378fe72c5409d89e", + "sha256:876f7df73b7e0d6474f9caa729b9cdbfc8e76de9075a4887dfd689e29e85c4ca", + "sha256:8976c54c0b864bc82b951bae06567566ac77ef63b90a773a69cd73aab47f4f4f", + "sha256:8d073d9b13574cfa11083cc7267c238b7a6ed563c2661e79192da4a25f09c82c", + "sha256:903cbdc350173605220edc19796fbea9b2203c8b3951fb7335abfa8ed37afda8", + "sha256:90e74be1dce05fce73451c62d1118671f78f47c9f6be3991c82b91063bf01fc9", + "sha256:9260332304b7e7828db56d43f08fc970a3ab741bf84ff10189361ea1b60c395b", + "sha256:92878b130d7ad71919c70b4f50ad23ec7fbf2d09a9c675f9179d49c4be869a63", + "sha256:92b0495bbb2150bbf14fc5d98fb6d76bcd1c526605a172709e602e6fedc96495", + "sha256:945b7abcfcfeae2cde17d8d900870f03536494245dda7ad4f8d056faa303256c", + "sha256:94688760720e3f5212731b3cb8d30267f9a045fb38fb3870254e7b9504246f31", + "sha256:9577449313cc94be89a4fe4b3e716c65f09cc19636d5a6b2861c4e80dddebd58", + "sha256:9636c710dc4ac4a281baa266a64f323b4cc165cec26836af702c44328b59a515", + "sha256:966039cf9047c7967febf7b9a52ec6f38f5464a4c7fbb5565e0224b7376fefff", + "sha256:9a70b575be8e57a28c80e90ed349ad3a851c3478524c70e36e07d6092ecd12c9", + "sha256:9a88c559456a79708592234d697645d92b599718f4cbbeaa6515f83ac63ca0ae", + "sha256:9a9c4c70501e23e8eb1675330686d1598eebfa14b6f0dbc8f00c2e081cc628fa", + "sha256:a04a33ee78a6f27b9b8528c1ca3c207b1df3b8b867a4cf2fcc4109986f35c227", + "sha256:a1ab890d43656c1d12c4a8dafb7fac5a2278ed3e4408102e0971f48b6ed4583d", + "sha256:a2619460d6795b70d0155e5bf016200ac8a63ab5397aa33588bb02b6c21759e6", + "sha256:a4549d96ded5b8efa71639b2160235415f6bdb8c83367615e2dbabcb72755c33", + "sha256:a55185e8983fef0b21abc1a0bbaa11eeb2fabdc651e2167f23defa9fe4eb999b", + "sha256:a9c321e8e1cdeac8aac698d09a90d98a049c9be8e8330c89cf2fcc517c96d51d", + "sha256:aa1b5dca97d323931fde2501172337384c958914d81a9dac7f00f0d4bfc76bc7", + "sha256:aec4580a7712a19b1f95cd41bed260fc6a31266d37ef941827772a4c199e8143", + "sha256:bda62b6d48442903e7bf56152108afb7f0f1293c2b9bef2f2c369defea76ab18", + "sha256:c061314845c08163b1784b6076ea5f075372461a32e6916f4e5f211fd4130b64", + "sha256:c21bfb61f71f191565885bf1bc29e0a186292d866b4880637b833848360bdc1b", + "sha256:c911aa02991c7c0d3639b6619b93a93210ff1e7f58bf7225d613abea10adc78e", + "sha256:cf83f58ad50dc0d39a2105cb26d4f359b38f42cef68b913170d4d47d97d97ba5", + "sha256:d38cb03f6b7cc26d542ff710adfe98e5f6d53878461c45456c97d3668297ec0d", + "sha256:d5b8b886b0248652d437f66e7c5ac318bbdcb2c7137a7e5327a68ca00b286f5f", + "sha256:d64c624da0e9d692d6eb0ff63a79656b59d76bf80773a17c5b0f835e4e8ef627", + "sha256:d873e72889e7fc5962ab58909f1adff338d7c2f49e450e5b5fe844eff8155a14", + "sha256:db8398c6721b98412a4f618da8022550c8b9c5d9214040646071b5deb4d4a393", + "sha256:dc1b3836b174b6db2fa8319f1926fb5445abd195dc963368092103f8579cb8ed", + "sha256:e44af39e6f8a17e5627dcd89715d8279bf3474153ff99aae031a936e5c5572e5", + "sha256:e4c3651d1f9fe2839a93fdf8fd1d5ca3a54975349894249f3b1b572bcc4bd577", + "sha256:e7dbff2c8d9027809b0cde663df44f3210da10ea377121d42896fb6ee405dd31", + "sha256:e9733f94029dd41702d573ef64752e2556e72aea14623d6dbb7a44ca1ccf30fd", + "sha256:e9cedc10e40dd6023c351ed8bfc7dcfce58204f15c321c3c1546b9c7b12562a4", + "sha256:ea8dcac10d86adaeead454bc25c97b68d0bda573d5fd6f86f5e21cf8f7906f88", + "sha256:ef88712160360cab3ca6471a4e5418243f8b267cf1fe1620879d1b5558babc71", + "sha256:f1e73a44844d9adbca9cf2c4132cd875933e83f3d4b23881fcaf82be83644c7d", + "sha256:f4c8f5ccf7230a9a94c1d836322783ed0c0ec2a151f3d53b2e0a67c89ad66970", + "sha256:f4f7fabd653459dcb004175235f310435959b1bb5dfa8878578391c6cc9ad944", + "sha256:f7168a39e8211107666d71b25693fd1b2bac0b33735ef744114c403c6cac21e1", + "sha256:f969ffb2b89c5cdf686652d7fb66252bc72126fa54d416317411497276056a18", + "sha256:fdeee6957f92e0c114f65c55cf8fe7eabb80cfacab64eea6864060913173f66d" ], "markers": "python_version >= '3.9'", - "version": "==3.4.0.post0" + "version": "==3.5.0" }, "importlib-resources": { "hashes": [ - "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", - "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec" + "sha256:0722d4c6212489c530f2a145a34c0a7a3b4721bc96a15fada5930e2a0b760708", + "sha256:1bd7b48b4088eddb2cd16382150bb515af0bd2c70128194392725f82ad2c96a1" ], "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==6.5.2" + "markers": "python_version >= '3.10'", + "version": "==7.1.0" }, "isodate": { "hashes": [ @@ -756,85 +862,99 @@ }, "librt": { "hashes": [ - "sha256:00105e7d541a8f2ee5be52caacea98a005e0478cfe78c8080fbb7b5d2b340c63", - "sha256:0241a6ed65e6666236ea78203a73d800dbed896cf12ae25d026d75dc1fcd1dac", - "sha256:03679b9856932b8c8f674e87aa3c55ea11c9274301f76ae8dc4d281bda55cf62", - "sha256:047164e5f68b7a8ebdf9fae91a3c2161d3192418aadd61ddd3a86a56cbe3dc85", - "sha256:171ca3a0a06c643bd0a2f62a8944e1902c94aa8e5da4db1ea9a8daf872685365", - "sha256:1a4ede613941d9c3470b0368be851df6bb78ab218635512d0370b27a277a0862", - "sha256:20e3946863d872f7cabf7f77c6c9d370b8b3d74333d3a32471c50d3a86c0a232", - "sha256:2991b6c3775383752b3ca0204842743256f3ad3deeb1d0adc227d56b78a9a850", - "sha256:31724b93baa91512bd0a376e7cf0b59d8b631ee17923b1218a65456fa9bda2e7", - "sha256:3469e1af9f1380e093ae06bedcbdd11e407ac0b303a56bbe9afb1d6824d4982d", - "sha256:389bd25a0db916e1d6bcb014f11aa9676cedaa485e9ec3752dfe19f196fd377b", - "sha256:3968762fec1b2ad34ce57458b6de25dbb4142713e9ca6279a0d352fa4e9f452b", - "sha256:39a4c76fee41007070f872b648cc2f711f9abf9a13d0c7162478043377b52c8e", - "sha256:3d1322800771bee4a91f3b4bd4e49abc7d35e65166821086e5afd1e6c0d9be44", - "sha256:41d7bb1e07916aeb12ae4a44e3025db3691c4149ab788d0315781b4d29b86afb", - "sha256:43d4e71b50763fcdcf64725ac680d8cfa1706c928b844794a7aa0fa9ac8e5f09", - "sha256:445b7304145e24c60288a2f172b5ce2ca35c0f81605f5299f3fa567e189d2e32", - "sha256:44e0c2cbc9bebd074cf2cdbe472ca185e824be4e74b1c63a8e934cea674bebf2", - "sha256:451e7ffcef8f785831fdb791bd69211f47e95dc4c6ddff68e589058806f044c6", - "sha256:46ef1f4b9b6cc364b11eea0ecc0897314447a66029ee1e55859acb3dd8757c93", - "sha256:4864045f49dc9c974dadb942ac56a74cd0479a2aafa51ce272c490a82322ea3c", - "sha256:4adc73614f0d3c97874f02f2c7fd2a27854e7e24ad532ea6b965459c5b757eca", - "sha256:4c3995abbbb60b3c129490fa985dfe6cac11d88fc3c36eeb4fb1449efbbb04fc", - "sha256:4d2f1e492cae964b3463a03dc77a7fe8742f7855d7258c7643f0ee32b6651dd3", - "sha256:535929b6eff670c593c34ff435d5440c3096f20fa72d63444608a5aef64dd581", - "sha256:5363427bc6a8c3b1719f8f3845ea53553d301382928a86e8fab7984426949bce", - "sha256:54feb7b4f2f6706bb82325e836a01be805770443e2400f706e824e91f6441dde", - "sha256:57175aa93f804d2c08d2edb7213e09276bd49097611aefc37e3fa38d1fb99ad0", - "sha256:5bcaaf624fd24e6a0cb14beac37677f90793a96864c67c064a91458611446e83", - "sha256:60c299e555f87e4c01b2eca085dfccda1dde87f5a604bb45c2906b8305819a93", - "sha256:631599598e2c76ded400c0a8722dec09217c89ff64dc54b060f598ed68e7d2a8", - "sha256:63937bd0f4d1cb56653dc7ae900d6c52c41f0015e25aaf9902481ee79943b33a", - "sha256:66daa6ac5de4288a5bbfbe55b4caa7bf0cd26b3269c7a476ffe8ce45f837f87d", - "sha256:6938cc2de153bc927ed8d71c7d2f2ae01b4e96359126c602721340eb7ce1a92d", - "sha256:6d772edc6a5f7835635c7562f6688e031f0b97e31d538412a852c49c9a6c92d5", - "sha256:6db5faf064b5bab9675c32a873436b31e01d66ca6984c6f7f92621656033a708", - "sha256:73fd300f501a052f2ba52ede721232212f3b06503fa12665408ecfc9d8fd149c", - "sha256:79feb4d00b2a4e0e05c9c56df707934f41fcb5fe53fd9efb7549068d0495b758", - "sha256:7aa7d5457b6c542ecaed79cec4ad98534373c9757383973e638ccced0f11f46d", - "sha256:7b0803e9008c62a7ef79058233db7ff6f37a9933b8f2573c05b07ddafa226611", - "sha256:7e03bea66af33c95ce3addf87a9bf1fcad8d33e757bc479957ddbc0e4f7207ac", - "sha256:864c4b7083eeee250ed55135d2127b260d7eb4b5e953a9e5df09c852e327961b", - "sha256:8766ece9de08527deabcd7cb1b4f1a967a385d26e33e536d6d8913db6ef74f06", - "sha256:87808a8d1e0bd62a01cafc41f0fd6818b5a5d0ca0d8a55326a81643cdda8f873", - "sha256:907ad09cfab21e3c86e8f1f87858f7049d1097f77196959c033612f532b4e592", - "sha256:95b67aa7eff150f075fda09d11f6bfb26edffd300f6ab1666759547581e8f666", - "sha256:978e8b5f13e52cf23a9e80f3286d7546baa70bc4ef35b51d97a709d0b28e537c", - "sha256:9b6943885b2d49c48d0cff23b16be830ba46b0152d98f62de49e735c6e655a63", - "sha256:9c1ba843ae20db09b9d5c80475376168feb2640ce91cd9906414f23cc267a1ff", - "sha256:a14229ac62adcf1b90a15992f1ab9c69ae8b99ffb23cb64a90878a6e8a2f5b81", - "sha256:a36515b1328dc5b3ffce79fe204985ca8572525452eacabee2166f44bb387b2c", - "sha256:ac9c8a458245c7de80bc1b9765b177055efff5803f08e548dd4bb9ab9a8d789b", - "sha256:ad64a14b1e56e702e19b24aae108f18ad1bf7777f3af5fcd39f87d0c5a814449", - "sha256:b09c52ed43a461994716082ee7d87618096851319bf695d57ec123f2ab708951", - "sha256:b45306a1fc5f53c9330fbee134d8b3227fe5da2ab09813b892790400aa49352d", - "sha256:b5b007bb22ea4b255d3ee39dfd06d12534de2fcc3438567d9f48cdaf67ae1ae3", - "sha256:b7e7f140c5169798f90b80d6e607ed2ba5059784968a004107c88ad61fb3641d", - "sha256:b9122094e3f24aa759c38f46bd8863433820654927370250f460ae75488b66ea", - "sha256:bb7a7807523a31f03061288cc4ffc065d684c39db7644c676b47d89553c0d714", - "sha256:be927c3c94c74b05128089a955fba86501c3b544d1d300282cc1b4bd370cb418", - "sha256:bfde8a130bd0f239e45503ab39fab239ace094d63ee1d6b67c25a63d741c0f71", - "sha256:c6f8947d3dfd7f91066c5b4385812c18be26c9d5a99ca56667547f2c39149d94", - "sha256:c7e8f88f79308d86d8f39c491773cbb533d6cb7fa6476f35d711076ee04fceb6", - "sha256:ca916919793a77e4a98d4a1701e345d337ce53be4a16620f063191f7322ac80f", - "sha256:cf243da9e42d914036fd362ac3fa77d80a41cadcd11ad789b1b5eec4daaf67ca", - "sha256:d6f254d096d84156a46a84861183c183d30734e52383602443292644d895047c", - "sha256:dbd79caaf77a3f590cbe32dc2447f718772d6eea59656a7dcb9311161b10fa75", - "sha256:ddb52499d0b3ed4aa88746aaf6f36a08314677d5c346234c3987ddc506404eac", - "sha256:e90a8e237753c83b8e484d478d9a996dc5e39fd5bd4c6ce32563bc8123f132be", - "sha256:e9c0afebbe6ce177ae8edba0c7c4d626f2a0fc12c33bb993d163817c41a7a05c", - "sha256:f11b300027ce19a34f6d24ebb0a25fd0e24a9d53353225a5c1e6cadbf2916b2e", - "sha256:f1ade7f31675db00b514b98f9ab9a7698c7282dad4be7492589109471852d398", - "sha256:f8f4a901a3fa28969d6e4519deceab56c55a09d691ea7b12ca830e2fa3461e34", - "sha256:fdec6e2368ae4f796fc72fad7fd4bd1753715187e6d870932b0904609e7c878e", - "sha256:ff3e9c11aa260c31493d4b3197d1e28dd07768594a4f92bec4506849d736248f", - "sha256:ff71447cb778a4f772ddc4ce360e6ba9c95527ed84a52096bd1bbf9fee2ec7c0" + "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", + "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", + "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", + "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5", + "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", + "sha256:0cad8a4d6a8ff03c9b76f9414caccd78e7cfbc8a2e12fa334d8e1d9932753783", + "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", + "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", + "sha256:0ef69ac715f3cd8e5cd252cb2aebfa72c015492aacc339d5d7bf8fef3c62c677", + "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d", + "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", + "sha256:140695816ddf3c86eb972981a26f35efd871c44b0c3aed44c8cd01749386617f", + "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412", + "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc", + "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", + "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", + "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", + "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", + "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", + "sha256:41dc19fe150b69716c8ece4f76773a9e8813fe3e35e032a58b4d46423fb8d7c0", + "sha256:461bbceede621f1ffb8839755f8663e886087ee7af16294cab7fb4d782c62eeb", + "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d", + "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", + "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", + "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", + "sha256:4e8bd98ea9c47ae90b319a087ab28dac493f1ffbc1ecd1f28fcdbf3b7e1108d1", + "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9", + "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", + "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", + "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b", + "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", + "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", + "sha256:624a40c4a4ad7773315c287276cd024509b2c66ff5904f504bfc08d2c70293ab", + "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c", + "sha256:6bd72d903911d995ab666dbd1871f8b1e80925a699af8063fbf50053329fb05f", + "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", + "sha256:6e94ebfcfa2d5e9926d6c3b9aa4617ffc42a845b4321fb84021b872358c82a0f", + "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", + "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7", + "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", + "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", + "sha256:7a80a71e1fda83cc752a9141e87aae7fef279538597564d670e9ce513f286192", + "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", + "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", + "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", + "sha256:7e82e642ab0f7608ce2fe53d76ca2280a9ee33a1b06556142c7c6fe80a86fc33", + "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", + "sha256:84308fc49423ce6475d1c5d1985cd69a8ca9f0325fc7d5f81bb690a3f3625d4e", + "sha256:88145c15c67731d54283d135b03244028c750cc9edc334a96a4f5950ebdb2884", + "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", + "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73", + "sha256:92f7ff819c197fc30473190a12c2856f325ac90aabfccbeb2072d28cc2e234e3", + "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", + "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29", + "sha256:94663a21534637f0e787ec2a2a756022df6e5b7b2335a5cdd7d8e33d68a2af89", + "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", + "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", + "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89", + "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", + "sha256:9c028a9442a18e266955d364ce42259136e79a7ba14d773e0d778d5f70cd56f1", + "sha256:9d36a51b3d93320b686588e27123f4995804dbf1bce81df78c02fc3c6eea9280", + "sha256:9f1692105a02bcf853f355032a5fdc5494358ef83d8fd22d16de375c85cec3f5", + "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", + "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", + "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", + "sha256:ae627397a2f351560440d872d6f7c8dbb4072e57868e7b2fc5b8b430fe489d45", + "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", + "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", + "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", + "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", + "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", + "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", + "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", + "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", + "sha256:d00f3ac06a2a8b246327f11e186a53a100a4d5c7ed52346367e5ec751d51586c", + "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", + "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", + "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", + "sha256:dc329359321b67d24efdf4bc69012b0597001649544db662c001db5a0184794c", + "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", + "sha256:dec7db73758c2b54953fd8b7fe348c45188fe26b39ee18446196edd08453a5d4", + "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", + "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", + "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5", + "sha256:f37aa505b3cf60701562eddb32df74b12a9e380c207fd8b06dd157a943ac7ea0", + "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", + "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", + "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", + "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", + "sha256:ff0fbaf5f44a21beeb0110f2ab64f45135a9536a834b79c0d1ef018f2786bbfa" ], "markers": "python_version >= '3.9'", - "version": "==0.7.8" + "version": "==0.11.0" }, "linear-tsv": { "hashes": [ @@ -844,157 +964,151 @@ }, "lxml": { "hashes": [ - "sha256:058027e261afed589eddcfe530fcc6f3402d7fd7e89bfd0532df82ebc1563dba", - "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8", - "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", - "sha256:08b9d5e803c2e4725ae9e8559ee880e5328ed61aa0935244e0515d7d9dbec0aa", - "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f", - "sha256:0aa7070978f893954008ab73bb9e3c24a7c56c054e00566a21b553dc18105fca", - "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf", - "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607", - "sha256:17f68764f35fd78d7c4cc4ef209a184c38b65440378013d24b8aecd327c3e0c8", - "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452", - "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700", - "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d", - "sha256:1e786a464c191ca43b133906c6903a7e4d56bef376b75d97ccbb8ec5cf1f0a4b", - "sha256:1ea99340b3c729beea786f78c38f60f4795622f36e305d9c9be402201efdc3b7", - "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d", - "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", - "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a", - "sha256:24a8e756c982c001ca8d59e87c80c4d9dcd4d9b44a4cbeb8d9be4482c514d41d", - "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca", - "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb", - "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed", - "sha256:2613e67de13d619fd283d58bda40bff0ee07739f624ffee8b13b631abf33083d", - "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e", - "sha256:2c8458c2cdd29589a8367c09c8f030f1d202be673f0ca224ec18590b3b9fb694", - "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd", - "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659", - "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c", - "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314", - "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5", - "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", - "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938", - "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a", - "sha256:3efe1b21c7801ffa29a1112fab3b0f643628c30472d507f39544fd48e9549e34", - "sha256:3fee0851639d06276e6b387f1c190eb9d7f06f7f53514e966b26bae46481ec90", - "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6", - "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba", - "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285", - "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553", - "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d", - "sha256:452b899faa64f1805943ec1c0c9ebeaece01a1af83e130b69cdefeda180bb42c", - "sha256:45f93e6f75123f88d7f0cfd90f2d05f441b808562bf0bc01070a00f53f5028b5", - "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601", - "sha256:4ddb1049fa0579d0cbd00503ad8c58b9ab34d1254c77bc6a5576d96ec7853dba", - "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37", - "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", - "sha256:5921d924aa5468c939d95c9814fa9f9b5935a6ff4e679e26aaf2951f74043512", - "sha256:59c45e125140b2c4b33920d21d83681940ca29f0b83f8629ea1a2196dc8cfe6a", - "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438", - "sha256:5d444858b9f07cefff6455b983aea9a67f7462ba1f6cbe4a21e8bf6791bf2153", - "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9", - "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d", - "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", - "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f", - "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", - "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46", - "sha256:66328dabea70b5ba7e53d94aa774b733cf66686535f3bc9250a7aab53a91caaf", - "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f", - "sha256:6cdaefac66e8b8f30e37a9b4768a391e1f8a16a7526d5bc77a7928408ef68e93", - "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", - "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", - "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1", - "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", - "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", - "sha256:71695772df6acea9f3c0e59e44ba8ac50c4f125217e84aab21074a1a55e7e5c9", - "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", - "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7", - "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f", - "sha256:817ef43a0c0b4a77bd166dc9a09a555394105ff3374777ad41f453526e37f9cb", - "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916", - "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec", - "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9", - "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d", - "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679", - "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0", - "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", - "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917", - "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c", - "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d", - "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338", - "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d", - "sha256:995e783eb0374c120f528f807443ad5a83a656a8624c467ea73781fc5f8a8304", - "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77", - "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba", - "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", - "sha256:a656ca105115f6b766bba324f23a67914d9c728dafec57638e2b92a9dcd76c62", - "sha256:a6b5b39cc7e2998f968f05309e666103b53e2edd01df8dc51b90d734c0825444", - "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a", - "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", - "sha256:a8ffaeec5dfea5881d4c9d8913a32d10cfe3923495386106e4a24d45300ef79c", - "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d", - "sha256:ac02dc29fd397608f8eb15ac1610ae2f2f0154b03f631e6d724d9e2ad4ee2c84", - "sha256:af85529ae8d2a453feee4c780d9406a5e3b17cee0dd75c18bd31adcd584debc3", - "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe", - "sha256:b2142a376b40b6736dfc214fd2902409e9e3857eff554fed2d3c60f097e62a62", - "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8", - "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178", - "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0", - "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7", - "sha256:b42f4d86b451c2f9d06ffb4f8bbc776e04df3ba070b9fe2657804b1b40277c48", - "sha256:b738f7e648735714bbb82bdfd030203360cfeab7f6e8a34772b3c8c8b820568c", - "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9", - "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7", - "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048", - "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c", - "sha256:bc456d04db0515ce3320d714a1eac7a97774ff0849e7718b492d957da4631dd4", - "sha256:bc532422ff26b304cfb62b328826bd995c96154ffd2bac4544f37dbb95ecaa8f", - "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b", - "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", - "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", - "sha256:c54d83a2188a10ebdba573f16bd97135d06c9ef60c3dc495315c7a28c80a263f", - "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee", - "sha256:cb233f9c95f83707dae461b12b720c1af9c28c2d19208e1be03387222151daf5", - "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", - "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272", - "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9", - "sha256:d4aec24d6b72ee457ec665344a29acb2d35937d5192faebe429ea02633151aad", - "sha256:d6690ec5ec1cce0385cb20896b16be35247ac8c2046e493d03232f1c2414d321", - "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a", - "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312", - "sha256:dacf3c64ef3f7440e3167aa4b49aa9e0fb99e0aa4f9ff03795640bf94531bcb0", - "sha256:daf42de090d59db025af61ce6bdb2521f0f102ea0e6ea310f13c17610a97da4c", - "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37", - "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964", - "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484", - "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e", - "sha256:e237b807d68a61fc3b1e845407e27e5eb8ef69bc93fe8505337c1acb4ee300b6", - "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078", - "sha256:e748d4cf8fef2526bb2a589a417eba0c8674e29ffcb570ce2ceca44f1e567bf6", - "sha256:e77dd455b9a16bbd2a5036a63ddbd479c19572af81b624e79ef422f929eef388", - "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", - "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2", - "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df", - "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd", - "sha256:f2a50c3c1d11cad0ebebbac357a97b26aa79d2bcaf46f256551152aa85d3a4d1", - "sha256:f2e3b1a6bb38de0bc713edd4d612969dd250ca8b724be8d460001a387507021c", - "sha256:f952dacaa552f3bb8834908dddd500ba7d508e6ea6eb8c52eb2d28f48ca06a31", - "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed", - "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2", - "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", - "sha256:fe659f6b5d10fb5a17f00a50eb903eb277a71ee35df4615db573c069bcf967ac" + "sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2", + "sha256:07a4a68e286ee7a1ed7dfb8af83e615757c0ccfe9f18c6b4ea6771388d9ba8c9", + "sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60", + "sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c", + "sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7", + "sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a", + "sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83", + "sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072", + "sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8", + "sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462", + "sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0", + "sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085", + "sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f", + "sha256:1dde6131244bba38a17c745836ba190bc753fd73c9291666287fd0a3fa3dcf30", + "sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1", + "sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77", + "sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740", + "sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b", + "sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c", + "sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621", + "sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e", + "sha256:32ab449a5486f6c758e849bb86710d0e45edc24a04e250c01555f8f5653958f8", + "sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca", + "sha256:34c2d737beabfe35baada43941ed519251e9a12e779031496bcd5d539fcfd730", + "sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245", + "sha256:37a58976370f36d9329d118ad0b953c5aeb9119ac9c6a4e258942a225d0573a1", + "sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004", + "sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d", + "sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52", + "sha256:3abf332af33a74288675d936fe861fd4344da0dd6622193fbc4f2bfbb35536b5", + "sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf", + "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc", + "sha256:441dd227fa0690eb9fc81edabc63cdcefc212bba99b906dcf6e32cc1a9d3e533", + "sha256:469e3618338bd7ab5beb412d2439825479fcf0dab99e394ca563dbc4eaf6c834", + "sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947", + "sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a", + "sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2", + "sha256:53c909b62a0532183542fed00c5a7218258c56292d409bc789886fe1cb04c438", + "sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc", + "sha256:556e94a63c9b04716f8e4de2abb65775061f846e89331b6c5be79183a24f98ea", + "sha256:55b03549819867ea141c0202242c4816c82e52ec36e7e648db9d8da5a3dc3ed6", + "sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e", + "sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c", + "sha256:5b7328b46d49fc9477d91ae8f6d55340347d827b7734ba3ea33faae0efef1383", + "sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955", + "sha256:5bec7d03d78d853597d6107854c2310ce3f761fd218fe9fe91d5101fcf6c2efe", + "sha256:5c6bf403fbb3b3e348a561a5f4f0b9961835657981c802a1df03653eef8a9074", + "sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c", + "sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a", + "sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb", + "sha256:639f6c857d91d9be29bd7502348d6736dab168b54b5158cd899abf11684dc186", + "sha256:640f97d43d867bcb9c75b3af013b64850756b746cb6bce8ace83b70da3abba9d", + "sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1", + "sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f", + "sha256:6689e828a94eee4f139408c337bb198e014724bb8a8c26d3cfac49d119ed69a6", + "sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736", + "sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6", + "sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2", + "sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b", + "sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7", + "sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14", + "sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009", + "sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca", + "sha256:76447f65250ed2501ead1a1552f5ce8edff159a86f308348e6a9c4acb5e1f1b4", + "sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635", + "sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee", + "sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e", + "sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9", + "sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603", + "sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08", + "sha256:83b6b30eb131da7a75b601f28c5d6971e6ed3e887919bf6b6a1ad3c2df289080", + "sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525", + "sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5", + "sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f", + "sha256:88136950da4d13c318bde414ce10219931937851327f44328f2df4d2c4614067", + "sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e", + "sha256:8be8ad51249698103d24b0571df35a10990fbe93dd043b6c024172189485f5e3", + "sha256:8d43ca737b20e106e4aebc42b2f3ae19f00ba63d7eb731698ee083d72d15646f", + "sha256:8dadbe5b217ff35b6a8d16610dd710219b59b76d13f0e3f0d9f36786206e4485", + "sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13", + "sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383", + "sha256:98fc784c2c1440667aeedf8465bdfe10208acf0ead656a2c68627299f546b315", + "sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e", + "sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c", + "sha256:9f76acfb5f68ba982635a53fd985a8044be98a35b43232c2a1ee235ffab3e1dd", + "sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099", + "sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660", + "sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510", + "sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a", + "sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b", + "sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5", + "sha256:aae97dfdb60715c164419ac2532a76d013c3918a665eb6cb7288098b5f349aaf", + "sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28", + "sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00", + "sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef", + "sha256:add8cf6ddf9a65116119a28ece0f7886e30af27ba724a7594305f1d1b58a92a1", + "sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955", + "sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590", + "sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137", + "sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf", + "sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40", + "sha256:bdebcc8a75d38c7598dfb2c9ed852d7a9eb4a10d6e2d0764b919b802bf32ac88", + "sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e", + "sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840", + "sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2", + "sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca", + "sha256:c674693f055fa2495de12292cb45e9944199d8eaef5a2dec45175c7c61cb73e3", + "sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465", + "sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc", + "sha256:c9a4b821dc7055bf9e05ff5719e18ec501f75c0f0bbfabd573b277559780833d", + "sha256:c9f79d5325907f13e1be0b3e4dacc1049d1dffc4aeee3c995284bea5fe0fab7d", + "sha256:cd312b9692e831d2ffcad61eab31d91d4b4655a962e61de8fb410472cbcd37aa", + "sha256:cea3f4c1af79af13cdb2da0c028111d8f8522d4f22a000c82385535f24e5cf3a", + "sha256:cecdd5dfdc87b1fd87dbf81d4b037a544f47f4c744200a67013771682d67686a", + "sha256:cf9d57306d848218f3601fee7601fab1a327c942d56e2e97610583cb4dd74206", + "sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e", + "sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785", + "sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8", + "sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a", + "sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b", + "sha256:e07c65f443c887bbcf31cc1771d932ecc192a5273943589b3c7572b749f1ffb2", + "sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6", + "sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6", + "sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354", + "sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818", + "sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84", + "sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909", + "sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038", + "sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d", + "sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2", + "sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf", + "sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc", + "sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d", + "sha256:ffecec8eb889b58ba9be5b95fb1cc78e22ea8eedea38e8736a1568fe1979250e" ], "markers": "python_version >= '3.8'", - "version": "==6.0.2" + "version": "==6.1.1" }, "mako": { "hashes": [ - "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", - "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59" + "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9", + "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a" ], "markers": "python_version >= '3.8'", - "version": "==1.3.10" + "version": "==1.3.12" }, "markdown": { "hashes": [ @@ -1007,115 +1121,86 @@ }, "markdown-it-py": { "hashes": [ - "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", - "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" + "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", + "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a" ], "markers": "python_version >= '3.10'", - "version": "==4.0.0" + "version": "==4.2.0" }, "marko": { "hashes": [ - "sha256:6940308e655f63733ca518c47a68ec9510279dbb916c83616e4c4b5829f052e8", - "sha256:f064ae8c10416285ad1d96048dc11e98ef04e662d3342ae416f662b70aa7959e" + "sha256:8e1d7a0387281e59dfbc52a381b58c570156970e36b2bbe047f8a3a2f368cacc", + "sha256:e31ec2875383bc62f9093d16babed5a2c2cde601c00d834ea935a2222120ec19" ], "markers": "python_version >= '3.9'", - "version": "==2.2.2" + "version": "==2.2.3" }, "markupsafe": { "hashes": [ - "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", - "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", - "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", - "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", - "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", - "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", - "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", - "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", - "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", - "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", - "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", - "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", - "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", - "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", - "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", - "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", - "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", - "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", - "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", - "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", - "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", - "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", - "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", - "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", - "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", - "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", - "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", - "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", - "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", - "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", - "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", - "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", - "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", - "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", - "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", - "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", - "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", - "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", - "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", - "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", - "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", - "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", - "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", - "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", - "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", - "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", - "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", - "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", - "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", - "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", - "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", - "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", - "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", - "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", - "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", - "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", - "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", - "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", - "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", - "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", - "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", - "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", - "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", - "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", - "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", - "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", - "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", - "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", - "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", - "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", - "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", - "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", - "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", - "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", - "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", - "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", - "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", - "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", - "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", - "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", - "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", - "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", - "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", - "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", - "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", - "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", - "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", - "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", - "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50" + "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", + "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", + "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", + "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", + "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", + "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", + "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", + "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", + "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", + "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", + "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", + "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", + "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", + "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", + "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", + "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", + "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", + "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", + "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", + "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", + "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", + "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", + "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", + "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", + "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", + "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", + "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", + "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", + "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", + "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", + "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", + "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", + "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", + "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", + "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", + "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", + "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", + "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", + "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", + "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", + "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", + "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", + "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", + "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", + "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", + "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", + "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", + "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", + "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", + "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", + "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", + "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", + "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", + "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", + "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", + "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", + "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", + "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", + "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", + "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68" ], "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==3.0.3" + "markers": "python_version >= '3.7'", + "version": "==2.1.5" }, "mdurl": { "hashes": [ @@ -1170,47 +1255,53 @@ }, "mypy": { "hashes": [ - "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", - "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b", - "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", - "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", - "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", - "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", - "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac", - "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", - "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", - "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24", - "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", - "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", - "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e", - "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec", - "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3", - "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", - "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", - "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", - "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", - "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1", - "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", - "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67", - "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", - "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a", - "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", - "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", - "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376", - "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", - "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", - "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", - "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74", - "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", - "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", - "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6", - "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", - "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", - "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", - "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e" + "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", + "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", + "sha256:11a6beb180257a805961aea9ec591bbd0bd17f1e18d35b8456d57aee5bedfedc", + "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", + "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", + "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", + "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", + "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", + "sha256:47cebf61abde7c088a4e27718a8b13a81655686b2e9c251f5c0915a802248166", + "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", + "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", + "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", + "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", + "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", + "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", + "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", + "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", + "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", + "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", + "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", + "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", + "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", + "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", + "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", + "sha256:8ef78c1d306bbf9a8a12f526c44902c9c28dffd6c52c52bf6a72641ce18d3849", + "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", + "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", + "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", + "sha256:aea7f7a8a55b459c34275fc468ada6ca7c173a5e43a68f5dbe588a563d8a06b8", + "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", + "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", + "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", + "sha256:c209a90853081ff01d01ee895cafe10f7db1474e0d95beaeef0f6c1db9119bbd", + "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", + "sha256:c989640253f0d76843e9c6c1bbf4bd48c5e85ada61bde4beb37cb3eca035685e", + "sha256:d57a90ae5e872138a425ec328edbc9b235d1934c4377881a33ec05b341acc9a8", + "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", + "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", + "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", + "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", + "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", + "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", + "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", + "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780" ], - "markers": "python_version >= '3.9'", - "version": "==1.19.1" + "markers": "python_version >= '3.10'", + "version": "==2.1.0" }, "mypy-extensions": { "hashes": [ @@ -1322,11 +1413,11 @@ }, "pathspec": { "hashes": [ - "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", - "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723" + "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", + "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189" ], "markers": "python_version >= '3.9'", - "version": "==1.0.4" + "version": "==1.1.1" }, "petl": { "hashes": [ @@ -1338,12 +1429,12 @@ }, "pika": { "hashes": [ - "sha256:0779a7c1fafd805672796085560d290213a465e4f6f76a6fb19e378d8041a14f", - "sha256:b2a327ddddf8570b4965b3576ac77091b850262d34ce8c1d8cb4e4146aa4145f" + "sha256:2daae7bd422a0fc4f4879fd48c9a1932ed74a0bc7172e1e5f9bde63a101ed074", + "sha256:e851f3e4992adfbf8eb64e9b86d94e3382f92ba0200055abedbb29676b8e713b" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==1.3.2" + "version": "==1.4.1" }, "polib": { "hashes": [ @@ -1375,11 +1466,11 @@ }, "pyasn1": { "hashes": [ - "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", - "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b" + "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", + "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde" ], "markers": "python_version >= '3.8'", - "version": "==0.6.2" + "version": "==0.6.3" }, "pycountry": { "hashes": [ @@ -1512,11 +1603,11 @@ }, "pygments": { "hashes": [ - "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", - "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", + "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176" ], - "markers": "python_version >= '3.8'", - "version": "==2.19.2" + "markers": "python_version >= '3.9'", + "version": "==2.20.0" }, "pyjwt": { "hashes": [ @@ -1621,11 +1712,11 @@ }, "pytz": { "hashes": [ - "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", - "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00" + "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", + "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a" ], "index": "pypi", - "version": "==2025.2" + "version": "==2026.2" }, "pyyaml": { "hashes": [ @@ -1729,11 +1820,11 @@ }, "rich": { "hashes": [ - "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", - "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8" + "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", + "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36" ], - "markers": "python_full_version >= '3.8.0'", - "version": "==14.3.2" + "markers": "python_full_version >= '3.9.0'", + "version": "==15.0.0" }, "rq": { "hashes": [ @@ -1754,20 +1845,20 @@ }, "s3transfer": { "hashes": [ - "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", - "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920" + "sha256:239c13b09e65ad0346e1be7348b8a202dcad44ac7ea7c6eb858fc881dce739b6", + "sha256:3760b8b7ec1315da54048b2d626276732bee4300d054d492d4e1d43e20d4ecbd" ], - "markers": "python_version >= '3.9'", - "version": "==0.16.0" + "markers": "python_version >= '3.10'", + "version": "==0.18.0" }, "setuptools": { "hashes": [ - "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", - "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173" + "sha256:128ce7b8f33c3079fd1b067ecbb4051a66e8526e7b65f6cec075dfc650ddfa88", + "sha256:e147c0549f27767ba362f9da434eab9c5dc0045d5304feb602a0af001089fc51" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==80.10.2" + "version": "==79.0.1" }, "shellingham": { "hashes": [ @@ -1779,11 +1870,11 @@ }, "simpleeval": { "hashes": [ - "sha256:67bbf246040ac3b57c29cf048657b9cf31d4e7b9d6659684daa08ca8f1e45829", - "sha256:e3bdbb8c82c26297c9a153902d0fd1858a6c3774bf53ff4f134788c3f2035c38" + "sha256:1e10e5f9fec597814444e20c0892ed15162fa214c8a88f434b5b077cf2fef85b", + "sha256:97ac271bfd8f2af9e7b9a36ceea67617f26fa873f9d5ae1922f64d4c1442534b" ], "markers": "python_version >= '3.9'", - "version": "==1.0.3" + "version": "==1.0.7" }, "simplejson": { "hashes": [ @@ -1987,11 +2078,11 @@ }, "tabulate": { "hashes": [ - "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", - "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f" + "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", + "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3" ], - "markers": "python_version >= '3.7'", - "version": "==0.9.0" + "markers": "python_version >= '3.10'", + "version": "==0.10.0" }, "tabulator": { "hashes": [ @@ -2009,67 +2100,67 @@ }, "tomli": { "hashes": [ - "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", - "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", - "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", - "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", - "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", - "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", - "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", - "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", - "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", - "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", - "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", - "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", - "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", - "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", - "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", - "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", - "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", - "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", - "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", - "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", - "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", - "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", - "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", - "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", - "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", - "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", - "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", - "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", - "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", - "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", - "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", - "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", - "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", - "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", - "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", - "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", - "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", - "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", - "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", - "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", - "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", - "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", - "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", - "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", - "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", - "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", - "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087" + "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", + "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", + "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", + "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", + "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", + "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", + "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", + "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", + "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", + "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", + "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", + "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", + "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", + "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", + "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", + "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", + "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", + "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", + "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", + "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", + "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", + "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", + "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", + "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", + "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", + "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", + "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", + "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", + "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", + "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", + "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", + "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", + "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", + "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", + "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", + "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", + "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", + "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", + "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", + "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", + "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", + "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", + "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", + "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", + "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", + "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", + "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049" ], "markers": "python_version >= '3.8'", - "version": "==2.4.0" + "version": "==2.4.1" }, "typer": { "extras": [ "all" ], "hashes": [ - "sha256:7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01", - "sha256:ea835607cd752343b6b2b7ce676893e5a0324082268b48f27aa058bdb7d2145d" + "sha256:3e2b9352f535e5303ef27806dadc2c8647687bdca5c902f03fec3fb88f46a46a", + "sha256:e70549ec5a403ca8a0bf0802ddd9f3c6ff7a14ccbb859b01b697baa943636f33" ], - "markers": "python_version >= '3.9'", - "version": "==0.21.1" + "markers": "python_version >= '3.10'", + "version": "==0.26.3" }, "typing-extensions": { "hashes": [ @@ -2090,11 +2181,11 @@ }, "tzdata": { "hashes": [ - "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", - "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7" + "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", + "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7" ], "markers": "python_version >= '2'", - "version": "==2025.3" + "version": "==2026.2" }, "tzlocal": { "hashes": [ @@ -2113,11 +2204,11 @@ }, "urllib3": { "hashes": [ - "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", - "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4" + "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", + "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897" ], - "markers": "python_version >= '3.9'", - "version": "==2.6.3" + "markers": "python_version >= '3.10'", + "version": "==2.7.0" }, "validators": { "hashes": [ @@ -2200,11 +2291,11 @@ }, "wtforms": { "hashes": [ - "sha256:583bad77ba1dd7286463f21e11aa3043ca4869d03575921d1a1698d0715e0fd4", - "sha256:df3e6b70f3192e92623128123ec8dca3067df9cfadd43d59681e210cfb8d4682" + "sha256:72b90d5d921bd3119252069cf0301e9c13915f9e52792652bc91c5dda4b79e56", + "sha256:7b00c73f8670f35d4edb0293dcd81b980528bee72fd662b182aaba27ae570b93" ], - "markers": "python_version >= '3.9'", - "version": "==3.2.1" + "markers": "python_version >= '3.10'", + "version": "==3.2.2" }, "xlrd": { "hashes": [ @@ -2274,6 +2365,14 @@ "markers": "python_version >= '3.9'", "version": "==0.7.16" }, + "annotated-doc": { + "hashes": [ + "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", + "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4" + ], + "markers": "python_version >= '3.8'", + "version": "==0.0.4" + }, "arrow": { "hashes": [ "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", @@ -2310,10 +2409,11 @@ }, "binaryornot": { "hashes": [ - "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061", - "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4" + "sha256:900adfd5e1b821255ba7e63139b0396b14c88b9286e74e03b6f51e0200331337", + "sha256:cc8d57cfa71d74ff8c28a7726734d53a851d02fad9e3a5581fb807f989f702f0" ], - "version": "==0.4.4" + "markers": "python_version >= '3.10'", + "version": "==0.6.0" }, "blinker": { "hashes": [ @@ -2326,28 +2426,20 @@ }, "build": { "hashes": [ - "sha256:6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596", - "sha256:f1b91b925aa322be454f8330c6fb48b465da993d1e7e7e6fa35027ec49f3c936" + "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", + "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647" ], - "markers": "python_version >= '3.9'", - "version": "==1.4.0" + "markers": "python_version >= '3.10'", + "version": "==1.5.0" }, "certifi": { "hashes": [ - "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", - "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120" + "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", + "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2026.1.4" - }, - "chardet": { - "hashes": [ - "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", - "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970" - ], - "markers": "python_version >= '3.7'", - "version": "==5.2.0" + "version": "==2026.5.20" }, "charset-normalizer": { "hashes": [ @@ -2539,26 +2631,20 @@ }, "coveralls": { "hashes": [ - "sha256:3940f613eac6b3c14d1425741929e1d15f57666f5e7ae0572bbe92357bd6f7ee", - "sha256:7c21ffa2808d3052fa0cfca3842a9f3d21cc8eada02538c192d932199e5f07d4" + "sha256:bfacfda2d443c24fc90d67035027cec15015fff2dbd036427e8bf8f4953dda2e", + "sha256:dab364025ba80cbb95ce56c6fc62cd9172d7fd637060ea235dde99d9b46a4494" ], "index": "pypi", "markers": "python_version >= '3.10' and python_version < '4.0'", - "version": "==4.0.2" + "version": "==4.1.0" }, "decorator": { "hashes": [ - "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", - "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a" + "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", + "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c" ], "markers": "python_version >= '3.8'", - "version": "==5.2.1" - }, - "docopt": { - "hashes": [ - "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" - ], - "version": "==0.6.2" + "version": "==5.3.1" }, "docutils": { "hashes": [ @@ -2631,19 +2717,19 @@ }, "idna": { "hashes": [ - "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", - "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + "sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c", + "sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f" ], - "markers": "python_version >= '3.8'", - "version": "==3.11" + "markers": "python_version >= '3.9'", + "version": "==3.17" }, "imagesize": { "hashes": [ - "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", - "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a" + "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", + "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.4.1" + "markers": "python_version >= '3.10' and python_version < '3.15'", + "version": "==2.0.0" }, "incremental": { "hashes": [ @@ -2680,11 +2766,11 @@ }, "ipython": { "hashes": [ - "sha256:750162629d800ac65bb3b543a14e7a74b0e88063eac9b92124d4b2aa3f6d8e86", - "sha256:9cfea8c903ce0867cc2f23199ed8545eb741f3a69420bfcf3743ad1cec856d39" + "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", + "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f" ], "markers": "python_version >= '3.10'", - "version": "==8.38.0" + "version": "==8.39.0" }, "itsdangerous": { "hashes": [ @@ -2696,11 +2782,11 @@ }, "jedi": { "hashes": [ - "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", - "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9" + "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", + "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011" ], - "markers": "python_version >= '3.6'", - "version": "==0.19.2" + "markers": "python_version >= '3.10'", + "version": "==0.20.0" }, "jinja2": { "hashes": [ @@ -2728,115 +2814,86 @@ }, "markdown-it-py": { "hashes": [ - "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", - "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" + "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", + "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a" ], "markers": "python_version >= '3.10'", - "version": "==4.0.0" + "version": "==4.2.0" }, "markupsafe": { "hashes": [ - "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", - "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", - "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", - "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", - "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", - "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", - "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", - "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", - "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", - "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", - "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", - "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", - "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", - "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", - "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", - "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", - "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", - "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", - "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", - "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", - "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", - "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", - "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", - "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", - "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", - "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", - "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", - "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", - "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", - "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", - "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", - "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", - "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", - "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", - "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", - "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", - "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", - "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", - "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", - "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", - "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", - "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", - "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", - "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", - "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", - "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", - "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", - "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", - "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", - "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", - "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", - "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", - "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", - "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", - "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", - "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", - "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", - "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", - "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", - "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", - "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", - "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", - "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", - "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", - "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", - "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", - "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", - "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", - "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", - "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", - "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", - "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", - "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", - "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", - "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", - "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", - "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", - "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", - "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", - "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", - "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", - "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", - "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", - "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", - "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", - "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", - "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", - "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", - "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50" + "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", + "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", + "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", + "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", + "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", + "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", + "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", + "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", + "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", + "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", + "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", + "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", + "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", + "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", + "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", + "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", + "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", + "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", + "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", + "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", + "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", + "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", + "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", + "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", + "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", + "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", + "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", + "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", + "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", + "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", + "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", + "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", + "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", + "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", + "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", + "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", + "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", + "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", + "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", + "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", + "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", + "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", + "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", + "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", + "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", + "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", + "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", + "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", + "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", + "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", + "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", + "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", + "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", + "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", + "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", + "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", + "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", + "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", + "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", + "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68" ], "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==3.0.3" + "markers": "python_version >= '3.7'", + "version": "==2.1.5" }, "matplotlib-inline": { "hashes": [ - "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", - "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe" + "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", + "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79" ], "markers": "python_version >= '3.9'", - "version": "==0.2.1" + "version": "==0.2.2" }, "mdurl": { "hashes": [ @@ -2846,6 +2903,15 @@ "markers": "python_version >= '3.7'", "version": "==0.1.2" }, + "mock": { + "hashes": [ + "sha256:4e460e818629b4b173f32d08bf30d3af8123afbb8e04bb5707a1fd4799e503f0", + "sha256:7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==5.2.0" + }, "packaging": { "hashes": [ "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", @@ -2857,11 +2923,11 @@ }, "parso": { "hashes": [ - "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", - "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887" + "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", + "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1" ], "markers": "python_version >= '3.6'", - "version": "==0.8.5" + "version": "==0.8.7" }, "pexpect": { "hashes": [ @@ -2960,11 +3026,11 @@ }, "pip": { "hashes": [ - "sha256:bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b", - "sha256:c4037d8a277c89b320abe636d59f91e6d0922d08a05b60e85e53b296613346d8" + "sha256:99cb1c2899893b075ff56e4ed0af55669a955b49ad7fb8d8603ecdaf4ed653fb", + "sha256:d36762751d156a4ee895de8af39aa0abeeeb577f93a2eca6ab62467bbf0f8a78" ], - "markers": "python_version >= '3.9'", - "version": "==26.0.1" + "markers": "python_version >= '3.10'", + "version": "==26.1.1" }, "pip-tools": { "hashes": [ @@ -3005,13 +3071,22 @@ ], "version": "==0.2.3" }, + "pyfakefs": { + "hashes": [ + "sha256:6df12a7cf657637a1b036bc20059727c642f92990e90fee2fb003daa3cda6ca1", + "sha256:8959fe7058ba7efa65694b7979e123e27921a58f557a88628be93f0a936e6757" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==4.6.3" + }, "pygments": { "hashes": [ - "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", - "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", + "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176" ], - "markers": "python_version >= '3.8'", - "version": "==2.19.2" + "markers": "python_version >= '3.9'", + "version": "==2.20.0" }, "pyproject-hooks": { "hashes": [ @@ -3188,20 +3263,28 @@ }, "rich": { "hashes": [ - "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", - "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8" + "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", + "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36" ], - "markers": "python_full_version >= '3.8.0'", - "version": "==14.3.2" + "markers": "python_full_version >= '3.9.0'", + "version": "==15.0.0" }, "setuptools": { "hashes": [ - "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", - "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173" + "sha256:128ce7b8f33c3079fd1b067ecbb4051a66e8526e7b65f6cec075dfc650ddfa88", + "sha256:e147c0549f27767ba362f9da434eab9c5dc0045d5304feb602a0af001089fc51" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==80.10.2" + "version": "==79.0.1" + }, + "shellingham": { + "hashes": [ + "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", + "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" + ], + "markers": "python_version >= '3.7'", + "version": "==1.5.4" }, "six": { "hashes": [ @@ -3214,19 +3297,19 @@ }, "snowballstemmer": { "hashes": [ - "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", - "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895" + "sha256:17e6d1da216aa07db6dad37139ea70cf13c4b2e9a096f6e64a9648fc657d3154", + "sha256:fd9e34526b23340cd23ffea6c9f9760974ecc2c2ac9e1d81401443ccdb2a801f" ], - "markers": "python_version not in '3.0, 3.1, 3.2'", - "version": "==3.0.1" + "markers": "python_version >= '3.3'", + "version": "==3.1.0" }, "soupsieve": { "hashes": [ - "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", - "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95" + "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", + "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65" ], "markers": "python_version >= '3.9'", - "version": "==2.8.3" + "version": "==2.8.4" }, "sphinx": { "hashes": [ @@ -3327,56 +3410,56 @@ }, "tomli": { "hashes": [ - "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", - "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", - "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", - "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", - "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", - "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", - "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", - "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", - "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", - "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", - "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", - "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", - "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", - "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", - "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", - "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", - "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", - "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", - "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", - "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", - "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", - "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", - "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", - "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", - "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", - "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", - "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", - "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", - "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", - "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", - "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", - "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", - "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", - "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", - "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", - "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", - "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", - "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", - "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", - "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", - "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", - "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", - "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", - "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", - "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", - "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", - "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087" + "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", + "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", + "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", + "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", + "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", + "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", + "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", + "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", + "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", + "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", + "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", + "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", + "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", + "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", + "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", + "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", + "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", + "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", + "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", + "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", + "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", + "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", + "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", + "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", + "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", + "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", + "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", + "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", + "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", + "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", + "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", + "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", + "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", + "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", + "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", + "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", + "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", + "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", + "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", + "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", + "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", + "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", + "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", + "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", + "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", + "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", + "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049" ], "markers": "python_version >= '3.8'", - "version": "==2.4.0" + "version": "==2.4.1" }, "towncrier": { "hashes": [ @@ -3389,11 +3472,22 @@ }, "traitlets": { "hashes": [ - "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", - "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f" + "sha256:4fead733f81cf1c4c938e06f8ca4633896833c9d89eff878159457f4d4392971", + "sha256:fb36a18867a6803deab09f3c5e0fa81bb7b26a5c9e82501c9933f759166eff40" ], - "markers": "python_version >= '3.8'", - "version": "==5.14.3" + "markers": "python_version >= '3.9'", + "version": "==5.15.0" + }, + "typer": { + "extras": [ + "all" + ], + "hashes": [ + "sha256:3e2b9352f535e5303ef27806dadc2c8647687bdca5c902f03fec3fb88f46a46a", + "sha256:e70549ec5a403ca8a0bf0802ddd9f3c6ff7a14ccbb859b01b697baa943636f33" + ], + "markers": "python_version >= '3.10'", + "version": "==0.26.3" }, "typing-extensions": { "hashes": [ @@ -3406,27 +3500,27 @@ }, "tzdata": { "hashes": [ - "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", - "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7" + "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", + "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7" ], "markers": "python_version >= '2'", - "version": "==2025.3" + "version": "==2026.2" }, "urllib3": { "hashes": [ - "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", - "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4" + "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", + "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897" ], - "markers": "python_version >= '3.9'", - "version": "==2.6.3" + "markers": "python_version >= '3.10'", + "version": "==2.7.0" }, "wcwidth": { "hashes": [ - "sha256:53123b7af053c74e9fe2e92ac810301f6139e64379031f7124574212fb3b4091", - "sha256:d584eff31cd4753e1e5ff6c12e1edfdb324c995713f75d26c29807bb84bf649e" + "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2", + "sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0" ], "markers": "python_version >= '3.8'", - "version": "==0.5.3" + "version": "==0.7.0" }, "werkzeug": { "extras": [ @@ -3441,11 +3535,11 @@ }, "wheel": { "hashes": [ - "sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d", - "sha256:e3e79874b07d776c40bd6033f8ddf76a7dad46a7b8aa1b2787a83083519a1803" + "sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced", + "sha256:cc72bd1009ba0cf63922e28f94d9d83b920aa2bb28f798a31d0691b02fa3c9b3" ], "markers": "python_version >= '3.9'", - "version": "==0.46.3" + "version": "==0.47.0" } } } diff --git a/README.md b/README.md index 686d4102..620cdc13 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ To create and setup the test databases: adx testsetup ``` -Tests should be run with the version of nosetests-2.7 installed in CKAN's virtual environment. There is an alias set up inside the docker container called "ckan-nosetests" that points to this executable. +Extensions are tested with pytest. The `adx test` command wraps `ckan-pytest` (an alias inside the container pointing to pytest in CKAN's virtual environment) and runs it against the extension's `test.ini`: ```shell adx test extension_name @@ -177,16 +177,16 @@ e.g. adx test restricted ``` -To run specific test case you can you -k pytest arg: +To run a specific test case you can use pytest's -k arg: ```shell adx test restricted -k test_regression_example ``` -To run the ckan core tests: +To run the ckan core tests against the mounted `test-core.ini`: ```shell -docker exec -it ckan ckan-nosetests --ckan --with-pylons=/usr/lib/ckan/venv/src/ckan/test-core.ini ckan ckanext +docker exec -it ckan ckan-pytest --ckan-ini=/etc/ckan/test-core.ini --pyargs ckan ``` ## Debugging with ipdb diff --git a/ci_setup.sh b/ci_setup.sh deleted file mode 100755 index a1d245af..00000000 --- a/ci_setup.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env bash - -# Remove all containers and volumes -echo "Docker cleanup" -docker-compose down --rmi all -v --remove-orphans -sudo docker volume prune -f - -if [ -v "$CHANGE_ID" ] -then - BRANCH="$GIT_BRANCH" -else - BRANCH="origin/pr/$CHANGE_ID" -fi -echo "CHANGE_ID: ${CHANGE_ID}" - -# check if custom ckan image should be built -git diff -s --exit-code ./ckan -if [ $? == 1 ]; then - CKAN_IMAGE_TAG="$CHANGE_BRANCH" - export CKAN_IMAGE_TAG - echo "CKAN_IMAGE_TAG: ${CKAN_IMAGE_TAG}" -fi - -echo "Preparing environment" -cd ../ -# add adx script to PATH -export PATH=$WORKSPACE/adx_develop/:$PATH -# disable db restart during test setup -export SKIP_DB_RESTART=True -# prepare environment -cp "$WORKSPACE"/adx_develop/dev.env "$WORKSPACE"/adx_develop/.env -# Init submodules -adx init -# Setup environment -yes | adx setup -git fetch origin +refs/pull/*/merge:refs/remotes/origin/pr/* && git checkout "${BRANCH}" -cd "$WORKSPACE"/adx_develop/ && git fetch origin +refs/pull/*/merge:refs/remotes/origin/pr/* && git checkout "${BRANCH}" -cd "$WORKSPACE" || exit -echo "running ./adx build" -adx build -echo "running ./adx up" -adx up -echo "Running ./adx testsetup" -adx testsetup -echo "Show docker-compose containers" -adx dc ps - -echo "Waiting for CKAN container" -counter=0 -while ! docker logs ckan |grep 'CKAN bootstrapping finished, environment ready'; - do - ((counter=counter+1)) - if [ $counter -ge 80 ]; then - echo "This is taking too long, break!" - echo "Some logs first:" - echo "CKAN container logs:" - docker logs ckan - echo "DB container logs:" - docker logs db - echo "List of containers:" - adx dc ps - exit 1 - fi - echo "Bootstraping not finished, pass $counter" - sleep 10 - done diff --git a/ci_test.sh b/ci_test.sh deleted file mode 100755 index 8fec6f23..00000000 --- a/ci_test.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash - -# Run tests with set -e - exit on error -# set -e - -# add adx script to PATH -export PATH=$WORKSPACE/adx_develop/:$PATH - -# check if custom ckan image should be built -git diff -s --exit-code ./ckan -if [ $? == 1 ]; then - CKAN_IMAGE_TAG="$CHANGE_BRANCH" - export CKAN_IMAGE_TAG -fi - -error(){ - echo "CKAN ${1} test did fail, check logs, docker output below:" - echo "*** CKAN container logs start ***" - sudo docker logs ckan - echo "*** CKAN container logs end ***" - echo "DB container logs:" - docker logs db - return 1 -} - - -run_adx_test(){ - echo "Running tests for CKAN ${1}" - adx test "${1}" --no-interaction - retVal=$? - echo "Exit code: ${retVal}" - if [ ${retVal} -ne 0 ]; then - error "${1}" - fi -} - - -run_adx_test "${1}" diff --git a/ckan/ckan_supervisor.conf b/ckan/ckan_supervisor.conf index 7b171b91..3da04dbf 100644 --- a/ckan/ckan_supervisor.conf +++ b/ckan/ckan_supervisor.conf @@ -5,7 +5,7 @@ file=/tmp/supervisor.sock [supervisord] nodaemon=true -logfile=/dev/fd/1 +logfile=/dev/stdout logfile_maxbytes=0 pidfile=/tmp/supervisord.pid @@ -17,41 +17,41 @@ serverurl=unix:///tmp/supervisor.sock ; =============================== [program:ckan_gather_consumer] -command=/usr/local/bin/ckan --config=/etc/ckan/ckan.ini harvester gather-consumer +command=%(ENV_CKAN_VENV)s/bin/ckan --config=%(ENV_CONFIG)s harvester gather-consumer ; user that owns virtual environment. user=root -stdout_logfile=/dev/fd/1 +stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 -stderr_logfile=/dev/fd/2 +stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 autostart=true autorestart=true [program:ckan_fetch_consumer] -command=/usr/local/bin/ckan --config=/etc/ckan/ckan.ini harvester fetch-consumer +command=%(ENV_CKAN_VENV)s/bin/ckan --config=%(ENV_CONFIG)s harvester fetch-consumer ; user that owns virtual environment. user=root -stdout_logfile=/dev/fd/1 +stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 -stderr_logfile=/dev/fd/2 +stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 autostart=true autorestart=true [program:ckan_harvester_run] -command=/bin/bash -c "sleep 2m && /usr/local/bin/ckan --config=/etc/ckan/ckan.ini harvester run " +command=/bin/bash -c "sleep 2m && %(ENV_CKAN_VENV)s/bin/ckan --config=%(ENV_CONFIG)s harvester run " ; user that owns virtual environment. user=root -stdout_logfile=/dev/fd/1 +stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 -stderr_logfile=/dev/fd/2 +stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 autostart=true autorestart=true @@ -63,11 +63,11 @@ autorestart=true [program:ckan-worker] ; Use the full paths to the virtualenv and your configuration file here. -command=/usr/local/bin/ckan -c /etc/ckan/ckan.ini jobs worker +command=%(ENV_CKAN_VENV)s/bin/ckan -c %(ENV_CONFIG)s jobs worker ; User the worker runs as. -user=ckan +user=root ; Start just a single worker. Increase this number if you have many or @@ -77,9 +77,9 @@ process_name=%(program_name)s-%(process_num)02d ; Log files. -stdout_logfile=/dev/fd/1 +stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 -stderr_logfile=/dev/fd/2 +stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 ; Make sure that the worker is started on system start and automatically @@ -95,3 +95,31 @@ startsecs=10 ; Need to wait for currently executing tasks to finish at shutdown. ; Increase this if you have very long running tasks. stopwaitsecs = 600 + +; =============================== +; nginx reverse proxy +; =============================== +[program:nginx] +command=/usr/sbin/nginx -g "daemon off;" +user=root +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autostart=true +autorestart=true +priority=10 + +; =============================== +; uWSGI CKAN application +; =============================== +[program:uwsgi] +command=/usr/local/bin/uwsgi --ini /usr/lib/adx/uwsgi.ini +user=root +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autostart=true +autorestart=true +priority=20 diff --git a/ckan/container_build_and_push.sh b/ckan/container_build_and_push.sh deleted file mode 100755 index c91a115c..00000000 --- a/ckan/container_build_and_push.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -docker build -t fjelltopp/ckan:development_base -f ckan/Dockerfile ./ckan/ && \ -docker push fjelltopp/ckan:development_base diff --git a/deploy/Dockerfile.prod b/deploy/Dockerfile.prod new file mode 100644 index 00000000..d42c6fbd --- /dev/null +++ b/deploy/Dockerfile.prod @@ -0,0 +1,96 @@ +FROM python:3.10 + +# OCI Annotations +LABEL org.opencontainers.image.maintainer="support@fjelltopp.org" +LABEL org.opencontainers.image.title="CKAN-ADX" +LABEL org.opencontainers.image.description="Fjelltopp's ADX CKAN production image" + +ARG CKAN_SITE_URL + +# Set timezone +ENV TZ=UTC +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# Setting the locale +ENV LC_ALL=en_US.UTF-8 +RUN apt-get update && \ + apt-get install --no-install-recommends -y locales && \ + sed -i "/$LC_ALL/s/^# //g" /etc/locale.gen && \ + dpkg-reconfigure --frontend=noninteractive locales && \ + update-locale LANG=${LC_ALL} + +# Define environment variables +ENV CKAN_HOME /usr/lib/adx +ENV CKAN_VENV $CKAN_HOME/venv +ENV CKAN_CONFIG /etc/ckan +ENV CKAN_STORAGE_PATH=/var/lib/ckan +ENV PATH=${CKAN_VENV}/bin:${PATH} + +# Install required system packages +RUN apt-get -q -y update \ + && apt-get -q -y install \ + libpq-dev \ + libmagic-dev \ + libxml2-dev \ + libxslt-dev \ + libgeos-dev \ + libssl-dev \ + libffi-dev \ + postgresql-client \ + build-essential \ + git-core \ + vim \ + wget \ + curl \ + xmlsec1 \ + jq \ + supervisor \ + nginx \ + && apt-get -q clean \ + && rm -rf /var/lib/apt/lists/* + +# Add CKAN user +RUN useradd -r -u 900 -m -c "ckan account" -d $CKAN_HOME -s /bin/false ckan + +# Install pipenv and uwsgi +RUN pip3 install pipenv uwsgi + +# Copy submodules and Pipfile +COPY submodules /usr/lib/adx/submodules +COPY Pipfile Pipfile.lock /usr/lib/adx/ + +# Install Python dependencies +WORKDIR /usr/lib/adx +RUN mkdir .venv && pipenv sync -v && ln -s .venv venv + +# Install Node.js and yarn for React build +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ + apt-get install -y nodejs && \ + npm install --global yarn + +# Build React components +RUN yarn --cwd /usr/lib/adx/submodules/ckanext-unaids/ckanext/unaids/react/ && \ + yarn --cwd /usr/lib/adx/submodules/ckanext-unaids/ckanext/unaids/react/ build && \ + chmod -R 777 /usr/lib/adx/submodules/ckanext-unaids/ckanext/unaids/assets/build + +RUN chown -R ckan:ckan /usr/lib/adx/ +RUN mkdir -p /var/lib/ckan/resources && chmod 777 -R /var/lib/ckan + +# Create symlink for backward compatibility with configs that reference /usr/lib/ckan +RUN ln -s /usr/lib/adx /usr/lib/ckan + +# Copy entrypoint and config files +COPY deploy/ckan-entrypoint-prod.sh /ckan-entrypoint.sh +COPY deploy/base.ini $CKAN_CONFIG/base.ini +COPY ckan/adx_who.ini $CKAN_CONFIG/who.ini +COPY ckan/ckan_supervisor.conf /etc/supervisor/conf.d/ckan_supervisor.conf +COPY deploy/uwsgi.ini /usr/lib/adx/uwsgi.ini +COPY deploy/nginx.conf /etc/nginx/nginx.conf + +RUN chmod +x /*.sh + +USER root +EXPOSE 5000 + +ENTRYPOINT ["/ckan-entrypoint.sh"] +CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"] diff --git a/deploy/base.ini b/deploy/base.ini new file mode 100644 index 00000000..9e02371c --- /dev/null +++ b/deploy/base.ini @@ -0,0 +1,355 @@ +# +# CKAN - Pylons configuration +# +# These are some of the configuration options available for your CKAN +# instance. Check the documentation in 'doc/configuration.rst' or at the +# following URL for a description of what they do and the full list of +# available options: +# +# http://docs.ckan.org/en/latest/maintaining/configuration.html +# +# The %(here)s variable will be replaced with the parent directory of this file +# + +[DEFAULT] + +# WARNING: *THIS SETTING MUST BE SET TO FALSE ON A PUBLIC ENVIRONMENT* +# With debug mode enabled, a visitor to your site could execute malicious commands. +debug = false + +[app:main] +use = egg:ckan + +## Development settings +ckan.devserver.host = localhost +ckan.devserver.port = 5000 + + +## CKAN <2.11 Session settings +cache_dir = /tmp/%(ckan.site_id)s/ +beaker.session.key = ckan +beaker.session.type = ext:redis +beaker.session.url = redis://redis:6379/3 +beaker.session.cookie_expires = 86400 +beaker.session.timeout = 86400 +# This is the secret token that the beaker library uses to hash the cookie sent +# to the client. `ckan generate config` generates a unique value for this each +# time it generates a config file. +# NOTE: Must be set directly - CKAN 2.11 doesn't support separate secrets.ini loading +beaker.session.secret = ${BEAKER_SESSION_SECRET} + +## CKAN >=2.11 Session settings +## Using default cookie session storage until this issue is resolved: +## https://github.com/ckan/ckan/issues/8547 +## SESSION_TYPE = redis +## SESSION_COOKIE_NAME = ckan +## SESSION_PERMANENT = true +## PERMANENT_SESSION_LIFETIME = 86400 +# The secret token that is used for session management and other security related tasks as well +SECRET_KEY = ${SECRET_KEY} + +# CSRF Protection - enabled by default (WTF_CSRF_ENABLED = True) +# FileUploader in ckanext-unaids sends X-CSRFToken header + +# `ckan generate config` generates a unique value for this each time it generates +# a config file. +app_instance_uuid = 853f1fc8-aba1-411e-9071-44e3cdce64dd + +# repoze.who config +who.config_file = %(here)s/who.ini +who.log_level = warning +who.log_file = %(cache_dir)s/who_log.ini +# Session timeout (user logged out after period of inactivity, in seconds). +# Inactive by default, so the session doesn't expire. +# who.timeout = 86400 + +# PostgreSQL' full-text search parameters +ckan.datastore.default_fts_lang = english +ckan.datastore.default_fts_index_method = gist + +## Site Settings + +# ckan.site_url = # configured with env CKAN_SITE_URL +# ckan.use_pylons_response_cleanup_middleware = true +ckan.resource_formats = /usr/lib/ckan/submodules/ckanext-unaids/ckanext/unaids/resource_formats.json + +## Upload Settings +ckan.upload.user.types = image +ckan.upload.user.mimetypes = image/png image/bmp image/jpeg image/svg+xml + +## Authorization Settings +ckan.auth.anon_create_dataset = false +ckan.auth.create_unowned_dataset = false +ckan.auth.create_dataset_if_not_in_organization = false +ckan.auth.user_create_groups = false +ckan.auth.user_create_organizations = false +ckan.auth.user_delete_groups = true +ckan.auth.user_delete_organizations = true +ckan.auth.create_user_via_api = false +ckan.auth.create_user_via_web = true +ckan.auth.roles_that_cascade_to_sub_groups = admin +ckan.auth.public_user_details = true +ckan.auth.public_activity_stream_detail = true +ckan.auth.allow_dataset_collaborators = true +ckan.auth.allow_admin_collaborators = true +ckan.auth.create_default_api_keys = false + +## API Token Settings +api_token.nbytes = 60 +# NOTE: string: prefix is required - CKAN treats bare values as filenames +# These must be set directly (CKAN 2.11 doesn't load secrets.ini separately) +api_token.jwt.encode.secret = string:${JWT_SECRET} +api_token.jwt.decode.secret = string:${JWT_SECRET} +api_token.jwt.algorithm = HS256 + +## API Token: expire_api_token plugin +expire_api_token.default_lifetime = 3600 + +## Search Settings + +ckan.site_id = default +#solr_url = http://127.0.0.1:8983/solr +solr_timeout = 60 + + +## Redis Settings + +# URL to your Redis instance, including the database to be used. +# ckan.redis.url = redis://localhost:6379/0 # Fjelltopp sets through ENV VAR + + +## CORS Settings + +# If cors.origin_allow_all is true, all origins are allowed. +# If false, the cors.origin_whitelist is used. +# ckan.cors.origin_allow_all = true +# cors.origin_whitelist is a space separated list of allowed domains. +# ckan.cors.origin_whitelist = http://example1.com http://example2.com + +## Cache settings that are set by CKAN core +ckan.cache_enabled = true +ckan.cache_expires = 604800 +ckan.static_max_age = 604800 + +## Plugins Settings + +ckanext.emailasusername.auto_generate_username_from_fullname = True +ckanext.emailasusername.require_user_email_input_confirmation = False + +# Note: Add ``datastore`` to enable the CKAN DataStore +# Add ``datapusher`` to enable DataPusher +# Add ``resource_proxy`` to enable resorce proxying and get around the +# same origin policy +ckan.plugins = activity unaids fork scheming_datasets blob_storage saml2auth emailasusername restricted authz_service stats resource_proxy datastore datapusher validation ytp_request pages dhis2harvester_plugin dhis2_pivot_tables_harvester harvest versions + text_view image_view datatables_view geo_view geojson_view auth + +# Giftless/LFS server URL - set in env.ini (staging.ini or production.ini) +# ckanext.blob_storage.storage_service_url = (from env.ini) + +ckanext.authz_service.jwt_private_key_file = /etc/ckan/jwt-rs256.key +ckanext.authz_service.jwt_public_key_file = /etc/ckan/jwt-rs256.key.pub + +# Define which views should be created by default +# (plugins must be loaded in ckan.plugins) +### make sure plugins is updated for this to work +ckan.views.default_views = datatables_view geojson_view pdf_view image_view text_view officedocs_view + +# Customize which text formats the text_view plugin will show +ckan.preview.json_formats = json +#ckan.preview.xml_formats = xml rdf rdf+xml owl+xml atom rss +#ckan.preview.text_formats = txt plain text/plain + +# Customize which image formats the image_view plugin will show +#ckan.preview.image_formats = png jpeg jpg gif + +## Front-End Settings + +ckan.site_title = The AIDS Data Repository +ckan.site_logo = /adr_simple.png +ckan.site_description = Tools to streamline the process of managing and sharing HIV data. +ckan.featured_orgs = unaids uganda +ckan.favicon = /images/favicon.ico +ckan.gravatar_default = identicon +ckan.preview.direct = png jpg gif +ckan.preview.loadable = html htm rdf+xml owl+xml xml n3 n-triples turtle plain atom csv tsv rss txt json +ckan.display_timezone = server + +# package_hide_extras = for_search_index_only +#package_edit_return_url = http://another.frontend/dataset/ +#package_new_return_url = http://another.frontend/dataset/ +#licenses_group_url = http://licenses.opendefinition.org/licenses/groups/ckan.json +# ckan.template_footer_end = + + +## Internationalisation Settings +ckan.locale_default = en +ckan.locale_order = en fr pt_PT +ckan.locales_offered = en fr pt_PT +ckan.locales_filtered_out = en_GB + +## Feeds Settings + +ckan.feeds.authority_name = +ckan.feeds.date = +ckan.feeds.author_name = +ckan.feeds.author_link = + +## Storage Settings + +#ckan.storage_path = /var/lib/ckan +ckan.max_resource_size = 100 +#ckan.max_image_size = 2 + +## Webassets Settings +ckan.webassets.use_x_sendfile = true +ckan.webassets.path = /var/lib/ckan/webassets + + +## Datapusher settings + +# Make sure you have set up the DataStore + +ckan.datapusher.formats = csv xls xlsx tsv application/csv application/vnd.ms-excel application/vnd.openxmlformats-officedocument.spreadsheetml.sheet geojson +ckan.datapusher.url = http://datapusher:8800 +# Generate a real token: ckan -c /etc/ckan/production.ini user token add admin datapusher +ckan.datapusher.api_token = ${CKAN_DATAPUSHER_API_TOKEN} +ckan.datapusher.callback_url_base = http://ckan:5000/ +ckan.datapusher.assume_task_stale_after = 3600 + +# Resource Proxy settings +# Preview size limit, default: 1MB +#ckan.resource_proxy.max_file_size = 1048576 +# Size of chunks to read/write. +#ckan.resource_proxy.chunk_size = 4096 + +## Activity Streams Settings + +ckan.activity_streams_enabled = true +ckan.activity_list_limit = 31 +ckan.activity_streams_email_notifications = false +ckan.email_notifications_since = 2 days +ckan.hide_activity_from_users = %(ckan.site_id)s + +## Email settings +# email_to triggers CKAN's error mail handler, which has a bug in 2.11 +# (ContextualFilter accesses request.path during startup log outside request context). +# Uncomment when SMTP is configured and CKAN bug is fixed. +# email_to = support@fjelltopp.org + + +## Background Job Settings + +# Additional CKAN settings for extensions, configurable via group_vars +# ckan.route_after_login = validate_user_affiliation.check_user_affiliation +ckan.jobs.timeout = 500 +ckan.datasets_per_page = 70 +ckan.group_and_organization_list_all_fields_max = 1000 +ckan.group_and_organization_list_max = 1000 + +# Scheming +scheming.dataset_schemas_directory = /usr/lib/ckan/submodules/unaids_data_specifications/package_schemas +scheming.presets = ckanext.unaids:presets.json + ckanext.scheming:presets.json + ckanext.validation:presets.json + +# Blob storage +ckanext.blob_storage.use_scheming_file_uploader = True + +# Validation +ckanext.unaids.schema_directory = /usr/lib/ckan/submodules/unaids_data_specifications/table_schemas +ckanext.validation.run_on_create_async = True +ckanext.validation.run_on_update_async = True +ckanext.validation.run_on_create_sync = False +ckanext.validation.run_on_update_sync = False +ckanext.validation.allow_invalid_data = True +ckanext.validation.formats = csv xlsx xls geojson +ckanext.validation.default_validation_options = {"limit_rows": 50000, "limit_errors": 100, "skip_errors": ["extra-header"], "schema_sync": true} + +# Email as username +emailasusername.search_by_username_and_email = True + +# Pages/CMS +ckanext.pages.editor = ckeditor +ckanext.pages.allow_html = True +ckanext.pages.about_menu = True +ckanext.pages.group_menu = False +ckanext.pages.blog = False + +# Harvest +ckan.harvest.mq.hostname = redis +ckan.harvest.mq.type = redis +ckan.harvest.mq.port = 6379 +ckan.harvest.mq.redis_db = 2 + +# Spatial +ckanext.spatial.common_map.type = custom +ckanext.spatial.common_map.custom.url = https://tile.openstreetmap.org/{z}/{x}/{y}.png +ckanext.spatial.common_map.attribution = © OpenStreetMap contributors + +# SAML2.0 - Auth0 IDP +ckanext.saml2auth.idp_metadata.location = remote +ckanext.saml2auth.idp_metadata.remote_url = https://dev-udfgla0l.eu.auth0.com/samlp/metadata/txdzFtnQXamucc3PhJGIoqxoPnRxZY6K +ckanext.saml2auth.idp_metadata.remote_cert = /tmp/saml_idp.crt +ckanext.saml2auth.user_fullname = http://schemas.xmlsoap.org/ws/2005/05/identity/claims/fullname +ckanext.saml2auth.user_email = http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress +ckanext.saml2auth.want_response_signed = False +ckanext.saml2auth.want_assertions_signed = True +ckanext.saml2auth.logout_requests_signed = False + +# OAuth2 +ckanext.unaids.oauth2_required_scope = access:adr + +# Recaptcha - DISABLED for staging +# ckan.recaptcha.publickey = +# ckan.recaptcha.privatekey = + +## Settings configured via environment variables (not in any .ini file) +# +# sqlalchemy.url ← CKAN_SQLALCHEMY_URL (ckan-secrets) +# ckan.site_url ← CKAN_SITE_URL +# solr_url ← CKAN_SOLR_URL +# ckan.redis.url ← CKAN_REDIS_URL +# ckan.datastore.write_url ← CKAN_DATASTORE_WRITE_URL (ckan-secrets) +# ckan.datastore.read_url ← CKAN_DATASTORE_READ_URL (ckan-secrets) +# ckan.max_resource_size ← CKAN_MAX_UPLOAD_SIZE_MB + +## Logging configuration +[loggers] +keys = root, ckan, ckanext, werkzeug + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console + +[logger_werkzeug] +level = WARNING +handlers = console +qualname = werkzeug +propagate = 0 + +[logger_ckan] +level = INFO +handlers = console +qualname = ckan +propagate = 0 + +[logger_ckanext] +level = DEBUG +handlers = console +qualname = ckanext +propagate = 0 + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s diff --git a/deploy/ckan-entrypoint-prod.sh b/deploy/ckan-entrypoint-prod.sh new file mode 100644 index 00000000..661d087e --- /dev/null +++ b/deploy/ckan-entrypoint-prod.sh @@ -0,0 +1,92 @@ +#!/bin/bash +set -e + +# URL for the primary database, in the format expected by sqlalchemy +: "${CKAN_SQLALCHEMY_URL:=}" +: "${CKAN_SOLR_URL:=}" +: "${CKAN_REDIS_URL:=}" +: "${CKAN_DATAPUSHER_URL:=}" + +export CKAN_HOME=/usr/lib/adx +export CKAN_VENV=$CKAN_HOME/venv +export PATH=${CKAN_VENV}/bin:${PATH} + +# Combine config files using Python ConfigParser +# Order: base.ini < env.ini < secrets.ini (later values override earlier) +echo "Combining configuration files..." +python3 << 'PYEOF' +import configparser +import os + +config = configparser.ConfigParser() +config.read('/etc/ckan/base.ini') +if os.path.exists('/etc/ckan/env.ini'): + config.read('/etc/ckan/env.ini') # Environment-specific overrides +config.read('/etc/ckan/secrets.ini') # Secrets override all + +with open('/tmp/production.ini', 'w') as f: + config.write(f) +PYEOF +export CONFIG="/tmp/production.ini" +export CKAN_INI="/tmp/production.ini" + +abort () { + echo "$@" >&2 + exit 1 +} + +set_environment () { + export CKAN_SITE_ID=${CKAN_SITE_ID} + export CKAN_SITE_URL=${CKAN_SITE_URL} + export CKAN_SQLALCHEMY_URL=${CKAN_SQLALCHEMY_URL} + export CKAN_SOLR_URL=${CKAN_SOLR_URL} + export CKAN_REDIS_URL=${CKAN_REDIS_URL} + export CKAN_STORAGE_PATH=/var/lib/ckan + export CKAN_DATAPUSHER_URL=${CKAN_DATAPUSHER_URL} + export CKAN_DATASTORE_WRITE_URL=${CKAN_DATASTORE_WRITE_URL} + export CKAN_DATASTORE_READ_URL=${CKAN_DATASTORE_READ_URL} + export CKAN_SMTP_SERVER=${CKAN_SMTP_SERVER} + export CKAN_SMTP_STARTTLS=${CKAN_SMTP_STARTTLS} + export CKAN_SMTP_USER=${CKAN_SMTP_USER} + export CKAN_SMTP_PASSWORD=${CKAN_SMTP_PASSWORD} + export CKAN_SMTP_MAIL_FROM=${CKAN_SMTP_MAIL_FROM} + export CKAN_MAX_UPLOAD_SIZE_MB=${CKAN_MAX_UPLOAD_SIZE_MB} + if [ -n "${ADR_CKAN_SAML_IDP_CERT}" ]; then + echo "${ADR_CKAN_SAML_IDP_CERT}" > /tmp/saml_idp.crt || echo "Warning: Could not write SAML IDP cert" + fi +} + +# Validate required environment variables +if [ -z "$CKAN_SQLALCHEMY_URL" ]; then + abort "ERROR: no CKAN_SQLALCHEMY_URL specified" +fi + +if [ -z "$CKAN_SOLR_URL" ]; then + abort "ERROR: no CKAN_SOLR_URL specified" +fi + +if [ -z "$CKAN_REDIS_URL" ]; then + abort "ERROR: no CKAN_REDIS_URL specified" +fi + +if [ -z "$CKAN_DATAPUSHER_URL" ]; then + abort "ERROR: no CKAN_DATAPUSHER_URL specified" +fi + +set_environment +echo "CKAN production environment ready" + +# Initialize CKAN database and run plugin migrations +echo "Initializing CKAN database..." +ckan --config="$CONFIG" db init || echo "CKAN database already initialized" + +echo "Setting up DataStore permissions..." +ckan --config="$CONFIG" datastore set-permissions | psql "${CKAN_DATASTORE_WRITE_URL}" || echo "Warning: DataStore set-permissions failed or already applied" + +echo "Running database migrations for plugins..." +ckan --config="$CONFIG" db upgrade -p pages || echo "Warning: ckanext-pages migration failed or already applied" +# ckan --config="$CONFIG" versions initdb || echo "Warning: ckanext-versions initdb failed or already applied" +# ckan --config="$CONFIG" validation init-db || echo "Warning: ckanext-validation init-db failed or already applied" +ckan --config="$CONFIG" unaids initdb || echo "Warning: ckanext-unaids initdb failed or already applied" + +exec "$@" diff --git a/deploy/nginx.conf b/deploy/nginx.conf new file mode 100644 index 00000000..82838923 --- /dev/null +++ b/deploy/nginx.conf @@ -0,0 +1,72 @@ +worker_processes auto; +error_log /dev/stderr warn; +pid /tmp/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /dev/stdout main; + + sendfile on; + tcp_nopush on; + keepalive_timeout 65; + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml; + + upstream ckan { + server 127.0.0.1:8000; + } + + server { + listen 5000; + server_name _; + + client_max_body_size 256M; + + # Internal location for X-Accel-Redirect (x-sendfile) + # CKAN sends X-Accel-Redirect with the full filesystem path + location /var/lib/ckan/webassets/ { + internal; + alias /var/lib/ckan/webassets/; + expires 7d; + add_header Cache-Control "public, max-age=604800" always; + add_header Vary "Accept-Encoding" always; + } + + # Other static assets - set proper caching headers + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + proxy_pass http://ckan; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Override Cache-Control from origin - remove no-cache, set public caching + proxy_hide_header Cache-Control; + proxy_hide_header Vary; + add_header Cache-Control "public, max-age=604800" always; + add_header Vary "Accept-Encoding" always; + } + + # All other requests (dynamic HTML) - never cache at edge + location / { + proxy_pass http://ckan; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 300s; + proxy_connect_timeout 75s; + add_header Cache-Control "no-store" always; + } + } +} diff --git a/deploy/production.ini b/deploy/production.ini new file mode 100644 index 00000000..4e584eea --- /dev/null +++ b/deploy/production.ini @@ -0,0 +1,7 @@ +# Production environment overrides +# Merged with base.ini at container startup + +[app:main] +ckanext.blob_storage.storage_service_url = https://p-giftless-e5eeh9gnhegzhed0.z01.azurefd.net + +ckanext.saml2auth.idp_metadata.remote_url = https://auth-hivtools.unaids.org/samlp/metadata/M3TzWiL728R3iQz3dpSy6dAsYMbtUm6T diff --git a/deploy/secrets.ini.template b/deploy/secrets.ini.template new file mode 100644 index 00000000..9cedb839 --- /dev/null +++ b/deploy/secrets.ini.template @@ -0,0 +1,14 @@ +# CKAN Secrets Configuration +# This file is mounted from a Kubernetes Secret +# Values here override/supplement production.ini +# +# Generate secrets with: openssl rand -base64 32 + +[app:main] +beaker.session.secret = +SECRET_KEY = +api_token.jwt.encode.secret = string: +api_token.jwt.decode.secret = string: + +# Datapusher API token (generate via CKAN CLI: ckan -c /tmp/production.ini user token add ckan_admin datapusher) +ckan.datapusher.api_token = diff --git a/deploy/staging.ini b/deploy/staging.ini new file mode 100644 index 00000000..e31d2980 --- /dev/null +++ b/deploy/staging.ini @@ -0,0 +1,10 @@ +# Staging environment overrides +# Merged with base.ini at container startup + +[app:main] +ckanext.blob_storage.storage_service_url = https://s-giftless-frgxc2hfd9dretb8.z01.azurefd.net + +## SMTP via Azure Communication Services +smtp.server = smtp.azurecomm.net:587 +smtp.starttls = true +smtp.mail_from = admin@adr-s.fjelltopp.org diff --git a/deploy/sync/Dockerfile.sync b/deploy/sync/Dockerfile.sync new file mode 100644 index 00000000..e44955f6 --- /dev/null +++ b/deploy/sync/Dockerfile.sync @@ -0,0 +1,27 @@ +# Image for the nightly prod -> staging sync CronJob. +# Target: adracr.azurecr.io/adr-sync +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update -qq && apt-get install -y -qq \ + ca-certificates curl gnupg lsb-release tar python3 python3-pip python3-requests \ + && install -d /usr/share/postgresql-common/pgdg \ + && curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \ + -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc \ + && echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \ + > /etc/apt/sources.list.d/pgdg.list \ + && apt-get update -qq \ + && apt-get install -y -qq postgresql-client-16 \ + && curl -fsSL https://aka.ms/downloadazcopy-v10-linux -o /tmp/azcopy.tgz \ + && tar -xzf /tmp/azcopy.tgz -C /tmp \ + && install /tmp/azcopy_linux_*/azcopy /usr/local/bin/azcopy \ + && curl -fsSL -o /usr/local/bin/kubectl \ + "https://dl.k8s.io/release/$(curl -fsSL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \ + && chmod +x /usr/local/bin/kubectl \ + && rm -rf /var/lib/apt/lists/* /tmp/azcopy* + +COPY sync.py /usr/local/bin/sync.py +RUN chmod +x /usr/local/bin/sync.py + +ENTRYPOINT ["python3", "/usr/local/bin/sync.py"] diff --git a/deploy/sync/cronjob.yaml b/deploy/sync/cronjob.yaml new file mode 100644 index 00000000..d9217553 --- /dev/null +++ b/deploy/sync/cronjob.yaml @@ -0,0 +1,80 @@ +# Nightly prod -> staging sync CronJob. +# Apply with: kubectl apply -f deploy/sync/cronjob.yaml +# Manual one-off run: kubectl create job --from=cronjob/adr-sync adr-sync-manual-$(date +%s) -n adr-s +apiVersion: batch/v1 +kind: CronJob +metadata: + name: adr-sync + namespace: adr-s +spec: + schedule: "0 1 * * *" # 01:00 UTC daily + timeZone: "Etc/UTC" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 7 + jobTemplate: + spec: + backoffLimit: 0 # don't retry — re-run manually after fixing + activeDeadlineSeconds: 14400 # 4 hours; datastore restore alone is ~90 min + template: + spec: + restartPolicy: Never + serviceAccountName: adr-sync # needs `kubectl exec` on the ckan deployment + containers: + - name: sync + image: adracr.azurecr.io/adr-sync:latest + imagePullPolicy: Always + resources: + requests: + cpu: "500m" + memory: "1Gi" + limits: + cpu: "2" + memory: "4Gi" + envFrom: + - secretRef: + name: adr-sync-secrets + env: + - name: CKAN_NAMESPACE + value: "adr-s" + - name: CKAN_DEPLOYMENT + value: "deploy/ckan" +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: adr-sync + namespace: adr-s +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: adr-sync + namespace: adr-s +rules: + - apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["get", "patch"] + - apiGroups: ["apps"] + resources: ["deployments/scale"] + verbs: ["get", "patch", "update"] + - apiGroups: [""] + resources: ["pods", "pods/exec"] + verbs: ["get", "list", "create", "watch"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: adr-sync + namespace: adr-s +subjects: + - kind: ServiceAccount + name: adr-sync + namespace: adr-s +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: adr-sync diff --git a/deploy/sync/secrets.yaml.template b/deploy/sync/secrets.yaml.template new file mode 100644 index 00000000..8b06fd77 --- /dev/null +++ b/deploy/sync/secrets.yaml.template @@ -0,0 +1,54 @@ +# Template for the k8s Secret consumed by the adr-sync CronJob. +# +# Do NOT commit a populated copy. Either: +# 1. Use sealed-secrets / external-secrets and commit the encrypted form. +# 2. Apply manually with `kubectl apply -f` from a local copy that lives +# only on the operator's machine. +# +# All values are strings. The SAS tokens should be issued with the minimum +# necessary permissions: +# - PROD_LFS_SAS: read+list on adr-p-datalake +# - SNAPSHOTS_SAS: read+list+write+delete on adr-snapshots (full) +# - STAGING_LFS_SAS: read+list+write+delete on adr-s-datalake +# +# Auth0 M2M scopes: read:users create:users (for users-exports job). +# Use the canonical tenant domain (not the custom domain) for AUTH0_PROD_DOMAIN — +# the Management API token endpoint validates against the canonical hostname. +apiVersion: v1 +kind: Secret +metadata: + name: adr-sync-secrets + namespace: adr-s +type: Opaque +stringData: + # Postgres connection URLs — use the ckan_admin (or equivalent) role with + # access to BOTH the ckan and datastore databases. The sync derives the + # datastore URL by swapping the path component. Do NOT use the limited + # `datastore` role here; it can't SELECT the UUID-named resource tables. + PROD_CKAN_PG_URL: "postgresql://ckan_admin:PASS@adr-p-eun-db001.postgres.database.azure.com/ckan?sslmode=require" + STAGING_CKAN_PG_URL: "postgresql://ckan_admin:PASS@adr-s-eun-db001.postgres.database.azure.com/ckan?sslmode=require" + + # Storage + SNAPSHOTS_ACCOUNT: "adrsnapshotsta" + SNAPSHOTS_CONTAINER: "snapshots" + SNAPSHOTS_SAS: "sv=...&sig=..." # full rwl(d) on the container + PROD_LFS_ACCOUNT: "adrpeunsta" + PROD_LFS_CONTAINER: "adr-p-datalake" + PROD_LFS_SAS: "sv=...&sig=..." # read+list only + STAGING_LFS_ACCOUNT: "adrseunsta" + STAGING_LFS_CONTAINER: "adr-s-datalake" + STAGING_LFS_SAS: "sv=...&sig=..." # rwl(d) + + # Auth0 — single tenant, M2M creds for the users-exports backup job. + # Env var names are AUTH0_PROD_* for backward-compat with earlier secret revisions; + # there is no AUTH0_DEV_* anymore because prod and staging share one tenant. + AUTH0_PROD_DOMAIN: "dev-udfgla0l.eu.auth0.com" # canonical, not the vanity custom domain + AUTH0_PROD_CLIENT_ID: "..." + AUTH0_PROD_CLIENT_SECRET: "..." + + # DataPusher token rotation — raw JWT signing secret (no "string:" prefix). + # Find it in ckan-ini-secrets: api_token.jwt.encode.secret = string: + CKAN_JWT_SECRET: "..." + + # Slack (optional — omit to disable notifications) + SLACK_WEBHOOK_URL: "https://hooks.slack.com/services/..." diff --git a/deploy/sync/sync.py b/deploy/sync/sync.py new file mode 100644 index 00000000..1701f07d --- /dev/null +++ b/deploy/sync/sync.py @@ -0,0 +1,606 @@ +#!/usr/bin/env python3 +""" +Nightly prod -> staging sync, doubling as the backup pipeline. + +Flow: + prod -> adr-snapshots (backup phase) + adr-snapshots -> staging (apply phase) + +Each artefact (Postgres dumps, LFS blobs, Auth0 users) is written to +adr-snapshots first, then the staging side reads from there. Prod is +read once per night regardless of how many places we restore to. +""" +from __future__ import annotations + +import base64 +import dataclasses +import datetime as dt +import hashlib +import hmac +import json +import logging +import os +import re +import secrets +import subprocess +import sys +import tempfile +import time +import urllib.parse +import urllib.request + +import requests + +log = logging.getLogger("sync") + +RUNDATE = os.environ.get("RUNDATE") or dt.datetime.utcnow().strftime("%Y%m%d") + + +@dataclasses.dataclass +class Config: + # Postgres — single admin URL per environment (ckan_admin user); the + # datastore URL is derived by swapping the DB path (same role has perms + # on both DBs). We deliberately don't use CKAN_DATASTORE_WRITE_URL because + # that role can't SELECT from the UUID-named resource tables. + prod_ckan_url: str + staging_ckan_url: str + + # Storage (snapshots + prod + staging Azure accounts) + snapshots_account: str + snapshots_container: str + snapshots_sas: str + prod_lfs_account: str + prod_lfs_container: str + prod_lfs_sas: str + staging_lfs_account: str + staging_lfs_container: str + staging_lfs_sas: str + + # Auth0 (single shared tenant; we only need export creds for backups) + auth0_domain: str + auth0_client_id: str + auth0_client_secret: str + + # CKAN reindex + ckan_namespace: str + ckan_deployment: str + + # DataPusher token rotation + ckan_jwt_secret: str + ckan_ini_secret_name: str + + # Slack + slack_webhook: str | None + + @classmethod + def from_env(cls) -> "Config": + def req(name: str) -> str: + v = os.environ.get(name) + if not v: + raise SystemExit(f"missing required env var: {name}") + return v + + return cls( + prod_ckan_url=req("PROD_CKAN_PG_URL"), + staging_ckan_url=req("STAGING_CKAN_PG_URL"), + snapshots_account=req("SNAPSHOTS_ACCOUNT"), + snapshots_container=req("SNAPSHOTS_CONTAINER"), + snapshots_sas=req("SNAPSHOTS_SAS"), + prod_lfs_account=req("PROD_LFS_ACCOUNT"), + prod_lfs_container=req("PROD_LFS_CONTAINER"), + prod_lfs_sas=req("PROD_LFS_SAS"), + staging_lfs_account=req("STAGING_LFS_ACCOUNT"), + staging_lfs_container=req("STAGING_LFS_CONTAINER"), + staging_lfs_sas=req("STAGING_LFS_SAS"), + auth0_domain=req("AUTH0_PROD_DOMAIN"), + auth0_client_id=req("AUTH0_PROD_CLIENT_ID"), + auth0_client_secret=req("AUTH0_PROD_CLIENT_SECRET"), + ckan_namespace=os.environ.get("CKAN_NAMESPACE", "adr-s"), + ckan_deployment=os.environ.get("CKAN_DEPLOYMENT", "deploy/ckan"), + ckan_jwt_secret=req("CKAN_JWT_SECRET"), + ckan_ini_secret_name=os.environ.get("CKAN_INI_SECRET_NAME", "ckan-ini-secrets"), + slack_webhook=os.environ.get("SLACK_WEBHOOK_URL"), + ) + + +def run(cmd: list[str], **kw) -> subprocess.CompletedProcess: + log.info("$ %s", " ".join(c if not _is_secret(c) else "" for c in cmd)) + try: + return subprocess.run(cmd, check=True, **kw) + except subprocess.CalledProcessError as e: + # Rebuild the exception so the traceback doesn't echo redacted args. + scrubbed = [c if not _is_secret(c) else "" for c in e.cmd] + raise subprocess.CalledProcessError(e.returncode, scrubbed) from None + + +def _is_secret(s: str) -> bool: + return any(k in s for k in ("postgresql://", "?sv=", "client_secret")) + + +def pg_env(url: str, db_override: str | None = None) -> dict[str, str]: + """Convert a postgresql:// URL into libpq env vars so creds never appear + on the subprocess command line (and therefore never end up in + CalledProcessError tracebacks).""" + u = urllib.parse.urlsplit(url) + env = dict(os.environ) + env["PGHOST"] = u.hostname or "" + if u.port: + env["PGPORT"] = str(u.port) + if u.username: + env["PGUSER"] = urllib.parse.unquote(u.username) + if u.password: + env["PGPASSWORD"] = urllib.parse.unquote(u.password) + env["PGDATABASE"] = db_override or (u.path.lstrip("/") if u.path else "") + # Azure Postgres Flexible Server requires TLS. + env.setdefault("PGSSLMODE", "require") + return env + + +def slack(cfg: Config, text: str, level: str = "INFO") -> None: + if not cfg.slack_webhook: + return + payload = {"text": f"[adr-sync {RUNDATE}] [{level}] {text}"} + try: + requests.post(cfg.slack_webhook, json=payload, timeout=10) + except Exception as e: + log.warning("slack notify failed: %s", e) + + +# ---------- Postgres ---------- + +def pg_dump_to_blob(cfg: Config, admin_url: str, db_name: str) -> str: + """pg_dump custom format, then upload to snapshots. db_name overrides the + DB component of admin_url so the same admin role can dump both ckan and + datastore. Set SKIP_PG_BACKUP=1 to reuse an existing dump for this RUNDATE + (useful when iterating on later pipeline stages).""" + blob_path = f"postgres/{RUNDATE}/{db_name}.dump" + if os.environ.get("SKIP_PG_BACKUP") == "1": + log.info("SKIP_PG_BACKUP=1: reusing existing %s without re-dumping", blob_path) + return blob_path + blob_url = ( + f"https://{cfg.snapshots_account}.blob.core.windows.net/" + f"{cfg.snapshots_container}/{blob_path}?{cfg.snapshots_sas}" + ) + with tempfile.NamedTemporaryFile(suffix=f".{db_name}.dump", delete=False) as tmp: + tmp_path = tmp.name + try: + run( + ["pg_dump", "-Fc", "--no-owner", "--no-privileges", "-f", tmp_path], + env=pg_env(admin_url, db_override=db_name), + ) + run(["azcopy", "copy", tmp_path, blob_url, "--overwrite=true"]) + finally: + try: + os.unlink(tmp_path) + except FileNotFoundError: + pass + log.info("postgres %s dumped to %s", db_name, blob_path) + return blob_path + + +def _assert_staging_url(admin_url: str) -> None: + """Refuse destructive DB operations against anything but staging. + Staging Azure Postgres host is adr-s-eun-db001; the `-s-` segment is + the marker.""" + host = urllib.parse.urlsplit(admin_url).hostname or "" + if "-s-" not in host: + raise RuntimeError(f"refusing to drop database on non-staging host {host!r}") + + +def _drop_and_recreate_db(admin_url: str, db_name: str) -> None: + """Terminate sessions on db_name then DROP + CREATE it. Run against the + `postgres` meta-DB on the same server. + + We don't use `DROP DATABASE ... WITH (FORCE)` because it requires + pg_signal_backend, which Azure Flexible Server doesn't grant to + non-admin roles like ckan_admin. The terminate filter `usename = + current_user` only kills sessions we own; an Azure system superuser + session on `ckan` would otherwise refuse termination and abort the + whole DROP. main() scales CKAN + datapusher to 0 before this runs + so the only sessions left are stragglers from monitoring / extension + workers. + + Each statement is its own -c invocation: DROP/CREATE DATABASE cannot + run inside a transaction, and psql wraps multi-statement -c in one.""" + _assert_staging_url(admin_url) + log.info("dropping and recreating database %s on staging", db_name) + run( + ["psql", "-v", "ON_ERROR_STOP=1", + "-c", ( + f"SELECT pg_terminate_backend(pid) FROM pg_stat_activity " + f"WHERE datname = '{db_name}' AND pid <> pg_backend_pid() " + f"AND usename = current_user;" + ), + "-c", f'DROP DATABASE IF EXISTS "{db_name}";', + "-c", f'CREATE DATABASE "{db_name}";'], + env=pg_env(admin_url, db_override="postgres"), + ) +def _scale(cfg: Config, deployments: list[str], replicas: int) -> None: + """Scale staging deployments. Used to stop CKAN + datapusher before + DROP DATABASE so their connections don't block it. datapusher + connects to Postgres as a different role (`datastore`) than the + sync runs as (`ckan_admin`), so we can't terminate its sessions + from inside the sync — scaling it to 0 is the only way to clear + them. On scale-up we don't wait; the sync moves on and the pods + come back in parallel with the rest of the apply phase.""" + for name in deployments: + run(["kubectl", "scale", "deployment", name, + f"--replicas={replicas}", "-n", cfg.ckan_namespace]) + if replicas == 0: + for name in deployments: + run(["kubectl", "wait", f"--for=delete", "pod", + "-l", f"app={name}", "-n", cfg.ckan_namespace, "--timeout=120s"]) + + +def pg_restore_from_blob(cfg: Config, blob_path: str, admin_url: str, db_name: str) -> None: + blob_url = ( + f"https://{cfg.snapshots_account}.blob.core.windows.net/" + f"{cfg.snapshots_container}/{blob_path}?{cfg.snapshots_sas}" + ) + with tempfile.NamedTemporaryFile(suffix=f".{db_name}.dump", delete=False) as tmp: + tmp_path = tmp.name + try: + run(["azcopy", "copy", blob_url, tmp_path, "--overwrite=true"]) + + # Drop and recreate the target database so pg_restore lands on a + # genuinely empty DB. Avoids the 22-minute serial DROP phase that + # --clean --if-exists would otherwise spend on thousands of + # UUID-named datastore tables, and the lock/memory pressure that a + # single DROP SCHEMA CASCADE causes. + _drop_and_recreate_db(admin_url, db_name) + + # pg_restore requires -d/--dbname on the command line (PGDATABASE + # alone is not sufficient). + # Single-threaded restore — parallel jobs OOM the staging Postgres + # building large indexes (the `activity` table PK alone needs ~24MB + # work_mem; 3 workers in parallel blow the budget). + # pg_restore exits 1 if ANY error occurred. In practice the prod + # dump throws a handful of benign errors per restore: duplicate + # CREATE INDEX statements from plugins that register the same + # index twice (ckanext-harvest), plus a couple of real data + # duplicates (pages_alembic_version_pkc, user_name_key) that + # predate the migration. Match the AWS-era behaviour: log loudly + # but keep going — aborting here would skip the datastore restore + # and reindex that follow. + result = subprocess.run( + ["pg_restore", "--no-owner", "--no-privileges", + "-d", db_name, tmp_path], + env=pg_env(admin_url, db_override=db_name), + ) + if result.returncode != 0: + log.warning( + "pg_restore for %s exited %d — see job stderr for per-row errors", + db_name, result.returncode, + ) + finally: + try: + os.unlink(tmp_path) + except FileNotFoundError: + pass + + +# ---------- LFS ---------- + +def _blob_url(account: str, container: str, sas: str, prefix: str = "") -> str: + """Build an azcopy-friendly blob URL. Trailing slash on the path forces + azcopy to treat the target as a directory.""" + path = container.rstrip("/") + if prefix: + path = f"{path}/{prefix.strip('/')}/" + else: + path = f"{path}/" + return f"https://{account}.blob.core.windows.net/{path}?{sas}" + + +def azcopy_sync(src_url: str, dst_url: str) -> None: + # delete-destination=true to mirror; recursive is default for sync + run(["azcopy", "sync", src_url, dst_url, "--delete-destination=true"]) + + +# ---------- Auth0 ---------- + +def auth0_token(domain: str, client_id: str, client_secret: str) -> str: + """Get an Auth0 Management API M2M token.""" + r = requests.post( + f"https://{domain}/oauth/token", + data={ + "grant_type": "client_credentials", + "client_id": client_id, + "client_secret": client_secret, + "audience": f"https://{domain}/api/v2/", + }, + timeout=30, + ) + r.raise_for_status() + return r.json()["access_token"] + + +def auth0_export_users(cfg: Config) -> str: + """Create an export job, poll for completion, download users.json.gz, upload to snapshots.""" + token = auth0_token(cfg.auth0_domain, cfg.auth0_client_id, cfg.auth0_client_secret) + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + + fields = [ + {"name": k} for k in ( + "app_metadata", "blocked", "created_at", "email", "email_verified", + "family_name", "given_name", "identities", "name", "nickname", + "user_id", "user_metadata", "username", + ) + ] + r = requests.post( + f"https://{cfg.auth0_domain}/api/v2/jobs/users-exports", + headers=headers, json={"format": "json", "fields": fields}, timeout=30, + ) + r.raise_for_status() + job_id = r.json()["id"] + log.info("auth0 export job created: %s", job_id) + + download_url = _poll_job(cfg.auth0_domain, token, job_id) + blob_path = f"auth0/{RUNDATE}_users.json.gz" + blob_url = ( + f"https://{cfg.snapshots_account}.blob.core.windows.net/" + f"{cfg.snapshots_container}/{blob_path}?{cfg.snapshots_sas}" + ) + + with tempfile.NamedTemporaryFile(suffix=".users.json.gz", delete=False) as tmp: + tmp_path = tmp.name + try: + urllib.request.urlretrieve(download_url, tmp_path) + run(["azcopy", "copy", tmp_path, blob_url, "--overwrite=true"]) + finally: + try: + os.unlink(tmp_path) + except FileNotFoundError: + pass + return blob_path + + +def _poll_job(domain: str, token: str, job_id: str, timeout_s: int = 600) -> str: + """Poll an Auth0 job until completed; return location (for exports) or raise.""" + headers = {"Authorization": f"Bearer {token}"} + deadline = time.time() + timeout_s + while time.time() < deadline: + r = requests.get(f"https://{domain}/api/v2/jobs/{job_id}", headers=headers, timeout=30) + r.raise_for_status() + body = r.json() + status = body.get("status") + if status == "completed": + return body.get("location", "") + if status == "failed": + raise RuntimeError(f"auth0 job {job_id} failed: {body}") + time.sleep(5) + raise RuntimeError(f"auth0 job {job_id} timed out after {timeout_s}s") + + +# ---------- CKAN ---------- + +def ckan_migrate(cfg: Config) -> None: + """Run CKAN core + per-extension migrations against the freshly- + restored DB. The AWS-era sync didn't do this — staging code is often + ahead of prod's schema and a raw data-copy leaves new extensions + complaining at boot until each runs its initdb. + + Core alembic runs once (`ckan db upgrade`). After that we discover + enabled plugins from `ckan.plugins` in the live config and try each + one's `initdb` / `init-db` subcommand. Most plugins don't have one — + those exit code 2 (Click "no such command") and we swallow silently. + This way a new extension that needs initdb works without editing + sync.py.""" + _ckan_exec(cfg, ["db", "upgrade"]) + + plugins = _enabled_plugins(cfg) + log.info("post-restore: probing %d plugins for initdb", len(plugins)) + for plugin in plugins: + for sub in ("initdb", "init-db"): + rc = _ckan_exec(cfg, [plugin, sub], quiet_codes={2}) + if rc == 0: + break # this plugin's initdb succeeded; don't try the other variant + + +def _ckan_exec(cfg: Config, args: list[str], quiet_codes: set[int] | None = None) -> int: + """Run `ckan -c /tmp/production.ini ` inside the CKAN pod. + Returns the exit code. Logs a warning on non-zero unless the code + is in quiet_codes (e.g. {2} for "no such Click subcommand").""" + result = subprocess.run( + ["kubectl", "exec", "-n", cfg.ckan_namespace, cfg.ckan_deployment, "--", + "ckan", "-c", "/tmp/production.ini", *args], + check=False, + ) + if result.returncode != 0 and (not quiet_codes or result.returncode not in quiet_codes): + log.warning("ckan %s exited %d", " ".join(args), result.returncode) + return result.returncode + + +def _enabled_plugins(cfg: Config) -> list[str]: + """Parse the `ckan.plugins =` value from /tmp/production.ini inside the + CKAN pod and return the plugin names. The value spans multiple lines + using ConfigParser-style indentation (see deploy/base.ini), so we + capture the first match line and any following indented continuation + lines until the next non-indented line or EOF.""" + awk_prog = ( + r'/^ckan\.plugins[[:space:]]*=/ {sub(/^[^=]*=/, ""); print; in_val=1; next}' + r' in_val && /^[[:space:]]/ {print; next}' + r' in_val {exit}' + ) + result = subprocess.run( + ["kubectl", "exec", "-n", cfg.ckan_namespace, cfg.ckan_deployment, "--", + "awk", awk_prog, "/tmp/production.ini"], + capture_output=True, text=True, check=True, + ) + return result.stdout.split() + + +def grant_datastore_permissions(cfg: Config) -> None: + """Re-apply datastore role GRANTs after restore. + + pg_restore runs with --no-privileges, which strips every GRANT from + the restored datastore DB. The datastore_ro role then can't SELECT + from _table_metadata, so DataPusher's `datastore_search` call 500s, + push_to_datastore aborts before it pushes any rows, and the + `complete` callback that creates datatables_view never fires — + uploads succeed but views don't appear. + + `ckan datastore set-permissions` prints the canonical GRANT script + to stdout; we pipe it into psql as ckan_admin against the datastore + DB.""" + result = subprocess.run( + ["kubectl", "exec", "-n", cfg.ckan_namespace, cfg.ckan_deployment, "--", + "ckan", "-c", "/tmp/production.ini", "datastore", "set-permissions"], + capture_output=True, text=True, check=True, + ) + run( + ["psql", "-v", "ON_ERROR_STOP=1"], + env=pg_env(cfg.staging_ckan_url, db_override="datastore"), + input=result.stdout, text=True, + ) + + +def ckan_reindex(cfg: Config) -> None: + # CKAN config is merged at startup to /tmp/production.ini + # (base.ini + env.ini + secrets.ini); see deploy/base.ini. + # --clear wipes Solr before reindexing. Without it, datasets that + # existed in staging Solr but not in the freshly-restored DB linger + # in the index; `package_search` then returns IDs that the ckanext- + # restricted plugin tries to load from the DB, raising ObjectNotFound + # and 500-ing the home page. + # --force skips datasets that fail to serialize (a CKAN-level data + # quirk, not a sync issue) instead of aborting. One known per-dataset + # failure: a dataset whose `config` field exceeds Solr's 32766-byte + # max term length — that one stays out of the index. + result = subprocess.run( + [ + "kubectl", "exec", "-n", cfg.ckan_namespace, cfg.ckan_deployment, "--", + "ckan", "-c", "/tmp/production.ini", "search-index", "rebuild", "--force", "--clear", + ], + check=False, + ) + if result.returncode != 0: + log.warning("ckan search-index rebuild exited %d — check logs for per-dataset failures", result.returncode) + + +# ---------- DataPusher token rotation ---------- + +def _make_api_token(jwt_secret: str) -> tuple[str, str]: + """Generate a CKAN-compatible HS256 JWT API token. + + Replicates what `ckan user token add` produces: header.payload.sig with + no expiry claim. The jti is the DB row ID; the token string is what goes + in ckan.datapusher.api_token. + + Returns (jti, token_string). + """ + jti = secrets.token_urlsafe(48) + header = base64.urlsafe_b64encode( + json.dumps({"alg": "HS256", "typ": "JWT"}, separators=(",", ":")).encode() + ).rstrip(b"=").decode() + payload = base64.urlsafe_b64encode( + json.dumps({"jti": jti, "iat": int(time.time())}, separators=(",", ":")).encode() + ).rstrip(b"=").decode() + msg = f"{header}.{payload}" + sig = hmac.new(jwt_secret.encode(), msg.encode(), hashlib.sha256).digest() + sig_b64 = base64.urlsafe_b64encode(sig).rstrip(b"=").decode() + return jti, f"{msg}.{sig_b64}" + + +def refresh_datapusher_token(cfg: Config) -> None: + """Regenerate the DataPusher API token after a DB restore. + + The nightly sync replaces staging's DB with a prod snapshot. Any token + previously stored only in staging's api_token table is wiped, breaking + DataPusher's ability to call back to CKAN. This function: + + 1. Generates a fresh JWT using the same secret CKAN uses. + 2. Inserts a matching row into the restored staging DB. + 3. Patches ckan-ini-secrets with the new token value. + + Called inside the scale-to-0 window, before CKAN scales back up, so the + pod starts with a token that already exists in the DB. + """ + jti, token = _make_api_token(cfg.ckan_jwt_secret) + + run( + ["psql", "-v", "ON_ERROR_STOP=1", + "-c", "DELETE FROM api_token WHERE name = 'datapusher_staging';", + "-c", ( + f"INSERT INTO api_token (id, name, user_id) " + f"SELECT '{jti}', 'datapusher_staging', id " + f"FROM public.user WHERE name = 'admin' LIMIT 1;" + )], + env=pg_env(cfg.staging_ckan_url), + ) + + result = subprocess.run( + ["kubectl", "get", "secret", cfg.ckan_ini_secret_name, + "-n", cfg.ckan_namespace, "-o", "jsonpath={.data.secrets\\.ini}"], + capture_output=True, text=True, check=True, + ) + current_ini = base64.b64decode(result.stdout.strip()).decode() + new_ini = re.sub( + r"(?m)^ckan\.datapusher\.api_token\s*=.*$", + f"ckan.datapusher.api_token = {token}", + current_ini, + ) + if new_ini == current_ini: + log.warning("refresh_datapusher_token: ckan.datapusher.api_token not found in secrets.ini — skipping patch") + return + new_b64 = base64.b64encode(new_ini.encode()).decode() + run( + ["kubectl", "patch", "secret", cfg.ckan_ini_secret_name, + "-n", cfg.ckan_namespace, "--type=merge", + "-p", json.dumps({"data": {"secrets.ini": new_b64}})], + ) + log.info("datapusher api token refreshed (jti=%s…)", jti[:16]) + + +# ---------- main ---------- + +def main() -> int: + logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") + cfg = Config.from_env() + slack(cfg, "sync starting") + + try: + # Backup phase + log.info("=== backup phase ===") + ckan_dump = pg_dump_to_blob(cfg, cfg.prod_ckan_url, "ckan") + ds_dump = pg_dump_to_blob(cfg, cfg.prod_ckan_url, "datastore") + azcopy_sync( + _blob_url(cfg.prod_lfs_account, cfg.prod_lfs_container, cfg.prod_lfs_sas), + _blob_url(cfg.snapshots_account, cfg.snapshots_container, cfg.snapshots_sas, prefix="lfs"), + ) + auth0_export_users(cfg) + slack(cfg, "backup phase done") + + # Apply phase + log.info("=== apply phase ===") + # Scale CKAN + datapusher to 0 so DROP DATABASE on staging isn't + # blocked. finally: scale back regardless of restore outcome, but + # note that k8s SIGKILL on activeDeadlineSeconds bypasses finally + # — if the job is killed mid-restore, ckan + datapusher need + # manual `kubectl scale --replicas=1` to come back. + scaled = ["ckan", "datapusher"] + try: + _scale(cfg, scaled, 0) + pg_restore_from_blob(cfg, ckan_dump, cfg.staging_ckan_url, "ckan") + pg_restore_from_blob(cfg, ds_dump, cfg.staging_ckan_url, "datastore") + refresh_datapusher_token(cfg) + finally: + _scale(cfg, scaled, 1) + azcopy_sync( + _blob_url(cfg.snapshots_account, cfg.snapshots_container, cfg.snapshots_sas, prefix="lfs"), + _blob_url(cfg.staging_lfs_account, cfg.staging_lfs_container, cfg.staging_lfs_sas), + ) + ckan_migrate(cfg) + grant_datastore_permissions(cfg) + ckan_reindex(cfg) + + slack(cfg, "sync OK", level="OK") + return 0 + except Exception as e: + log.exception("sync failed") + slack(cfg, f"sync FAILED: {e}", level="ERROR") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/deploy/uwsgi.ini b/deploy/uwsgi.ini new file mode 100644 index 00000000..ed801ff9 --- /dev/null +++ b/deploy/uwsgi.ini @@ -0,0 +1,18 @@ +[uwsgi] +http = 127.0.0.1:8000 +virtualenv = /usr/lib/adx/venv +wsgi-file = /usr/lib/adx/submodules/ckan/wsgi.py +callable = application +processes = 4 +threads = 8 +enable-threads = true +master = true +# Recycle workers after N requests to bound SQLAlchemy session-cache +# and Python heap growth; without this, per-request latency climbs +# within a single worker's lifetime under sustained load. +max-requests = 500 +# Time-bound a request so a hung handler can't pin one of the 32 slots +# indefinitely (e.g. a slow downstream call). harakiri-verbose logs +# a Python traceback for whichever frame was running when killed. +harakiri = 60 +harakiri-verbose = true diff --git a/docs/nightly-sync.md b/docs/nightly-sync.md new file mode 100644 index 00000000..49fa4540 --- /dev/null +++ b/docs/nightly-sync.md @@ -0,0 +1,140 @@ +# Nightly Prod → Staging Sync + +A K8s `CronJob` (`adr-sync` in namespace `adr-s`) refreshes the staging environment from production every night at 01:00 UTC, while simultaneously producing dated backups in a dedicated Azure storage account. + +Prod is read **once** per night. The staging-apply phase reads exclusively from `adr-snapshots`. Staging can be re-restored at any time without re-hitting prod by re-running the apply phase against a chosen `${RUNDATE}`. + +The apply phase scales staging `ckan` and `datapusher` deployments to 0 before running `DROP DATABASE`, then scales them back to 1 after the restores complete. Staging downtime per nightly run is currently ~2 hours, mostly taken by the `pg_restore` step. + +## Auth0 layout + +There's one Auth0 tenant (canonical `dev-udfgla0l.eu.auth0.com`) with the custom domain `auth-hivtools.unaids.org` promoted on top. + +Inside the tenant: one SAML SP application per environment (prod CKAN, staging CKAN) plus one M2M application for adr-sync's nightly users-exports backup. `user_id` values are tenant-scoped, so the saml_ids in CKAN's `plugin_extras` already match across environments and `process_user()`'s saml_id lookup hits immediately after a sync. + +## Files + +| Path | Purpose | +| ---- | ------- | +| `deploy/sync/sync.py` | Orchestrator (Python). Env-driven. | +| `deploy/sync/Dockerfile.sync` | Built and pushed as `adracr.azurecr.io/adr-sync`. | +| `deploy/sync/cronjob.yaml` | CronJob + ServiceAccount + Role + RoleBinding in `adr-s`. | +| `deploy/sync/secrets.yaml.template` | Shape of `adr-sync-secrets`. Populate locally — **never** commit a real copy. | +| `docs/nightly-sync.md` | This file. | + +## Operations + +### Manual one-off run + +```bash +kubectl create job --from=cronjob/adr-sync adr-sync-manual-$(date +%s) -n adr-s +kubectl logs -f -l job-name=adr-sync-manual-... -n adr-s +``` + +### Rotating SAS tokens + +The three SAS tokens in `adr-sync-secrets` (`PROD_LFS_SAS`, `SNAPSHOTS_SAS`, `STAGING_LFS_SAS`) were issued with a **365-day** expiry (currently 22-05-2027). Once they expire, `azcopy` calls will fail with `403 AuthenticationFailed` and the nightly job will start dying at the backup or apply phase. + +Rotate them like this (one storage account at a time): + +```bash +# Pick an expiry one year out, UTC, ISO-8601. +EXPIRY=$(date -u -v+365d +%Y-%m-%dT%H:%MZ) # macOS +# EXPIRY=$(date -u -d '+365 days' +%Y-%m-%dT%H:%MZ) # GNU/Linux + +# Get an account key (or use --auth-mode login + --as-user for a user-delegation SAS). +KEY=$(az storage account keys list -g ADR-EUN-01 -n adrpdatalake \ + --query '[0].value' -o tsv) + +# PROD_LFS_SAS — read + list on the adr-p-datalake container. +az storage container generate-sas \ + --account-name adrpdatalake --name adr-p-datalake \ + --permissions rl --expiry "$EXPIRY" \ + --account-key "$KEY" -o tsv + +# SNAPSHOTS_SAS — full r/w/l/d on the snapshots container. +az storage container generate-sas \ + --account-name adrsnapshotsta --name snapshots \ + --permissions rwdl --expiry "$EXPIRY" \ + --account-key "$(az storage account keys list -g ADR-EUN-01 -n adrsnapshotsta --query '[0].value' -o tsv)" \ + -o tsv + +# STAGING_LFS_SAS — full r/w/l/d on the adr-s-datalake container. +az storage container generate-sas \ + --account-name adrsdatalake --name adr-s-datalake \ + --permissions rwdl --expiry "$EXPIRY" \ + --account-key "$(az storage account keys list -g ADR-EUN-01 -n adrsdatalake --query '[0].value' -o tsv)" \ + -o tsv +``` + +Each command prints the bare query-string SAS (no leading `?`). Patch the existing secret in place — don't recreate it, since other keys like `PROD_CKAN_PG_URL` live in the same secret: + +```bash +kubectl patch secret adr-sync-secrets -n adr-s \ + --type='json' -p="$(jq -nc \ + --arg prod "$(printf %s "$PROD_SAS" | base64)" \ + --arg snap "$(printf %s "$SNAP_SAS" | base64)" \ + --arg stag "$(printf %s "$STAG_SAS" | base64)" \ + '[ + {op:"replace", path:"/data/PROD_LFS_SAS", value:$prod}, + {op:"replace", path:"/data/SNAPSHOTS_SAS", value:$snap}, + {op:"replace", path:"/data/STAGING_LFS_SAS", value:$stag} + ]')" +``` + +### Cleaning up + +`adr-snapshots` retention is driven by an Azure Blob lifecycle management policy on the storage account — no manual cleanup needed for routine operation. + +The policy has two rules, scoped by blob prefix: + +| Prefix | Tier transitions | Delete | +| ------ | ---------------- | ------ | +| `postgres/`, `auth0/` | cool @ 30 days, archive @ 60 days | 365 days | +| `lfs/` (old versions only) | cool @ 7 days, archive @ 30 days | 90 days | + +Notes: + +- The `lfs/` rule targets **previous versions** only (blob versioning is on for that prefix). The current version of each LFS object stays hot indefinitely — staging needs to read it on every apply phase. +- `postgres/` snapshots are written once per night under `${RUNDATE}/`. Auth0 exports are written once per night under the `auth0/` prefix with `${RUNDATE}` in the filename (for example, `auth0/{RUNDATE}_users.json.gz`), so the age clock starts the moment each blob lands. +- "Days" are measured from last modification, per Azure's `daysAfterModificationGreaterThan` semantics. +- View / edit the policy in the portal under `adrsnapshotsta → Data management → Lifecycle management`, or via `az storage account management-policy show -g ADR-EUN-01 --account-name adrsnapshotsta`. + +To restore staging from an archived snapshot you must first rehydrate the blob (Azure archive tier is offline — rehydration takes hours). For routine "restore yesterday's prod into staging" workflows you're always in the hot tier, so this only matters for forensic restores older than a month. + +### Failure modes + +| Symptom | Likely cause | Fix | +| ------- | ------------ | --- | +| `pg_dump` 28000 auth failed | Postgres password rotated | Update `PROD_CKAN_PG_URL` in `adr-sync-secrets`. | +| `azcopy` 403 AuthenticationFailed | SAS expired | Re-mint SAS, update secret. | +| Auth0 export job times out | Token expired or M2M scope missing | Check tenant logs in Auth0 dashboard. | +| Auth0 import reports per-row errors | Email collisions, malformed `user_id` | See job `/errors`. Often fine to ignore; will retry next night. | +| `ckan search-index rebuild` fails | Solr not ready / Postgres restore incomplete | Check the Solr live-patch state (see `adr-solr-live-patches` memory). | +| Home page returns 500 `Dataset not found` after sync | Reindex ran without `--clear`; stale Solr docs point at deleted DB rows | Re-run `ckan search-index rebuild --force --clear` against staging. | +| `CRITI ckanext.X: requires database setup` after sync | Staging code declares a table that prod's DB doesn't have; the sync wipes staging to prod's schema | The sync's `ckan_migrate` step runs `ckan db upgrade`, then iterates over every plugin in `ckan.plugins` and tries `ckan initdb` / `init-db`. Plugins without one are silently skipped. No code change needed for new extensions — just enable them in `ckan.plugins` and the next sync will run their initdb. | +| `pg_restore` reports ~7 ignored errors | 2 real data duplicates in prod (`pages_alembic_version_pkc`, `user_name_key`) + 5 benign duplicate-index entries from ckanext-harvest | Expected. Fix the prod data duplicates separately. | +| `ckan` + `datapusher` stuck at 0 replicas after a failed/killed job | k8s SIGKILL on `activeDeadlineSeconds` bypasses Python's `finally:` | `kubectl scale deploy ckan datapusher --replicas=1 -n adr-s`. | +| Job killed at `activeDeadlineSeconds` | Restore + reindex exceeded 4h | Investigate Postgres / pg_restore slowness; bump deadline if real. | + +## Cost + +Same-region Azure Blob copy is free (bandwidth $0). Storage all-in: + +- Postgres dumps: ~800 MB/day × 30 days hot + 365 days cool/archive → cents/month. +- Auth0 exports: KB-scale; negligible. +- LFS mirror: 50 GB current version + ~1-2% daily delta retained as old + versions, ageing to cool/archive — under $2/month. + +Total: comfortably under $5/month. + +## Related work + +- Replaces the AWS-era `fjelltopp/adx_toolbox` scripts: + `sh_scripts/rds_snapshot_restore/sync_development.sh`, + `sh_scripts/storage_sync/dev_storage_sync.sh`, + `sh_scripts/backup_scripts/auth0_backup_users.sh`. +- The `auth0_users_db_and_infra_backup.py` Management API patterns are + reused (copy-adapted) in `deploy/sync/sync.py`. +- The migration pod scripts in `migrate/` use the same k8s Secret sourcing + pattern for credentials (see `migrate/launch_prod_migration_pod.sh`). diff --git a/rebuild_solr_index.sh b/rebuild_solr_index.sh new file mode 100755 index 00000000..a0072ef9 --- /dev/null +++ b/rebuild_solr_index.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Rebuild the CKAN Solr search index inside the running ckan container. +# Run this from the repo root after a stack restart to make all datasets visible. +# +# Usage: ./rebuild_solr_index.sh + +set -e + +CONTAINER="ckan" +CONFIG="/etc/ckan/ckan.ini" + +echo "Rebuilding Solr search index in container '${CONTAINER}'..." +docker exec "${CONTAINER}" /usr/local/bin/ckan --config="${CONFIG}" search-index rebuild +echo "Done. Solr index rebuilt successfully." diff --git a/submodules/ckanext-auth b/submodules/ckanext-auth index 3ee3dab7..e83911ba 160000 --- a/submodules/ckanext-auth +++ b/submodules/ckanext-auth @@ -1 +1 @@ -Subproject commit 3ee3dab7d6975e2c785e84babeb8905fcaba373b +Subproject commit e83911ba2b6d4447546a4d14e2e9c6d680088698 diff --git a/submodules/ckanext-authz-service b/submodules/ckanext-authz-service index bd2c71be..c53890d5 160000 --- a/submodules/ckanext-authz-service +++ b/submodules/ckanext-authz-service @@ -1 +1 @@ -Subproject commit bd2c71be6e78acbe1cc184373fa9aa66deb4f503 +Subproject commit c53890d584d88ccfb448996ab67b7f3785bff762 diff --git a/submodules/ckanext-blob-storage b/submodules/ckanext-blob-storage index 2efa7cf5..8e9916c2 160000 --- a/submodules/ckanext-blob-storage +++ b/submodules/ckanext-blob-storage @@ -1 +1 @@ -Subproject commit 2efa7cf539fff244d638c60c61d793f8f97496c7 +Subproject commit 8e9916c2f2748ed69b44f6b5493cdc4b6ddfd4c3 diff --git a/submodules/ckanext-dhis2harvester b/submodules/ckanext-dhis2harvester index 9677b7e0..97ad70ab 160000 --- a/submodules/ckanext-dhis2harvester +++ b/submodules/ckanext-dhis2harvester @@ -1 +1 @@ -Subproject commit 9677b7e03b48259c4e3faf0a8e35a26bb1f70e3c +Subproject commit 97ad70ab29a2578eb7851b4c259c9f986f2a59d5 diff --git a/submodules/ckanext-emailasusername b/submodules/ckanext-emailasusername index a3229acd..e96ee33d 160000 --- a/submodules/ckanext-emailasusername +++ b/submodules/ckanext-emailasusername @@ -1 +1 @@ -Subproject commit a3229acd1a4c34e65d07bc7af6cf17674ac1d681 +Subproject commit e96ee33d76e7608ffd3cf4f35c65051af6fbbdf9 diff --git a/submodules/ckanext-fork b/submodules/ckanext-fork index 8be01412..2533f81a 160000 --- a/submodules/ckanext-fork +++ b/submodules/ckanext-fork @@ -1 +1 @@ -Subproject commit 8be014128999cbe71455510ee57f30eb32a06a9f +Subproject commit 2533f81ad328f9ff76eb13cc2ea04c9ae5bcf4ea diff --git a/submodules/ckanext-harvest b/submodules/ckanext-harvest index 56e70b9f..e3da85db 160000 --- a/submodules/ckanext-harvest +++ b/submodules/ckanext-harvest @@ -1 +1 @@ -Subproject commit 56e70b9f57919c368e9bbdb3d102ff3e740dbcce +Subproject commit e3da85db914e4dec91a43ee8ba7394d73303f707 diff --git a/submodules/ckanext-restricted b/submodules/ckanext-restricted index 4186c672..d357aa2e 160000 --- a/submodules/ckanext-restricted +++ b/submodules/ckanext-restricted @@ -1 +1 @@ -Subproject commit 4186c672cda061d967693fd74ab8584f6be48ae5 +Subproject commit d357aa2e556980204c20e9cd7b17bbbc5a838d40 diff --git a/submodules/ckanext-saml2auth b/submodules/ckanext-saml2auth index 7834868e..1429850e 160000 --- a/submodules/ckanext-saml2auth +++ b/submodules/ckanext-saml2auth @@ -1 +1 @@ -Subproject commit 7834868e9e782af2f6d39e078c2a600991874948 +Subproject commit 1429850ef307a497209cd1ad6f1b495ec9b833fa diff --git a/submodules/ckanext-scheming b/submodules/ckanext-scheming index 766f705d..237ac4ca 160000 --- a/submodules/ckanext-scheming +++ b/submodules/ckanext-scheming @@ -1 +1 @@ -Subproject commit 766f705d2be42ce0268b173f1eec1dcf8ccdafe3 +Subproject commit 237ac4ca1824a965b186eb19bf969ab548f6b890 diff --git a/submodules/ckanext-unaids b/submodules/ckanext-unaids index 864e9da2..705409a0 160000 --- a/submodules/ckanext-unaids +++ b/submodules/ckanext-unaids @@ -1 +1 @@ -Subproject commit 864e9da2ec684cd18c29a6738dac693a648eaf02 +Subproject commit 705409a0be99e8bb80059cc4676b743e6b892a6e diff --git a/submodules/ckanext-validation b/submodules/ckanext-validation index 0372f8d2..4148a0f0 160000 --- a/submodules/ckanext-validation +++ b/submodules/ckanext-validation @@ -1 +1 @@ -Subproject commit 0372f8d2f3864d61db8988a627b7be7cfbff913c +Subproject commit 4148a0f092090ef4a6e77c18efc6226e34b90063 diff --git a/submodules/ckanext-versions b/submodules/ckanext-versions index 61a843d9..d44cb3a4 160000 --- a/submodules/ckanext-versions +++ b/submodules/ckanext-versions @@ -1 +1 @@ -Subproject commit 61a843d90eef68c6e03e265ec827d8561d914877 +Subproject commit d44cb3a4e2d43971c75ed3f4e20c52b701764ef3 diff --git a/submodules/ckanext-ytp-request b/submodules/ckanext-ytp-request index 1e606717..32220ad8 160000 --- a/submodules/ckanext-ytp-request +++ b/submodules/ckanext-ytp-request @@ -1 +1 @@ -Subproject commit 1e606717ec229c95ce1633b95e3ee9ff0479a74f +Subproject commit 32220ad82a5e3fa2089fd31f0f528fde72a45bff diff --git a/submodules/datapusher b/submodules/datapusher index a01fdd1e..59f8f4d8 160000 --- a/submodules/datapusher +++ b/submodules/datapusher @@ -1 +1 @@ -Subproject commit a01fdd1e1fdef6ae256ce00e0a124e2e13f76141 +Subproject commit 59f8f4d8f3e2cd2a43e76bb037b2a58152240850 diff --git a/submodules/giftless b/submodules/giftless index 3f664e72..06e31f1f 160000 --- a/submodules/giftless +++ b/submodules/giftless @@ -1 +1 @@ -Subproject commit 3f664e7232f2e1e58709260f87d4c76d4fc90b93 +Subproject commit 06e31f1fb7d04355c9e2b8d98d6bac82ba520245 diff --git a/util/__init__.py b/util/__init__.py index ec7585c7..80a64094 100644 --- a/util/__init__.py +++ b/util/__init__.py @@ -5,7 +5,7 @@ SUDO = os.environ.get('ADX_SUDO', '') -compose = ["docker-compose"] +compose = ["docker", "compose"] COMPOSE_PATH = os.path.join( os.path.abspath(os.path.dirname(__file__)), @@ -162,7 +162,12 @@ def run_tests(args, extra): # for cases like ckanext-ytp-requests repository which uses ytp_requests test directory internally extension_sub_path = extension_sub_path.replace("-", "_") retcode = call_command([ + # Blank out the SMTP server for test runs so tests never reach the + # dev stack's smtp4dev and send real mail. Some suites (e.g. + # ckanext-unaids' send_dataset_transfer_emails error path) assert that + # sending fails when no server is configured. f'docker exec {args.interaction} -e CKAN_SQLALCHEMY_URL={CKAN_TEST_SQLALCHEMY_URL} ' + f'-e CKAN_SMTP_SERVER= ' f'ckan /usr/local/bin/ckan-pytest --capture=no --disable-warnings ' f'--ckan-ini={extension_path}/test.ini ' f'{extension_path}/{extension_sub_path}/tests '