diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000000..2da2f923b0 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,55 @@ +codecov: + require_ci_to_pass: true + +comment: + layout: reach, diff, flags, files, footer + behavior: default + require_changes: false + +coverage: + status: + project: + default: + target: auto + threshold: 1% + base: auto + backend: + flags: + - backend + target: 80% + threshold: 1% + base: auto + frontend: + flags: + - frontend + target: 80% + threshold: 1% + base: auto + patch: + default: + target: auto + threshold: 0% + backend: + flags: + - backend + target: auto + threshold: 0% + frontend: + flags: + - frontend + target: auto + threshold: 0% + +fixes: + - /app/::frontend/ + - /home/owasp/::backend/ + +flags: + backend: + paths: + - backend/ + carryforward: true + frontend: + paths: + - frontend/ + carryforward: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 26898ec789..69c0abf597 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -40,6 +40,11 @@ updates: schedule: interval: daily + - package-ecosystem: docker + directory: /docker/semgrep + schedule: + interval: daily + - package-ecosystem: github-actions directory: / schedule: diff --git a/.github/workflows/check-pr-issue.yaml b/.github/workflows/check-pr-issue.yaml index b906449e9f..f05c6d3101 100644 --- a/.github/workflows/check-pr-issue.yaml +++ b/.github/workflows/check-pr-issue.yaml @@ -15,8 +15,10 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + # NOSEMGREP: yaml.github-actions.security.pull-request-target-code-checkout.pull-request-target-code-checkout + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: + # Explicitly checking out base branch (target repository), not PR code. ref: ${{ github.event.pull_request.base.sha }} - name: Check PR linked issue and assignee diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml index 3501b3c4ed..d2dca23917 100644 --- a/.github/workflows/run-ci-cd.yaml +++ b/.github/workflows/run-ci-cd.yaml @@ -32,20 +32,20 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Install Poetry run: pipx install poetry - name: Set up Python - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: cache: 'poetry' cache-dependency-path: backend/poetry.lock python-version: '3.13' - name: Set up pre-commit cache - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb + uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 with: path: ~/.cache/pre-commit key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }} @@ -78,7 +78,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Install pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 @@ -113,7 +113,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Docker buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f @@ -143,7 +143,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Setup Trivy uses: aquasecurity/setup-trivy@3fb12ec12f41e471780db15c232d5dd185dcb514 @@ -169,7 +169,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Setup Trivy uses: aquasecurity/setup-trivy@3fb12ec12f41e471780db15c232d5dd185dcb514 @@ -184,17 +184,44 @@ jobs: trivy-config: trivy.yaml timeout-minutes: 5 + run-security-scan: + name: Run security scan + needs: + - check-frontend + - pre-commit + - spellcheck + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + + - name: Run Security CI Scan + run: make security-scan + + - name: Upload security scan report + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: semgrep-results-run-${{ github.run_number }} + path: semgrep-security-report.txt + retention-days: 14 + timeout-minutes: 5 + run-backend-tests: name: Run backend tests needs: + - run-security-scan - scan-code - scan-ci-dependencies permissions: contents: read + id-token: write runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Docker buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f @@ -215,20 +242,38 @@ jobs: - name: Run backend tests run: | - docker run -e DJANGO_SETTINGS_MODULE=settings.test --env-file backend/.env.example owasp/nest:test-backend-latest pytest + CONTAINER_ID=$(docker create \ + -e DJANGO_SETTINGS_MODULE=settings.test \ + --env-file backend/.env.example \ + owasp/nest:test-backend-latest pytest) + docker start -a $CONTAINER_ID + EXIT_CODE=$? + docker cp $CONTAINER_ID:/home/owasp/coverage.xml backend/coverage.xml 2>/dev/null || true + docker rm $CONTAINER_ID >/dev/null 2>&1 + exit $EXIT_CODE + + - name: Upload coverage report to Codecov + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de + with: + fail_ci_if_error: false + files: backend/coverage.xml + flags: backend + use_oidc: true timeout-minutes: 5 run-frontend-unit-tests: name: Run frontend unit tests needs: + - run-security-scan - scan-code - scan-ci-dependencies permissions: contents: read + id-token: write runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Docker buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f @@ -249,12 +294,28 @@ jobs: - name: Run frontend unit tests run: | - docker run --env-file frontend/.env.example owasp/nest:test-frontend-unit-latest pnpm run test:unit + CONTAINER_ID=$(docker create \ + --env-file frontend/.env.example \ + owasp/nest:test-frontend-unit-latest pnpm run test:unit) + docker start -a $CONTAINER_ID + EXIT_CODE=$? + docker cp $CONTAINER_ID:/app/coverage frontend/coverage 2>/dev/null || true + docker rm $CONTAINER_ID >/dev/null 2>&1 + exit $EXIT_CODE + + - name: Upload coverage report to Codecov + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de + with: + fail_ci_if_error: false + files: frontend/coverage/lcov.info + flags: frontend + use_oidc: true timeout-minutes: 5 run-frontend-e2e-tests: name: Run frontend e2e tests needs: + - run-security-scan - scan-code - scan-ci-dependencies permissions: @@ -285,7 +346,7 @@ jobs: - 6379:6379 steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Docker buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f @@ -342,11 +403,12 @@ jobs: - name: Run frontend end-to-end tests run: | docker run --env-file frontend/.env.e2e.example owasp/nest:test-frontend-e2e-latest pnpm run test:e2e - timeout-minutes: 10 + timeout-minutes: 15 run-frontend-a11y-tests: name: Run frontend accessibility tests needs: + - run-security-scan - scan-code - scan-ci-dependencies permissions: @@ -354,7 +416,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Docker buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f @@ -397,7 +459,9 @@ jobs: run-graphql-fuzz-tests: name: Run GraphQL fuzz tests + if: false needs: + - run-security-scan - scan-code - scan-ci-dependencies uses: ./.github/workflows/run-fuzz-tests.yaml @@ -407,6 +471,7 @@ jobs: run-rest-fuzz-tests: name: Run REST fuzz tests needs: + - run-security-scan - scan-code - scan-ci-dependencies uses: ./.github/workflows/run-fuzz-tests.yaml @@ -433,7 +498,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 @@ -557,7 +622,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Setup Trivy uses: aquasecurity/setup-trivy@3fb12ec12f41e471780db15c232d5dd185dcb514 @@ -601,7 +666,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 @@ -701,7 +766,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 @@ -869,7 +934,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Install pnpm uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 @@ -901,7 +966,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Run baseline scan uses: zaproxy/action-baseline@de8ad967d3548d44ef623df22cf95c3b0baf8b25 @@ -939,7 +1004,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 @@ -1045,7 +1110,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Setup Trivy uses: aquasecurity/setup-trivy@3fb12ec12f41e471780db15c232d5dd185dcb514 @@ -1090,7 +1155,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Prepare SSH key env: @@ -1218,7 +1283,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Prepare SSH key env: @@ -1246,7 +1311,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Run baseline scan uses: zaproxy/action-baseline@de8ad967d3548d44ef623df22cf95c3b0baf8b25 diff --git a/.github/workflows/run-code-ql.yaml b/.github/workflows/run-code-ql.yaml index 6c756cbc1d..3ab53ce305 100644 --- a/.github/workflows/run-code-ql.yaml +++ b/.github/workflows/run-code-ql.yaml @@ -28,7 +28,7 @@ jobs: - python steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Initialize CodeQL uses: github/codeql-action/init@cdefb33c0f6224e58673d9004f47f7cb3e328b89 diff --git a/.github/workflows/run-fuzz-tests.yaml b/.github/workflows/run-fuzz-tests.yaml index 0de9a396b2..a299f73573 100644 --- a/.github/workflows/run-fuzz-tests.yaml +++ b/.github/workflows/run-fuzz-tests.yaml @@ -43,7 +43,7 @@ jobs: - 6379:6379 steps: - name: Check out repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Docker buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f diff --git a/.github/workflows/update-nest-test-images.yaml b/.github/workflows/update-nest-test-images.yaml index 8c0bb4a0c8..5fa11c01d1 100644 --- a/.github/workflows/update-nest-test-images.yaml +++ b/.github/workflows/update-nest-test-images.yaml @@ -19,7 +19,7 @@ jobs: contents: read runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Set up Docker buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f diff --git a/.gitignore b/.gitignore index 23c5777f4c..d0bd39a787 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,8 @@ design/ frontend/blob-report/ frontend/coverage frontend/dist +frontend/.next +frontend/out frontend/npm-debug.log* frontend/playwright-report/ frontend/playwright/.cache/ @@ -61,3 +63,4 @@ venv/ # Snyk Security Extension - AI Rules (auto-generated) .cursor/rules/snyk_rules.mdc +semgrep-security-report.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e974cea4a9..ca9565cd55 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: - --args=--config=__GIT_WORKING_DIR__/infrastructure/.tflint.hcl - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.13 + rev: v0.14.14 hooks: - id: ruff-check args: diff --git a/Makefile b/Makefile index faba777dac..7f2649e631 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,8 @@ include docs/Makefile include frontend/Makefile include infrastructure/Makefile +.PHONY: build clean check pre-commit prune run scan-images security-scan test update + MAKEFLAGS += --no-print-directory build: @@ -59,6 +61,44 @@ scan-images: \ scan-backend-image \ scan-frontend-image +security-scan: + @echo "Running Security Scan..." + @docker run --rm \ + -v "$(PWD):/src" \ + -w /src \ + $$(grep -E '^FROM semgrep/semgrep:' docker/semgrep/Dockerfile | sed 's/^FROM //') \ + semgrep \ + --config p/ci \ + --config p/command-injection \ + --config p/cwe-top-25 \ + --config p/default \ + --config p/django \ + --config p/docker \ + --config p/docker-compose \ + --config p/dockerfile \ + --config p/javascript \ + --config p/nextjs \ + --config p/nginx \ + --config p/nodejs \ + --config p/owasp-top-ten \ + --config p/python \ + --config p/r2c-security-audit \ + --config p/react \ + --config p/secrets \ + --config p/secure-defaults \ + --config p/security-audit \ + --config p/security-headers \ + --config p/sql-injection \ + --config p/terraform \ + --config p/typescript \ + --error \ + --skip-unknown-extensions \ + --timeout 10 \ + --timeout-threshold 3 \ + --text \ + --text-output=semgrep-security-report.txt \ + . + test: \ test-nest-app diff --git a/README.md b/README.md index d0c33c15e2..c17ac89dc1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Issues](https://img.shields.io/github/issues/owasp/nest?color=blue&style=for-the-badge&label=Issues)](https://github.com/OWASP/Nest/issues) [![Pull Requests](https://img.shields.io/github/issues-pr/owasp/nest?color=blue&style=for-the-badge&label=Pull%20Requests)](https://github.com/OWASP/Nest/pulls) -[![OpenSSF](https://img.shields.io/badge/OpenSSF-84%25-blue?style=for-the-badge)](https://www.bestpractices.dev/projects/10174) [![Snyk Security](https://img.shields.io/badge/Snyk-Scanned-blue?style=for-the-badge)](https://snyk.io) +[![OpenSSF](https://img.shields.io/badge/OpenSSF-passing-blue?style=for-the-badge)](https://www.bestpractices.dev/projects/10174) [![Snyk Security](https://img.shields.io/badge/Snyk-Scanning-blue?style=for-the-badge)](https://snyk.io) [![Forks](https://img.shields.io/github/forks/owasp/nest?style=for-the-badge&label=Forks)](https://github.com/OWASP/Nest/network/members) [![Stars](https://img.shields.io/github/stars/owasp/nest?style=for-the-badge&label=Stars)](https://github.com/OWASP/Nest/stargazers) diff --git a/backend/apps/api/decorators/cache.py b/backend/apps/api/decorators/cache.py index 655710cfae..48454e07ea 100644 --- a/backend/apps/api/decorators/cache.py +++ b/backend/apps/api/decorators/cache.py @@ -13,7 +13,8 @@ def generate_key( prefix: str, ): """Generate a cache key for a request.""" - return f"{prefix}:{request.get_full_path()}" + # Suppress warning as this generates an internal cache key, not a user-facing HTML response. + return f"{prefix}:{request.get_full_path()}" # NOSEMGREP: python.flask.security.audit.directly-returned-format-string.directly-returned-format-string # noqa: E501 def cache_response( diff --git a/backend/apps/api/internal/extensions/__init__.py b/backend/apps/api/internal/extensions/__init__.py new file mode 100644 index 0000000000..4b350753ac --- /dev/null +++ b/backend/apps/api/internal/extensions/__init__.py @@ -0,0 +1 @@ +"""Strawberry extensions.""" diff --git a/backend/apps/api/internal/extensions/cache.py b/backend/apps/api/internal/extensions/cache.py new file mode 100644 index 0000000000..1d2396ad12 --- /dev/null +++ b/backend/apps/api/internal/extensions/cache.py @@ -0,0 +1,106 @@ +"""Strawberry cache extension.""" + +import hashlib +import json +from functools import lru_cache + +from django.conf import settings +from django.core.cache import cache +from django.core.serializers.json import DjangoJSONEncoder +from strawberry.extensions import SchemaExtension +from strawberry.permission import PermissionExtension +from strawberry.schema import Schema +from strawberry.utils.str_converters import to_camel_case + + +@lru_cache(maxsize=1) +def get_protected_fields(schema: Schema) -> tuple[str, ...]: + """Get protected field names. + + Args: + schema (Schema): The GraphQL schema. + + Returns: + tuple[str, ...]: Tuple of protected field names in camelCase. + + """ + return tuple( + to_camel_case(field.name) + for field in getattr( + getattr(schema.schema_converter.type_map.get("Query"), "definition", None), + "fields", + (), + ) + if any(isinstance(ext, PermissionExtension) for ext in field.extensions) + ) + + +def generate_key(field_name: str, field_args: dict) -> str: + """Generate a unique cache key for a query. + + Args: + field_name (str): The GraphQL field name. + field_args (dict): The field's arguments. + + Returns: + str: The unique cache key. + + """ + key = f"{field_name}:{json.dumps(field_args, cls=DjangoJSONEncoder, sort_keys=True)}" + return f"{settings.GRAPHQL_RESOLVER_CACHE_PREFIX}-{hashlib.sha256(key.encode()).hexdigest()}" + + +def invalidate_cache(field_name: str, field_args: dict) -> bool: + """Invalidate a specific GraphQL query from the resolver cache. + + Args: + field_name: The GraphQL field name (e.g., 'getProgram'). + field_args: The field's arguments as a dict (e.g., {'programKey': 'my-program'}). + + Returns: + True if cache was invalidated, False if key didn't exist. + + """ + return cache.delete(generate_key(field_name, field_args)) + + +def invalidate_program_cache(program_key: str) -> None: + """Invalidate all GraphQL caches related to a program. + + Args: + program_key: The program's key identifier. + + """ + invalidate_cache("getProgram", {"programKey": program_key}) + invalidate_cache("getProgramModules", {"programKey": program_key}) + + +def invalidate_module_cache(module_key: str, program_key: str) -> None: + """Invalidate all GraphQL caches related to a module. + + Args: + module_key: The module's key identifier. + program_key: The program's key identifier. + + """ + invalidate_cache("getModule", {"moduleKey": module_key, "programKey": program_key}) + invalidate_program_cache(program_key) + + +class CacheExtension(SchemaExtension): + """Cache extension.""" + + def resolve(self, _next, root, info, *args, **kwargs): + """Wrap the resolver to provide caching.""" + if ( + info.field_name.startswith("__") + or info.parent_type.name != "Query" + or info.field_name in get_protected_fields(self.execution_context.schema) + ): + return _next(root, info, *args, **kwargs) + + return cache.get_or_set( + generate_key(info.field_name, kwargs), + lambda: _next(root, info, *args, **kwargs), + settings.GRAPHQL_RESOLVER_CACHE_TIME_SECONDS, + ) diff --git a/backend/apps/api/internal/mutations/api_key.py b/backend/apps/api/internal/mutations/api_key.py index edbcc49a13..dda381789e 100644 --- a/backend/apps/api/internal/mutations/api_key.py +++ b/backend/apps/api/internal/mutations/api_key.py @@ -77,7 +77,10 @@ def create_api_key(self, info: Info, name: str, expires_at: datetime) -> CreateA message="API key created successfully.", ) except IntegrityError as err: - logger.warning("Error creating API key: %s", err) + # Logging the error message string only, not the sensitive API key credential itself. # noqa: E501 + logger.warning( # NOSEMGREP: python.lang.security.audit.logging.logger-credential-leak.python-logger-credential-disclosure # noqa: E501 + "Error creating API key: %s", err + ) return CreateApiKeyResult( ok=False, code="ERROR", diff --git a/backend/apps/common/extensions.py b/backend/apps/common/extensions.py deleted file mode 100644 index 21e9373950..0000000000 --- a/backend/apps/common/extensions.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Strawberry extensions.""" - -import hashlib -import json -from functools import lru_cache - -from django.conf import settings -from django.core.cache import cache -from strawberry.extensions import SchemaExtension -from strawberry.permission import PermissionExtension -from strawberry.schema import Schema -from strawberry.utils.str_converters import to_camel_case - - -@lru_cache(maxsize=1) -def get_protected_fields(schema: Schema) -> tuple[str, ...]: - """Get protected field names. - - Args: - schema (Schema): The GraphQL schema. - - Returns: - tuple[str, ...]: Tuple of protected field names in camelCase. - - """ - query_type = schema.schema_converter.type_map.get("Query") - fields = getattr(getattr(query_type, "definition", None), "fields", ()) - return tuple( - to_camel_case(field.name) - for field in fields - if any(isinstance(ext, PermissionExtension) for ext in field.extensions) - ) - - -class CacheExtension(SchemaExtension): - """CacheExtension class.""" - - def generate_key(self, field_name: str, field_args: dict) -> str: - """Generate a unique cache key for a query. - - Args: - field_name (str): The GraphQL field name. - field_args (dict): The field's arguments. - - Returns: - str: The unique cache key. - - """ - key = f"{field_name}:{json.dumps(field_args, sort_keys=True)}" - return ( - f"{settings.GRAPHQL_RESOLVER_CACHE_PREFIX}-{hashlib.sha256(key.encode()).hexdigest()}" - ) - - def resolve(self, _next, root, info, *args, **kwargs): - """Wrap the resolver to provide caching.""" - if ( - info.field_name.startswith("__") - or info.parent_type.name != "Query" - or info.field_name in get_protected_fields(self.execution_context.schema) - ): - return _next(root, info, *args, **kwargs) - - return cache.get_or_set( - self.generate_key(info.field_name, kwargs), - lambda: _next(root, info, *args, **kwargs), - settings.GRAPHQL_RESOLVER_CACHE_TIME_SECONDS, - ) diff --git a/backend/apps/common/management/commands/purge_data.py b/backend/apps/common/management/commands/purge_data.py index 54f4252aae..2bdacbf2a4 100644 --- a/backend/apps/common/management/commands/purge_data.py +++ b/backend/apps/common/management/commands/purge_data.py @@ -3,6 +3,7 @@ from django.apps import apps from django.core.management.base import BaseCommand from django.db import connection +from psycopg2 import sql class Command(BaseCommand): @@ -22,5 +23,10 @@ def handle(self, *_args, **options) -> None: apps.get_app_config(nest_app).get_models(), key=lambda m: m.__name__, ): - cursor.execute(f"TRUNCATE TABLE {model._meta.db_table} CASCADE") # NOSONAR + # Suppress false positive sqlalchemy warning. + cursor.execute( # NOSEMGREP: python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query # noqa: E501 + sql.SQL("TRUNCATE TABLE {} CASCADE").format( + sql.Identifier(model._meta.db_table) + ) + ) print(f"Purged {nest_app}.{model.__name__}") diff --git a/backend/apps/common/template_loader.py b/backend/apps/common/template_loader.py index 5eadd805b2..3b41ad4575 100644 --- a/backend/apps/common/template_loader.py +++ b/backend/apps/common/template_loader.py @@ -8,5 +8,10 @@ SLACK_TEMPLATES_DIR = BASE_DIR / "slack" / "templates" VIDEO_TEMPLATES_DIR = BASE_DIR / "owasp" / "templates" / "video" -slack_env = Environment(loader=FileSystemLoader(SLACK_TEMPLATES_DIR), autoescape=True) -video_env = Environment(loader=FileSystemLoader(VIDEO_TEMPLATES_DIR), autoescape=True) +# Suppress false positive Flask security warning. +slack_env = Environment( # NOSEMGREP: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2 # noqa: E501 + loader=FileSystemLoader(SLACK_TEMPLATES_DIR), autoescape=True +) +video_env = Environment( # NOSEMGREP: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2 # noqa: E501 + loader=FileSystemLoader(VIDEO_TEMPLATES_DIR), autoescape=True +) diff --git a/backend/apps/github/admin/issue.py b/backend/apps/github/admin/issue.py index 3c397dcd85..17f87e64c8 100644 --- a/backend/apps/github/admin/issue.py +++ b/backend/apps/github/admin/issue.py @@ -1,7 +1,7 @@ """GitHub app Issue model admin.""" from django.contrib import admin -from django.utils.safestring import mark_safe +from django.utils.html import format_html from apps.github.models.issue import Issue @@ -38,7 +38,7 @@ def custom_field_github_url(self, obj) -> str: str: A safe HTML link to the issue on GitHub. """ - return mark_safe(f"↗️") # noqa: S308 + return format_html("↗️", obj.url) custom_field_github_url.short_description = "GitHub 🔗" diff --git a/backend/apps/github/admin/pull_request.py b/backend/apps/github/admin/pull_request.py index 02937114b4..f5c5d00f3d 100644 --- a/backend/apps/github/admin/pull_request.py +++ b/backend/apps/github/admin/pull_request.py @@ -1,7 +1,7 @@ """GitHub app PullRequest model admin.""" from django.contrib import admin -from django.utils.safestring import mark_safe +from django.utils.html import format_html from apps.github.models.pull_request import PullRequest @@ -44,7 +44,7 @@ def custom_field_github_url(self, obj: PullRequest) -> str: str: A safe HTML link to the pull request on GitHub. """ - return mark_safe(f"↗️") # noqa: S308 + return format_html("↗️", obj.url) custom_field_github_url.short_description = "GitHub 🔗" diff --git a/backend/apps/github/admin/repository.py b/backend/apps/github/admin/repository.py index 66dcdc18aa..ba17eb19ca 100644 --- a/backend/apps/github/admin/repository.py +++ b/backend/apps/github/admin/repository.py @@ -1,7 +1,7 @@ """GitHub app Repository model admin.""" from django.contrib import admin -from django.utils.safestring import mark_safe +from django.utils.html import format_html from apps.github.models.repository import Repository @@ -46,8 +46,10 @@ def custom_field_github_url(self, obj) -> str: str: A safe HTML link to the repository on GitHub. """ - return mark_safe( # noqa: S308 - f"↗️" + return format_html( + "↗️", + obj.owner.login, + obj.name, ) def custom_field_title(self, obj: Repository) -> str: diff --git a/backend/apps/github/models/mixins/repository.py b/backend/apps/github/models/mixins/repository.py index 6bd7b27b6c..5586d1e9c0 100644 --- a/backend/apps/github/models/mixins/repository.py +++ b/backend/apps/github/models/mixins/repository.py @@ -14,7 +14,12 @@ class RepositoryIndexMixin: @property def is_indexable(self) -> bool: - """Repositories to index.""" + """Determine if the repository should be indexed. + + Returns: + bool: True if repository meets all indexing criteria, False otherwise. + + """ return ( not self.is_archived and not self.is_empty @@ -24,90 +29,182 @@ def is_indexable(self) -> bool: @property def idx_commits_count(self) -> int: - """Return commits count for indexing.""" + """Get the total number of commits in this repository for indexing. + + Returns: + int: The total commit count of the repository. + + """ return self.commits_count @property def idx_contributors_count(self) -> int: - """Return contributors count for indexing.""" + """Get the total number of contributors to this repository for indexing. + + Returns: + int: The total number of unique contributors. + + """ return self.contributors_count @property def idx_created_at(self) -> float: - """Return created at for indexing.""" + """Get the repository creation timestamp for indexing. + + Returns: + float: Unix timestamp when the repository was created. + + """ return self.created_at.timestamp() @property def idx_description(self) -> str: - """Return description for indexing.""" + """Get the repository description for indexing. + + Returns: + str: The repository's description text. + + """ return self.description @property def idx_forks_count(self) -> int: - """Return forks count for indexing.""" + """Get the total number of repository forks for indexing. + + Returns: + int: The total number of times this repository has been forked. + + """ return self.forks_count @property def idx_has_funding_yml(self) -> bool: - """Return has funding.yml for indexing.""" + """Check if the repository has a FUNDING.yml file for indexing. + + Returns: + bool: True if the repository contains a FUNDING.yml file, False otherwise. + + """ return self.has_funding_yml @property def idx_key(self) -> str: - """Return key for indexing.""" + """Get the unique Nest key identifier for this repository for indexing. + + Returns: + str: The repository's unique Nest key. + + """ return self.nest_key @property def idx_languages(self) -> list[str]: - """Return languages for indexing.""" + """Get the programming languages used in this repository for indexing. + + Returns: + list[str]: A list of programming language names detected in the repository. + + """ return self.languages @property def idx_license(self) -> str: - """Return license for indexing.""" + """Get the repository's license identifier for indexing. + + Returns: + str: The license identifier. + + """ return self.license @property def idx_name(self) -> str: - """Return name for indexing.""" + """Get the repository name for indexing. + + Returns: + str: The name of the repository. + + """ return self.name @property def idx_open_issues_count(self) -> int: - """Return open issues count for indexing.""" + """Get the total number of open issues in this repository for indexing. + + Returns: + int: The count of currently open issues. + + """ return self.open_issues_count @property def idx_project_key(self) -> str: - """Return project key for indexing.""" + """Get the Nest key of the associated project for indexing. + + Returns: + str: The unique Nest key of the project this repository belongs to, + or an empty string if no project is associated. + + """ return self.project.nest_key if self.project else "" @property def idx_pushed_at(self) -> float: - """Return pushed at for indexing.""" + """Get the timestamp of the last push to this repository for indexing. + + Returns: + float: Unix timestamp of the most recent push. + + """ return self.pushed_at.timestamp() @property def idx_size(self) -> int: - """Return size for indexing.""" + """Get the repository size in kilobytes for indexing. + + Returns: + int: The repository size in KB. + + """ return self.size @property def idx_stars_count(self) -> int: - """Return stars count for indexing.""" + """Get the total number of stars this repository has received for indexing. + + Returns: + int: The total count of stars on the repository. + + """ return self.stars_count @property def idx_subscribers_count(self) -> int: - """Return subscribers count for indexing.""" - return self.stars_count + """Get the total number of subscribers for this repository for indexing. + + Returns: + int: The count of users watching this repository. + + """ + return self.subscribers_count @property def idx_top_contributors(self) -> list[dict[str, Any]]: - """Return top contributors for indexing.""" + """Get the list of top contributors to this repository for indexing. + + Returns: + list[dict[str, Any]]: A list of dictionaries containing information about + the top contributors to this repository. + + """ return RepositoryContributor.get_top_contributors(repository=self.key) @property - def idx_topics(self): - """Return topics for indexing.""" + def idx_topics(self) -> list[str]: + """Get the topics associated with this repository for indexing. + + Returns: + list: A list of topics that categorize this repository. + + """ return self.topics diff --git a/backend/apps/github/models/mixins/user.py b/backend/apps/github/models/mixins/user.py index bdbb2c2f19..ed25aedc26 100644 --- a/backend/apps/github/models/mixins/user.py +++ b/backend/apps/github/models/mixins/user.py @@ -12,7 +12,12 @@ class UserIndexMixin: @property def is_indexable(self): - """Users to index.""" + """Determine if the user should be indexed. + + Returns: + bool: True if the user meets all indexing criteria, False otherwise. + + """ return ( not self.is_bot and not self.login.endswith(("Bot", "-bot")) @@ -21,77 +26,154 @@ def is_indexable(self): @property def idx_avatar_url(self) -> str: - """Return avatar URL for indexing.""" + """Get the user's avatar URL for indexing. + + Returns: + str: The URL of the user's avatar image. + + """ return self.avatar_url @property def idx_badge_count(self) -> int: - """Return badge count for indexing.""" + """Get the number of active badges associated with the user. + + Returns: + int: The count of active user badges. + + """ return self.user_badges.filter(is_active=True).count() @property def idx_bio(self) -> str: - """Return bio for indexing.""" + """Get the user's biography text for indexing. + + Returns: + str: The user's bio, if provided; otherwise an empty string. + + """ return self.bio @property def idx_company(self) -> str: - """Return company for indexing.""" + """Get the user's company affiliation for indexing. + + Returns: + str: The company name associated with the user's profile, if any. + + """ return self.company @property def idx_created_at(self) -> float: - """Return created at timestamp for indexing.""" + """Get the account creation timestamp for indexing. + + Returns: + float: Unix timestamp when the account was created. + + """ return self.created_at.timestamp() @property def idx_email(self) -> str: - """Return email for indexing.""" + """Get the user's email address for indexing. + + Returns: + str: The user's email address, if available. + + """ return self.email @property def idx_key(self) -> str: - """Return key for indexing.""" + """Get the unique key identifier for this user used in indexing. + + Returns: + str: The user's login, used as the unique key. + + """ return self.login @property def idx_followers_count(self) -> int: - """Return followers count for indexing.""" + """Get the number of followers for the user. + + Returns: + int: Total count of users following this user. + + """ return self.followers_count @property def idx_following_count(self) -> int: - """Return following count for indexing.""" + """Get the number of users the user is following. + + Returns: + int: Total count of users this user follows. + + """ return self.following_count @property def idx_location(self) -> str: - """Return location for indexing.""" + """Get the user's location for indexing. + + Returns: + str: The user's self-declared location, if available. + + """ return self.location @property def idx_login(self) -> str: - """Return login for indexing.""" + """Get the user's login for indexing. + + Returns: + str: The user's GitHub login handle. + + """ return self.login @property def idx_name(self) -> str: - """Return name for indexing.""" + """Get the user's display name for indexing. + + Returns: + str: The full name displayed on the user's profile, if provided. + + """ return self.name @property def idx_public_repositories_count(self) -> int: - """Return public repositories count for indexing.""" + """Get the number of public repositories owned by the user. + + Returns: + int: Total count of public repositories owned by the user. + + """ return self.public_repositories_count @property def idx_title(self) -> str: - """Return title for indexing.""" + """Get the user's profile title or headline for indexing. + + Returns: + str: The title or headline associated with the user. + + """ return self.title @property def idx_contributions(self): - """Return contributions for indexing.""" + """Get a summary of the user's top repository contributions for indexing. + + Returns: + list[dict]: A list of contribution summaries, each including counts and + metadata about the contributed repositories (e.g., name, stars, + forks, license, and latest release information). + + """ from apps.github.models.repository_contributor import RepositoryContributor return [ @@ -123,12 +205,23 @@ def idx_contributions(self): @property def idx_contributions_count(self) -> int: - """Return contributions count for indexing.""" + """Get the total number of contributions made by the user. + + Returns: + int: Aggregate contributions count across repositories. + + """ return self.contributions_count @property def idx_issues(self) -> list[dict]: - """Return issues for indexing.""" + """Get recent issues associated with the user for indexing. + + Returns: + list[dict]: A list of issue summaries including timestamps, counts, + identifiers, titles, URLs, and minimal repository metadata. + + """ return [ { "created_at": i.created_at.timestamp(), @@ -149,12 +242,24 @@ def idx_issues(self) -> list[dict]: @property def idx_issues_count(self) -> int: - """Return issues count for indexing.""" + """Get the total number of issues associated with the user. + + Returns: + int: Count of issues linked to the user. + + """ return self.issues.count() @property def idx_releases(self) -> list[dict]: - """Return releases for indexing.""" + """Get releases associated with the user for indexing. + + Returns: + list[dict]: A list of release summaries including pre-release flag, + names, published timestamps, tags, URLs, and minimal repository + metadata. + + """ return [ { "is_pre_release": r.is_pre_release, @@ -175,15 +280,30 @@ def idx_releases(self) -> list[dict]: @property def idx_releases_count(self) -> int: - """Return releases count for indexing.""" + """Get the total number of releases associated with the user. + + Returns: + int: Count of releases linked to the user. + + """ return self.releases.count() @property def idx_updated_at(self) -> float: - """Return updated at timestamp for indexing.""" + """Get the last profile update timestamp for indexing. + + Returns: + float: Unix timestamp (seconds since epoch) of the last update. + + """ return self.updated_at.timestamp() @property def idx_url(self) -> str: - """Return GitHub profile URL for indexing.""" + """Get the user's GitHub profile URL for indexing. + + Returns: + str: The URL to the user's GitHub profile page. + + """ return self.url diff --git a/backend/apps/mentorship/api/internal/mutations/module.py b/backend/apps/mentorship/api/internal/mutations/module.py index 96e95c3dd4..5244ee69cf 100644 --- a/backend/apps/mentorship/api/internal/mutations/module.py +++ b/backend/apps/mentorship/api/internal/mutations/module.py @@ -8,6 +8,7 @@ from django.db import transaction from django.utils import timezone +from apps.api.internal.extensions.cache import invalidate_module_cache, invalidate_program_cache from apps.github.models import User as GithubUser from apps.mentorship.api.internal.nodes.module import ( CreateModuleInput, @@ -20,6 +21,7 @@ from apps.nest.api.internal.permissions import IsAuthenticated from apps.owasp.models import Project +ASSIGNEE_NOT_FOUND_MSG = "Assignee not found." ISSUE_NOT_FOUND_MSG = "Issue not found in this module." MODULE_NOT_FOUND_MSG = "Module not found." @@ -38,6 +40,7 @@ def resolve_mentors_from_logins(logins: list[str]) -> set[Mentor]: msg = f"GitHub user '{login}' not found." logger.warning(msg, exc_info=True) raise ValueError(msg) from e + return mentors @@ -119,6 +122,8 @@ def create_module(self, info: strawberry.Info, input_data: CreateModuleInput) -> mentors_to_set.add(creator_as_mentor) module.mentors.set(list(mentors_to_set)) + transaction.on_commit(lambda: invalidate_program_cache(program.key)) + return module @strawberry.mutation(permission_classes=[IsAuthenticated]) @@ -141,7 +146,7 @@ def assign_issue_to_user( .first() ) if module is None: - raise ObjectDoesNotExist(msg=MODULE_NOT_FOUND_MSG) + raise ObjectDoesNotExist(MODULE_NOT_FOUND_MSG) mentor = Mentor.objects.filter(nest_user=user).first() if mentor is None: @@ -151,11 +156,11 @@ def assign_issue_to_user( gh_user = GithubUser.objects.filter(login=user_login).first() if gh_user is None: - raise ObjectDoesNotExist(msg="Assignee not found.") + raise ObjectDoesNotExist(ASSIGNEE_NOT_FOUND_MSG) issue = module.issues.filter(number=issue_number).first() if issue is None: - raise ObjectDoesNotExist(msg=ISSUE_NOT_FOUND_MSG) + raise ObjectDoesNotExist(ISSUE_NOT_FOUND_MSG) issue.assignees.add(gh_user) @@ -183,7 +188,7 @@ def unassign_issue_from_user( .first() ) if module is None: - raise ObjectDoesNotExist(msg=MODULE_NOT_FOUND_MSG) + raise ObjectDoesNotExist(MODULE_NOT_FOUND_MSG) mentor = Mentor.objects.filter(nest_user=user).first() if mentor is None: @@ -193,11 +198,12 @@ def unassign_issue_from_user( gh_user = GithubUser.objects.filter(login=user_login).first() if gh_user is None: - raise ObjectDoesNotExist(msg="Assignee not found.") + raise ObjectDoesNotExist(ASSIGNEE_NOT_FOUND_MSG) issue = module.issues.filter(number=issue_number).first() if issue is None: - raise ObjectDoesNotExist(msg=f"Issue {issue_number} not found in this module.") + msg = f"Issue {issue_number} not found in this module." + raise ObjectDoesNotExist(msg) issue.assignees.remove(gh_user) @@ -223,7 +229,7 @@ def set_task_deadline( .first() ) if module is None: - raise ObjectDoesNotExist(msg=MODULE_NOT_FOUND_MSG) + raise ObjectDoesNotExist(MODULE_NOT_FOUND_MSG) mentor = Mentor.objects.filter(nest_user=user).first() if mentor is None: @@ -238,7 +244,7 @@ def set_task_deadline( .first() ) if issue is None: - raise ObjectDoesNotExist(msg=ISSUE_NOT_FOUND_MSG) + raise ObjectDoesNotExist(ISSUE_NOT_FOUND_MSG) assignees = issue.assignees.all() if not assignees.exists(): @@ -285,7 +291,7 @@ def clear_task_deadline( .first() ) if module is None: - raise ObjectDoesNotExist(msg=MODULE_NOT_FOUND_MSG) + raise ObjectDoesNotExist(MODULE_NOT_FOUND_MSG) mentor = Mentor.objects.filter(nest_user=user).first() if mentor is None: @@ -300,7 +306,7 @@ def clear_task_deadline( .first() ) if issue is None: - raise ObjectDoesNotExist(msg=ISSUE_NOT_FOUND_MSG) + raise ObjectDoesNotExist(ISSUE_NOT_FOUND_MSG) assignees = issue.assignees.all() if not assignees.exists(): @@ -330,8 +336,9 @@ def update_module(self, info: strawberry.Info, input_data: UpdateModuleInput) -> module = Module.objects.select_related("program").get( key=input_data.key, program__key=input_data.program_key ) + old_module_key = module.key except Module.DoesNotExist as e: - raise ObjectDoesNotExist(msg=MODULE_NOT_FOUND_MSG) from e + raise ObjectDoesNotExist(MODULE_NOT_FOUND_MSG) from e try: creator_as_mentor = Mentor.objects.get(nest_user=user) @@ -400,4 +407,13 @@ def update_module(self, info: strawberry.Info, input_data: UpdateModuleInput) -> module.program.save(update_fields=["experience_levels"]) + program_key = module.program.key + + def _invalidate(): + invalidate_module_cache(old_module_key, program_key) + if module.key != old_module_key: + invalidate_module_cache(module.key, program_key) + + transaction.on_commit(_invalidate) + return module diff --git a/backend/apps/mentorship/api/internal/mutations/program.py b/backend/apps/mentorship/api/internal/mutations/program.py index 0905e28c53..88bba96ad8 100644 --- a/backend/apps/mentorship/api/internal/mutations/program.py +++ b/backend/apps/mentorship/api/internal/mutations/program.py @@ -6,6 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist, PermissionDenied, ValidationError from django.db import transaction +from apps.api.internal.extensions.cache import invalidate_program_cache from apps.mentorship.api.internal.mutations.module import resolve_mentors_from_logins from apps.mentorship.api.internal.nodes.enum import ProgramStatusEnum from apps.mentorship.api.internal.nodes.program import ( @@ -76,6 +77,7 @@ def update_program(self, info: strawberry.Info, input_data: UpdateProgramInput) try: program = Program.objects.get(key=input_data.key) + old_key = program.key except Program.DoesNotExist as err: msg = f"Program with key '{input_data.key}' not found." logger.warning(msg, exc_info=True) @@ -133,6 +135,13 @@ def update_program(self, info: strawberry.Info, input_data: UpdateProgramInput) admins_to_set = resolve_mentors_from_logins(input_data.admin_logins) program.admins.set(admins_to_set) + def _invalidate(): + invalidate_program_cache(old_key) + if program.key != old_key: + invalidate_program_cache(program.key) + + transaction.on_commit(_invalidate) + return program @strawberry.mutation(permission_classes=[IsAuthenticated]) @@ -161,6 +170,8 @@ def update_program_status( program.status = input_data.status.value program.save() + transaction.on_commit(lambda: invalidate_program_cache(program.key)) + logger.info("Updated status of program '%s' to '%s'", program.key, program.status) return program diff --git a/backend/apps/mentorship/api/internal/nodes/module.py b/backend/apps/mentorship/api/internal/nodes/module.py index 8ccbdf46f1..0a224089d3 100644 --- a/backend/apps/mentorship/api/internal/nodes/module.py +++ b/backend/apps/mentorship/api/internal/nodes/module.py @@ -5,8 +5,10 @@ import strawberry from apps.github.api.internal.nodes.issue import IssueNode +from apps.github.api.internal.nodes.pull_request import PullRequestNode from apps.github.api.internal.nodes.user import UserNode from apps.github.models import Label +from apps.github.models.pull_request import PullRequest from apps.github.models.user import User from apps.mentorship.api.internal.nodes.enum import ExperienceLevelEnum from apps.mentorship.api.internal.nodes.mentor import MentorNode @@ -158,6 +160,17 @@ def task_assigned_at(self, issue_number: int) -> datetime | None: .first() ) + @strawberry.field + def recent_pull_requests(self, limit: int = 5) -> list[PullRequestNode]: + """Return recent pull requests linked to issues in this module.""" + issue_ids = self.issues.values_list("id", flat=True) + return list( + PullRequest.objects.filter(related_issues__id__in=issue_ids) + .select_related("author") + .distinct() + .order_by("-created_at")[:limit] + ) + @strawberry.input class CreateModuleInput: diff --git a/backend/apps/mentorship/api/internal/nodes/program.py b/backend/apps/mentorship/api/internal/nodes/program.py index 463caa076f..bee27227ec 100644 --- a/backend/apps/mentorship/api/internal/nodes/program.py +++ b/backend/apps/mentorship/api/internal/nodes/program.py @@ -4,6 +4,8 @@ import strawberry +from apps.github.api.internal.nodes.milestone import MilestoneNode # noqa: TC001 +from apps.github.models.milestone import Milestone from apps.mentorship.api.internal.nodes.enum import ( ExperienceLevelEnum, ProgramStatusEnum, @@ -33,6 +35,19 @@ def admins(self) -> list[MentorNode] | None: """Get the list of program administrators.""" return self.admins.all() + @strawberry.field + def recent_milestones(self) -> list["MilestoneNode"]: + """Get the list of recent milestones for the program.""" + project_ids = self.modules.values_list("project_id", flat=True) + + return ( + Milestone.open_milestones.filter(repository__project__in=project_ids) + .select_related("repository__organization", "author") + .prefetch_related("labels") + .order_by("-created_at") + .distinct() + ) + @strawberry.type class PaginatedPrograms: diff --git a/backend/apps/mentorship/signals/__init__.py b/backend/apps/mentorship/signals/__init__.py index 376a8c1d0e..00b53d5574 100644 --- a/backend/apps/mentorship/signals/__init__.py +++ b/backend/apps/mentorship/signals/__init__.py @@ -1 +1 @@ -from .program import ProgramPostSaveHandler +from .program import program_post_save_clear_algolia_cache diff --git a/backend/apps/mentorship/signals/program.py b/backend/apps/mentorship/signals/program.py index 6e8200197a..66b5f3462a 100644 --- a/backend/apps/mentorship/signals/program.py +++ b/backend/apps/mentorship/signals/program.py @@ -11,14 +11,8 @@ logger = logging.getLogger(__name__) -class ProgramPostSaveHandler: - """Handles post_save signal for Program model to clear Algolia cache.""" - - @receiver(post_save, sender=Program) - def program_post_save_clear_algolia_cache(sender, instance, **kwargs): # noqa: N805 - """Signal handler to clear Algolia cache for the Program index. - - The sender, instance, and kwargs arguments are provided by the post_save signal. - """ - logger.info("Signal received for program '%s'. Clearing 'programs' index.", instance.name) - clear_index_cache("programs") +@receiver(post_save, sender=Program) +def program_post_save_clear_algolia_cache(sender, instance, **kwargs): # noqa: ARG001 + """Signal handler to clear Algolia cache for the Program index.""" + logger.info("Signal received for program '%s'. Clearing 'programs' index.", instance.name) + clear_index_cache("programs") diff --git a/backend/apps/owasp/admin/mixins.py b/backend/apps/owasp/admin/mixins.py index 9addfc4a6e..59b7839484 100644 --- a/backend/apps/owasp/admin/mixins.py +++ b/backend/apps/owasp/admin/mixins.py @@ -2,7 +2,7 @@ from django.contrib.contenttypes.admin import GenericTabularInline from django.contrib.contenttypes.models import ContentType -from django.utils.html import escape +from django.utils.html import format_html from django.utils.safestring import mark_safe from apps.owasp.admin.widgets import ChannelIdWidget @@ -106,10 +106,10 @@ def custom_field_github_urls(self, obj): return "" return self._format_github_link(obj.owasp_repository) - return mark_safe( # noqa: S308 - " ".join( - [self._format_github_link(repository) for repository in obj.repositories.all()] - ) + links = [self._format_github_link(repository) for repository in obj.repositories.all()] + # Use mark_safe since links are already safely formatted HTML from format_html + return mark_safe( # noqa: S308 # NOSEMGREP: python.django.security.audit.avoid-mark-safe.avoid-mark-safe + " ".join(links) ) def custom_field_owasp_url(self, obj): @@ -117,9 +117,7 @@ def custom_field_owasp_url(self, obj): if not hasattr(obj, "key") or not obj.key: return "" - return mark_safe( # noqa: S308 - f"↗️" - ) + return format_html("↗️", obj.key) def _format_github_link(self, repository): """Format a single GitHub repository link.""" @@ -130,9 +128,10 @@ def _format_github_link(self, repository): if not hasattr(repository, "key") or not repository.key: return "" - return mark_safe( # noqa: S308 - f"↗️" + return format_html( + "↗️", + repository.owner.login, + repository.key, ) custom_field_github_urls.short_description = "GitHub 🔗" diff --git a/backend/apps/owasp/admin/widgets.py b/backend/apps/owasp/admin/widgets.py index 7765ade609..409fdee692 100644 --- a/backend/apps/owasp/admin/widgets.py +++ b/backend/apps/owasp/admin/widgets.py @@ -21,4 +21,7 @@ def render(self, name, value, attrs=None, renderer=None): f"id='lookup_id_{name}' title='Look up related objects'>" ) - return mark_safe(f"{widget_html}{search_button}") # noqa: S308 + # Use mark_safe since both widget_html and search_button are already HTML + return mark_safe( # noqa: S308 # NOSEMGREP: python.django.security.audit.avoid-mark-safe.avoid-mark-safe + f"{widget_html} {search_button}" + ) diff --git a/backend/apps/owasp/models/project.py b/backend/apps/owasp/models/project.py index 0a6ecdcafb..37305845b7 100644 --- a/backend/apps/owasp/models/project.py +++ b/backend/apps/owasp/models/project.py @@ -159,44 +159,84 @@ def __str__(self) -> str: @property def entity_leaders(self) -> list[EntityMember]: - """Return project leaders.""" + """Get the list of project leaders. + + Returns: + list[EntityMember]: Up to the top leaders limited by `MAX_LEADERS_COUNT`. + + """ return super().entity_leaders[:MAX_LEADERS_COUNT] @property def health_score(self) -> float | None: - """Return project health score.""" + """Get the latest computed health score for the project. + + Returns: + float | None: The most recent health score, or None if unavailable. + + """ return self.last_health_metrics.score if self.last_health_metrics else None @property def is_code_type(self) -> bool: - """Indicate whether project has CODE type.""" + """Check if the project's type is CODE. + + Returns: + bool: True if the project type equals `ProjectType.CODE`. + + """ return self.type == ProjectType.CODE @property def is_documentation_type(self) -> bool: - """Indicate whether project has DOCUMENTATION type.""" + """Check if the project's type is DOCUMENTATION. + + Returns: + bool: True if the project type equals `ProjectType.DOCUMENTATION`. + + """ return self.type == ProjectType.DOCUMENTATION @property def is_funding_requirements_compliant(self) -> bool: - """Indicate whether project is compliant with funding requirements.""" + """Check if the project complies with funding requirements. + + Returns: + bool: True if all related repositories are funding-policy compliant. + + """ return not self.repositories.filter(is_funding_policy_compliant=False).exists() @property def is_leader_requirements_compliant(self) -> bool: - """Indicate whether project is compliant with project leaders requirements.""" + """Check if the project satisfies OWASP leader requirements. + + Returns: + bool: True if the project has multiple leaders (more than one). + + """ # https://owasp.org/www-committee-project/#div-practice # Have multiple Project Leaders who are not all employed by the same company. return self.leaders_count > 1 @property def is_tool_type(self) -> bool: - """Indicate whether project has TOOL type.""" + """Check if the project's type is TOOL. + + Returns: + bool: True if the project type equals `ProjectType.TOOL`. + + """ return self.type == ProjectType.TOOL @property def issues(self): - """Return issues.""" + """Get issues across the project's repositories. + + Returns: + QuerySet[Issue]: A queryset of issues with related entities prefetched. + + """ return ( Issue.objects.filter( repository__in=self.repositories.all(), @@ -215,32 +255,62 @@ def issues(self): @property def issues_count(self) -> int: - """Return count of issues.""" + """Get the total number of issues. + + Returns: + int: Count of issues across the project's repositories. + + """ return self.issues.count() @property def last_health_metrics(self) -> ProjectHealthMetrics | None: - """Return last health metrics for the project.""" + """Get the most recent health metrics for the project. + + Returns: + ProjectHealthMetrics | None: Latest metrics record or None if missing. + + """ return self.health_metrics.order_by("-nest_created_at").first() @property def leaders_count(self) -> int: - """Return the count of leaders.""" + """Get the number of project leaders. + + Returns: + int: Count of leaders derived from `leaders_raw`. + + """ return len(self.leaders_raw) @property def nest_key(self) -> str: - """Get Nest key.""" + """Get the Nest-specific project key. + + Returns: + str: The project key with the "www-project-" prefix removed. + + """ return self.key.replace("www-project-", "") @property def nest_url(self) -> str: - """Get Nest URL for project.""" + """Get the absolute Nest URL for this project. + + Returns: + str: The full Nest URL pointing to the project's page. + + """ return get_absolute_url(f"/projects/{self.nest_key}") @property def open_issues(self): - """Return open issues.""" + """Get open issues across the project's repositories. + + Returns: + QuerySet[Issue]: A queryset of open issues with repository related data. + + """ return Issue.open_issues.filter( repository__in=self.repositories.all(), ).select_related( @@ -249,17 +319,32 @@ def open_issues(self): @property def open_pull_requests_count(self) -> int: - """Return count of open pull requests.""" + """Get the number of open pull requests. + + Returns: + int: Count of pull requests currently in the "open" state. + + """ return self.pull_requests.filter(state="open").count() @property def owasp_page_last_updated_at(self) -> datetime.datetime | None: - """Return the last updated date of the OWASP page.""" + """Get the last updated timestamp of the project's OWASP page. + + Returns: + datetime.datetime | None: The OWASP page's last update time, or None. + + """ return self.owasp_repository.updated_at if self.owasp_repository else None @property def pull_requests(self): - """Return pull requests.""" + """Get pull requests across the project's repositories. + + Returns: + QuerySet[PullRequest]: A queryset of pull requests with related data. + + """ return ( PullRequest.objects.filter( repository__in=self.repositories.all(), @@ -278,19 +363,34 @@ def pull_requests(self): @property def pull_requests_count(self) -> int: - """Return count of pull requests.""" + """Get the total number of pull requests. + + Returns: + int: Count of pull requests across the project's repositories. + + """ return self.pull_requests.count() @property def pull_request_last_created_at(self) -> datetime.datetime | None: - """Return last created pull request.""" + """Get the most recent pull request creation timestamp. + + Returns: + datetime.datetime | None: Latest `created_at` timestamp, or None. + + """ return self.pull_requests.aggregate( models.Max("created_at"), )["created_at__max"] @property - def published_releases(self): - """Return project releases.""" + def published_releases(self) -> models.QuerySet[Release]: + """Get published releases across the project's repositories. + + Returns: + QuerySet[Release]: A queryset of non-draft releases with related data. + + """ return Release.objects.filter( is_draft=False, published_at__isnull=False, @@ -302,8 +402,13 @@ def published_releases(self): ) @property - def recent_milestones(self): - """Return recent milestones.""" + def recent_milestones(self) -> models.QuerySet[Milestone]: + """Get milestones across the project's repositories. + + Returns: + QuerySet[Milestone]: A queryset of milestones with related data. + + """ return ( Milestone.objects.filter( repository__in=self.repositories.all(), @@ -319,7 +424,12 @@ def recent_milestones(self): @property def recent_releases_count(self) -> int: - """Return count of recent releases per a specific period.""" + """Get the number of recent releases. + + Returns: + int: Count of releases published recently. + + """ recent_period = timezone.now() - datetime.timedelta(days=60) return self.published_releases.filter( published_at__gte=recent_period, @@ -327,17 +437,32 @@ def recent_releases_count(self) -> int: @property def repositories_count(self) -> int: - """Return count of repositories.""" + """Get the number of repositories associated with this project. + + Returns: + int: Count of repositories linked to the project. + + """ return self.repositories.count() @property def unanswered_issues_count(self) -> int: - """Return count of unanswered issues.""" + """Get the number of issues with no comments. + + Returns: + int: Count of issues where `comments_count` equals zero. + + """ return self.issues.filter(comments_count=0).count() @property def unassigned_issues_count(self) -> int: - """Return count of unassigned issues.""" + """Get the number of issues with no assignees. + + Returns: + int: Count of issues where no user is assigned. + + """ return self.issues.filter(assignees__isnull=True).count() def deactivate(self) -> None: diff --git a/backend/apps/slack/views.py b/backend/apps/slack/views.py index a3983bf11c..229479e23b 100644 --- a/backend/apps/slack/views.py +++ b/backend/apps/slack/views.py @@ -9,7 +9,7 @@ slack_handler = SlackRequestHandler(SlackConfig.app) -@csrf_exempt # NOSONAR +@csrf_exempt # NOSEMGREP: python.django.security.audit.csrf-exempt.no-csrf-exempt # NOSONAR @require_POST # NOSONAR def slack_request_handler(request): """Handle Slack requests. @@ -20,5 +20,10 @@ def slack_request_handler(request): Returns: HttpResponse: The response generated by the SlackRequestHandler. + Note: + CSRF exemption is required for webhook endpoints. Security is ensured through + Slack's request signature verification, which is automatically handled by + slack-bolt's SlackRequestHandler when signing_secret is configured in SlackConfig.app. + """ return slack_handler.handle(request) diff --git a/backend/poetry.lock b/backend/poetry.lock index 6b3b6411d9..1d6aae8ac5 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -171,14 +171,14 @@ frozenlist = ">=1.1.0" [[package]] name = "algoliasearch" -version = "4.35.3" +version = "4.35.4" description = "A fully-featured and blazing-fast Python API client to interact with Algolia." optional = false python-versions = ">=3.8.1" groups = ["main"] files = [ - {file = "algoliasearch-4.35.3-py3-none-any.whl", hash = "sha256:5cfd74be60f4844677e5a95737b04044f05815f744447c5ec0eec9459c064c45"}, - {file = "algoliasearch-4.35.3.tar.gz", hash = "sha256:14ece549317cb6ca6c435cc1324c2ab4f5425598bc1b35fa48b1d1d01a0ca39c"}, + {file = "algoliasearch-4.35.4-py3-none-any.whl", hash = "sha256:4a61efd953357ebc2fd8687bcb8e470a3947f7988367384457ab793172456eda"}, + {file = "algoliasearch-4.35.4.tar.gz", hash = "sha256:7d958459fa2ec80aa610a425737edec7e6a4d2f704649952b8edd63e4bff44ca"}, ] [package.dependencies] @@ -306,18 +306,18 @@ wrapt = "*" [[package]] name = "boto3" -version = "1.42.30" +version = "1.42.34" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.42.30-py3-none-any.whl", hash = "sha256:d7e548bea65e0ae2c465c77de937bc686b591aee6a352d5a19a16bc751e591c1"}, - {file = "boto3-1.42.30.tar.gz", hash = "sha256:ba9cd2f7819637d15bfbeb63af4c567fcc8a7dcd7b93dd12734ec58601169538"}, + {file = "boto3-1.42.34-py3-none-any.whl", hash = "sha256:db3fb539e3f806b911ec4ca991f2f8bff333c5f0b87132a82e28b521fc5ec164"}, + {file = "boto3-1.42.34.tar.gz", hash = "sha256:75d7443c81a029283442fad138629be1eefaa3e6d430c28118a0f4cdbd57855d"}, ] [package.dependencies] -botocore = ">=1.42.30,<1.43.0" +botocore = ">=1.42.34,<1.43.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.16.0,<0.17.0" @@ -326,14 +326,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.42.30" +version = "1.42.34" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.42.30-py3-none-any.whl", hash = "sha256:97070a438cac92430bb7b65f8ebd7075224f4a289719da4ee293d22d1e98db02"}, - {file = "botocore-1.42.30.tar.gz", hash = "sha256:9bf1662b8273d5cc3828a49f71ca85abf4e021011c1f0a71f41a2ea5769a5116"}, + {file = "botocore-1.42.34-py3-none-any.whl", hash = "sha256:94099b5d09d0c4bfa6414fb3cffd54275ce6e51d7ba016f17a0e79f9274f68f7"}, + {file = "botocore-1.42.34.tar.gz", hash = "sha256:92e44747da7890270d8dcc494ecc61fc315438440c55e00dc37a57d402b1bb66"}, ] [package.dependencies] @@ -771,104 +771,104 @@ markers = {main = "platform_system == \"Windows\"", test = "sys_platform == \"wi [[package]] name = "coverage" -version = "7.13.1" +version = "7.13.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["test"] files = [ - {file = "coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147"}, - {file = "coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d"}, - {file = "coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0"}, - {file = "coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90"}, - {file = "coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d"}, - {file = "coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b"}, - {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6"}, - {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e"}, - {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae"}, - {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29"}, - {file = "coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f"}, - {file = "coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1"}, - {file = "coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88"}, - {file = "coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3"}, - {file = "coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9"}, - {file = "coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee"}, - {file = "coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf"}, - {file = "coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3"}, - {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef"}, - {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851"}, - {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb"}, - {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba"}, - {file = "coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19"}, - {file = "coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a"}, - {file = "coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c"}, - {file = "coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3"}, - {file = "coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e"}, - {file = "coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c"}, - {file = "coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62"}, - {file = "coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968"}, - {file = "coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e"}, - {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f"}, - {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee"}, - {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf"}, - {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c"}, - {file = "coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7"}, - {file = "coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6"}, - {file = "coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c"}, - {file = "coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78"}, - {file = "coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b"}, - {file = "coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd"}, - {file = "coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992"}, - {file = "coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4"}, - {file = "coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a"}, - {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766"}, - {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4"}, - {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398"}, - {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784"}, - {file = "coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461"}, - {file = "coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500"}, - {file = "coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9"}, - {file = "coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc"}, - {file = "coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a"}, - {file = "coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4"}, - {file = "coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6"}, - {file = "coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1"}, - {file = "coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd"}, - {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c"}, - {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0"}, - {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e"}, - {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53"}, - {file = "coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842"}, - {file = "coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2"}, - {file = "coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09"}, - {file = "coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894"}, - {file = "coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a"}, - {file = "coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f"}, - {file = "coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909"}, - {file = "coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4"}, - {file = "coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75"}, - {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9"}, - {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465"}, - {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864"}, - {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9"}, - {file = "coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5"}, - {file = "coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a"}, - {file = "coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0"}, - {file = "coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a"}, - {file = "coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6"}, - {file = "coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673"}, - {file = "coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5"}, - {file = "coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d"}, - {file = "coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8"}, - {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486"}, - {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564"}, - {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7"}, - {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416"}, - {file = "coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f"}, - {file = "coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79"}, - {file = "coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4"}, - {file = "coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573"}, - {file = "coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd"}, + {file = "coverage-7.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4af3b01763909f477ea17c962e2cca8f39b350a4e46e3a30838b2c12e31b81b"}, + {file = "coverage-7.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:36393bd2841fa0b59498f75466ee9bdec4f770d3254f031f23e8fd8e140ffdd2"}, + {file = "coverage-7.13.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9cc7573518b7e2186bd229b1a0fe24a807273798832c27032c4510f47ffdb896"}, + {file = "coverage-7.13.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca9566769b69a5e216a4e176d54b9df88f29d750c5b78dbb899e379b4e14b30c"}, + {file = "coverage-7.13.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c9bdea644e94fd66d75a6f7e9a97bb822371e1fe7eadae2cacd50fcbc28e4dc"}, + {file = "coverage-7.13.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5bd447332ec4f45838c1ad42268ce21ca87c40deb86eabd59888859b66be22a5"}, + {file = "coverage-7.13.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7c79ad5c28a16a1277e1187cf83ea8dafdcc689a784228a7d390f19776db7c31"}, + {file = "coverage-7.13.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:76e06ccacd1fb6ada5d076ed98a8c6f66e2e6acd3df02819e2ee29fd637b76ad"}, + {file = "coverage-7.13.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:49d49e9a5e9f4dc3d3dac95278a020afa6d6bdd41f63608a76fa05a719d5b66f"}, + {file = "coverage-7.13.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed2bce0e7bfa53f7b0b01c722da289ef6ad4c18ebd52b1f93704c21f116360c8"}, + {file = "coverage-7.13.2-cp310-cp310-win32.whl", hash = "sha256:1574983178b35b9af4db4a9f7328a18a14a0a0ce76ffaa1c1bacb4cc82089a7c"}, + {file = "coverage-7.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:a360a8baeb038928ceb996f5623a4cd508728f8f13e08d4e96ce161702f3dd99"}, + {file = "coverage-7.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:060ebf6f2c51aff5ba38e1f43a2095e087389b1c69d559fde6049a4b0001320e"}, + {file = "coverage-7.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1ea8ca9db5e7469cd364552985e15911548ea5b69c48a17291f0cac70484b2e"}, + {file = "coverage-7.13.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b780090d15fd58f07cf2011943e25a5f0c1c894384b13a216b6c86c8a8a7c508"}, + {file = "coverage-7.13.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88a800258d83acb803c38175b4495d293656d5fac48659c953c18e5f539a274b"}, + {file = "coverage-7.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6326e18e9a553e674d948536a04a80d850a5eeefe2aae2e6d7cf05d54046c01b"}, + {file = "coverage-7.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59562de3f797979e1ff07c587e2ac36ba60ca59d16c211eceaa579c266c5022f"}, + {file = "coverage-7.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27ba1ed6f66b0e2d61bfa78874dffd4f8c3a12f8e2b5410e515ab345ba7bc9c3"}, + {file = "coverage-7.13.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8be48da4d47cc68754ce643ea50b3234557cbefe47c2f120495e7bd0a2756f2b"}, + {file = "coverage-7.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2a47a4223d3361b91176aedd9d4e05844ca67d7188456227b6bf5e436630c9a1"}, + {file = "coverage-7.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6f141b468740197d6bd38f2b26ade124363228cc3f9858bd9924ab059e00059"}, + {file = "coverage-7.13.2-cp311-cp311-win32.whl", hash = "sha256:89567798404af067604246e01a49ef907d112edf2b75ef814b1364d5ce267031"}, + {file = "coverage-7.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:21dd57941804ae2ac7e921771a5e21bbf9aabec317a041d164853ad0a96ce31e"}, + {file = "coverage-7.13.2-cp311-cp311-win_arm64.whl", hash = "sha256:10758e0586c134a0bafa28f2d37dd2cdb5e4a90de25c0fc0c77dabbad46eca28"}, + {file = "coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d"}, + {file = "coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3"}, + {file = "coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99"}, + {file = "coverage-7.13.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:264657171406c114787b441484de620e03d8f7202f113d62fcd3d9688baa3e6f"}, + {file = "coverage-7.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae47d8dcd3ded0155afbb59c62bd8ab07ea0fd4902e1c40567439e6db9dcaf2f"}, + {file = "coverage-7.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a0b33e9fd838220b007ce8f299114d406c1e8edb21336af4c97a26ecfd185aa"}, + {file = "coverage-7.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3becbea7f3ce9a2d4d430f223ec15888e4deb31395840a79e916368d6004cce"}, + {file = "coverage-7.13.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f819c727a6e6eeb8711e4ce63d78c620f69630a2e9d53bc95ca5379f57b6ba94"}, + {file = "coverage-7.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4f7b71757a3ab19f7ba286e04c181004c1d61be921795ee8ba6970fd0ec91da5"}, + {file = "coverage-7.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b7fc50d2afd2e6b4f6f2f403b70103d280a8e0cb35320cbbe6debcda02a1030b"}, + {file = "coverage-7.13.2-cp312-cp312-win32.whl", hash = "sha256:292250282cf9bcf206b543d7608bda17ca6fc151f4cbae949fc7e115112fbd41"}, + {file = "coverage-7.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:eeea10169fac01549a7921d27a3e517194ae254b542102267bef7a93ed38c40e"}, + {file = "coverage-7.13.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a5b567f0b635b592c917f96b9a9cb3dbd4c320d03f4bf94e9084e494f2e8894"}, + {file = "coverage-7.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed75de7d1217cf3b99365d110975f83af0528c849ef5180a12fd91b5064df9d6"}, + {file = "coverage-7.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97e596de8fa9bada4d88fde64a3f4d37f1b6131e4faa32bad7808abc79887ddc"}, + {file = "coverage-7.13.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:68c86173562ed4413345410c9480a8d64864ac5e54a5cda236748031e094229f"}, + {file = "coverage-7.13.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7be4d613638d678b2b3773b8f687537b284d7074695a43fe2fbbfc0e31ceaed1"}, + {file = "coverage-7.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7f63ce526a96acd0e16c4af8b50b64334239550402fb1607ce6a584a6d62ce9"}, + {file = "coverage-7.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:406821f37f864f968e29ac14c3fccae0fec9fdeba48327f0341decf4daf92d7c"}, + {file = "coverage-7.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ee68e5a4e3e5443623406b905db447dceddffee0dceb39f4e0cd9ec2a35004b5"}, + {file = "coverage-7.13.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2ee0e58cca0c17dd9c6c1cdde02bb705c7b3fbfa5f3b0b5afeda20d4ebff8ef4"}, + {file = "coverage-7.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e5bbb5018bf76a56aabdb64246b5288d5ae1b7d0dd4d0534fe86df2c2992d1c"}, + {file = "coverage-7.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a55516c68ef3e08e134e818d5e308ffa6b1337cc8b092b69b24287bf07d38e31"}, + {file = "coverage-7.13.2-cp313-cp313-win32.whl", hash = "sha256:5b20211c47a8abf4abc3319d8ce2464864fa9f30c5fcaf958a3eed92f4f1fef8"}, + {file = "coverage-7.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:14f500232e521201cf031549fb1ebdfc0a40f401cf519157f76c397e586c3beb"}, + {file = "coverage-7.13.2-cp313-cp313-win_arm64.whl", hash = "sha256:9779310cb5a9778a60c899f075a8514c89fa6d10131445c2207fc893e0b14557"}, + {file = "coverage-7.13.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e64fa5a1e41ce5df6b547cbc3d3699381c9e2c2c369c67837e716ed0f549d48e"}, + {file = "coverage-7.13.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b01899e82a04085b6561eb233fd688474f57455e8ad35cd82286463ba06332b7"}, + {file = "coverage-7.13.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:838943bea48be0e2768b0cf7819544cdedc1bbb2f28427eabb6eb8c9eb2285d3"}, + {file = "coverage-7.13.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:93d1d25ec2b27e90bcfef7012992d1f5121b51161b8bffcda756a816cf13c2c3"}, + {file = "coverage-7.13.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93b57142f9621b0d12349c43fc7741fe578e4bc914c1e5a54142856cfc0bf421"}, + {file = "coverage-7.13.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f06799ae1bdfff7ccb8665d75f8291c69110ba9585253de254688aa8a1ccc6c5"}, + {file = "coverage-7.13.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f9405ab4f81d490811b1d91c7a20361135a2df4c170e7f0b747a794da5b7f23"}, + {file = "coverage-7.13.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f9ab1d5b86f8fbc97a5b3cd6280a3fd85fef3b028689d8a2c00918f0d82c728c"}, + {file = "coverage-7.13.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:f674f59712d67e841525b99e5e2b595250e39b529c3bda14764e4f625a3fa01f"}, + {file = "coverage-7.13.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c6cadac7b8ace1ba9144feb1ae3cb787a6065ba6d23ffc59a934b16406c26573"}, + {file = "coverage-7.13.2-cp313-cp313t-win32.whl", hash = "sha256:14ae4146465f8e6e6253eba0cccd57423e598a4cb925958b240c805300918343"}, + {file = "coverage-7.13.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9074896edd705a05769e3de0eac0a8388484b503b68863dd06d5e473f874fd47"}, + {file = "coverage-7.13.2-cp313-cp313t-win_arm64.whl", hash = "sha256:69e526e14f3f854eda573d3cf40cffd29a1a91c684743d904c33dbdcd0e0f3e7"}, + {file = "coverage-7.13.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:387a825f43d680e7310e6f325b2167dd093bc8ffd933b83e9aa0983cf6e0a2ef"}, + {file = "coverage-7.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f0d7fea9d8e5d778cd5a9e8fc38308ad688f02040e883cdc13311ef2748cb40f"}, + {file = "coverage-7.13.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080afb413be106c95c4ee96b4fffdc9e2fa56a8bbf90b5c0918e5c4449412f5"}, + {file = "coverage-7.13.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7fc042ba3c7ce25b8a9f097eb0f32a5ce1ccdb639d9eec114e26def98e1f8a4"}, + {file = "coverage-7.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0ba505e021557f7f8173ee8cd6b926373d8653e5ff7581ae2efce1b11ef4c27"}, + {file = "coverage-7.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7de326f80e3451bd5cc7239ab46c73ddb658fe0b7649476bc7413572d36cd548"}, + {file = "coverage-7.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:abaea04f1e7e34841d4a7b343904a3f59481f62f9df39e2cd399d69a187a9660"}, + {file = "coverage-7.13.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9f93959ee0c604bccd8e0697be21de0887b1f73efcc3aa73a3ec0fd13feace92"}, + {file = "coverage-7.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:13fe81ead04e34e105bf1b3c9f9cdf32ce31736ee5d90a8d2de02b9d3e1bcb82"}, + {file = "coverage-7.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d6d16b0f71120e365741bca2cb473ca6fe38930bc5431c5e850ba949f708f892"}, + {file = "coverage-7.13.2-cp314-cp314-win32.whl", hash = "sha256:9b2f4714bb7d99ba3790ee095b3b4ac94767e1347fe424278a0b10acb3ff04fe"}, + {file = "coverage-7.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4121a90823a063d717a96e0a0529c727fb31ea889369a0ee3ec00ed99bf6859"}, + {file = "coverage-7.13.2-cp314-cp314-win_arm64.whl", hash = "sha256:6873f0271b4a15a33e7590f338d823f6f66f91ed147a03938d7ce26efd04eee6"}, + {file = "coverage-7.13.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f61d349f5b7cd95c34017f1927ee379bfbe9884300d74e07cf630ccf7a610c1b"}, + {file = "coverage-7.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a43d34ce714f4ca674c0d90beb760eb05aad906f2c47580ccee9da8fe8bfb417"}, + {file = "coverage-7.13.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bff1b04cb9d4900ce5c56c4942f047dc7efe57e2608cb7c3c8936e9970ccdbee"}, + {file = "coverage-7.13.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6ae99e4560963ad8e163e819e5d77d413d331fd00566c1e0856aa252303552c1"}, + {file = "coverage-7.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e79a8c7d461820257d9aa43716c4efc55366d7b292e46b5b37165be1d377405d"}, + {file = "coverage-7.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:060ee84f6a769d40c492711911a76811b4befb6fba50abb450371abb720f5bd6"}, + {file = "coverage-7.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bca209d001fd03ea2d978f8a4985093240a355c93078aee3f799852c23f561a"}, + {file = "coverage-7.13.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6b8092aa38d72f091db61ef83cb66076f18f02da3e1a75039a4f218629600e04"}, + {file = "coverage-7.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4a3158dc2dcce5200d91ec28cd315c999eebff355437d2765840555d765a6e5f"}, + {file = "coverage-7.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3973f353b2d70bd9796cc12f532a05945232ccae966456c8ed7034cb96bbfd6f"}, + {file = "coverage-7.13.2-cp314-cp314t-win32.whl", hash = "sha256:79f6506a678a59d4ded048dc72f1859ebede8ec2b9a2d509ebe161f01c2879d3"}, + {file = "coverage-7.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:196bfeabdccc5a020a57d5a368c681e3a6ceb0447d153aeccc1ab4d70a5032ba"}, + {file = "coverage-7.13.2-cp314-cp314t-win_arm64.whl", hash = "sha256:69269ab58783e090bfbf5b916ab3d188126e22d6070bbfc93098fdd474ef937c"}, + {file = "coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5"}, + {file = "coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3"}, ] [package.extras] @@ -1277,14 +1277,14 @@ dev = ["mypy (>=1.15)"] [[package]] name = "elevenlabs" -version = "2.31.0" +version = "2.32.0" description = "" optional = false python-versions = "<4.0,>=3.8" groups = ["video"] files = [ - {file = "elevenlabs-2.31.0-py3-none-any.whl", hash = "sha256:163062ab3c8da274a8fa43887974658e0b670b81ab887fbf53579c29e90dd433"}, - {file = "elevenlabs-2.31.0.tar.gz", hash = "sha256:709eca626b67e19b379a099f46af608786f269c3d8824ff1adc04749f7d8b23b"}, + {file = "elevenlabs-2.32.0-py3-none-any.whl", hash = "sha256:396c8ff4a229a88ea3120e02c44ef840dda057c7f439b9fa070222dc4b644a4d"}, + {file = "elevenlabs-2.32.0.tar.gz", hash = "sha256:b3f7d41c12a1054b0b0130555099b6b8011cf134239ecedb1d4286e96461fd36"}, ] [package.dependencies] @@ -1638,61 +1638,66 @@ files = [ [[package]] name = "greenlet" -version = "3.3.0" +version = "3.3.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.10" groups = ["main"] markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\"" files = [ - {file = "greenlet-3.3.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6f8496d434d5cb2dce025773ba5597f71f5410ae499d5dd9533e0653258cdb3d"}, - {file = "greenlet-3.3.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b96dc7eef78fd404e022e165ec55327f935b9b52ff355b067eb4a0267fc1cffb"}, - {file = "greenlet-3.3.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73631cd5cccbcfe63e3f9492aaa664d278fda0ce5c3d43aeda8e77317e38efbd"}, - {file = "greenlet-3.3.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b299a0cb979f5d7197442dccc3aee67fce53500cd88951b7e6c35575701c980b"}, - {file = "greenlet-3.3.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dee147740789a4632cace364816046e43310b59ff8fb79833ab043aefa72fd5"}, - {file = "greenlet-3.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:39b28e339fc3c348427560494e28d8a6f3561c8d2bcf7d706e1c624ed8d822b9"}, - {file = "greenlet-3.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3c374782c2935cc63b2a27ba8708471de4ad1abaa862ffdb1ef45a643ddbb7d"}, - {file = "greenlet-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b49e7ed51876b459bd645d83db257f0180e345d3f768a35a85437a24d5a49082"}, - {file = "greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e"}, - {file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62"}, - {file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32"}, - {file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45"}, - {file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948"}, - {file = "greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794"}, - {file = "greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5"}, - {file = "greenlet-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:7652ee180d16d447a683c04e4c5f6441bae7ba7b17ffd9f6b3aff4605e9e6f71"}, - {file = "greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb"}, - {file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3"}, - {file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655"}, - {file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7"}, - {file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b"}, - {file = "greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53"}, - {file = "greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614"}, - {file = "greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39"}, - {file = "greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739"}, - {file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808"}, - {file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54"}, - {file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492"}, - {file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527"}, - {file = "greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39"}, - {file = "greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8"}, - {file = "greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38"}, - {file = "greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f"}, - {file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365"}, - {file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3"}, - {file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45"}, - {file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955"}, - {file = "greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55"}, - {file = "greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc"}, - {file = "greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170"}, - {file = "greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931"}, - {file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388"}, - {file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3"}, - {file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221"}, - {file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b"}, - {file = "greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd"}, - {file = "greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9"}, - {file = "greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb"}, + {file = "greenlet-3.3.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:04bee4775f40ecefcdaa9d115ab44736cd4b9c5fba733575bfe9379419582e13"}, + {file = "greenlet-3.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e1457f4fed12a50e427988a07f0f9df53cf0ee8da23fab16e6732c2ec909d4"}, + {file = "greenlet-3.3.1-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:070472cd156f0656f86f92e954591644e158fd65aa415ffbe2d44ca77656a8f5"}, + {file = "greenlet-3.3.1-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1108b61b06b5224656121c3c8ee8876161c491cbe74e5c519e0634c837cf93d5"}, + {file = "greenlet-3.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a300354f27dd86bae5fbf7002e6dd2b3255cd372e9242c933faf5e859b703fe"}, + {file = "greenlet-3.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e84b51cbebf9ae573b5fbd15df88887815e3253fc000a7d0ff95170e8f7e9729"}, + {file = "greenlet-3.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0093bd1a06d899892427217f0ff2a3c8f306182b8c754336d32e2d587c131b4"}, + {file = "greenlet-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:7932f5f57609b6a3b82cc11877709aa7a98e3308983ed93552a1c377069b20c8"}, + {file = "greenlet-3.3.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5fd23b9bc6d37b563211c6abbb1b3cab27db385a4449af5c32e932f93017080c"}, + {file = "greenlet-3.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f51496a0bfbaa9d74d36a52d2580d1ef5ed4fdfcff0a73730abfbbbe1403dd"}, + {file = "greenlet-3.3.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb0feb07fe6e6a74615ee62a880007d976cf739b6669cce95daa7373d4fc69c5"}, + {file = "greenlet-3.3.1-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:67ea3fc73c8cd92f42467a72b75e8f05ed51a0e9b1d15398c913416f2dafd49f"}, + {file = "greenlet-3.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39eda9ba259cc9801da05351eaa8576e9aa83eb9411e8f0c299e05d712a210f2"}, + {file = "greenlet-3.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e2e7e882f83149f0a71ac822ebf156d902e7a5d22c9045e3e0d1daf59cee2cc9"}, + {file = "greenlet-3.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80aa4d79eb5564f2e0a6144fcc744b5a37c56c4a92d60920720e99210d88db0f"}, + {file = "greenlet-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:32e4ca9777c5addcbf42ff3915d99030d8e00173a56f80001fb3875998fe410b"}, + {file = "greenlet-3.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:da19609432f353fed186cc1b85e9440db93d489f198b4bdf42ae19cc9d9ac9b4"}, + {file = "greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975"}, + {file = "greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36"}, + {file = "greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba"}, + {file = "greenlet-3.3.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c620051669fd04ac6b60ebc70478210119c56e2d5d5df848baec4312e260e4ca"}, + {file = "greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336"}, + {file = "greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1"}, + {file = "greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149"}, + {file = "greenlet-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cc98b9c4e4870fa983436afa999d4eb16b12872fab7071423d5262fa7120d57a"}, + {file = "greenlet-3.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:bfb2d1763d777de5ee495c85309460f6fd8146e50ec9d0ae0183dbf6f0a829d1"}, + {file = "greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3"}, + {file = "greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac"}, + {file = "greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd"}, + {file = "greenlet-3.3.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:12184c61e5d64268a160226fb4818af4df02cfead8379d7f8b99a56c3a54ff3e"}, + {file = "greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3"}, + {file = "greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951"}, + {file = "greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2"}, + {file = "greenlet-3.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:27289986f4e5b0edec7b5a91063c109f0276abb09a7e9bdab08437525977c946"}, + {file = "greenlet-3.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:2f080e028001c5273e0b42690eaf359aeef9cb1389da0f171ea51a5dc3c7608d"}, + {file = "greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5"}, + {file = "greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b"}, + {file = "greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e"}, + {file = "greenlet-3.3.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e0f3878ca3a3ff63ab4ea478585942b53df66ddde327b59ecb191b19dbbd62d"}, + {file = "greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f"}, + {file = "greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683"}, + {file = "greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1"}, + {file = "greenlet-3.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:96aff77af063b607f2489473484e39a0bbae730f2ea90c9e5606c9b73c44174a"}, + {file = "greenlet-3.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:b066e8b50e28b503f604fa538adc764a638b38cf8e81e025011d26e8a627fa79"}, + {file = "greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242"}, + {file = "greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774"}, + {file = "greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97"}, + {file = "greenlet-3.3.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b9721549a95db96689458a1e0ae32412ca18776ed004463df3a9299c1b257ab"}, + {file = "greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2"}, + {file = "greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53"}, + {file = "greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249"}, + {file = "greenlet-3.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:301860987846c24cb8964bdec0e31a96ad4a2a801b41b4ef40963c1b44f33451"}, + {file = "greenlet-3.3.1.tar.gz", hash = "sha256:41848f3230b58c08bb43dee542e74a2a2e34d3c59dc3076cec9151aeeedcae98"}, ] [package.extras] @@ -1701,25 +1706,25 @@ test = ["objgraph", "psutil", "setuptools"] [[package]] name = "gunicorn" -version = "23.0.0" +version = "24.1.1" description = "WSGI HTTP Server for UNIX" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, - {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, + {file = "gunicorn-24.1.1-py3-none-any.whl", hash = "sha256:757f6b621fc4f7581a90600b2cd9df593461f06a41d7259cb9b94499dc4095a8"}, + {file = "gunicorn-24.1.1.tar.gz", hash = "sha256:f006d110e5cb3102859b4f5cd48335dbd9cc28d0d27cd24ddbdafa6c60929408"}, ] [package.dependencies] packaging = "*" [package.extras] -eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] -gevent = ["gevent (>=1.4.0)"] +eventlet = ["eventlet (>=0.40.3)"] +gevent = ["gevent (>=24.10.1)"] setproctitle = ["setproctitle"] -testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] -tornado = ["tornado (>=0.2)"] +testing = ["coverage", "eventlet (>=0.40.3)", "gevent (>=24.10.1)", "pytest", "pytest-asyncio", "pytest-cov"] +tornado = ["tornado (>=6.5.0)"] [[package]] name = "h11" @@ -1993,14 +1998,14 @@ files = [ [[package]] name = "jmespath" -version = "1.0.1" +version = "1.1.0" description = "JSON Matching Expressions" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, - {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, + {file = "jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64"}, + {file = "jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d"}, ] [[package]] @@ -2454,18 +2459,18 @@ htmlsoup = ["BeautifulSoup4"] [[package]] name = "markdown" -version = "3.10" +version = "3.10.1" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c"}, - {file = "markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e"}, + {file = "markdown-3.10.1-py3-none-any.whl", hash = "sha256:867d788939fe33e4b736426f5b9f651ad0c0ae0ecf89df0ca5d1176c70812fe3"}, + {file = "markdown-3.10.1.tar.gz", hash = "sha256:1c19c10bd5c14ac948c53d0d762a04e2fa35a6d58a6b7b1e6bfcbe6fefc0001a"}, ] [package.extras] -docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python] (>=0.28.3)"] testing = ["coverage", "pyyaml"] [[package]] @@ -2589,158 +2594,158 @@ tests = ["pytest", "simplejson"] [[package]] name = "multidict" -version = "6.7.0" +version = "6.7.1" description = "multidict implementation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349"}, - {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e"}, - {file = "multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3"}, - {file = "multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046"}, - {file = "multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32"}, - {file = "multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73"}, - {file = "multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc"}, - {file = "multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62"}, - {file = "multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84"}, - {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0"}, - {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e"}, - {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4"}, - {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648"}, - {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111"}, - {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36"}, - {file = "multidict-6.7.0-cp310-cp310-win32.whl", hash = "sha256:afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85"}, - {file = "multidict-6.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7"}, - {file = "multidict-6.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0"}, - {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc"}, - {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721"}, - {file = "multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6"}, - {file = "multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c"}, - {file = "multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7"}, - {file = "multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7"}, - {file = "multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9"}, - {file = "multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8"}, - {file = "multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd"}, - {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb"}, - {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6"}, - {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2"}, - {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff"}, - {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b"}, - {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34"}, - {file = "multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff"}, - {file = "multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81"}, - {file = "multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912"}, - {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184"}, - {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45"}, - {file = "multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa"}, - {file = "multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7"}, - {file = "multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e"}, - {file = "multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546"}, - {file = "multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4"}, - {file = "multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1"}, - {file = "multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d"}, - {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304"}, - {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12"}, - {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62"}, - {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0"}, - {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a"}, - {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8"}, - {file = "multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4"}, - {file = "multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b"}, - {file = "multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec"}, - {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6"}, - {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159"}, - {file = "multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca"}, - {file = "multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8"}, - {file = "multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60"}, - {file = "multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4"}, - {file = "multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f"}, - {file = "multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf"}, - {file = "multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32"}, - {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036"}, - {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec"}, - {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e"}, - {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64"}, - {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd"}, - {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288"}, - {file = "multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17"}, - {file = "multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390"}, - {file = "multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e"}, - {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00"}, - {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb"}, - {file = "multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b"}, - {file = "multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c"}, - {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1"}, - {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b"}, - {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5"}, - {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad"}, - {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c"}, - {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5"}, - {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10"}, - {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754"}, - {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c"}, - {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762"}, - {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6"}, - {file = "multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d"}, - {file = "multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6"}, - {file = "multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792"}, - {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842"}, - {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b"}, - {file = "multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38"}, - {file = "multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128"}, - {file = "multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34"}, - {file = "multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99"}, - {file = "multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202"}, - {file = "multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1"}, - {file = "multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3"}, - {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d"}, - {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6"}, - {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7"}, - {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb"}, - {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f"}, - {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f"}, - {file = "multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885"}, - {file = "multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c"}, - {file = "multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000"}, - {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63"}, - {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718"}, - {file = "multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2"}, - {file = "multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e"}, - {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064"}, - {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e"}, - {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd"}, - {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a"}, - {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96"}, - {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e"}, - {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599"}, - {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394"}, - {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38"}, - {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9"}, - {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0"}, - {file = "multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13"}, - {file = "multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd"}, - {file = "multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827"}, - {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:363eb68a0a59bd2303216d2346e6c441ba10d36d1f9969fcb6f1ba700de7bb5c"}, - {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d874eb056410ca05fed180b6642e680373688efafc7f077b2a2f61811e873a40"}, - {file = "multidict-6.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b55d5497b51afdfde55925e04a022f1de14d4f4f25cdfd4f5d9b0aa96166851"}, - {file = "multidict-6.7.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f8e5c0031b90ca9ce555e2e8fd5c3b02a25f14989cbc310701823832c99eb687"}, - {file = "multidict-6.7.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cf41880c991716f3c7cec48e2f19ae4045fc9db5fc9cff27347ada24d710bb5"}, - {file = "multidict-6.7.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cfc12a8630a29d601f48d47787bd7eb730e475e83edb5d6c5084317463373eb"}, - {file = "multidict-6.7.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3996b50c3237c4aec17459217c1e7bbdead9a22a0fcd3c365564fbd16439dde6"}, - {file = "multidict-6.7.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7f5170993a0dd3ab871c74f45c0a21a4e2c37a2f2b01b5f722a2ad9c6650469e"}, - {file = "multidict-6.7.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec81878ddf0e98817def1e77d4f50dae5ef5b0e4fe796fae3bd674304172416e"}, - {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9281bf5b34f59afbc6b1e477a372e9526b66ca446f4bf62592839c195a718b32"}, - {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:68af405971779d8b37198726f2b6fe3955db846fee42db7a4286fc542203934c"}, - {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ba3ef510467abb0667421a286dc906e30eb08569365f5cdb131d7aff7c2dd84"}, - {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b61189b29081a20c7e4e0b49b44d5d44bb0dc92be3c6d06a11cc043f81bf9329"}, - {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fb287618b9c7aa3bf8d825f02d9201b2f13078a5ed3b293c8f4d953917d84d5e"}, - {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:521f33e377ff64b96c4c556b81c55d0cfffb96a11c194fd0c3f1e56f3d8dd5a4"}, - {file = "multidict-6.7.0-cp39-cp39-win32.whl", hash = "sha256:ce8fdc2dca699f8dbf055a61d73eaa10482569ad20ee3c36ef9641f69afa8c91"}, - {file = "multidict-6.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:7e73299c99939f089dd9b2120a04a516b95cdf8c1cd2b18c53ebf0de80b1f18f"}, - {file = "multidict-6.7.0-cp39-cp39-win_arm64.whl", hash = "sha256:6bdce131e14b04fd34a809b6380dbfd826065c3e2fe8a50dbae659fa0c390546"}, - {file = "multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3"}, - {file = "multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5"}, + {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, + {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, + {file = "multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505"}, + {file = "multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122"}, + {file = "multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df"}, + {file = "multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa"}, + {file = "multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a"}, + {file = "multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b"}, + {file = "multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba"}, + {file = "multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511"}, + {file = "multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19"}, + {file = "multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33"}, + {file = "multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3"}, + {file = "multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5"}, + {file = "multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108"}, + {file = "multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32"}, + {file = "multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8"}, + {file = "multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b"}, + {file = "multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d"}, + {file = "multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f"}, + {file = "multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2"}, + {file = "multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7"}, + {file = "multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5"}, + {file = "multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65573858d27cdeaca41893185677dc82395159aa28875a8867af66532d413a8f"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c524c6fb8fc342793708ab111c4dbc90ff9abd568de220432500e47e990c0358"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa23b001d968faef416ff70dc0f1ab045517b9b42a90edd3e9bcdb06479e31d5"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6704fa2b7453b2fb121740555fa1ee20cd98c4d011120caf4d2b8d4e7c76eec0"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:121a34e5bfa410cdf2c8c49716de160de3b1dbcd86b49656f5681e4543bcd1a8"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:026d264228bcd637d4e060844e39cdc60f86c479e463d49075dedc21b18fbbe0"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e697826df7eb63418ee190fd06ce9f1803593bb4b9517d08c60d9b9a7f69d8f"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb08271280173720e9fea9ede98e5231defcbad90f1624bea26f32ec8a956e2f"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6b3228e1d80af737b72925ce5fb4daf5a335e49cd7ab77ed7b9fdfbf58c526e"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3943debf0fbb57bdde5901695c11094a9a36723e5c03875f87718ee15ca2f4d2"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:98c5787b0a0d9a41d9311eae44c3b76e6753def8d8870ab501320efe75a6a5f8"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08ccb2a6dc72009093ebe7f3f073e5ec5964cba9a706fa94b1a1484039b87941"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb351f72c26dc9abe338ca7294661aa22969ad8ffe7ef7d5541d19f368dc854a"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac1c665bad8b5d762f5f85ebe4d94130c26965f11de70c708c75671297c776de"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fa6609d0364f4f6f58351b4659a1f3e0e898ba2a8c5cac04cb2c7bc556b0bc5"}, + {file = "multidict-6.7.1-cp39-cp39-win32.whl", hash = "sha256:6f77ce314a29263e67adadc7e7c1bc699fcb3a305059ab973d038f87caa42ed0"}, + {file = "multidict-6.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:f537b55778cd3cbee430abe3131255d3a78202e0f9ea7ffc6ada893a4bcaeea4"}, + {file = "multidict-6.7.1-cp39-cp39-win_arm64.whl", hash = "sha256:749aa54f578f2e5f439538706a475aa844bfa8ef75854b1401e6e528e4937cf9"}, + {file = "multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56"}, + {file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"}, ] [[package]] @@ -3478,14 +3483,14 @@ files = [ [[package]] name = "pycparser" -version = "2.23" +version = "3.0" description = "C parser in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main", "video"] files = [ - {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, - {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, + {file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"}, + {file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"}, ] markers = {main = "platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"", video = "implementation_name != \"PyPy\""} @@ -3783,14 +3788,14 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=7.4.0)", "pytest-cov (>=2.10.1)", " [[package]] name = "pyparsing" -version = "3.3.1" +version = "3.3.2" description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82"}, - {file = "pyparsing-3.3.1.tar.gz", hash = "sha256:47fad0f17ac1e2cad3de3b458570fbc9b03560aa029ed5e16ee5554da9a2251c"}, + {file = "pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d"}, + {file = "pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc"}, ] [package.extras] @@ -4562,31 +4567,31 @@ redis = ">=3.5,<6 || >6" [[package]] name = "ruff" -version = "0.14.13" +version = "0.14.14" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.14.13-py3-none-linux_armv6l.whl", hash = "sha256:76f62c62cd37c276cb03a275b198c7c15bd1d60c989f944db08a8c1c2dbec18b"}, - {file = "ruff-0.14.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:914a8023ece0528d5cc33f5a684f5f38199bbb566a04815c2c211d8f40b5d0ed"}, - {file = "ruff-0.14.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d24899478c35ebfa730597a4a775d430ad0d5631b8647a3ab368c29b7e7bd063"}, - {file = "ruff-0.14.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aaf3870f14d925bbaf18b8a2347ee0ae7d95a2e490e4d4aea6813ed15ebc80e"}, - {file = "ruff-0.14.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac5b7f63dd3b27cc811850f5ffd8fff845b00ad70e60b043aabf8d6ecc304e09"}, - {file = "ruff-0.14.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2b1097750d90ba82ce4ba676e85230a0ed694178ca5e61aa9b459970b3eb9"}, - {file = "ruff-0.14.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d0bf87705acbbcb8d4c24b2d77fbb73d40210a95c3903b443cd9e30824a5032"}, - {file = "ruff-0.14.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3eb5da8e2c9e9f13431032fdcbe7681de9ceda5835efee3269417c13f1fed5c"}, - {file = "ruff-0.14.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:642442b42957093811cd8d2140dfadd19c7417030a7a68cf8d51fcdd5f217427"}, - {file = "ruff-0.14.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4acdf009f32b46f6e8864af19cbf6841eaaed8638e65c8dac845aea0d703c841"}, - {file = "ruff-0.14.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:591a7f68860ea4e003917d19b5c4f5ac39ff558f162dc753a2c5de897fd5502c"}, - {file = "ruff-0.14.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:774c77e841cc6e046fc3e91623ce0903d1cd07e3a36b1a9fe79b81dab3de506b"}, - {file = "ruff-0.14.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:61f4e40077a1248436772bb6512db5fc4457fe4c49e7a94ea7c5088655dd21ae"}, - {file = "ruff-0.14.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6d02f1428357fae9e98ac7aa94b7e966fd24151088510d32cf6f902d6c09235e"}, - {file = "ruff-0.14.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e399341472ce15237be0c0ae5fbceca4b04cd9bebab1a2b2c979e015455d8f0c"}, - {file = "ruff-0.14.13-py3-none-win32.whl", hash = "sha256:ef720f529aec113968b45dfdb838ac8934e519711da53a0456038a0efecbd680"}, - {file = "ruff-0.14.13-py3-none-win_amd64.whl", hash = "sha256:6070bd026e409734b9257e03e3ef18c6e1a216f0435c6751d7a8ec69cb59abef"}, - {file = "ruff-0.14.13-py3-none-win_arm64.whl", hash = "sha256:7ab819e14f1ad9fe39f246cfcc435880ef7a9390d81a2b6ac7e01039083dd247"}, - {file = "ruff-0.14.13.tar.gz", hash = "sha256:83cd6c0763190784b99650a20fec7633c59f6ebe41c5cc9d45ee42749563ad47"}, + {file = "ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed"}, + {file = "ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c"}, + {file = "ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de"}, + {file = "ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e"}, + {file = "ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8"}, + {file = "ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906"}, + {file = "ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480"}, + {file = "ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df"}, + {file = "ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b"}, + {file = "ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974"}, + {file = "ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66"}, + {file = "ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13"}, + {file = "ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412"}, + {file = "ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3"}, + {file = "ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b"}, + {file = "ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167"}, + {file = "ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd"}, + {file = "ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c"}, + {file = "ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b"}, ] [[package]] @@ -4609,14 +4614,14 @@ crt = ["botocore[crt] (>=1.37.4,<2.0a.0)"] [[package]] name = "sentry-sdk" -version = "2.49.0" +version = "2.50.0" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = ">=3.6" groups = ["main"] files = [ - {file = "sentry_sdk-2.49.0-py2.py3-none-any.whl", hash = "sha256:6ea78499133874445a20fe9c826c9e960070abeb7ae0cdf930314ab16bb97aa0"}, - {file = "sentry_sdk-2.49.0.tar.gz", hash = "sha256:c1878599cde410d481c04ef50ee3aedd4f600e4d0d253f4763041e468b332c30"}, + {file = "sentry_sdk-2.50.0-py2.py3-none-any.whl", hash = "sha256:0ef0ed7168657ceb5a0be081f4102d92042a125462d1d1a29277992e344e749e"}, + {file = "sentry_sdk-2.50.0.tar.gz", hash = "sha256:873437a989ee1b8b25579847bae8384515bf18cfed231b06c591b735c1781fe3"}, ] [package.dependencies] @@ -4748,64 +4753,71 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.45" +version = "2.0.46" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "sqlalchemy-2.0.45-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c64772786d9eee72d4d3784c28f0a636af5b0a29f3fe26ff11f55efe90c0bd85"}, - {file = "sqlalchemy-2.0.45-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7ae64ebf7657395824a19bca98ab10eb9a3ecb026bf09524014f1bb81cb598d4"}, - {file = "sqlalchemy-2.0.45-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f02325709d1b1a1489f23a39b318e175a171497374149eae74d612634b234c0"}, - {file = "sqlalchemy-2.0.45-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2c3684fca8a05f0ac1d9a21c1f4a266983a7ea9180efb80ffeb03861ecd01a0"}, - {file = "sqlalchemy-2.0.45-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040f6f0545b3b7da6b9317fc3e922c9a98fc7243b2a1b39f78390fc0942f7826"}, - {file = "sqlalchemy-2.0.45-cp310-cp310-win32.whl", hash = "sha256:830d434d609fe7bfa47c425c445a8b37929f140a7a44cdaf77f6d34df3a7296a"}, - {file = "sqlalchemy-2.0.45-cp310-cp310-win_amd64.whl", hash = "sha256:0209d9753671b0da74da2cfbb9ecf9c02f72a759e4b018b3ab35f244c91842c7"}, - {file = "sqlalchemy-2.0.45-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e90a344c644a4fa871eb01809c32096487928bd2038bf10f3e4515cb688cc56"}, - {file = "sqlalchemy-2.0.45-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8c8b41b97fba5f62349aa285654230296829672fc9939cd7f35aab246d1c08b"}, - {file = "sqlalchemy-2.0.45-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12c694ed6468333a090d2f60950e4250b928f457e4962389553d6ba5fe9951ac"}, - {file = "sqlalchemy-2.0.45-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f7d27a1d977a1cfef38a0e2e1ca86f09c4212666ce34e6ae542f3ed0a33bc606"}, - {file = "sqlalchemy-2.0.45-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d62e47f5d8a50099b17e2bfc1b0c7d7ecd8ba6b46b1507b58cc4f05eefc3bb1c"}, - {file = "sqlalchemy-2.0.45-cp311-cp311-win32.whl", hash = "sha256:3c5f76216e7b85770d5bb5130ddd11ee89f4d52b11783674a662c7dd57018177"}, - {file = "sqlalchemy-2.0.45-cp311-cp311-win_amd64.whl", hash = "sha256:a15b98adb7f277316f2c276c090259129ee4afca783495e212048daf846654b2"}, - {file = "sqlalchemy-2.0.45-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3ee2aac15169fb0d45822983631466d60b762085bc4535cd39e66bea362df5f"}, - {file = "sqlalchemy-2.0.45-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba547ac0b361ab4f1608afbc8432db669bd0819b3e12e29fb5fa9529a8bba81d"}, - {file = "sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:215f0528b914e5c75ef2559f69dca86878a3beeb0c1be7279d77f18e8d180ed4"}, - {file = "sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:107029bf4f43d076d4011f1afb74f7c3e2ea029ec82eb23d8527d5e909e97aa6"}, - {file = "sqlalchemy-2.0.45-cp312-cp312-win32.whl", hash = "sha256:0c9f6ada57b58420a2c0277ff853abe40b9e9449f8d7d231763c6bc30f5c4953"}, - {file = "sqlalchemy-2.0.45-cp312-cp312-win_amd64.whl", hash = "sha256:8defe5737c6d2179c7997242d6473587c3beb52e557f5ef0187277009f73e5e1"}, - {file = "sqlalchemy-2.0.45-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe187fc31a54d7fd90352f34e8c008cf3ad5d064d08fedd3de2e8df83eb4a1cf"}, - {file = "sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:672c45cae53ba88e0dad74b9027dddd09ef6f441e927786b05bec75d949fbb2e"}, - {file = "sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:470daea2c1ce73910f08caf10575676a37159a6d16c4da33d0033546bddebc9b"}, - {file = "sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9c6378449e0940476577047150fd09e242529b761dc887c9808a9a937fe990c8"}, - {file = "sqlalchemy-2.0.45-cp313-cp313-win32.whl", hash = "sha256:4b6bec67ca45bc166c8729910bd2a87f1c0407ee955df110d78948f5b5827e8a"}, - {file = "sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl", hash = "sha256:afbf47dc4de31fa38fd491f3705cac5307d21d4bb828a4f020ee59af412744ee"}, - {file = "sqlalchemy-2.0.45-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83d7009f40ce619d483d26ac1b757dfe3167b39921379a8bd1b596cf02dab4a6"}, - {file = "sqlalchemy-2.0.45-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d8a2ca754e5415cde2b656c27900b19d50ba076aa05ce66e2207623d3fe41f5a"}, - {file = "sqlalchemy-2.0.45-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f46ec744e7f51275582e6a24326e10c49fbdd3fc99103e01376841213028774"}, - {file = "sqlalchemy-2.0.45-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:883c600c345123c033c2f6caca18def08f1f7f4c3ebeb591a63b6fceffc95cce"}, - {file = "sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2c0b74aa79e2deade948fe8593654c8ef4228c44ba862bb7c9585c8e0db90f33"}, - {file = "sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a420169cef179d4c9064365f42d779f1e5895ad26ca0c8b4c0233920973db74"}, - {file = "sqlalchemy-2.0.45-cp314-cp314-win32.whl", hash = "sha256:e50dcb81a5dfe4b7b4a4aa8f338116d127cb209559124f3694c70d6cd072b68f"}, - {file = "sqlalchemy-2.0.45-cp314-cp314-win_amd64.whl", hash = "sha256:4748601c8ea959e37e03d13dcda4a44837afcd1b21338e637f7c935b8da06177"}, - {file = "sqlalchemy-2.0.45-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd337d3526ec5298f67d6a30bbbe4ed7e5e68862f0bf6dd21d289f8d37b7d60b"}, - {file = "sqlalchemy-2.0.45-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9a62b446b7d86a3909abbcd1cd3cc550a832f99c2bc37c5b22e1925438b9367b"}, - {file = "sqlalchemy-2.0.45-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5964f832431b7cdfaaa22a660b4c7eb1dfcd6ed41375f67fd3e3440fd95cb3cc"}, - {file = "sqlalchemy-2.0.45-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee580ab50e748208754ae8980cec79ec205983d8cf8b3f7c39067f3d9f2c8e22"}, - {file = "sqlalchemy-2.0.45-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13e27397a7810163440c6bfed6b3fe46f1bfb2486eb540315a819abd2c004128"}, - {file = "sqlalchemy-2.0.45-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ed3635353e55d28e7f4a95c8eda98a5cdc0a0b40b528433fbd41a9ae88f55b3d"}, - {file = "sqlalchemy-2.0.45-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:db6834900338fb13a9123307f0c2cbb1f890a8656fcd5e5448ae3ad5bbe8d312"}, - {file = "sqlalchemy-2.0.45-cp38-cp38-win32.whl", hash = "sha256:1d8b4a7a8c9b537509d56d5cd10ecdcfbb95912d72480c8861524efecc6a3fff"}, - {file = "sqlalchemy-2.0.45-cp38-cp38-win_amd64.whl", hash = "sha256:ebd300afd2b62679203435f596b2601adafe546cb7282d5a0cd3ed99e423720f"}, - {file = "sqlalchemy-2.0.45-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d29b2b99d527dbc66dd87c3c3248a5dd789d974a507f4653c969999fc7c1191b"}, - {file = "sqlalchemy-2.0.45-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:59a8b8bd9c6bedf81ad07c8bd5543eedca55fe9b8780b2b628d495ba55f8db1e"}, - {file = "sqlalchemy-2.0.45-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd93c6f5d65f254ceabe97548c709e073d6da9883343adaa51bf1a913ce93f8e"}, - {file = "sqlalchemy-2.0.45-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6d0beadc2535157070c9c17ecf25ecec31e13c229a8f69196d7590bde8082bf1"}, - {file = "sqlalchemy-2.0.45-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e057f928ffe9c9b246a55b469c133b98a426297e1772ad24ce9f0c47d123bd5b"}, - {file = "sqlalchemy-2.0.45-cp39-cp39-win32.whl", hash = "sha256:c1c2091b1489435ff85728fafeb990f073e64f6f5e81d5cd53059773e8521eb6"}, - {file = "sqlalchemy-2.0.45-cp39-cp39-win_amd64.whl", hash = "sha256:56ead1f8dfb91a54a28cd1d072c74b3d635bcffbd25e50786533b822d4f2cde2"}, - {file = "sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0"}, - {file = "sqlalchemy-2.0.45.tar.gz", hash = "sha256:1632a4bda8d2d25703fdad6363058d882541bdaaee0e5e3ddfa0cd3229efce88"}, + {file = "sqlalchemy-2.0.46-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:895296687ad06dc9b11a024cf68e8d9d3943aa0b4964278d2553b86f1b267735"}, + {file = "sqlalchemy-2.0.46-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab65cb2885a9f80f979b85aa4e9c9165a31381ca322cbde7c638fe6eefd1ec39"}, + {file = "sqlalchemy-2.0.46-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:52fe29b3817bd191cc20bad564237c808967972c97fa683c04b28ec8979ae36f"}, + {file = "sqlalchemy-2.0.46-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:09168817d6c19954d3b7655da6ba87fcb3a62bb575fb396a81a8b6a9fadfe8b5"}, + {file = "sqlalchemy-2.0.46-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:be6c0466b4c25b44c5d82b0426b5501de3c424d7a3220e86cd32f319ba56798e"}, + {file = "sqlalchemy-2.0.46-cp310-cp310-win32.whl", hash = "sha256:1bc3f601f0a818d27bfe139f6766487d9c88502062a2cd3a7ee6c342e81d5047"}, + {file = "sqlalchemy-2.0.46-cp310-cp310-win_amd64.whl", hash = "sha256:e0c05aff5c6b1bb5fb46a87e0f9d2f733f83ef6cbbbcd5c642b6c01678268061"}, + {file = "sqlalchemy-2.0.46-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:261c4b1f101b4a411154f1da2b76497d73abbfc42740029205d4d01fa1052684"}, + {file = "sqlalchemy-2.0.46-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:181903fe8c1b9082995325f1b2e84ac078b1189e2819380c2303a5f90e114a62"}, + {file = "sqlalchemy-2.0.46-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:590be24e20e2424a4c3c1b0835e9405fa3d0af5823a1a9fc02e5dff56471515f"}, + {file = "sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7568fe771f974abadce52669ef3a03150ff03186d8eb82613bc8adc435a03f01"}, + {file = "sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf7e1e78af38047e08836d33502c7a278915698b7c2145d045f780201679999"}, + {file = "sqlalchemy-2.0.46-cp311-cp311-win32.whl", hash = "sha256:9d80ea2ac519c364a7286e8d765d6cd08648f5b21ca855a8017d9871f075542d"}, + {file = "sqlalchemy-2.0.46-cp311-cp311-win_amd64.whl", hash = "sha256:585af6afe518732d9ccd3aea33af2edaae4a7aa881af5d8f6f4fe3a368699597"}, + {file = "sqlalchemy-2.0.46-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a9a72b0da8387f15d5810f1facca8f879de9b85af8c645138cba61ea147968c"}, + {file = "sqlalchemy-2.0.46-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2347c3f0efc4de367ba00218e0ae5c4ba2306e47216ef80d6e31761ac97cb0b9"}, + {file = "sqlalchemy-2.0.46-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9094c8b3197db12aa6f05c51c05daaad0a92b8c9af5388569847b03b1007fb1b"}, + {file = "sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37fee2164cf21417478b6a906adc1a91d69ae9aba8f9533e67ce882f4bb1de53"}, + {file = "sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b1e14b2f6965a685c7128bd315e27387205429c2e339eeec55cb75ca4ab0ea2e"}, + {file = "sqlalchemy-2.0.46-cp312-cp312-win32.whl", hash = "sha256:412f26bb4ba942d52016edc8d12fb15d91d3cd46b0047ba46e424213ad407bcb"}, + {file = "sqlalchemy-2.0.46-cp312-cp312-win_amd64.whl", hash = "sha256:ea3cd46b6713a10216323cda3333514944e510aa691c945334713fca6b5279ff"}, + {file = "sqlalchemy-2.0.46-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a12da97cca70cea10d4b4fc602589c4511f96c1f8f6c11817620c021d21d00"}, + {file = "sqlalchemy-2.0.46-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af865c18752d416798dae13f83f38927c52f085c52e2f32b8ab0fef46fdd02c2"}, + {file = "sqlalchemy-2.0.46-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d679b5f318423eacb61f933a9a0f75535bfca7056daeadbf6bd5bcee6183aee"}, + {file = "sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64901e08c33462acc9ec3bad27fc7a5c2b6491665f2aa57564e57a4f5d7c52ad"}, + {file = "sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8ac45e8f4eaac0f9f8043ea0e224158855c6a4329fd4ee37c45c61e3beb518e"}, + {file = "sqlalchemy-2.0.46-cp313-cp313-win32.whl", hash = "sha256:8d3b44b3d0ab2f1319d71d9863d76eeb46766f8cf9e921ac293511804d39813f"}, + {file = "sqlalchemy-2.0.46-cp313-cp313-win_amd64.whl", hash = "sha256:77f8071d8fbcbb2dd11b7fd40dedd04e8ebe2eb80497916efedba844298065ef"}, + {file = "sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1e8cc6cc01da346dc92d9509a63033b9b1bda4fed7a7a7807ed385c7dccdc10"}, + {file = "sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96c7cca1a4babaaf3bfff3e4e606e38578856917e52f0384635a95b226c87764"}, + {file = "sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2a9f9aee38039cf4755891a1e50e1effcc42ea6ba053743f452c372c3152b1b"}, + {file = "sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db23b1bf8cfe1f7fda19018e7207b20cdb5168f83c437ff7e95d19e39289c447"}, + {file = "sqlalchemy-2.0.46-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bdd261bfd0895452006d5316cbf35739c53b9bb71a170a331fa0ea560b2ada"}, + {file = "sqlalchemy-2.0.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e462154edb9493f6c3ad2125931e273bbd0be8ae53f3ecd1c161ea9a1dd366"}, + {file = "sqlalchemy-2.0.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcdce05f056622a632f1d44bb47dbdb677f58cad393612280406ce37530eb6d"}, + {file = "sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e84b09a9b0f19accedcbeff5c2caf36e0dd537341a33aad8d680336152dc34e"}, + {file = "sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4f52f7291a92381e9b4de9050b0a65ce5d6a763333406861e33906b8aa4906bf"}, + {file = "sqlalchemy-2.0.46-cp314-cp314-win32.whl", hash = "sha256:70ed2830b169a9960193f4d4322d22be5c0925357d82cbf485b3369893350908"}, + {file = "sqlalchemy-2.0.46-cp314-cp314-win_amd64.whl", hash = "sha256:3c32e993bc57be6d177f7d5d31edb93f30726d798ad86ff9066d75d9bf2e0b6b"}, + {file = "sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4dafb537740eef640c4d6a7c254611dca2df87eaf6d14d6a5fca9d1f4c3fc0fa"}, + {file = "sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42a1643dc5427b69aca967dae540a90b0fbf57eaf248f13a90ea5930e0966863"}, + {file = "sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff33c6e6ad006bbc0f34f5faf941cfc62c45841c64c0a058ac38c799f15b5ede"}, + {file = "sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82ec52100ec1e6ec671563bbd02d7c7c8d0b9e71a0723c72f22ecf52d1755330"}, + {file = "sqlalchemy-2.0.46-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6ac245604295b521de49b465bab845e3afe6916bcb2147e5929c8041b4ec0545"}, + {file = "sqlalchemy-2.0.46-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e6199143d51e3e1168bedd98cc698397404a8f7508831b81b6a29b18b051069"}, + {file = "sqlalchemy-2.0.46-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:716be5bcabf327b6d5d265dbdc6213a01199be587224eb991ad0d37e83d728fd"}, + {file = "sqlalchemy-2.0.46-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6f827fd687fa1ba7f51699e1132129eac8db8003695513fcf13fc587e1bd47a5"}, + {file = "sqlalchemy-2.0.46-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c805fa6e5d461329fa02f53f88c914d189ea771b6821083937e79550bf31fc19"}, + {file = "sqlalchemy-2.0.46-cp38-cp38-win32.whl", hash = "sha256:3aac08f7546179889c62b53b18ebf1148b10244b3405569c93984b0388d016a7"}, + {file = "sqlalchemy-2.0.46-cp38-cp38-win_amd64.whl", hash = "sha256:0cc3117db526cad3e61074100bd2867b533e2c7dc1569e95c14089735d6fb4fe"}, + {file = "sqlalchemy-2.0.46-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:90bde6c6b1827565a95fde597da001212ab436f1b2e0c2dcc7246e14db26e2a3"}, + {file = "sqlalchemy-2.0.46-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94b1e5f3a5f1ff4f42d5daab047428cd45a3380e51e191360a35cef71c9a7a2a"}, + {file = "sqlalchemy-2.0.46-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93bb0aae40b52c57fd74ef9c6933c08c040ba98daf23ad33c3f9893494b8d3ce"}, + {file = "sqlalchemy-2.0.46-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c4e2cc868b7b5208aec6c960950b7bb821f82c2fe66446c92ee0a571765e91a5"}, + {file = "sqlalchemy-2.0.46-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:965c62be8256d10c11f8907e7a8d3e18127a4c527a5919d85fa87fd9ecc2cfdc"}, + {file = "sqlalchemy-2.0.46-cp39-cp39-win32.whl", hash = "sha256:9397b381dcee8a2d6b99447ae85ea2530dcac82ca494d1db877087a13e38926d"}, + {file = "sqlalchemy-2.0.46-cp39-cp39-win_amd64.whl", hash = "sha256:4396c948d8217e83e2c202fbdcc0389cf8c93d2c1c5e60fa5c5a955eae0e64be"}, + {file = "sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e"}, + {file = "sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7"}, ] [package.dependencies] @@ -4855,14 +4867,14 @@ doc = ["sphinx"] [[package]] name = "strawberry-graphql" -version = "0.289.0" +version = "0.289.5" description = "A library for creating GraphQL APIs" optional = false python-versions = "<4.0,>=3.10" groups = ["main"] files = [ - {file = "strawberry_graphql-0.289.0-py3-none-any.whl", hash = "sha256:8fd30c3de55bb518dae68088d43259c831a68a1a688870733a6fcba7512368d0"}, - {file = "strawberry_graphql-0.289.0.tar.gz", hash = "sha256:cb5b6718d7f6a99b4a5d6edf0d76a6b4e414e04b1ad93662dff402b52ed565f2"}, + {file = "strawberry_graphql-0.289.5-py3-none-any.whl", hash = "sha256:293288f849918d93ed17a32b9126b8fe13f8f0c847b5668e3794fa575665bca6"}, + {file = "strawberry_graphql-0.289.5.tar.gz", hash = "sha256:2e3eea4f4be04a2b3f1d00b6eafe9935b83595873788f799960180fb0d0be680"}, ] [package.dependencies] @@ -4894,14 +4906,14 @@ sanic = ["sanic (>=20.12.2)"] [[package]] name = "strawberry-graphql-django" -version = "0.73.1" +version = "0.74.1" description = "Strawberry GraphQL Django extension" optional = false python-versions = "<4.0,>=3.10" groups = ["main"] files = [ - {file = "strawberry_graphql_django-0.73.1-py3-none-any.whl", hash = "sha256:cf073975ade25c9c54557fad093113dfb2a4884fdd0abca1df2e320e2bae40e0"}, - {file = "strawberry_graphql_django-0.73.1.tar.gz", hash = "sha256:b3096c2e6abbfa8db6b25fe52393150b1dbe24f13a4c2eb6ac80625b0d933c3b"}, + {file = "strawberry_graphql_django-0.74.1-py3-none-any.whl", hash = "sha256:8b0d617ce1649cb244a307514613295558801e43bcc7ef8b21b59e93b4c36b2b"}, + {file = "strawberry_graphql_django-0.74.1.tar.gz", hash = "sha256:cc052fc89aa6da06507088ffd53a6d08612d8b996a4de5dfea22758225766ae2"}, ] [package.dependencies] @@ -5122,34 +5134,34 @@ zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "uuid-utils" -version = "0.13.0" +version = "0.14.0" description = "Fast, drop-in replacement for Python's uuid module, powered by Rust." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "uuid_utils-0.13.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:83628283e977fb212e756bc055df8fdd2f9f589a2e539ba1abe755b8ce8df7a4"}, - {file = "uuid_utils-0.13.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c47638ed6334ab19d80f73664f153b04bbb04ab8ce4298d10da6a292d4d21c47"}, - {file = "uuid_utils-0.13.0-cp39-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:b276b538c57733ed406948584912da422a604313c71479654848b84b9e19c9b0"}, - {file = "uuid_utils-0.13.0-cp39-abi3-manylinux_2_24_armv7l.whl", hash = "sha256:bdaf2b77e34b199cf04cde28399495fd1ed951de214a4ece1f3919b2f945bb06"}, - {file = "uuid_utils-0.13.0-cp39-abi3-manylinux_2_24_i686.whl", hash = "sha256:eb2f0baf81e82f9769a7684022dca8f3bf801ca1574a3e94df1876e9d6f9271e"}, - {file = "uuid_utils-0.13.0-cp39-abi3-manylinux_2_24_ppc64le.whl", hash = "sha256:6be6c4d11275f5cc402a4fdba6c2b1ce45fd3d99bb78716cd1cc2cbf6802b2ce"}, - {file = "uuid_utils-0.13.0-cp39-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:77621cf6ceca7f42173a642a01c01c216f9eaec3b7b65d093d2d6a433ca0a83d"}, - {file = "uuid_utils-0.13.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a5a9eb06c2bb86dd876cd7b2fe927fc8543d14c90d971581db6ffda4a02526f"}, - {file = "uuid_utils-0.13.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:775347c6110fb71360df17aac74132d8d47c1dbe71233ac98197fc872a791fd2"}, - {file = "uuid_utils-0.13.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf95f6370ad1a0910ee7b5ad5228fd19c4ae32fe3627389006adaf519408c41e"}, - {file = "uuid_utils-0.13.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5a88e23e0b2f4203fefe2ccbca5736ee06fcad10e61b5e7e39c8d7904bc13300"}, - {file = "uuid_utils-0.13.0-cp39-abi3-win32.whl", hash = "sha256:3e4f2cc54e6a99c0551158100ead528479ad2596847478cbad624977064ffce3"}, - {file = "uuid_utils-0.13.0-cp39-abi3-win_amd64.whl", hash = "sha256:046cb2756e1597b3de22d24851b769913e192135830486a0a70bf41327f0360c"}, - {file = "uuid_utils-0.13.0-cp39-abi3-win_arm64.whl", hash = "sha256:5447a680df6ef8a5a353976aaf4c97cc3a3a22b1ee13671c44227b921e3ae2a9"}, - {file = "uuid_utils-0.13.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e5182e2d95f38e65f2e5bce90648ef56987443da13e145afcd747e584f9bc69c"}, - {file = "uuid_utils-0.13.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e3909a8a1fbd79d7c8bdc874eeb83e23ccb7a7cb0aa821a49596cc96c0cce84b"}, - {file = "uuid_utils-0.13.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.whl", hash = "sha256:5dc4c9f749bd2511b8dcbf0891e658d7d86880022963db050722ad7b502b5e22"}, - {file = "uuid_utils-0.13.0-pp311-pypy311_pp73-manylinux_2_24_armv7l.whl", hash = "sha256:516adf07f5b2cdb88d50f489c702b5f1a75ae8b2639bfd254f4192d5f7ee261f"}, - {file = "uuid_utils-0.13.0-pp311-pypy311_pp73-manylinux_2_24_i686.whl", hash = "sha256:aeee3bd89e8de6184a3ab778ce19f5ce9ad32849d1be549516e0ddb257562d8d"}, - {file = "uuid_utils-0.13.0-pp311-pypy311_pp73-manylinux_2_24_ppc64le.whl", hash = "sha256:97985256c2e59b7caa51f5c8515f64d777328562a9c900ec65e9d627baf72737"}, - {file = "uuid_utils-0.13.0-pp311-pypy311_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:b7ccaa20e24c5f60f41a69ef571ed820737f9b0ade4cbeef56aaa8f80f5aa475"}, - {file = "uuid_utils-0.13.0.tar.gz", hash = "sha256:4c17df6427a9e23a4cd7fb9ee1efb53b8abb078660b9bdb2524ca8595022dfe1"}, + {file = "uuid_utils-0.14.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f6695c0bed8b18a904321e115afe73b34444bc8451d0ce3244a1ec3b84deb0e5"}, + {file = "uuid_utils-0.14.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:4f0a730bbf2d8bb2c11b93e1005e91769f2f533fa1125ed1f00fd15b6fcc732b"}, + {file = "uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40ce3fd1a4fdedae618fc3edc8faf91897012469169d600133470f49fd699ed3"}, + {file = "uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09ae4a98416a440e78f7d9543d11b11cae4bab538b7ed94ec5da5221481748f2"}, + {file = "uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:971e8c26b90d8ae727e7f2ac3ee23e265971d448b3672882f2eb44828b2b8c3e"}, + {file = "uuid_utils-0.14.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5cde1fa82804a8f9d2907b7aec2009d440062c63f04abbdb825fce717a5e860"}, + {file = "uuid_utils-0.14.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7343862a2359e0bd48a7f3dfb5105877a1728677818bb694d9f40703264a2db"}, + {file = "uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c51e4818fdb08ccec12dc7083a01f49507b4608770a0ab22368001685d59381b"}, + {file = "uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:181bbcccb6f93d80a8504b5bd47b311a1c31395139596edbc47b154b0685b533"}, + {file = "uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:5c8ae96101c3524ba8dbf762b6f05e9e9d896544786c503a727c5bf5cb9af1a7"}, + {file = "uuid_utils-0.14.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00ac3c6edfdaff7e1eed041f4800ae09a3361287be780d7610a90fdcde9befdc"}, + {file = "uuid_utils-0.14.0-cp39-abi3-win32.whl", hash = "sha256:ec2fd80adf8e0e6589d40699e6f6df94c93edcc16dd999be0438dd007c77b151"}, + {file = "uuid_utils-0.14.0-cp39-abi3-win_amd64.whl", hash = "sha256:efe881eb43a5504fad922644cb93d725fd8a6a6d949bd5a4b4b7d1a1587c7fd1"}, + {file = "uuid_utils-0.14.0-cp39-abi3-win_arm64.whl", hash = "sha256:32b372b8fd4ebd44d3a219e093fe981af4afdeda2994ee7db208ab065cfcd080"}, + {file = "uuid_utils-0.14.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:762e8d67992ac4d2454e24a141a1c82142b5bde10409818c62adbe9924ebc86d"}, + {file = "uuid_utils-0.14.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:40be5bf0b13aa849d9062abc86c198be6a25ff35316ce0b89fc25f3bac6d525e"}, + {file = "uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:191a90a6f3940d1b7322b6e6cceff4dd533c943659e0a15f788674407856a515"}, + {file = "uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4aa4525f4ad82f9d9c842f9a3703f1539c1808affbaec07bb1b842f6b8b96aa5"}, + {file = "uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdbd82ff20147461caefc375551595ecf77ebb384e46267f128aca45a0f2cdfc"}, + {file = "uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff57e8a5d540006ce73cf0841a643d445afe78ba12e75ac53a95ca2924a56be"}, + {file = "uuid_utils-0.14.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fd9112ca96978361201e669729784f26c71fecc9c13a7f8a07162c31bd4d1e2"}, + {file = "uuid_utils-0.14.0.tar.gz", hash = "sha256:fc5bac21e9933ea6c590433c11aa54aaca599f690c08069e364eb13a12f670b4"}, ] [[package]] @@ -5190,14 +5202,14 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "weasyprint" -version = "67.0" +version = "68.0" description = "The Awesome Document Factory" optional = false python-versions = ">=3.10" groups = ["video"] files = [ - {file = "weasyprint-67.0-py3-none-any.whl", hash = "sha256:abc2f40872ea01c29c11f7799dafc4b23c078335bf7777f72a8affeb36e1d201"}, - {file = "weasyprint-67.0.tar.gz", hash = "sha256:fdfbccf700e8086c8fd1607ec42e25d4b584512c29af2d9913587a4e448dead4"}, + {file = "weasyprint-68.0-py3-none-any.whl", hash = "sha256:c2cb40c71b50837c5971f00171c9e4078e8c9912dd7c217f3e90e068f11e8aa1"}, + {file = "weasyprint-68.0.tar.gz", hash = "sha256:447f40898b747cb44ac31a5d493d512e7441fd56e13f63744c099383bbf9cda9"}, ] [package.dependencies] @@ -5212,7 +5224,7 @@ tinyhtml5 = ">=2.0.0b1" [package.extras] doc = ["furo", "sphinx"] -test = ["pytest", "ruff"] +test = ["Pillow (>=12.1.0)", "pytest", "ruff"] [[package]] name = "webencodings" @@ -5317,18 +5329,21 @@ watchdog = ["watchdog (>=2.3)"] [[package]] name = "wheel" -version = "0.45.1" -description = "A built-package format for Python" +version = "0.46.3" +description = "Command line tool for manipulating wheel files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"}, - {file = "wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729"}, + {file = "wheel-0.46.3-py3-none-any.whl", hash = "sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d"}, + {file = "wheel-0.46.3.tar.gz", hash = "sha256:e3e79874b07d776c40bd6033f8ddf76a7dad46a7b8aa1b2787a83083519a1803"}, ] +[package.dependencies] +packaging = ">=24.0" + [package.extras] -test = ["pytest (>=6.0.0)", "setuptools (>=65)"] +test = ["pytest (>=6.0.0)", "setuptools (>=77)"] [[package]] name = "wrapt" @@ -5916,4 +5931,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = "^3.13" -content-hash = "056fb89434bf8f3733c03fce81175250b972bc40299d4894464e5e1d5f121c76" +content-hash = "eb0a59232bed3fb7c475dfdabb827affb2452e7f3e911011a09c7e409eb6acaf" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 0dfe0eaf6b..631e59d88e 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -24,7 +24,7 @@ django-rq = "^3.1" django-storages = { extras = [ "s3" ], version = "^1.14.4" } emoji = "^2.14.1" geopy = "^2.4.1" -gunicorn = "^23.0.0" +gunicorn = "^24.0.0" humanize = "^4.11.0" jinja2 = "^3.1.6" langchain = "^0.3.26" @@ -49,7 +49,7 @@ sentry-sdk = { extras = [ "django" ], version = "^2.20.0" } slack-bolt = "^1.22.0" slack-sdk = "^3.37.0" strawberry-graphql = { extras = [ "django" ], version = "^0.289.0" } -strawberry-graphql-django = "^0.73.0" +strawberry-graphql-django = "^0.74.1" thefuzz = "^0.22.1" zappa = "^0.61.4" @@ -71,7 +71,7 @@ elevenlabs = "^2.27.0" ffmpeg-python = "^0.2.0" pillow = "^12.1.0" pypdfium2 = "^5.2.0" -weasyprint = "^67.0" +weasyprint = "^68.0" [tool.ruff] target-version = "py313" @@ -146,6 +146,7 @@ addopts = [ "--cov-fail-under=80", "--cov-precision=2", "--cov-report=term-missing", + "--cov-report=xml", "--cov=.", "--dist=loadscope", "--durations-min=1", diff --git a/backend/settings/graphql.py b/backend/settings/graphql.py index 6020741d74..39eb24ab67 100644 --- a/backend/settings/graphql.py +++ b/backend/settings/graphql.py @@ -4,9 +4,9 @@ from strawberry.extensions import QueryDepthLimiter from strawberry_django.optimizer import DjangoOptimizerExtension +from apps.api.internal.extensions.cache import CacheExtension from apps.api.internal.mutations import ApiMutations from apps.api.internal.queries import ApiKeyQueries -from apps.common.extensions import CacheExtension from apps.github.api.internal.queries import GithubQuery from apps.mentorship.api.internal.mutations import ( ModuleMutation, diff --git a/backend/tests/apps/api/internal/extensions/__init__.py b/backend/tests/apps/api/internal/extensions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/tests/apps/common/extensions_test.py b/backend/tests/apps/api/internal/extensions/cache_test.py similarity index 56% rename from backend/tests/apps/common/extensions_test.py rename to backend/tests/apps/api/internal/extensions/cache_test.py index 3b7bb1c982..d1d0056129 100644 --- a/backend/tests/apps/common/extensions_test.py +++ b/backend/tests/apps/api/internal/extensions/cache_test.py @@ -1,51 +1,67 @@ """Tests for CacheExtension.""" +import datetime from unittest.mock import MagicMock, patch +from uuid import UUID import pytest +from django.conf import settings from strawberry.permission import PermissionExtension -from apps.common.extensions import CacheExtension, get_protected_fields +from apps.api.internal.extensions.cache import ( + CacheExtension, + generate_key, + get_protected_fields, + invalidate_module_cache, + invalidate_program_cache, +) class TestGenerateKey: - """Test cases for the generate_key method.""" + """Test cases for the generate_key function.""" - @pytest.fixture - def extension(self): - """Return a CacheExtension instance.""" - return CacheExtension() - - def test_creates_deterministic_hash(self, extension): + def test_creates_deterministic_hash(self): """Test that generate_key creates a deterministic hash key.""" - key1 = extension.generate_key("chapter", {"key": "germany"}) - key2 = extension.generate_key("chapter", {"key": "germany"}) + key1 = generate_key("chapter", {"key": "germany"}) + key2 = generate_key("chapter", {"key": "germany"}) assert key1 == key2 assert key1.startswith("graphql-") assert len(key1.split("-")[-1]) == 64 # SHA256 hex digest length - def test_differs_for_different_field_names(self, extension): + def test_differs_for_different_field_names(self): """Test that different field names produce different keys.""" - key1 = extension.generate_key("chapter", {"key": "germany"}) - key2 = extension.generate_key("project", {"key": "germany"}) + key1 = generate_key("chapter", {"key": "germany"}) + key2 = generate_key("project", {"key": "germany"}) assert key1 != key2 - def test_differs_for_different_args(self, extension): + def test_differs_for_different_args(self): """Test that different arguments produce different keys.""" - key1 = extension.generate_key("chapter", {"key": "germany"}) - key2 = extension.generate_key("chapter", {"key": "canada"}) + key1 = generate_key("chapter", {"key": "germany"}) + key2 = generate_key("chapter", {"key": "canada"}) assert key1 != key2 - def test_sorts_args_for_consistency(self, extension): + def test_sorts_args_for_consistency(self): """Test that argument order doesn't affect the key.""" - key1 = extension.generate_key("chapter", {"a": "1", "b": "2"}) - key2 = extension.generate_key("chapter", {"b": "2", "a": "1"}) + key1 = generate_key("chapter", {"a": "1", "b": "2"}) + key2 = generate_key("chapter", {"b": "2", "a": "1"}) assert key1 == key2 + def test_serializes_datetime_and_uuid_args(self): + """Test that generate_key handles datetime and UUID in args without error.""" + dt = datetime.datetime(2025, 1, 15, 12, 0, 0, tzinfo=datetime.UTC) + uid = UUID("550e8400-e29b-41d4-a716-446655440000") + field_args = {"at": dt, "id": uid} + + key1 = generate_key("someField", field_args) + key2 = generate_key("someField", field_args) + + assert key1 == key2 + assert key1.startswith(f"{settings.GRAPHQL_RESOLVER_CACHE_PREFIX}-") + class TestGetProtectedFields: """Test cases for the get_protected_fields function.""" @@ -90,7 +106,9 @@ class TestResolve: @pytest.fixture(autouse=True) def mock_protected_fields(self): """Patch get_protected_fields for all tests.""" - with patch("apps.common.extensions.get_protected_fields", return_value=("apiKeys",)): + with patch( + "apps.api.internal.extensions.cache.get_protected_fields", return_value=("apiKeys",) + ): yield @pytest.fixture @@ -140,7 +158,7 @@ def test_skips_protected_fields(self, extension, mock_info, mock_next): mock_next.assert_called_once() assert result == mock_next.return_value - @patch("apps.common.extensions.cache") + @patch("apps.api.internal.extensions.cache.cache") def test_returns_cached_result_on_hit(self, mock_cache, extension, mock_info, mock_next): """Test that cached result is returned on cache hit.""" cached_result = {"name": "Cached OWASP"} @@ -152,7 +170,7 @@ def test_returns_cached_result_on_hit(self, mock_cache, extension, mock_info, mo mock_cache.get_or_set.assert_called_once() mock_next.assert_not_called() - @patch("apps.common.extensions.cache") + @patch("apps.api.internal.extensions.cache.cache") def test_caches_result_on_miss(self, mock_cache, extension, mock_info, mock_next): """Test that result is cached on cache miss.""" mock_cache.get_or_set.side_effect = lambda _key, default, _timeout: default() @@ -161,3 +179,41 @@ def test_caches_result_on_miss(self, mock_cache, extension, mock_info, mock_next mock_next.assert_called_once() mock_cache.get_or_set.assert_called_once() + + +class TestInvalidationHelpers: + """Test cases for invalidation helper functions.""" + + @patch("apps.api.internal.extensions.cache.cache") + @patch("apps.api.internal.extensions.cache.generate_key") + def test_invalidate_program_cache_uses_camel_case_keys(self, mock_generate_key, mock_cache): + """Test that invalidate_program_cache uses correct camelCase keys.""" + mock_generate_key.side_effect = lambda name, _args: f"{name}-hashed" + + invalidate_program_cache("my-program") + + # Verify calls to generate_key use camelCase 'programKey' + assert mock_generate_key.call_count == 2 + mock_generate_key.assert_any_call("getProgram", {"programKey": "my-program"}) + mock_generate_key.assert_any_call("getProgramModules", {"programKey": "my-program"}) + + assert mock_cache.delete.call_count == 2 + mock_cache.delete.assert_any_call("getProgram-hashed") + mock_cache.delete.assert_any_call("getProgramModules-hashed") + + @patch("apps.api.internal.extensions.cache.cache") + @patch("apps.api.internal.extensions.cache.generate_key") + def test_invalidate_module_cache_uses_camel_case_keys(self, mock_generate_key, mock_cache): + """Test that invalidate_module_cache uses correct camelCase keys.""" + mock_generate_key.side_effect = lambda name, _args: f"{name}-hashed" + + invalidate_module_cache("module-1", "program-1") + + assert mock_generate_key.call_count == 3 + mock_generate_key.assert_any_call( + "getModule", {"moduleKey": "module-1", "programKey": "program-1"} + ) + mock_generate_key.assert_any_call("getProgram", {"programKey": "program-1"}) + mock_generate_key.assert_any_call("getProgramModules", {"programKey": "program-1"}) + + assert mock_cache.delete.call_count == 3 diff --git a/backend/tests/apps/common/management/commands/purge_data_test.py b/backend/tests/apps/common/management/commands/purge_data_test.py index b9942eda91..e423fc0e88 100644 --- a/backend/tests/apps/common/management/commands/purge_data_test.py +++ b/backend/tests/apps/common/management/commands/purge_data_test.py @@ -1,6 +1,7 @@ from unittest.mock import MagicMock, patch import pytest +from psycopg2 import sql from apps.common.management.commands.purge_data import Command @@ -48,5 +49,8 @@ def get_app_config_side_effect(app_name): for app_name in nest_apps: for model_name in mock_models[app_name]: table_name = f"{app_name.lower()}_{model_name.lower()}" - cursor_instance.execute.assert_any_call(f"TRUNCATE TABLE {table_name} CASCADE") + expected_query = sql.SQL("TRUNCATE TABLE {} CASCADE").format( + sql.Identifier(table_name) + ) + cursor_instance.execute.assert_any_call(expected_query) mock_print.assert_any_call(f"Purged {app_name}.{model_name}") diff --git a/backend/tests/apps/github/models/mixins/repository_test.py b/backend/tests/apps/github/models/mixins/repository_test.py index ef83099886..65cb79a672 100644 --- a/backend/tests/apps/github/models/mixins/repository_test.py +++ b/backend/tests/apps/github/models/mixins/repository_test.py @@ -5,31 +5,35 @@ from apps.github.models.mixins.repository import RepositoryIndexMixin from apps.github.models.release import Release +COMMITS_COUNT = 100 CONTRIBUTORS_COUNT = 5 FORKS_COUNT = 5 OPEN_ISSUES_COUNT = 5 +REPOSITORY_SIZE = 1024 STARS_COUNT = 5 +SUBSCRIBERS_COUNT = 10 @pytest.fixture def repository_index_mixin_instance(): instance = RepositoryIndexMixin() + instance.commits_count = COMMITS_COUNT instance.contributors_count = CONTRIBUTORS_COUNT + instance.created_at = datetime(2020, 1, 1, tzinfo=UTC) instance.description = "Description" instance.forks_count = FORKS_COUNT + instance.has_funding_yml = True instance.languages = ["Python", "JavaScript"] + instance.license = "MIT" instance.name = "Name" + instance.nest_key = "nest/key" instance.open_issues_count = OPEN_ISSUES_COUNT + instance.project = None instance.pushed_at = datetime(2021, 1, 1, tzinfo=UTC) + instance.size = REPOSITORY_SIZE instance.stars_count = STARS_COUNT + instance.subscribers_count = SUBSCRIBERS_COUNT instance.topics = ["Topic1", "Topic2"] - instance.created_at = datetime(2020, 1, 1, tzinfo=UTC) - instance.size = 1024 - instance.has_funding_yml = True - instance.license = "MIT" - instance.project = None - instance.commits_count = 100 - instance.nest_key = "nest/key" return instance @@ -104,22 +108,22 @@ def test_idx_top_contributors(self, repository_index_mixin_instance, mocker): @pytest.mark.parametrize( ("attr", "expected"), [ + ("idx_commits_count", 100), ("idx_contributors_count", CONTRIBUTORS_COUNT), + ("idx_created_at", datetime(2020, 1, 1, tzinfo=UTC).timestamp()), ("idx_description", "Description"), ("idx_forks_count", FORKS_COUNT), + ("idx_has_funding_yml", True), + ("idx_key", "nest/key"), ("idx_languages", ["Python", "JavaScript"]), + ("idx_license", "MIT"), ("idx_name", "Name"), ("idx_open_issues_count", OPEN_ISSUES_COUNT), ("idx_pushed_at", datetime(2021, 1, 1, tzinfo=UTC).timestamp()), + ("idx_size", 1024), ("idx_stars_count", STARS_COUNT), + ("idx_subscribers_count", SUBSCRIBERS_COUNT), ("idx_topics", ["Topic1", "Topic2"]), - ("idx_created_at", datetime(2020, 1, 1, tzinfo=UTC).timestamp()), - ("idx_has_funding_yml", True), - ("idx_license", "MIT"), - ("idx_subscribers_count", STARS_COUNT), - ("idx_commits_count", 100), - ("idx_size", 1024), - ("idx_key", "nest/key"), ], ) def test_repository_index(self, repository_index_mixin_instance, attr, expected): diff --git a/cspell/Dockerfile b/cspell/Dockerfile index 1e0069ca51..ae90478620 100644 --- a/cspell/Dockerfile +++ b/cspell/Dockerfile @@ -18,6 +18,6 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ WORKDIR /nest -ENTRYPOINT ["/opt/node/node_modules/.bin/cspell"] - USER node + +ENTRYPOINT ["/opt/node/node_modules/.bin/cspell"] diff --git a/cspell/custom-dict.txt b/cspell/custom-dict.txt index fd69ff3fa0..5a38a7975a 100644 --- a/cspell/custom-dict.txt +++ b/cspell/custom-dict.txt @@ -19,6 +19,7 @@ Héllo Kateryna NETTACKER NOASSERTION +NOSEMGREP NOSONAR Nadu Nominatim @@ -55,6 +56,7 @@ askowasp attisdropped bangbang bsky +carryforward certbot collectstatic coraza @@ -142,6 +144,7 @@ pygoat pymdownx pypoetry pyyaml +quasis rediss relativedelta repositorycontributor @@ -152,6 +155,7 @@ saft sakanashi samm schemathesis +semgrep seo skillstruck slackbot diff --git a/cspell/package.json b/cspell/package.json index d31b166e05..02937db6c3 100644 --- a/cspell/package.json +++ b/cspell/package.json @@ -3,7 +3,7 @@ "@cspell/dict-aws": "^4.0.17", "@cspell/dict-data-science": "^2.0.13", "@cspell/dict-en_us": "^4.4.27", - "@cspell/dict-fullstack": "^3.2.7", + "@cspell/dict-fullstack": "^3.2.8", "@cspell/dict-golang": "^6.0.26", "@cspell/dict-k8s": "^1.0.12", "@cspell/dict-people-names": "^1.1.16", diff --git a/cspell/pnpm-lock.yaml b/cspell/pnpm-lock.yaml index 663a888554..ac0ed4e897 100644 --- a/cspell/pnpm-lock.yaml +++ b/cspell/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^4.4.27 version: 4.4.27 '@cspell/dict-fullstack': - specifier: ^3.2.7 - version: 3.2.7 + specifier: ^3.2.8 + version: 3.2.8 '@cspell/dict-golang': specifier: ^6.0.26 version: 6.0.26 @@ -134,14 +134,14 @@ packages: '@cspell/dict-fsharp@1.1.1': resolution: {integrity: sha512-imhs0u87wEA4/cYjgzS0tAyaJpwG7vwtC8UyMFbwpmtw+/bgss+osNfyqhYRyS/ehVCWL17Ewx2UPkexjKyaBA==} - '@cspell/dict-fullstack@3.2.7': - resolution: {integrity: sha512-IxEk2YAwAJKYCUEgEeOg3QvTL4XLlyArJElFuMQevU1dPgHgzWElFevN5lsTFnvMFA1riYsVinqJJX0BanCFEg==} + '@cspell/dict-fullstack@3.2.8': + resolution: {integrity: sha512-J6EeoeThvx/DFrcA2rJiCA6vfqwJMbkG0IcXhlsmRZmasIpanmxgt90OEaUazbZahFiuJT8wrhgQ1QgD1MsqBw==} '@cspell/dict-gaming-terms@1.1.2': resolution: {integrity: sha512-9XnOvaoTBscq0xuD6KTEIkk9hhdfBkkvJAIsvw3JMcnp1214OCGW8+kako5RqQ2vTZR3Tnf3pc57o7VgkM0q1Q==} - '@cspell/dict-git@3.0.7': - resolution: {integrity: sha512-odOwVKgfxCQfiSb+nblQZc4ErXmnWEnv8XwkaI4sNJ7cNmojnvogYVeMqkXPjvfrgEcizEEA4URRD2Ms5PDk1w==} + '@cspell/dict-git@3.1.0': + resolution: {integrity: sha512-KEt9zGkxqGy2q1nwH4CbyqTSv5nadpn8BAlDnzlRcnL0Xb3LX9xTgSGShKvzb0bw35lHoYyLWN2ZKAqbC4pgGQ==} '@cspell/dict-golang@6.0.26': resolution: {integrity: sha512-YKA7Xm5KeOd14v5SQ4ll6afe9VSy3a2DWM7L9uBq4u3lXToRBQ1W5PRa+/Q9udd+DTURyVVnQ+7b9cnOlNxaRg==} @@ -193,11 +193,11 @@ packages: '@cspell/dict-monkeyc@1.0.12': resolution: {integrity: sha512-MN7Vs11TdP5mbdNFQP5x2Ac8zOBm97ARg6zM5Sb53YQt/eMvXOMvrep7+/+8NJXs0jkp70bBzjqU4APcqBFNAw==} - '@cspell/dict-node@5.0.8': - resolution: {integrity: sha512-AirZcN2i84ynev3p2/1NCPEhnNsHKMz9zciTngGoqpdItUb2bDt1nJBjwlsrFI78GZRph/VaqTVFwYikmncpXg==} + '@cspell/dict-node@5.0.9': + resolution: {integrity: sha512-hO+ga+uYZ/WA4OtiMEyKt5rDUlUyu3nXMf8KVEeqq2msYvAPdldKBGH7lGONg6R/rPhv53Rb+0Y1SLdoK1+7wQ==} - '@cspell/dict-npm@5.2.29': - resolution: {integrity: sha512-ZAef8JpYmbuHFT1zekj/YyImLPvZevjECw663EmG5GPePyNo4AfH8Dd2fFhaOyQ3P5I5LrkAhGwypnOfUxcssw==} + '@cspell/dict-npm@5.2.30': + resolution: {integrity: sha512-GHgl3wAkoi87y3AXE1Bogwt0wHCB3uydOx6p8ujjXg9Ba9twlOK2SzD6LrClHoWrnADuGZZIxLnesDA6GtlH4w==} '@cspell/dict-people-names@1.1.16': resolution: {integrity: sha512-jiV+V32DVdaMqpznnqqNNMNaKFtyaHnZvak7HrVLWulGgobilQk+8NzFO9mtkyDs7Pde7CEGSExBAvc+xZxgeA==} @@ -472,9 +472,9 @@ snapshots: '@cspell/dict-flutter': 1.1.1 '@cspell/dict-fonts': 4.0.5 '@cspell/dict-fsharp': 1.1.1 - '@cspell/dict-fullstack': 3.2.7 + '@cspell/dict-fullstack': 3.2.8 '@cspell/dict-gaming-terms': 1.1.2 - '@cspell/dict-git': 3.0.7 + '@cspell/dict-git': 3.1.0 '@cspell/dict-golang': 6.0.26 '@cspell/dict-google': 1.0.9 '@cspell/dict-haskell': 4.0.6 @@ -490,8 +490,8 @@ snapshots: '@cspell/dict-makefile': 1.0.5 '@cspell/dict-markdown': 2.0.14(@cspell/dict-css@4.0.19)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.14)(@cspell/dict-typescript@3.2.3) '@cspell/dict-monkeyc': 1.0.12 - '@cspell/dict-node': 5.0.8 - '@cspell/dict-npm': 5.2.29 + '@cspell/dict-node': 5.0.9 + '@cspell/dict-npm': 5.2.30 '@cspell/dict-php': 4.1.1 '@cspell/dict-powershell': 5.0.15 '@cspell/dict-public-licenses': 2.0.15 @@ -569,11 +569,11 @@ snapshots: '@cspell/dict-fsharp@1.1.1': {} - '@cspell/dict-fullstack@3.2.7': {} + '@cspell/dict-fullstack@3.2.8': {} '@cspell/dict-gaming-terms@1.1.2': {} - '@cspell/dict-git@3.0.7': {} + '@cspell/dict-git@3.1.0': {} '@cspell/dict-golang@6.0.26': {} @@ -610,9 +610,9 @@ snapshots: '@cspell/dict-monkeyc@1.0.12': {} - '@cspell/dict-node@5.0.8': {} + '@cspell/dict-node@5.0.9': {} - '@cspell/dict-npm@5.2.29': {} + '@cspell/dict-npm@5.2.30': {} '@cspell/dict-people-names@1.1.16': {} diff --git a/docker-compose/local/compose.yaml b/docker-compose/local/compose.yaml index 5d7a36265e..b7c94f401b 100644 --- a/docker-compose/local/compose.yaml +++ b/docker-compose/local/compose.yaml @@ -4,6 +4,7 @@ services: command: > sh -c ' python manage.py migrate && + python manage.py clear_cache && python manage.py runserver 0.0.0.0:8000 ' image: nest-local-backend @@ -23,7 +24,7 @@ services: DJANGO_DB_PORT: ${DJANGO_DB_PORT:-5432} DJANGO_DB_USER: ${DJANGO_DB_USER:-nest_user_dev} DJANGO_REDIS_HOST: ${DJANGO_REDIS_HOST:-nest-cache} - DJANGO_REDIS_PASSWORD: ${DJANGO_REDIS_HOST:-nest-cache-password} + DJANGO_REDIS_PASSWORD: ${DJANGO_REDIS_PASSWORD:-nest-cache-password} networks: - nest-network ports: diff --git a/docker/cspell/Dockerfile b/docker/cspell/Dockerfile index 1e0069ca51..ae90478620 100644 --- a/docker/cspell/Dockerfile +++ b/docker/cspell/Dockerfile @@ -18,6 +18,6 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ WORKDIR /nest -ENTRYPOINT ["/opt/node/node_modules/.bin/cspell"] - USER node + +ENTRYPOINT ["/opt/node/node_modules/.bin/cspell"] diff --git a/docker/frontend/Dockerfile b/docker/frontend/Dockerfile index ebe729236f..85cb73d0db 100644 --- a/docker/frontend/Dockerfile +++ b/docker/frontend/Dockerfile @@ -48,17 +48,17 @@ WORKDIR /app ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production -# Fix CVE-2026-23745: Update npm's bundled tar from 7.5.1 to 7.5.3 in runner stage +# Fix CVE-2026-23745: Update npm's bundled tar to 7.5.3 in runner stage # Note: Must download tar with npm pack BEFORE removing the old tar (npm needs it) RUN cd /tmp && \ - npm pack tar@7.5.3 && \ - tar -xzf tar-7.5.3.tgz && \ + npm pack tar@7.5.4 && \ + tar -xzf tar-7.5.4.tgz && \ TAR_DIR="/usr/local/lib/node_modules/npm/node_modules/tar" && \ rm -rf "${TAR_DIR}" && \ cp -r package "${TAR_DIR}" && \ chmod -R 755 "${TAR_DIR}" && \ - rm -rf package tar-7.5.3.tgz && \ - grep -q 'version.*7.5.3' "${TAR_DIR}/package.json" + rm -rf package tar-7.5.4.tgz && \ + grep -q 'version.*7.5.4' "${TAR_DIR}/package.json" RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 -G nodejs nextjs diff --git a/docker/frontend/Dockerfile.e2e.test b/docker/frontend/Dockerfile.e2e.test index bd03a0130c..a66206cbc2 100644 --- a/docker/frontend/Dockerfile.e2e.test +++ b/docker/frontend/Dockerfile.e2e.test @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/playwright:v1.57.0-jammy +FROM mcr.microsoft.com/playwright:v1.58.0-jammy ENV FORCE_COLOR=1 \ NPM_CACHE="/app/.npm" \ diff --git a/docker/semgrep/Dockerfile b/docker/semgrep/Dockerfile new file mode 100644 index 0000000000..619c2058cf --- /dev/null +++ b/docker/semgrep/Dockerfile @@ -0,0 +1 @@ +FROM semgrep/semgrep:1.148.0 diff --git a/docs/poetry.lock b/docs/poetry.lock index 62ac200794..eda0da3ceb 100644 --- a/docs/poetry.lock +++ b/docs/poetry.lock @@ -283,18 +283,18 @@ files = [ [[package]] name = "markdown" -version = "3.10" +version = "3.10.1" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c"}, - {file = "markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e"}, + {file = "markdown-3.10.1-py3-none-any.whl", hash = "sha256:867d788939fe33e4b736426f5b9f651ad0c0ae0ecf89df0ca5d1176c70812fe3"}, + {file = "markdown-3.10.1.tar.gz", hash = "sha256:1c19c10bd5c14ac948c53d0d762a04e2fa35a6d58a6b7b1e6bfcbe6fefc0001a"}, ] [package.extras] -docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python] (>=0.28.3)"] testing = ["coverage", "pyyaml"] [[package]] @@ -535,14 +535,14 @@ mkdocs = ">=1.4.1" [[package]] name = "mkdocstrings" -version = "1.0.0" +version = "1.0.2" description = "Automatic documentation from sources, for MkDocs." optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "mkdocstrings-1.0.0-py3-none-any.whl", hash = "sha256:4c50eb960bff6e05dfc631f6bc00dfabffbcb29c5ff25f676d64daae05ed82fa"}, - {file = "mkdocstrings-1.0.0.tar.gz", hash = "sha256:351a006dbb27aefce241ade110d3cd040c1145b7a3eb5fd5ac23f03ed67f401a"}, + {file = "mkdocstrings-1.0.2-py3-none-any.whl", hash = "sha256:41897815a8026c3634fe5d51472c3a569f92ded0ad8c7a640550873eea3b6817"}, + {file = "mkdocstrings-1.0.2.tar.gz", hash = "sha256:48edd0ccbcb9e30a3121684e165261a9d6af4d63385fc4f39a54a49ac3b32ea8"}, ] [package.dependencies] @@ -560,14 +560,14 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "packaging" -version = "25.0" +version = "26.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, + {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, + {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, ] [[package]] @@ -638,14 +638,14 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pymdown-extensions" -version = "10.20" +version = "10.20.1" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pymdown_extensions-10.20-py3-none-any.whl", hash = "sha256:ea9e62add865da80a271d00bfa1c0fa085b20d133fb3fc97afdc88e682f60b2f"}, - {file = "pymdown_extensions-10.20.tar.gz", hash = "sha256:5c73566ab0cf38c6ba084cb7c5ea64a119ae0500cce754ccb682761dfea13a52"}, + {file = "pymdown_extensions-10.20.1-py3-none-any.whl", hash = "sha256:24af7feacbca56504b313b7b418c4f5e1317bb5fea60f03d57be7fcc40912aa0"}, + {file = "pymdown_extensions-10.20.1.tar.gz", hash = "sha256:e7e39c865727338d434b55f1dd8da51febcffcaebd6e1a0b9c836243f660740a"}, ] [package.dependencies] diff --git a/frontend/__tests__/a11y/components/ActionButton.a11y.test.tsx b/frontend/__tests__/a11y/components/ActionButton.a11y.test.tsx index 802871689f..f0a3700137 100644 --- a/frontend/__tests__/a11y/components/ActionButton.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/ActionButton.a11y.test.tsx @@ -1,9 +1,7 @@ import { render } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import ActionButton from 'components/ActionButton' -expect.extend(toHaveNoViolations) - describe('ActionButton Accessibility', () => { it('should not have any accessibility violations when no url is provided', async () => { const { container } = render(Sample Text) diff --git a/frontend/__tests__/a11y/components/AnchorTitle.a11y.test.tsx b/frontend/__tests__/a11y/components/AnchorTitle.a11y.test.tsx index fa1a1a201e..f30cbb5209 100644 --- a/frontend/__tests__/a11y/components/AnchorTitle.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/AnchorTitle.a11y.test.tsx @@ -1,9 +1,7 @@ import { render } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import AnchorTitle from 'components/AnchorTitle' -expect.extend(toHaveNoViolations) - describe('AnchorTitle Accessibility', () => { it('should not have any accessibility violations', async () => { const { container } = render() diff --git a/frontend/__tests__/a11y/components/AutoScrollToTop.a11y.test.tsx b/frontend/__tests__/a11y/components/AutoScrollToTop.a11y.test.tsx index cb7418b321..d6034e5024 100644 --- a/frontend/__tests__/a11y/components/AutoScrollToTop.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/AutoScrollToTop.a11y.test.tsx @@ -1,9 +1,7 @@ import { render } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import AutoScrollToTop from 'components/AutoScrollToTop' -expect.extend(toHaveNoViolations) - jest.mock('next/navigation', () => ({ usePathname: () => '/test-path', })) diff --git a/frontend/__tests__/a11y/components/Badges.a11y.test.tsx b/frontend/__tests__/a11y/components/Badges.a11y.test.tsx index fb344b1770..3260ca3f21 100644 --- a/frontend/__tests__/a11y/components/Badges.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/Badges.a11y.test.tsx @@ -1,5 +1,5 @@ import { render } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import Badges from 'components/Badges' const defaultProps = { @@ -7,8 +7,6 @@ const defaultProps = { cssClass: 'medal', } -expect.extend(toHaveNoViolations) - describe('Badges Accessibility', () => { it('should not have any accessibility violations when tooltip is enabled', async () => { const { baseElement } = render() diff --git a/frontend/__tests__/a11y/components/BarChart.a11y.test.tsx b/frontend/__tests__/a11y/components/BarChart.a11y.test.tsx index 5829397c99..1c96af1403 100644 --- a/frontend/__tests__/a11y/components/BarChart.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/BarChart.a11y.test.tsx @@ -1,5 +1,5 @@ import { render } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import { ReactNode } from 'react' import BarChart from 'components/BarChart' @@ -70,8 +70,6 @@ jest.mock('components/SecondaryCard', () => { } }) -expect.extend(toHaveNoViolations) - describe('BarChart Accessibility', () => { it('should not have any accessibility violations', async () => { const { container } = render() diff --git a/frontend/__tests__/a11y/components/BreadCrumbs.a11y.test.tsx b/frontend/__tests__/a11y/components/BreadCrumbs.a11y.test.tsx index 775a35a797..d4652e2bb7 100644 --- a/frontend/__tests__/a11y/components/BreadCrumbs.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/BreadCrumbs.a11y.test.tsx @@ -1,8 +1,6 @@ import { Breadcrumbs } from '@heroui/react' import { render } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' - -expect.extend(toHaveNoViolations) +import { axe } from 'jest-axe' describe('Breadcrumbs a11y', () => { it('should not have any accessibility violations', async () => { diff --git a/frontend/__tests__/a11y/components/BreadCrumbsWrapper.a11y.test.tsx b/frontend/__tests__/a11y/components/BreadCrumbsWrapper.a11y.test.tsx index 03588a6b4d..8f2ce42686 100644 --- a/frontend/__tests__/a11y/components/BreadCrumbsWrapper.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/BreadCrumbsWrapper.a11y.test.tsx @@ -1,11 +1,9 @@ import { render } from '@testing-library/react' import { useBreadcrumbs } from 'hooks/useBreadcrumbs' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import { usePathname } from 'next/navigation' import BreadCrumbsWrapper from 'components/BreadCrumbsWrapper' -expect.extend(toHaveNoViolations) - jest.mock('next/navigation', () => ({ usePathname: jest.fn(), })) diff --git a/frontend/__tests__/a11y/components/CalendarButton.a11y.test.tsx b/frontend/__tests__/a11y/components/CalendarButton.a11y.test.tsx index 54a7fa84a1..e5b9e45d89 100644 --- a/frontend/__tests__/a11y/components/CalendarButton.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/CalendarButton.a11y.test.tsx @@ -1,10 +1,8 @@ import { render } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import { FaCalendarAlt } from 'react-icons/fa' import CalendarButton from 'components/CalendarButton' -expect.extend(toHaveNoViolations) - const mockEvent = { title: 'Test Event', description: 'Test description', diff --git a/frontend/__tests__/a11y/components/Card.a11y.test.tsx b/frontend/__tests__/a11y/components/Card.a11y.test.tsx index 70bfb52418..080ca71750 100644 --- a/frontend/__tests__/a11y/components/Card.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/Card.a11y.test.tsx @@ -1,5 +1,5 @@ import { render } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import { ReactNode } from 'react' import { FaCrown } from 'react-icons/fa6' import Card from 'components/Card' @@ -28,6 +28,7 @@ jest.mock('next/link', () => { }) const baseProps = { + cardKey: 'test-card-1', title: 'Test Project', url: 'https://github.com/test/project', summary: 'This is a test project summary', @@ -39,8 +40,6 @@ const baseProps = { }, } -expect.extend(toHaveNoViolations) - describe('Card Accessibility', () => { it('should not have any accessibility violations with minimal props', async () => { const { container } = render() @@ -52,7 +51,11 @@ describe('Card Accessibility', () => { it('should not have any accessibility violations when level is provided', async () => { const { container } = render( - + ) const results = await axe(container) @@ -61,7 +64,9 @@ describe('Card Accessibility', () => { }) it('should not have any accessibility violations when project name is provided', async () => { - const { container } = render() + const { container } = render( + + ) const results = await axe(container) @@ -72,6 +77,7 @@ describe('Card Accessibility', () => { const { container } = render( ) diff --git a/frontend/__tests__/a11y/components/CardDetailsPage.a11y.test.tsx b/frontend/__tests__/a11y/components/CardDetailsPage.a11y.test.tsx index b1e63f91ae..13e1711bc9 100644 --- a/frontend/__tests__/a11y/components/CardDetailsPage.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/CardDetailsPage.a11y.test.tsx @@ -1,6 +1,6 @@ import { mockChapterData } from '@mockData/mockChapterData' import { render } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import React from 'react' import { FaCode, FaTags } from 'react-icons/fa6' import { ExperienceLevelEnum } from 'types/__generated__/graphql' @@ -142,8 +142,6 @@ const defaultProps: DetailsCardProps = { socialLinks: [], } -expect.extend(toHaveNoViolations) - describe('CardDetailsPage a11y', () => { it('should have no accessibility violations', async () => { const { container } = render() @@ -182,6 +180,7 @@ describe('CardDetailsPage a11y', () => { endedAt: '2025-03-01', mentors: [ { + id: 'mentor-mentor1', login: 'mentor1', avatarUrl: 'https://avatars.githubusercontent.com/u/12345', name: 'Mentor One', diff --git a/frontend/__tests__/a11y/components/ChapterMap.a11y.test.tsx b/frontend/__tests__/a11y/components/ChapterMap.a11y.test.tsx index 27ef0166a7..ed07ec933c 100644 --- a/frontend/__tests__/a11y/components/ChapterMap.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/ChapterMap.a11y.test.tsx @@ -1,12 +1,10 @@ import { mockChapterData } from '@mockData/mockChapterData' import { screen, fireEvent, render, waitFor } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import * as L from 'leaflet' import React, { useEffect } from 'react' import ChapterMap from 'components/ChapterMap' -expect.extend(toHaveNoViolations) - const mockMap = { setView: jest.fn().mockReturnThis(), fitBounds: jest.fn().mockReturnThis(), diff --git a/frontend/__tests__/a11y/components/ContributionHeatmap.a11y.test.tsx b/frontend/__tests__/a11y/components/ContributionHeatmap.a11y.test.tsx index f1e7cc1bf7..32860b942c 100644 --- a/frontend/__tests__/a11y/components/ContributionHeatmap.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/ContributionHeatmap.a11y.test.tsx @@ -1,10 +1,8 @@ import { render, screen } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import { ReactNode } from 'react' import ContributionHeatmap from 'components/ContributionHeatmap' -expect.extend(toHaveNoViolations) - jest.mock('react-apexcharts', () => { return function MockChart(props: { options: unknown diff --git a/frontend/__tests__/a11y/components/ContributorAvatar.a11y.test.tsx b/frontend/__tests__/a11y/components/ContributorAvatar.a11y.test.tsx index b6fa9d9e8f..9d86907fb5 100644 --- a/frontend/__tests__/a11y/components/ContributorAvatar.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/ContributorAvatar.a11y.test.tsx @@ -1,11 +1,9 @@ import { render } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import { ReactNode } from 'react' import { Contributor } from 'types/contributor' import ContributorAvatar from 'components/ContributorAvatar' -expect.extend(toHaveNoViolations) - jest.mock('@heroui/tooltip', () => ({ Tooltip: ({ children, content, id }: { children: ReactNode; content: string; id: string }) => (
@@ -36,6 +34,7 @@ jest.mock('next/link', () => { }) const mockGitHubContributor: Contributor = { + id: 'contributor-jane-doe', login: 'jane-doe', name: 'Jane Doe', avatarUrl: 'https://avatars.githubusercontent.com/u/12345', diff --git a/frontend/__tests__/a11y/components/TopContributorsList.a11y.test.tsx b/frontend/__tests__/a11y/components/ContributorsList.a11y.test.tsx similarity index 73% rename from frontend/__tests__/a11y/components/TopContributorsList.a11y.test.tsx rename to frontend/__tests__/a11y/components/ContributorsList.a11y.test.tsx index a763ea4e34..0a6e5ef783 100644 --- a/frontend/__tests__/a11y/components/TopContributorsList.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/ContributorsList.a11y.test.tsx @@ -1,10 +1,9 @@ import { render } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import { ReactNode } from 'react' import { Contributor } from 'types/contributor' -import TopContributorsList from 'components/TopContributorsList' - -expect.extend(toHaveNoViolations) +import { getMemberUrl } from 'utils/urlFormatter' +import ContributorsList from 'components/ContributorsList' jest.mock('next/link', () => { return function MockLink({ @@ -28,6 +27,7 @@ jest.mock('next/link', () => { const mockContributors: Contributor[] = [ { + id: 'contributor-developer1-a11y', avatarUrl: 'https://github.com/developer1.avatar', login: 'developer1', name: 'Alex Developer', @@ -35,6 +35,7 @@ const mockContributors: Contributor[] = [ contributionsCount: 50, }, { + id: 'contributor-contributor2-a11y', avatarUrl: 'https://github.com/contributor2.avatar', login: 'contributor2', name: 'Jane Developer', @@ -42,6 +43,7 @@ const mockContributors: Contributor[] = [ contributionsCount: 30, }, { + id: 'contributor-user3-a11y', avatarUrl: 'https://github.com/user3.avatar', login: 'user3', name: '', @@ -50,9 +52,11 @@ const mockContributors: Contributor[] = [ }, ] -describe('TopContributorsList Accessibility', () => { +describe('ContributorsList Accessibility', () => { it('should not have any accessibility violations', async () => { - const { container } = render() + const { container } = render( + + ) const results = await axe(container) diff --git a/frontend/__tests__/a11y/components/DashboardCard.a11y.test.tsx b/frontend/__tests__/a11y/components/DashboardCard.a11y.test.tsx index 3f97aa505c..44ff2aa5c9 100644 --- a/frontend/__tests__/a11y/components/DashboardCard.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/DashboardCard.a11y.test.tsx @@ -1,10 +1,8 @@ import { render } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import { FaUser } from 'react-icons/fa' import DashboardCard from 'components/DashboardCard' -expect.extend(toHaveNoViolations) - const baseProps = { title: 'Test Card', icon: FaUser, diff --git a/frontend/__tests__/a11y/components/DisplayIcon.a11y.test.tsx b/frontend/__tests__/a11y/components/DisplayIcon.a11y.test.tsx index 0376309147..6254b5f50c 100644 --- a/frontend/__tests__/a11y/components/DisplayIcon.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/DisplayIcon.a11y.test.tsx @@ -1,10 +1,8 @@ import { render } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import { Icon } from 'types/icon' import DisplayIcon from 'components/DisplayIcon' -expect.extend(toHaveNoViolations) - const mockIcons: Icon = { starsCount: 1250, forksCount: 350, diff --git a/frontend/__tests__/a11y/components/DonutBarChart.a11y.test.tsx b/frontend/__tests__/a11y/components/DonutBarChart.a11y.test.tsx index 913858fb4c..56ea430446 100644 --- a/frontend/__tests__/a11y/components/DonutBarChart.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/DonutBarChart.a11y.test.tsx @@ -1,10 +1,8 @@ import { render } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import { FaChartPie } from 'react-icons/fa' import DonutBarChart from 'components/DonutBarChart' -expect.extend(toHaveNoViolations) - jest.mock('next/dynamic', () => { return jest.fn(() => { // Mock Chart component that mimics react-apexcharts diff --git a/frontend/__tests__/a11y/components/EntityActions.a11y.test.tsx b/frontend/__tests__/a11y/components/EntityActions.a11y.test.tsx index 78e422ac67..c684dbada2 100644 --- a/frontend/__tests__/a11y/components/EntityActions.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/EntityActions.a11y.test.tsx @@ -1,9 +1,7 @@ import { fireEvent, render, screen } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import EntityActions from 'components/EntityActions' -expect.extend(toHaveNoViolations) - describe('EntityActions a11y', () => { it('should not have any accessibility violations', async () => { const setStatus = jest.fn() diff --git a/frontend/__tests__/a11y/components/Footer.a11y.test.tsx b/frontend/__tests__/a11y/components/Footer.a11y.test.tsx index 23d79de291..ef8a4762be 100644 --- a/frontend/__tests__/a11y/components/Footer.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/Footer.a11y.test.tsx @@ -1,9 +1,7 @@ import { fireEvent, render, screen } from '@testing-library/react' -import { axe, toHaveNoViolations } from 'jest-axe' +import { axe } from 'jest-axe' import Footer from 'components/Footer' -expect.extend(toHaveNoViolations) - describe('Footer a11y', () => { it('should not have any accessibility violations', async () => { const { container } = render(