diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7d8742747..18f8eb480 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,6 +13,7 @@ updates: - "/lambdas/delta_backend" - "/lambdas/filenameprocessor" - "/lambdas/mesh_processor" + - "/lambdas/recordforwarder" - "/lambdas/recordprocessor" - "/sandbox" schedule: @@ -49,7 +50,7 @@ updates: - package-ecosystem: "pip" directories: - "/" - - "/backend" + - "/lambdas/backend" - "/lambdas/ack_backend" - "/lambdas/batch_processor_filter" - "/lambdas/delta_backend" @@ -57,6 +58,7 @@ updates: - "/lambdas/id_sync" - "/lambdas/mesh_processor" - "/lambdas/mns_subscription" + - "/lambdas/recordforwarder" - "/lambdas/recordprocessor" - "/lambdas/redis_sync" - "/lambdas/shared" diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index c8a96d8f1..0a062faec 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -97,39 +97,16 @@ jobs: aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY - - name: Run unittest with recordprocessor-coverage - working-directory: lambdas/recordprocessor - id: recordprocessor - env: - PYTHONPATH: ${{ env.LAMBDA_PATH }}/recordprocessor/src:${{ env.LAMBDA_PATH }}/recordprocessor/tests:${{ env.SHARED_PATH }}/src - continue-on-error: true - run: | - poetry install - poetry run coverage run --source=src -m unittest discover || echo "recordprocessor tests failed" >> ../../failed_tests.txt - poetry run coverage xml -o ../../recordprocessor-coverage.xml - - # This step is redundant - all of these tests will be run in the backend step below - - name: Run unittest with recordforwarder-coverage - working-directory: backend - id: recordforwarder - env: - PYTHONPATH: ${{ github.workspace }}/backend/src:${{ github.workspace }}/backend/tests - continue-on-error: true - run: | - poetry install - poetry run coverage run --source=src -m unittest discover -p "*batch*.py" || echo "recordforwarder tests failed" >> ../failed_tests.txt - poetry run coverage xml -o ../recordforwarder-coverage.xml - - name: Run unittest with coverage-fhir-api - working-directory: backend + working-directory: lambdas/backend env: - PYTHONPATH: ${{ github.workspace }}/backend/src:${{ github.workspace }}/backend/tests + PYTHONPATH: ${{ env.LAMBDA_PATH }}/backend/src:${{ env.LAMBDA_PATH }}/backend/tests:${{ env.SHARED_PATH }}/src:${{ env.SHARED_PATH }}/tests id: fhirapi continue-on-error: true run: | poetry install - poetry run coverage run --source=src -m unittest discover || echo "fhir-api tests failed" >> ../failed_tests.txt - poetry run coverage xml -o ../backend-coverage.xml + poetry run coverage run --source=src -m unittest discover || echo "fhir-api tests failed" >> ../../failed_tests.txt + poetry run coverage xml -o ../../backend-coverage.xml - name: Run unittest with coverage-ack-lambda working-directory: lambdas/ack_backend @@ -189,6 +166,8 @@ jobs: - name: Run unittest with coverage-mesh-processor working-directory: lambdas/mesh_processor id: meshprocessor + env: + PYTHONPATH: ${{ env.LAMBDA_PATH }}/mesh_processor/src:${{ env.LAMBDA_PATH }}/mesh_processor/tests:${{ env.SHARED_PATH }}/src continue-on-error: true run: | poetry install @@ -207,6 +186,28 @@ jobs: poetry run coverage report -m poetry run coverage xml -o ../../mns_subscription-coverage.xml + - name: Run unittest with recordforwarder-coverage + working-directory: lambdas/recordforwarder + id: recordforwarder + env: + PYTHONPATH: ${{ env.LAMBDA_PATH }}/recordforwarder/src:${{ env.LAMBDA_PATH }}/recordforwarder/tests:${{ env.SHARED_PATH }}/src:${{ env.SHARED_PATH }}/tests + continue-on-error: true + run: | + poetry install + poetry run coverage run --source=src -m unittest discover || echo "recordforwarder tests failed" >> ../../failed_tests.txt + poetry run coverage xml -o ../../recordforwarder-coverage.xml + + - name: Run unittest with recordprocessor-coverage + working-directory: lambdas/recordprocessor + id: recordprocessor + env: + PYTHONPATH: ${{ env.LAMBDA_PATH }}/recordprocessor/src:${{ env.LAMBDA_PATH }}/recordprocessor/tests:${{ env.SHARED_PATH }}/src + continue-on-error: true + run: | + poetry install + poetry run coverage run --source=src -m unittest discover || echo "recordprocessor tests failed" >> ../../failed_tests.txt + poetry run coverage xml -o ../../recordprocessor-coverage.xml + - name: Run unittest with redis_sync working-directory: lambdas/redis_sync id: redis_sync diff --git a/Makefile b/Makefile index 5cbc745a8..19311c263 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SHELL=/usr/bin/env bash -euo pipefail -PYTHON_PROJECT_DIRS_WITH_UNIT_TESTS = backend lambdas/ack_backend lambdas/batch_processor_filter lambdas/delta_backend lambdas/filenameprocessor lambdas/id_sync lambdas/mesh_processor lambdas/mns_subscription lambdas/recordprocessor lambdas/redis_sync lambdas/shared +PYTHON_PROJECT_DIRS_WITH_UNIT_TESTS = lambdas/backend lambdas/ack_backend lambdas/batch_processor_filter lambdas/delta_backend lambdas/filenameprocessor lambdas/id_sync lambdas/mesh_processor lambdas/mns_subscription lambdas/recordforwarder lambdas/recordprocessor lambdas/redis_sync lambdas/shared PYTHON_PROJECT_DIRS = tests/e2e tests/e2e_batch quality_checks $(PYTHON_PROJECT_DIRS_WITH_UNIT_TESTS) .PHONY: install lint format format-check clean publish build-proxy release initialise-all-python-venvs update-all-python-dependencies run-all-python-unit-tests build-all-docker-images diff --git a/README.md b/README.md index 57bb4f65b..70cfd6fa5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # immunisation-fhir-api +NB this line changed just to enable a PR for the staging branch. Please delete. + See `README.specification.md` for details of the API specification's development. ## Spelling diff --git a/backend/.vscode/launch.json.default b/backend/.vscode/launch.json.default deleted file mode 100644 index 69c1fc9c1..000000000 --- a/backend/.vscode/launch.json.default +++ /dev/null @@ -1,33 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "File", - "type": "debugpy", - "request": "launch", - "module": "unittest", - "args": [ - "${fileBasenameNoExtension}" // Run tests in the current file - ], - "console": "integratedTerminal", - "env": { - "PYTHONPATH": "${workspaceFolder}:${workspaceFolder}/src:${workspaceFolder}/tests" // Add root, src, and tests - } - }, - { - "name": "Here", - "type": "debugpy", - "request": "launch", - "module": "unittest", - "args": [ - "-k", - "${selectedText}" // Run the test method or class under the cursor - ], - "console": "integratedTerminal", - "justMyCode": true, - "env": { - "PYTHONPATH": "${workspaceFolder}:${workspaceFolder}/src:${workspaceFolder}/tests" // Add root, src, and tests - } - } - ] -} \ No newline at end of file diff --git a/backend/Makefile b/backend/Makefile deleted file mode 100644 index d37329d62..000000000 --- a/backend/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -build: - docker build -t imms-lambda-build -f lambda.Dockerfile . - -package: build - mkdir -p build - docker run --rm -v $(shell pwd)/build:/build imms-lambda-build - -test: - @PYTHONPATH=src:tests python -m unittest - -coverage-run: - @PYTHONPATH=src:tests coverage run --source=src -m unittest discover - -coverage-report: - coverage report -m - -.PHONY: build package test diff --git a/backend/lambda.Dockerfile b/backend/lambda.Dockerfile deleted file mode 100644 index 617a329b7..000000000 --- a/backend/lambda.Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM public.ecr.aws/lambda/python:3.11 as base - -# Create a non-root user with a specific UID and GID -RUN mkdir -p /home/appuser && \ - echo 'appuser:x:1001:1001::/home/appuser:/sbin/nologin' >> /etc/passwd && \ - echo 'appuser:x:1001:' >> /etc/group && \ - chown -R 1001:1001 /home/appuser && pip install "poetry~=2.1.4" - -# ----------------------------- -COPY poetry.lock pyproject.toml README.md ./ -RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi --no-root --only main - -# ----------------------------- -FROM base AS test -RUN poetry install --no-interaction --no-ansi --no-root -COPY src src -COPY tests tests -ENV DYNAMODB_TABLE_NAME=example_table -RUN python -m unittest -# ----------------------------- -FROM base AS build -COPY src . -RUN chmod 644 $(find . -type f) && chmod 755 $(find . -type d) -# Switch to the non-root user for running the container -USER 1001:1001 diff --git a/backend/src/cache.py b/backend/src/cache.py deleted file mode 100644 index f2d24e5e4..000000000 --- a/backend/src/cache.py +++ /dev/null @@ -1,34 +0,0 @@ -import json -from typing import Optional - - -class Cache: - """Key-value file cache""" - - def __init__(self, directory): - filename = f"{directory}/cache.json" - with open(filename, "a+") as self.cache_file: - self.cache_file.seek(0) - content = self.cache_file.read() - if len(content) == 0: - self.cache = {} - else: - self.cache = json.loads(content) - - def put(self, key: str, value: dict): - self.cache[key] = value - self._overwrite() - - def get(self, key: str) -> Optional[dict]: - return self.cache.get(key, None) - - def delete(self, key: str): - if key not in self.cache: - return - del self.cache[key] - - def _overwrite(self): - with open(self.cache_file.name, "w") as self.cache_file: - self.cache_file.seek(0) - self.cache_file.write(json.dumps(self.cache)) - self.cache_file.truncate() diff --git a/backend/src/clients.py b/backend/src/clients.py deleted file mode 100644 index 83b4a9569..000000000 --- a/backend/src/clients.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Initialise s3, kinesis, lambda and redis clients""" - -import logging -import os - -import redis -from boto3 import client as boto3_client - -REGION_NAME = os.getenv("AWS_REGION", "eu-west-2") - -s3_client = boto3_client("s3", region_name=REGION_NAME) -kinesis_client = boto3_client("kinesis", region_name=REGION_NAME) -lambda_client = boto3_client("lambda", region_name=REGION_NAME) -firehose_client = boto3_client("firehose", region_name=REGION_NAME) -sqs_client = boto3_client("sqs", region_name=REGION_NAME) - -REDIS_HOST = os.getenv("REDIS_HOST", "") -REDIS_PORT = int(os.getenv("REDIS_PORT", 6379)) - - -logging.basicConfig(level="INFO") -logger = logging.getLogger() -logger.info(f"Connecting to Redis at {REDIS_HOST}:{REDIS_PORT}") - -redis_client = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True) diff --git a/backend/src/constants.py b/backend/src/constants.py deleted file mode 100644 index 8b92e8730..000000000 --- a/backend/src/constants.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Constants""" - -# Constants for use within the test -VALID_NHS_NUMBER = "1345678940" # Valid for pre, FHIR and post validators -NHS_NUMBER_USED_IN_SAMPLE_DATA = "9000000009" -ADDRESS_UNKNOWN_POSTCODE = "ZZ99 3WZ" - - -class Urls: - """Urls which are expected to be used within the FHIR Immunization Resource json data""" - - nhs_number = "https://fhir.nhs.uk/Id/nhs-number" - vaccination_procedure = "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure" - snomed = "http://snomed.info/sct" # NOSONAR(S5332) - nhs_number_verification_status_structure_definition = ( - "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-NHSNumberVerificationStatus" - ) - nhs_number_verification_status_code_system = ( - "https://fhir.hl7.org.uk/CodeSystem/UKCore-NHSNumberVerificationStatusEngland" - ) - ods_organization_code = "https://fhir.nhs.uk/Id/ods-organization-code" - urn_school_number = "https://fhir.hl7.org.uk/Id/urn-school-number" - - -GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE = "Unable to process request. Issue may be transient." -SUPPLIER_PERMISSIONS_HASH_KEY = "supplier_permissions" -# Maximum response size for an AWS Lambda function -MAX_RESPONSE_SIZE_BYTES = 6 * 1024 * 1024 diff --git a/backend/src/log_firehose.py b/backend/src/log_firehose.py deleted file mode 100644 index eebcb0cf5..000000000 --- a/backend/src/log_firehose.py +++ /dev/null @@ -1,35 +0,0 @@ -import json -import logging -import os - -import boto3 -from botocore.config import Config - -STREAM_NAME = os.getenv("SPLUNK_FIREHOSE_NAME") -BOTO_CLIENT = boto3.client("firehose", config=Config(region_name="eu-west-2")) - -logging.basicConfig() -logger = logging.getLogger() -logger.setLevel("INFO") - - -class FirehoseLogger: - def __init__( - self, - stream_name: str = STREAM_NAME, - boto_client=BOTO_CLIENT, - ): - self.firehose_client = boto_client - self.delivery_stream_name = stream_name - - def send_log(self, log_message): - log_to_splunk = log_message - encoded_log_data = json.dumps(log_to_splunk).encode("utf-8") - try: - response = self.firehose_client.put_record( - DeliveryStreamName=self.delivery_stream_name, - Record={"Data": encoded_log_data}, - ) - logger.info(f"Log sent to Firehose: {response}") - except Exception as e: - logger.exception(f"Error sending log to Firehose: {e}") diff --git a/backend/src/models/errors.py b/backend/src/models/errors.py deleted file mode 100644 index 59b655815..000000000 --- a/backend/src/models/errors.py +++ /dev/null @@ -1,360 +0,0 @@ -import uuid -from dataclasses import dataclass -from enum import Enum -from typing import Any - - -class Severity(str, Enum): - error = "error" - warning = "warning" - - -class Code(str, Enum): - forbidden = "forbidden" - not_found = "not-found" - invalid = "invalid" - server_error = "exception" - invariant = "invariant" - not_supported = "not-supported" - duplicate = "duplicate" - # Added an unauthorized code its used when returning a response for an unauthorized vaccine type search. - unauthorized = "unauthorized" - - -@dataclass -class UnauthorizedError(RuntimeError): - @staticmethod - def to_operation_outcome() -> dict: - msg = "Unauthorized request" - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.forbidden, - diagnostics=msg, - ) - - -@dataclass -class UnauthorizedVaxError(RuntimeError): - @staticmethod - def to_operation_outcome() -> dict: - msg = "Unauthorized request for vaccine type" - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.forbidden, - diagnostics=msg, - ) - - -@dataclass -class UnauthorizedVaxOnRecordError(RuntimeError): - @staticmethod - def to_operation_outcome() -> dict: - msg = "Unauthorized request for vaccine type present in the stored immunization resource" - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.forbidden, - diagnostics=msg, - ) - - -@dataclass -class ResourceNotFoundError(RuntimeError): - """Return this error when the requested FHIR resource does not exist""" - - resource_type: str - resource_id: str - - def __str__(self): - return f"{self.resource_type} resource does not exist. ID: {self.resource_id}" - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.not_found, - diagnostics=self.__str__(), - ) - - -@dataclass -class ResourceFoundError(RuntimeError): - """Return this error when the requested FHIR resource does exist""" - - resource_type: str - resource_id: str - - def __str__(self): - return f"{self.resource_type} resource does exist. ID: {self.resource_id}" - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.not_found, - diagnostics=self.__str__(), - ) - - -@dataclass -class ResourceVersionNotProvided(RuntimeError): - """Return this error when client has failed to provide the FHIR resource version where required""" - - resource_type: str - - def __str__(self): - return f"Validation errors: {self.resource_type} resource version not specified in the request headers" - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.invariant, - diagnostics=self.__str__(), - ) - - -@dataclass -class UnhandledResponseError(RuntimeError): - """Use this error when the response from an external service (ex: dynamodb) can't be handled""" - - response: dict | str - message: str - - def __str__(self): - return f"{self.message}\n{self.response}" - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.server_error, - diagnostics=self.__str__(), - ) - - -class MandatoryError(Exception): - def __init__(self, message=None): - self.message = message - - -class ValidationError(RuntimeError): - def to_operation_outcome(self) -> dict: - pass - - -@dataclass -class InvalidImmunizationId(ValidationError): - """Use this when the unique Immunization ID is invalid""" - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.invalid, - diagnostics="Validation errors: the provided event ID is either missing or not in the expected format.", - ) - - -@dataclass -class InvalidPatientId(ValidationError): - """Use this when NHS Number is invalid or doesn't exist""" - - patient_identifier: str - - def __str__(self): - return f"NHS Number: {self.patient_identifier} is invalid or it doesn't exist." - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.server_error, - diagnostics=self.__str__(), - ) - - -@dataclass -class InvalidResourceVersion(ValidationError): - """Use this when the resource version is invalid""" - - resource_version: Any - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.invariant, - diagnostics=f"Validation errors: Immunization resource version:{self.resource_version} in the request " - f"headers is invalid.", - ) - - -@dataclass -class InconsistentIdentifierError(ValidationError): - """Use this when the local identifier in the payload does not match the existing identifier for the update.""" - - msg: str - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), severity=Severity.error, code=Code.invariant, diagnostics=self.msg - ) - - -@dataclass -class InconsistentIdError(ValidationError): - """Use this when the specified id in the message is inconsistent with the path - see: http://hl7.org/fhir/R4/http.html#update""" - - imms_id: str - - def __str__(self): - return ( - f"Validation errors: The provided immunization id:{self.imms_id} doesn't match with the content of the " - f"request body" - ) - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.invariant, - diagnostics=self.__str__(), - ) - - -@dataclass -class InconsistentResourceVersion(ValidationError): - """Use this when the resource version in the request and actual resource version do not match""" - - message: str - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.invariant, - diagnostics=self.message, - ) - - -@dataclass -class CustomValidationError(ValidationError): - """Custom validation error""" - - message: str - - def __str__(self): - return self.message - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.invariant, - diagnostics=self.__str__(), - ) - - -@dataclass -class IdentifierDuplicationError(RuntimeError): - """Fine grain validation""" - - identifier: str - - def __str__(self) -> str: - return f"The provided identifier: {self.identifier} is duplicated" - - def to_operation_outcome(self) -> dict: - msg = self.__str__() - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.duplicate, - diagnostics=msg, - ) - - -@dataclass -class InvalidJsonError(RuntimeError): - """Raised when client provides an invalid JSON payload""" - - message: str - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.invalid, - diagnostics=self.message, - ) - - -def create_operation_outcome(resource_id: str, severity: Severity, code: Code, diagnostics: str) -> dict: - """Create an OperationOutcome object. Do not use `fhir.resource` library since it adds unnecessary validations""" - return { - "resourceType": "OperationOutcome", - "id": resource_id, - "meta": {"profile": ["https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome"]}, - "issue": [ - { - "severity": severity, - "code": code, - "details": { - "coding": [ - { - "system": "https://fhir.nhs.uk/Codesystem/http-error-codes", - "code": code.upper(), - } - ] - }, - "diagnostics": diagnostics, - } - ], - } - - -@dataclass -class ParameterException(RuntimeError): - message: str - - def __str__(self): - return self.message - - -class UnauthorizedSystemError(RuntimeError): - def __init__(self, message="Unauthorized system"): - super().__init__(message) - self.message = message - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.forbidden, - diagnostics=self.message, - ) - - -class MessageNotSuccessfulError(Exception): - """ - Generic error message for any scenario which either prevents sending to the Imms API, or which results in a - non-successful response from the Imms API - """ - - def __init__(self, message=None): - self.message = message - - -class RecordProcessorError(Exception): - """ - Exception for re-raising exceptions which have already occurred in the Record Processor. - The diagnostics dictionary received from the Record Processor is passed to the exception as an argument - and is stored as an attribute. - """ - - def __init__(self, diagnostics_dictionary: dict): - self.diagnostics_dictionary = diagnostics_dictionary diff --git a/backend/tests/test_api_errors.py b/backend/tests/test_api_errors.py deleted file mode 100644 index 6474dd35d..000000000 --- a/backend/tests/test_api_errors.py +++ /dev/null @@ -1,22 +0,0 @@ -import unittest - -from models.errors import Code, Severity, create_operation_outcome - -"test" - - -class TestApiErrors(unittest.TestCase): - def test_error_to_uk_core2(self): - code = Code.not_found - - severity = Severity.error - diag = "a-diagnostic" - error_id = "a-id" - - error = create_operation_outcome(resource_id=error_id, severity=severity, code=code, diagnostics=diag) - - issue = error["issue"][0] - self.assertEqual(error["id"], error_id) - self.assertEqual(issue["code"], "not-found") - self.assertEqual(issue["severity"], "error") - self.assertEqual(issue["diagnostics"], diag) diff --git a/backend/tests/test_cache.py b/backend/tests/test_cache.py deleted file mode 100644 index e4d8754b2..000000000 --- a/backend/tests/test_cache.py +++ /dev/null @@ -1,71 +0,0 @@ -import json -import tempfile -import unittest - -from cache import Cache - -"test" - - -class TestCache(unittest.TestCase): - def setUp(self): - self.cache = Cache(tempfile.gettempdir()) - - def test_cache_put(self): - """it should store cache in specified key""" - value = {"foo": "a-foo", "bar": 42} - key = "a_key" - - # When - self.cache.put(key, value) - act_value = self.cache.get(key) - - # Then - self.assertDictEqual(value, act_value) - - def test_cache_put_overwrite(self): - """it should store updated cache value""" - value = {"foo": "a-foo", "bar": 42} - key = "a_key" - self.cache.put(key, value) - - new_value = {"foo": "new-foo"} - self.cache.put(key, new_value) - - # When - updated_value = self.cache.get(key) - - # Then - self.assertDictEqual(new_value, updated_value) - - def test_key_not_found(self): - """it should return None if key doesn't exist""" - value = self.cache.get("it-does-not-exist") - self.assertIsNone(value) - - def test_delete(self): - """it should delete key""" - key = "a_key" - self.cache.put(key, {"a": "b"}) - self.cache.delete(key) - - value = self.cache.get(key) - self.assertIsNone(value) - - def test_write_to_file(self): - """it should update the cache file""" - value = {"foo": "a-long-foo-so-to-make-sure-truncate-is-working", "bar": 42} - key = "a_key" - self.cache.put(key, value) - # Add one and delete to make sure file gets updated - self.cache.put("to-delete-key", {"x": "y"}) - self.cache.delete("to-delete-key") - - # When - new_value = {"a": "b"} - self.cache.put(key, new_value) - - # Then - with open(self.cache.cache_file.name, "r") as stored: - content = json.loads(stored.read()) - self.assertDictEqual(content[key], new_value) diff --git a/config/dev/permissions_config.json b/config/dev/permissions_config.json index 963cc08e8..0552d03fe 100644 --- a/config/dev/permissions_config.json +++ b/config/dev/permissions_config.json @@ -48,7 +48,19 @@ }, { "supplier": "RAVS", - "permissions": ["MMR.CRUDS", "RSV.CRUDS"], + "permissions": [ + "3IN1.CRUDS", + "COVID.CRUDS", + "FLU.CRUDS", + "HPV.CRUDS", + "MENACWY.CRUDS", + "MMR.CRUDS", + "MMRV.CRUDS", + "PNEUMOCOCCAL.CRUDS", + "PERTUSSIS.CRUDS", + "RSV.CRUDS", + "SHINGLES.CRUDS" + ], "ods_codes": ["X26", "X8E5B"] }, { @@ -56,6 +68,7 @@ "permissions": [ "3IN1.CRUDS", "COVID.CRUDS", + "FLU.CRUDS", "HPV.CRUDS", "MENACWY.CRUDS", "MMR.CRUDS", diff --git a/config/preprod/permissions_config.json b/config/preprod/permissions_config.json index f27ad6430..28286336b 100644 --- a/config/preprod/permissions_config.json +++ b/config/preprod/permissions_config.json @@ -38,7 +38,19 @@ }, { "supplier": "RAVS", - "permissions": ["MMR.CRUDS", "RSV.CRUDS"], + "permissions": [ + "3IN1.CRUDS", + "COVID.CRUDS", + "FLU.CRUDS", + "HPV.CRUDS", + "MENACWY.CRUDS", + "MMR.CRUDS", + "MMRV.CRUDS", + "PNEUMOCOCCAL.CRUDS", + "PERTUSSIS.CRUDS", + "RSV.CRUDS", + "SHINGLES.CRUDS" + ], "ods_codes": ["X26", "X8E5B"] }, { @@ -46,6 +58,7 @@ "permissions": [ "3IN1.CRUDS", "COVID.CRUDS", + "FLU.CRUDS", "HPV.CRUDS", "MENACWY.CRUDS", "MMR.CRUDS", diff --git a/immunisation-fhir-api.code-workspace b/immunisation-fhir-api.code-workspace index e90eaf910..7de6b0eb1 100644 --- a/immunisation-fhir-api.code-workspace +++ b/immunisation-fhir-api.code-workspace @@ -4,7 +4,7 @@ "path": ".", }, { - "path": "backend", + "path": "lambdas/backend", }, { "path": "lambdas/ack_backend", @@ -27,6 +27,9 @@ { "path": "lambdas/mns_subscription", }, + { + "path": "lambdas/recordforwarder", + }, { "path": "lambdas/recordprocessor", }, diff --git a/infrastructure/instance/forwarder_lambda.tf b/infrastructure/instance/forwarder_lambda.tf index 927d5545a..be1a34cc0 100644 --- a/infrastructure/instance/forwarder_lambda.tf +++ b/infrastructure/instance/forwarder_lambda.tf @@ -1,13 +1,8 @@ +# Define the directory containing the Docker image and calculate its SHA-256 hash for triggering redeployments locals { - forwarder_lambda_dir = abspath("${path.root}/../../backend") - forwarder_source_path = local.forwarder_lambda_dir - forwarder_path_include = ["**"] - forwarder_path_exclude = ["**/__pycache__/**"] - forwarder_files_include = setunion([for f in local.forwarder_path_include : fileset(local.forwarder_source_path, f)]...) - forwarder_files_exclude = setunion([for f in local.forwarder_path_exclude : fileset(local.forwarder_source_path, f)]...) - forwarder_files = sort(setsubtract(local.forwarder_files_include, local.forwarder_files_exclude)) - - forwarder_dir_sha = sha1(join("", [for f in local.forwarder_files : filesha1("${local.forwarder_source_path}/${f}")])) + forwarder_lambda_dir = abspath("${path.root}/../../lambdas/recordforwarder") + forwarder_lambda_files = fileset(local.forwarder_lambda_dir, "**") + forwarder_lambda_dir_sha = sha1(join("", [for f in local.forwarder_lambda_files : filesha1("${local.forwarder_lambda_dir}/${f}")])) } resource "aws_ecr_repository" "forwarder_lambda_repository" { @@ -18,13 +13,14 @@ resource "aws_ecr_repository" "forwarder_lambda_repository" { force_delete = local.is_temp } +# Module for building and pushing Docker image to ECR module "forwarding_docker_image" { - source = "terraform-aws-modules/lambda/aws//modules/docker-build" - version = "8.1.2" + source = "terraform-aws-modules/lambda/aws//modules/docker-build" + version = "8.1.2" + docker_file_path = "./recordforwarder/Dockerfile" - create_ecr_repo = false - ecr_repo = aws_ecr_repository.forwarder_lambda_repository.name - docker_file_path = "batch.Dockerfile" + create_ecr_repo = false + ecr_repo = aws_ecr_repository.forwarder_lambda_repository.name ecr_repo_lifecycle_policy = jsonencode({ rules = [ { @@ -44,9 +40,10 @@ module "forwarding_docker_image" { platform = "linux/amd64" use_image_tag = false - source_path = local.forwarder_lambda_dir + source_path = abspath("${path.root}/../../lambdas") triggers = { - dir_sha = local.forwarder_dir_sha + dir_sha = local.forwarder_lambda_dir_sha + shared_dir_sha = local.shared_dir_sha } } diff --git a/infrastructure/instance/lambda.tf b/infrastructure/instance/lambda.tf index 9aa6db205..5238061c8 100644 --- a/infrastructure/instance/lambda.tf +++ b/infrastructure/instance/lambda.tf @@ -1,13 +1,8 @@ +# Define the directory containing the Docker image and calculate its SHA-256 hash for triggering redeployments locals { - lambda_dir = abspath("${path.root}/../../backend") - source_path = local.lambda_dir - path_include = ["**"] - path_exclude = ["**/__pycache__/**"] - files_include = setunion([for f in local.path_include : fileset(local.source_path, f)]...) - files_exclude = setunion([for f in local.path_exclude : fileset(local.source_path, f)]...) - files = sort(setsubtract(local.files_include, local.files_exclude)) - - dir_sha = sha1(join("", [for f in local.files : filesha1("${local.source_path}/${f}")])) + lambda_dir = abspath("${path.root}/../../lambdas/backend") + lambda_files = fileset(local.lambda_dir, "**") + lambda_dir_sha = sha1(join("", [for f in local.lambda_files : filesha1("${local.lambda_dir}/${f}")])) } resource "aws_ecr_repository" "operation_lambda_repository" { @@ -18,14 +13,14 @@ resource "aws_ecr_repository" "operation_lambda_repository" { force_delete = local.is_temp } -#resource "docker_image" "lambda_function_docker" { +# Module for building and pushing Docker image to ECR module "docker_image" { source = "terraform-aws-modules/lambda/aws//modules/docker-build" version = "8.1.2" create_ecr_repo = false ecr_repo = "${local.prefix}-operation-lambda-repo" - docker_file_path = "lambda.Dockerfile" + docker_file_path = "./backend/Dockerfile" ecr_repo_lifecycle_policy = jsonencode({ "rules" : [ { @@ -45,9 +40,10 @@ module "docker_image" { platform = "linux/amd64" use_image_tag = false - source_path = local.lambda_dir + source_path = abspath("${path.root}/../../lambdas") triggers = { - dir_sha = local.dir_sha + dir_sha = local.lambda_dir_sha + shared_dir_sha = local.shared_dir_sha } } diff --git a/lambdas/ack_backend/poetry.lock b/lambdas/ack_backend/poetry.lock index f859ba9e6..353d96f29 100644 --- a/lambdas/ack_backend/poetry.lock +++ b/lambdas/ack_backend/poetry.lock @@ -2,18 +2,18 @@ [[package]] name = "boto3" -version = "1.40.68" +version = "1.40.72" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.40.68-py3-none-any.whl", hash = "sha256:4f08115e3a4d1e1056003e433d393e78c20da6af7753409992bb33fb69f04186"}, - {file = "boto3-1.40.68.tar.gz", hash = "sha256:c7994989e5bbba071b7c742adfba35773cf03e87f5d3f9f2b0a18c1664417b61"}, + {file = "boto3-1.40.72-py3-none-any.whl", hash = "sha256:1063a295712f2605d3e463e4dc1fe32fce17cf77a0f4d3bb14249d68533ee856"}, + {file = "boto3-1.40.72.tar.gz", hash = "sha256:58d30dd5e046789a760db7a49f817650b8ff08d8d169e127976a61f44b7c59ad"}, ] [package.dependencies] -botocore = ">=1.40.68,<1.41.0" +botocore = ">=1.40.72,<1.41.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.14.0,<0.15.0" @@ -22,14 +22,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.40.68" +version = "1.40.72" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.40.68-py3-none-any.whl", hash = "sha256:9d514f9c9054e1af055f2cbe9e0d6771d407a600206d45a01b54d5f09538fecb"}, - {file = "botocore-1.40.68.tar.gz", hash = "sha256:28f41b463d9f012a711ee8b61d4e26cd14ee3b450b816d5dee849aa79155e856"}, + {file = "botocore-1.40.72-py3-none-any.whl", hash = "sha256:4f859e5aaf871fe59aac431d6bba59cc0c8ed8a38da2a6a5345700bdc5c74b32"}, + {file = "botocore-1.40.72.tar.gz", hash = "sha256:f69199ff6570695556e733fa052f2739e01e0c592c9b60f843f84c77ba3bcdf3"}, ] [package.dependencies] @@ -275,104 +275,104 @@ files = [ [[package]] name = "coverage" -version = "7.11.0" +version = "7.11.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, - {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, - {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, - {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, - {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, - {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, - {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, - {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, - {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, - {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, - {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, - {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, - {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, - {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, - {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, - {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, - {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, - {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, - {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, - {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, - {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, - {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, - {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, - {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e"}, + {file = "coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb"}, + {file = "coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060"}, + {file = "coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7"}, + {file = "coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55"}, + {file = "coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405"}, + {file = "coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e"}, + {file = "coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055"}, + {file = "coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094"}, + {file = "coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c"}, + {file = "coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2"}, + {file = "coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76"}, + {file = "coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c"}, + {file = "coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac"}, + {file = "coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131"}, + {file = "coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a"}, + {file = "coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86"}, + {file = "coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820"}, + {file = "coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237"}, + {file = "coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9"}, + {file = "coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd"}, + {file = "coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe"}, + {file = "coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b"}, ] [package.extras] @@ -924,4 +924,4 @@ test = ["pytest", "pytest-cov"] [metadata] lock-version = "2.1" python-versions = "~3.11" -content-hash = "33195494d4784d10a7f0f919590e2c00510a862e930c84ff5ac34fce982a9419" +content-hash = "e0b8b9acf622be9771878aa46f84c1b626a00f9a09e55cbb995850180fe294cc" diff --git a/lambdas/ack_backend/pyproject.toml b/lambdas/ack_backend/pyproject.toml index e6387457b..fd2da7c20 100644 --- a/lambdas/ack_backend/pyproject.toml +++ b/lambdas/ack_backend/pyproject.toml @@ -11,11 +11,11 @@ packages = [ [tool.poetry.dependencies] python = "~3.11" -boto3 = "~1.40.68" +boto3 = "~1.40.72" mypy-boto3-dynamodb = "^1.40.44" freezegun = "^1.5.2" moto = "^5.1.16" -coverage = "^7.10.7" +coverage = "^7.11.3" [build-system] diff --git a/lambdas/ack_backend/src/update_ack_file.py b/lambdas/ack_backend/src/update_ack_file.py index 0407fe29f..94cc5814e 100644 --- a/lambdas/ack_backend/src/update_ack_file.py +++ b/lambdas/ack_backend/src/update_ack_file.py @@ -5,6 +5,7 @@ from botocore.exceptions import ClientError from audit_table import change_audit_table_status_to_processed +from common.aws_s3_utils import move_file from common.clients import get_s3_client, logger from constants import ( ACK_HEADERS, @@ -123,15 +124,3 @@ def update_ack_file( get_s3_client().upload_fileobj(csv_file_like_object, ack_bucket_name, temp_ack_file_key) logger.info("Ack file updated to %s: %s", ack_bucket_name, archive_ack_file_key) - - -def move_file(bucket_name: str, source_file_key: str, destination_file_key: str) -> None: - """Moves a file from one location to another within a single S3 bucket by copying and then deleting the file.""" - s3_client = get_s3_client() - s3_client.copy_object( - Bucket=bucket_name, - CopySource={"Bucket": bucket_name, "Key": source_file_key}, - Key=destination_file_key, - ) - s3_client.delete_object(Bucket=bucket_name, Key=source_file_key) - logger.info("File moved from %s to %s", source_file_key, destination_file_key) diff --git a/lambdas/ack_backend/tests/test_splunk_logging.py b/lambdas/ack_backend/tests/test_splunk_logging.py index aabc249b4..4d7b831d3 100644 --- a/lambdas/ack_backend/tests/test_splunk_logging.py +++ b/lambdas/ack_backend/tests/test_splunk_logging.py @@ -62,6 +62,7 @@ def run(self, result=None): # The logging_decorator.logger is patched individually in each test to allow for assertions to be made. # Any uses of the logger in other files will confound the tests and should be patched here. patch("update_ack_file.logger"), + patch("common.aws_s3_utils.logger"), # Time is incremented by 1.0 for each call to time.time for ease of testing. # Range is set to a large number (300) due to many calls being made to time.time for some tests. patch( diff --git a/lambdas/ack_backend/tests/test_update_ack_file_flow.py b/lambdas/ack_backend/tests/test_update_ack_file_flow.py index cde99e2aa..cf48c0dc7 100644 --- a/lambdas/ack_backend/tests/test_update_ack_file_flow.py +++ b/lambdas/ack_backend/tests/test_update_ack_file_flow.py @@ -69,26 +69,3 @@ def test_audit_table_updated_correctly_when_ack_process_complete(self): # Assert: Only check audit table update self.mock_change_audit_status.assert_called_once_with(file_key, message_id) - - def test_move_file(self): - """VED-167 test that the file has been moved to the appropriate location""" - bucket_name = "move-bucket" - file_key = "src/move_file_test.csv" - dest_key = "dest/move_file_test.csv" - self.s3_client.create_bucket( - Bucket=bucket_name, - CreateBucketConfiguration={"LocationConstraint": "eu-west-2"}, - ) - self.s3_client.put_object(Bucket=bucket_name, Key=file_key, Body="dummy content") - update_ack_file.move_file(bucket_name, file_key, dest_key) - # Assert the destination object exists - response = self.s3_client.get_object(Bucket=bucket_name, Key=dest_key) - content = response["Body"].read().decode() - self.assertEqual(content, "dummy content") - - # Assert the source object no longer exists - with self.assertRaises(self.s3_client.exceptions.NoSuchKey): - self.s3_client.get_object(Bucket=bucket_name, Key=file_key) - - # Logger assertion (if logger is mocked) - self.mock_logger.info.assert_called_with("File moved from %s to %s", file_key, dest_key) diff --git a/backend/.dockerignore b/lambdas/backend/.dockerignore similarity index 100% rename from backend/.dockerignore rename to lambdas/backend/.dockerignore diff --git a/backend/.envrc.default b/lambdas/backend/.envrc.default similarity index 100% rename from backend/.envrc.default rename to lambdas/backend/.envrc.default diff --git a/backend/.gitignore b/lambdas/backend/.gitignore similarity index 100% rename from backend/.gitignore rename to lambdas/backend/.gitignore diff --git a/backend/.vscode/settings.json.default b/lambdas/backend/.vscode/settings.json.default similarity index 100% rename from backend/.vscode/settings.json.default rename to lambdas/backend/.vscode/settings.json.default diff --git a/backend/batch.Dockerfile b/lambdas/backend/Dockerfile similarity index 60% rename from backend/batch.Dockerfile rename to lambdas/backend/Dockerfile index bd2be7d94..68f490f78 100644 --- a/backend/batch.Dockerfile +++ b/lambdas/backend/Dockerfile @@ -1,23 +1,33 @@ FROM public.ecr.aws/lambda/python:3.11 AS base -# Create a non-root user with a specific UID and GID + RUN mkdir -p /home/appuser && \ echo 'appuser:x:1001:1001::/home/appuser:/sbin/nologin' >> /etc/passwd && \ echo 'appuser:x:1001:' >> /etc/group && \ chown -R 1001:1001 /home/appuser && pip install "poetry~=2.1.4" -# ----------------------------- -COPY poetry.lock pyproject.toml README.md ./ +# Install Poetry dependencies +# Copy backend Poetry files +COPY ./backend/poetry.lock ./backend/pyproject.toml ./ + +# Install backend dependencies +WORKDIR /var/task RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi --no-root --only main -# ----------------------------- -FROM base AS test -COPY src src -COPY tests tests -RUN poetry install --no-interaction --no-ansi --no-root && \ - pytest --disable-warnings tests + # ----------------------------- FROM base AS build -COPY src . + +# Set working directory back to Lambda task root +WORKDIR /var/task + +# Copy shared source code +COPY ./shared/src/common ./common + +# Copy backend source code +COPY ./backend/src . + +# Set correct permissions RUN chmod 644 $(find . -type f) && chmod 755 $(find . -type d) + # Switch to the non-root user for running the container USER 1001:1001 -CMD ["forwarding_batch_lambda.forward_lambda_handler"] + diff --git a/lambdas/backend/Makefile b/lambdas/backend/Makefile new file mode 100644 index 000000000..e9701b718 --- /dev/null +++ b/lambdas/backend/Makefile @@ -0,0 +1,22 @@ +TEST_ENV := @PYTHONPATH=src:tests:../shared/src:../shared/tests + +build: + docker build -t imms-lambda-build -f Dockerfile . + +package: build + mkdir -p build + docker run --rm -v $(shell pwd)/build:/build imms-lambda-build + +test: + $(TEST_ENV) python -m unittest + +coverage-run: + $(TEST_ENV) coverage run --source=src -m unittest discover + +coverage-report: + $(TEST_ENV) coverage report -m + +coverage-html: + $(TEST_ENV) coverage html + +.PHONY: build package test diff --git a/backend/README.md b/lambdas/backend/README.md similarity index 100% rename from backend/README.md rename to lambdas/backend/README.md diff --git a/backend/__init__.py b/lambdas/backend/__init__.py similarity index 100% rename from backend/__init__.py rename to lambdas/backend/__init__.py diff --git a/backend/poetry.lock b/lambdas/backend/poetry.lock similarity index 99% rename from backend/poetry.lock rename to lambdas/backend/poetry.lock index 6c2d3a1e2..c7205e94c 100644 --- a/backend/poetry.lock +++ b/lambdas/backend/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "async-timeout" diff --git a/backend/pyproject.toml b/lambdas/backend/pyproject.toml similarity index 91% rename from backend/pyproject.toml rename to lambdas/backend/pyproject.toml index 042c92ed0..731b6959a 100644 --- a/backend/pyproject.toml +++ b/lambdas/backend/pyproject.toml @@ -4,7 +4,10 @@ version = "0.1.0" description = "" authors = ["Your Name "] readme = "README.md" -packages = [{include = "src"}] +packages = [ + {include = "src"}, + {include = "common", from = "../shared/src"} +] [tool.poetry.dependencies] python = "~3.11" diff --git a/backend/src/__init__.py b/lambdas/backend/src/__init__.py similarity index 100% rename from backend/src/__init__.py rename to lambdas/backend/src/__init__.py diff --git a/backend/src/authorisation/__init__.py b/lambdas/backend/src/authorisation/__init__.py similarity index 100% rename from backend/src/authorisation/__init__.py rename to lambdas/backend/src/authorisation/__init__.py diff --git a/backend/src/authorisation/api_operation_code.py b/lambdas/backend/src/authorisation/api_operation_code.py similarity index 100% rename from backend/src/authorisation/api_operation_code.py rename to lambdas/backend/src/authorisation/api_operation_code.py diff --git a/backend/src/authorisation/authoriser.py b/lambdas/backend/src/authorisation/authoriser.py similarity index 92% rename from backend/src/authorisation/authoriser.py rename to lambdas/backend/src/authorisation/authoriser.py index e3e0194dc..4e92647b9 100644 --- a/backend/src/authorisation/authoriser.py +++ b/lambdas/backend/src/authorisation/authoriser.py @@ -3,16 +3,14 @@ import json from authorisation.api_operation_code import ApiOperationCode -from clients import logger, redis_client -from constants import SUPPLIER_PERMISSIONS_HASH_KEY +from common.clients import logger +from common.models.constants import SUPPLIER_PERMISSIONS_HASH_KEY +from common.redis_client import get_redis_client class Authoriser: """Authoriser class. Used for authorising operations on FHIR vaccinations.""" - def __init__(self): - self._cache_client = redis_client - @staticmethod def _expand_permissions( permissions: list[str], @@ -35,7 +33,7 @@ def _expand_permissions( return expanded_permissions def _get_supplier_permissions(self, supplier_system: str) -> dict[str, list[ApiOperationCode]]: - raw_permissions_data = self._cache_client.hget(SUPPLIER_PERMISSIONS_HASH_KEY, supplier_system) + raw_permissions_data = get_redis_client().hget(SUPPLIER_PERMISSIONS_HASH_KEY, supplier_system) permissions_data = json.loads(raw_permissions_data) if raw_permissions_data else [] return self._expand_permissions(permissions_data) diff --git a/lambdas/backend/src/constants.py b/lambdas/backend/src/constants.py new file mode 100644 index 000000000..3e571191a --- /dev/null +++ b/lambdas/backend/src/constants.py @@ -0,0 +1,5 @@ +"""Constants""" + +GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE = "Unable to process request. Issue may be transient." +# Maximum response size for an AWS Lambda function +MAX_RESPONSE_SIZE_BYTES = 6 * 1024 * 1024 diff --git a/backend/src/batch/__init__.py b/lambdas/backend/src/controller/__init__.py similarity index 100% rename from backend/src/batch/__init__.py rename to lambdas/backend/src/controller/__init__.py diff --git a/backend/src/controller/aws_apig_event_utils.py b/lambdas/backend/src/controller/aws_apig_event_utils.py similarity index 89% rename from backend/src/controller/aws_apig_event_utils.py rename to lambdas/backend/src/controller/aws_apig_event_utils.py index d337d28fb..02a890c0a 100644 --- a/backend/src/controller/aws_apig_event_utils.py +++ b/lambdas/backend/src/controller/aws_apig_event_utils.py @@ -5,7 +5,7 @@ from aws_lambda_typing.events import APIGatewayProxyEventV1 from controller.constants import E_TAG_HEADER_NAME, SUPPLIER_SYSTEM_HEADER_NAME -from models.errors import ResourceVersionNotProvided, UnauthorizedError +from models.errors import ResourceVersionNotProvidedError, UnauthorizedError from utils import dict_utils @@ -30,6 +30,6 @@ def get_resource_version_header(event: APIGatewayProxyEventV1) -> str: resource_version_header: Optional[str] = dict_utils.get_field(dict(event), "headers", E_TAG_HEADER_NAME) if resource_version_header is None: - raise ResourceVersionNotProvided(resource_type="Immunization") + raise ResourceVersionNotProvidedError(resource_type="Immunization") return resource_version_header diff --git a/backend/src/controller/aws_apig_response_utils.py b/lambdas/backend/src/controller/aws_apig_response_utils.py similarity index 100% rename from backend/src/controller/aws_apig_response_utils.py rename to lambdas/backend/src/controller/aws_apig_response_utils.py diff --git a/backend/src/controller/constants.py b/lambdas/backend/src/controller/constants.py similarity index 100% rename from backend/src/controller/constants.py rename to lambdas/backend/src/controller/constants.py diff --git a/backend/src/controller/fhir_api_exception_handler.py b/lambdas/backend/src/controller/fhir_api_exception_handler.py similarity index 82% rename from backend/src/controller/fhir_api_exception_handler.py rename to lambdas/backend/src/controller/fhir_api_exception_handler.py index e4f6d9567..7bd3db71c 100644 --- a/backend/src/controller/fhir_api_exception_handler.py +++ b/lambdas/backend/src/controller/fhir_api_exception_handler.py @@ -4,21 +4,24 @@ import uuid from typing import Callable, Type -from clients import logger +from common.clients import logger +from common.models.errors import ( + CustomValidationError, + IdentifierDuplicationError, + InconsistentIdentifierError, + InconsistentResourceVersionError, + ResourceNotFoundError, +) from constants import GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE from controller.aws_apig_response_utils import create_response from models.errors import ( Code, - CustomValidationError, - IdentifierDuplicationError, - InconsistentIdentifierError, InconsistentIdError, - InconsistentResourceVersion, - InvalidImmunizationId, + InvalidImmunizationIdError, InvalidJsonError, - InvalidResourceVersion, - ResourceNotFoundError, - ResourceVersionNotProvided, + InvalidResourceVersionError, + InvalidStoredDataError, + ResourceVersionNotProvidedError, Severity, UnauthorizedError, UnauthorizedVaxError, @@ -27,19 +30,20 @@ ) _CUSTOM_EXCEPTION_TO_STATUS_MAP: dict[Type[Exception], int] = { - InconsistentResourceVersion: 400, + InconsistentResourceVersionError: 400, InconsistentIdentifierError: 400, # Identifier refers to the local FHIR identifier composed of system and value. InconsistentIdError: 400, # ID refers to the top-level ID of the FHIR resource. - InvalidImmunizationId: 400, + InvalidImmunizationIdError: 400, InvalidJsonError: 400, - InvalidResourceVersion: 400, + InvalidResourceVersionError: 400, CustomValidationError: 400, - ResourceVersionNotProvided: 400, + ResourceVersionNotProvidedError: 400, UnauthorizedError: 403, UnauthorizedVaxError: 403, ResourceNotFoundError: 404, IdentifierDuplicationError: 422, UnhandledResponseError: 500, + InvalidStoredDataError: 500, } diff --git a/backend/src/controller/fhir_controller.py b/lambdas/backend/src/controller/fhir_controller.py similarity index 96% rename from backend/src/controller/fhir_controller.py rename to lambdas/backend/src/controller/fhir_controller.py index 80f8c1d31..580b7f2e8 100644 --- a/backend/src/controller/fhir_controller.py +++ b/lambdas/backend/src/controller/fhir_controller.py @@ -10,6 +10,7 @@ from aws_lambda_typing.events import APIGatewayProxyEventV1 +from common.models.utils.generic_utils import check_keys_in_sources from controller.aws_apig_event_utils import ( get_path_parameter, get_resource_version_header, @@ -21,16 +22,15 @@ from models.errors import ( Code, InconsistentIdError, - InvalidImmunizationId, + InvalidImmunizationIdError, InvalidJsonError, - InvalidResourceVersion, - ParameterException, + InvalidResourceVersionError, + ParameterExceptionError, Severity, UnauthorizedError, UnauthorizedVaxError, create_operation_outcome, ) -from models.utils.generic_utils import check_keys_in_sources from parameter_parser import create_query_string, process_params, process_search_params from repository.fhir_repository import ImmunizationRepository, create_table from service.fhir_service import FhirService, get_service_url @@ -100,7 +100,7 @@ def get_immunization_by_id(self, aws_event: APIGatewayProxyEventV1) -> dict: imms_id = get_path_parameter(aws_event, "id") if not self._is_valid_immunization_id(imms_id): - raise InvalidImmunizationId() + raise InvalidImmunizationIdError() supplier_system = get_supplier_system_header(aws_event) @@ -132,10 +132,10 @@ def update_immunization(self, aws_event: APIGatewayProxyEventV1) -> dict: resource_version = get_resource_version_header(aws_event) if not self._is_valid_immunization_id(imms_id): - raise InvalidImmunizationId() + raise InvalidImmunizationIdError() if not self._is_valid_resource_version(resource_version): - raise InvalidResourceVersion(resource_version=resource_version) + raise InvalidResourceVersionError(resource_version=resource_version) try: immunization = json.loads(aws_event["body"], parse_float=Decimal) @@ -156,7 +156,7 @@ def delete_immunization(self, aws_event: APIGatewayProxyEventV1) -> dict: imms_id = get_path_parameter(aws_event, "id") if not self._is_valid_immunization_id(imms_id): - raise InvalidImmunizationId() + raise InvalidImmunizationIdError() supplier_system = get_supplier_system_header(aws_event) @@ -167,10 +167,10 @@ def delete_immunization(self, aws_event: APIGatewayProxyEventV1) -> dict: def search_immunizations(self, aws_event: APIGatewayProxyEventV1) -> dict: try: search_params = process_search_params(process_params(aws_event)) - except ParameterException as e: + except ParameterExceptionError as e: return self._create_bad_request(e.message) if search_params is None: - raise Exception("Failed to parse parameters.") + raise ParameterExceptionError("Failed to parse parameters.") # Check vaxx type permissions- start try: diff --git a/backend/src/create_imms_handler.py b/lambdas/backend/src/create_imms_handler.py similarity index 100% rename from backend/src/create_imms_handler.py rename to lambdas/backend/src/create_imms_handler.py diff --git a/backend/src/delete_imms_handler.py b/lambdas/backend/src/delete_imms_handler.py similarity index 100% rename from backend/src/delete_imms_handler.py rename to lambdas/backend/src/delete_imms_handler.py diff --git a/backend/src/filter.py b/lambdas/backend/src/filter.py similarity index 97% rename from backend/src/filter.py rename to lambdas/backend/src/filter.py index 61cd8a198..0b47f6432 100644 --- a/backend/src/filter.py +++ b/lambdas/backend/src/filter.py @@ -1,7 +1,7 @@ """Functions for filtering a FHIR Immunization Resource""" -from constants import Urls -from models.utils.generic_utils import ( +from common.models.constants import Urls +from common.models.utils.generic_utils import ( get_contained_patient, get_contained_practitioner, is_actor_referencing_contained_resource, diff --git a/backend/src/get_imms_handler.py b/lambdas/backend/src/get_imms_handler.py similarity index 100% rename from backend/src/get_imms_handler.py rename to lambdas/backend/src/get_imms_handler.py diff --git a/backend/src/get_status_handler.py b/lambdas/backend/src/get_status_handler.py similarity index 100% rename from backend/src/get_status_handler.py rename to lambdas/backend/src/get_status_handler.py diff --git a/backend/src/local_lambda.py b/lambdas/backend/src/local_lambda.py similarity index 100% rename from backend/src/local_lambda.py rename to lambdas/backend/src/local_lambda.py diff --git a/backend/src/log_structure.py b/lambdas/backend/src/log_structure.py similarity index 61% rename from backend/src/log_structure.py rename to lambdas/backend/src/log_structure.py index e819c8c9c..22c9b326b 100644 --- a/backend/src/log_structure.py +++ b/lambdas/backend/src/log_structure.py @@ -1,18 +1,11 @@ import json -import logging import time from datetime import datetime from functools import wraps -from log_firehose import FirehoseLogger -from models.utils.validation_utils import get_vaccine_type - -logging.basicConfig() -logger = logging.getLogger() -logger.setLevel("INFO") - - -firehose_logger = FirehoseLogger() +from common.clients import STREAM_NAME, logger +from common.log_firehose import send_log_to_firehose +from common.models.utils.validation_utils import get_vaccine_type def _log_data_from_body(event) -> dict: @@ -36,6 +29,34 @@ def _log_data_from_body(event) -> dict: return log_data +def _get_operation_outcome(result) -> dict: + operation_outcome = {} + status = "500" + status_code = "Exception" + diagnostics = str() + record = str() + if isinstance(result, dict): + status = str(result["statusCode"]) + status_code = "Completed successfully" + if result.get("headers"): + result_headers = result["headers"] + if result_headers.get("Location"): + record = result_headers["Location"] + if result.get("body"): + ops_outcome = json.loads(result["body"]) + if ops_outcome.get("issue"): + outcome_body = ops_outcome["issue"][0] + status_code = outcome_body["code"] + diagnostics = outcome_body["diagnostics"] + operation_outcome["status"] = status + operation_outcome["status_code"] = status_code + if len(diagnostics) > 1: + operation_outcome["diagnostics"] = diagnostics + if len(record) > 1: + operation_outcome["record"] = record + return operation_outcome + + def function_info(func): """This decorator prints the execution information for the decorated function.""" @@ -58,43 +79,19 @@ def wrapper(*args, **kwargs): "actual_path": actual_path, "resource_path": resource_path, } - operation_outcome = dict() - firehose_log = dict() + firehose_log = {} start = time.time() try: result = func(*args, **kwargs) end = time.time() log_data["time_taken"] = f"{round(end - start, 5)}s" log_data.update(_log_data_from_body(event)) - status = "500" - status_code = "Exception" - diagnostics = str() - record = str() - if isinstance(result, dict): - status = str(result["statusCode"]) - status_code = "Completed successfully" - if result.get("headers"): - result_headers = result["headers"] - if result_headers.get("Location"): - record = result_headers["Location"] - if result.get("body"): - ops_outcome = json.loads(result["body"]) - logger.info(f"ops_outcome: {ops_outcome}") - if ops_outcome.get("issue"): - outcome_body = ops_outcome["issue"][0] - status_code = outcome_body["code"] - diagnostics = outcome_body["diagnostics"] - operation_outcome["status"] = status - operation_outcome["status_code"] = status_code - if len(diagnostics) > 1: - operation_outcome["diagnostics"] = diagnostics - if len(record) > 1: - operation_outcome["record"] = record + operation_outcome = _get_operation_outcome(result) + log_data["operation_outcome"] = operation_outcome logger.info(json.dumps(log_data)) firehose_log["event"] = log_data - firehose_logger.send_log(firehose_log) - + send_log_to_firehose(STREAM_NAME, firehose_log) return result except Exception as e: @@ -104,7 +101,7 @@ def wrapper(*args, **kwargs): log_data.update(_log_data_from_body(event)) logger.exception(json.dumps(log_data)) firehose_log["event"] = log_data - firehose_logger.send_log(firehose_log) + send_log_to_firehose(STREAM_NAME, firehose_log) raise return wrapper diff --git a/lambdas/backend/src/models/errors.py b/lambdas/backend/src/models/errors.py new file mode 100644 index 000000000..c53e50569 --- /dev/null +++ b/lambdas/backend/src/models/errors.py @@ -0,0 +1,175 @@ +import uuid +from dataclasses import dataclass +from enum import Enum +from typing import Any + +from common.models.errors import ApiValidationError, Severity, create_operation_outcome + + +class Code(str, Enum): + forbidden = "forbidden" + not_found = "not-found" + invalid = "invalid" + server_error = "exception" + invariant = "invariant" + not_supported = "not-supported" + duplicate = "duplicate" + # Added an unauthorized code its used when returning a response for an unauthorized vaccine type search. + unauthorized = "unauthorized" + + +@dataclass +class UnhandledResponseError(RuntimeError): + """Use this error when the response from an external service (ex: dynamodb) can't be handled""" + + # Differs from errors.py in that code is Code.server.error rather than Code.exception + response: dict | str + message: str + + def __str__(self): + return f"{self.message}\n{self.response}" + + def to_operation_outcome(self) -> dict: + return create_operation_outcome( + resource_id=str(uuid.uuid4()), + severity=Severity.error, + code=Code.server_error, + diagnostics=self.__str__(), + ) + + +@dataclass +class UnauthorizedError(RuntimeError): + # The Unauthorized*Error classes differ from errors.py in that they carry no arguments + @staticmethod + def to_operation_outcome() -> dict: + msg = "Unauthorized request" + return create_operation_outcome( + resource_id=str(uuid.uuid4()), + severity=Severity.error, + code=Code.forbidden, + diagnostics=msg, + ) + + +@dataclass +class UnauthorizedVaxError(RuntimeError): + @staticmethod + def to_operation_outcome() -> dict: + msg = "Unauthorized request for vaccine type" + return create_operation_outcome( + resource_id=str(uuid.uuid4()), + severity=Severity.error, + code=Code.forbidden, + diagnostics=msg, + ) + + +@dataclass +class ResourceVersionNotProvidedError(RuntimeError): + """Return this error when client has failed to provide the FHIR resource version where required""" + + resource_type: str + + def __str__(self): + return f"Validation errors: {self.resource_type} resource version not specified in the request headers" + + def to_operation_outcome(self) -> dict: + return create_operation_outcome( + resource_id=str(uuid.uuid4()), + severity=Severity.error, + code=Code.invariant, + diagnostics=self.__str__(), + ) + + +@dataclass +class ParameterExceptionError(RuntimeError): + message: str + + def __str__(self): + return self.message + + +@dataclass +class InvalidImmunizationIdError(ApiValidationError): + """Use this when the unique Immunization ID is invalid""" + + def to_operation_outcome(self) -> dict: + return create_operation_outcome( + resource_id=str(uuid.uuid4()), + severity=Severity.error, + code=Code.invalid, + diagnostics="Validation errors: the provided event ID is either missing or not in the expected format.", + ) + + +@dataclass +class InvalidResourceVersionError(ApiValidationError): + """Use this when the resource version is invalid""" + + resource_version: Any + + def to_operation_outcome(self) -> dict: + return create_operation_outcome( + resource_id=str(uuid.uuid4()), + severity=Severity.error, + code=Code.invariant, + diagnostics=f"Validation errors: Immunization resource version:{self.resource_version} in the request " + f"headers is invalid.", + ) + + +@dataclass +class InconsistentIdError(ApiValidationError): + """Use this when the specified id in the message is inconsistent with the path + see: http://hl7.org/fhir/R4/http.html#update""" + + imms_id: str + + def __str__(self): + return ( + f"Validation errors: The provided immunization id:{self.imms_id} doesn't match with the content of the " + f"request body" + ) + + def to_operation_outcome(self) -> dict: + return create_operation_outcome( + resource_id=str(uuid.uuid4()), + severity=Severity.error, + code=Code.invariant, + diagnostics=self.__str__(), + ) + + +@dataclass +class InvalidJsonError(RuntimeError): + """Raised when client provides an invalid JSON payload""" + + message: str + + def to_operation_outcome(self) -> dict: + return create_operation_outcome( + resource_id=str(uuid.uuid4()), + severity=Severity.error, + code=Code.invalid, + diagnostics=self.message, + ) + + +@dataclass +class InvalidStoredDataError(RuntimeError): + """Use this when a piece of stored data is invalid and the operation cannot be completed""" + + data_type: str + + def __str__(self): + return f"Invalid data stored for immunization record: {self.data_type}" + + def to_operation_outcome(self) -> dict: + return create_operation_outcome( + resource_id=str(uuid.uuid4()), + severity=Severity.error, + code=Code.server_error, + diagnostics=self.__str__(), + ) diff --git a/backend/src/not_found_handler.py b/lambdas/backend/src/not_found_handler.py similarity index 100% rename from backend/src/not_found_handler.py rename to lambdas/backend/src/not_found_handler.py diff --git a/backend/src/parameter_parser.py b/lambdas/backend/src/parameter_parser.py similarity index 85% rename from backend/src/parameter_parser.py rename to lambdas/backend/src/parameter_parser.py index 9c3db7aeb..b4f33b6dc 100644 --- a/backend/src/parameter_parser.py +++ b/lambdas/backend/src/parameter_parser.py @@ -6,10 +6,10 @@ from aws_lambda_typing.events import APIGatewayProxyEventV1 -from clients import redis_client -from models.constants import Constants -from models.errors import ParameterException -from models.utils.generic_utils import nhs_number_mod11_check +from common.models.constants import Constants +from common.models.utils.generic_utils import nhs_number_mod11_check +from common.redis_client import get_redis_client +from models.errors import ParameterExceptionError ERROR_MESSAGE_DUPLICATED_PARAMETERS = 'Parameters may not be duplicated. Use commas for "or".' @@ -48,12 +48,12 @@ def process_patient_identifier(identifier_params: ParamContainer) -> str: patient_identifier = patient_identifiers[0] if len(patient_identifiers) == 1 else None if patient_identifier is None: - raise ParameterException(f"Search parameter {patient_identifier_key} must have one value.") + raise ParameterExceptionError(f"Search parameter {patient_identifier_key} must have one value.") patient_identifier_parts = patient_identifier.split("|") identifier_system = patient_identifier_parts[0] if len(patient_identifier_parts) != 2 or identifier_system != patient_identifier_system: - raise ParameterException( + raise ParameterExceptionError( "patient.identifier must be in the format of " f'"{patient_identifier_system}|{{NHS number}}" ' f'e.g. "{patient_identifier_system}|9000000009"' @@ -61,7 +61,7 @@ def process_patient_identifier(identifier_params: ParamContainer) -> str: nhs_number = patient_identifier_parts[1] if not nhs_number_mod11_check(nhs_number): - raise ParameterException("Search parameter patient.identifier must be a valid NHS number.") + raise ParameterExceptionError("Search parameter patient.identifier must be a valid NHS number.") return nhs_number @@ -75,11 +75,11 @@ def process_immunization_target(imms_params: ParamContainer) -> list[str]: vaccine_type for vaccine_type in set(imms_params.get(immunization_target_key, [])) if vaccine_type is not None ] if len(vaccine_types) < 1: - raise ParameterException(f"Search parameter {immunization_target_key} must have one or more values.") + raise ParameterExceptionError(f"Search parameter {immunization_target_key} must have one or more values.") - valid_vaccine_types = redis_client.hkeys(Constants.VACCINE_TYPE_TO_DISEASES_HASH_KEY) + valid_vaccine_types = get_redis_client().hkeys(Constants.VACCINE_TYPE_TO_DISEASES_HASH_KEY) if any(x not in valid_vaccine_types for x in vaccine_types): - raise ParameterException( + raise ParameterExceptionError( f"immunization-target must be one or more of the following: {', '.join(valid_vaccine_types)}" ) @@ -148,7 +148,7 @@ def process_search_params(params: ParamContainer) -> SearchParams: errors.append(f"Search parameter {date_from_key} must be before {date_to_key}") if errors: - raise ParameterException("; ".join(errors)) + raise ParameterExceptionError("; ".join(errors)) return SearchParams(patient_identifier, vaccine_types, date_from, date_to, include) @@ -162,8 +162,8 @@ def split_and_flatten(input: list[str]): def parse_multi_value_query_parameters( multi_value_query_params: dict[str, list[str]], ) -> ParamContainer: - if any([len(v) > 1 for k, v in multi_value_query_params.items()]): - raise ParameterException(ERROR_MESSAGE_DUPLICATED_PARAMETERS) + if any(len(v) > 1 for k, v in multi_value_query_params.items()): + raise ParameterExceptionError(ERROR_MESSAGE_DUPLICATED_PARAMETERS) params = [(k, split_and_flatten(v)) for k, v in multi_value_query_params.items()] return dict(params) @@ -176,9 +176,9 @@ def parse_body_params(aws_event: APIGatewayProxyEventV1) -> ParamContainer: decoded_body = base64.b64decode(body).decode("utf-8") parsed_body = parse_qs(decoded_body) - if any([len(v) > 1 for k, v in parsed_body.items()]): - raise ParameterException(ERROR_MESSAGE_DUPLICATED_PARAMETERS) - items = dict((k, split_and_flatten(v)) for k, v in parsed_body.items()) + if any(len(v) > 1 for k, v in parsed_body.items()): + raise ParameterExceptionError(ERROR_MESSAGE_DUPLICATED_PARAMETERS) + items = {k: split_and_flatten(v) for k, v in parsed_body.items()} return items return {} @@ -186,7 +186,7 @@ def parse_body_params(aws_event: APIGatewayProxyEventV1) -> ParamContainer: body_params = parse_body_params(aws_event) if len(set(query_params.keys()) & set(body_params.keys())) > 0: - raise ParameterException(ERROR_MESSAGE_DUPLICATED_PARAMETERS) + raise ParameterExceptionError(ERROR_MESSAGE_DUPLICATED_PARAMETERS) parsed_params = { key: sorted(query_params.get(key, []) + body_params.get(key, [])) diff --git a/backend/src/controller/__init__.py b/lambdas/backend/src/repository/__init__.py similarity index 100% rename from backend/src/controller/__init__.py rename to lambdas/backend/src/repository/__init__.py diff --git a/backend/src/repository/fhir_repository.py b/lambdas/backend/src/repository/fhir_repository.py similarity index 84% rename from backend/src/repository/fhir_repository.py rename to lambdas/backend/src/repository/fhir_repository.py index 9cea36d81..371017129 100644 --- a/backend/src/repository/fhir_repository.py +++ b/lambdas/backend/src/repository/fhir_repository.py @@ -9,20 +9,22 @@ from boto3.dynamodb.conditions import Attr, Key from botocore.config import Config from fhir.resources.R4B.fhirtypes import Id +from fhir.resources.R4B.identifier import Identifier from fhir.resources.R4B.immunization import Immunization from mypy_boto3_dynamodb.service_resource import DynamoDBServiceResource, Table from responses import logger -from models.constants import Constants -from models.errors import ( - ResourceNotFoundError, - UnhandledResponseError, +from common.models.constants import Constants +from common.models.errors import ResourceNotFoundError +from common.models.immunization_record_metadata import ImmunizationRecordMetadata +from common.models.utils.generic_utils import ( + get_contained_patient, + get_nhs_number, ) -from models.immunization_record_metadata import ImmunizationRecordMetadata -from models.utils.generic_utils import get_contained_patient -from models.utils.validation_utils import ( +from common.models.utils.validation_utils import ( get_vaccine_type, ) +from models.errors import InvalidStoredDataError, UnhandledResponseError def create_table(table_name=None, endpoint_url=None, region_name="eu-west-2"): @@ -49,12 +51,16 @@ def _query_identifier(table, index, pk, identifier): return queryresponse -def get_nhs_number(imms): - try: - nhs_number = [x for x in imms["contained"] if x["resourceType"] == "Patient"][0]["identifier"][0]["value"] - except (KeyError, IndexError): - nhs_number = "TBC" - return nhs_number +def get_fhir_identifier_from_identifier_pk(identifier_pk: str) -> Identifier: + split_identifier = identifier_pk.split("#", 1) + + if len(split_identifier) != 2: + raise InvalidStoredDataError(data_type="identifier") + + supplier_code = split_identifier[0] + supplier_unique_id = split_identifier[1] + + return Identifier(system=supplier_code, value=supplier_unique_id) @dataclass @@ -108,10 +114,10 @@ def get_immunization_by_identifier(self, identifier_pk: str) -> tuple[Optional[d else: return None, None - def get_immunization_and_resource_meta_by_id( + def get_immunization_resource_and_metadata_by_id( self, imms_id: str, include_deleted: bool = False ) -> tuple[Optional[dict], Optional[ImmunizationRecordMetadata]]: - """Retrieves the immunization and resource metadata from the VEDS table""" + """Retrieves the immunization resource and metadata from the VEDS table""" response = self.table.get_item(Key={"PK": _make_immunization_pk(imms_id)}) item = response.get("Item") @@ -126,7 +132,18 @@ def get_immunization_and_resource_meta_by_id( if is_deleted and not include_deleted: return None, None - imms_record_meta = ImmunizationRecordMetadata(int(item.get("Version")), is_deleted, is_reinstated) + # The FHIR Identifier which is returned in the metadata is based on the IdentifierPK from the database because + # we keep this attribute up to date in case of any changes rather than modifying the JSON resource. For example, + # when we performed the V2 to V5 data migration as part of issue VED-893. + + identifier_pk = item.get("IdentifierPK") + + if identifier_pk is None: + raise InvalidStoredDataError(data_type="identifier") + + identifier = get_fhir_identifier_from_identifier_pk(identifier_pk) + + imms_record_meta = ImmunizationRecordMetadata(identifier, int(item.get("Version")), is_deleted, is_reinstated) return json.loads(item.get("Resource", {})), imms_record_meta @@ -223,27 +240,21 @@ def _perform_dynamo_update( else Attr("PK").eq(attr.pk) & Attr("DeletedAt").not_exists() ) + expression_attribute_values = { + ":timestamp": attr.timestamp, + ":patient_pk": attr.patient_pk, + ":patient_sk": attr.patient_sk, + ":imms_resource_val": json.dumps(attr.resource, use_decimal=True), + ":operation": "UPDATE", + ":version": updated_version, + ":supplier_system": supplier_system, + } if reinstate_operation_required: - expression_attribute_values = { - ":timestamp": attr.timestamp, - ":patient_pk": attr.patient_pk, - ":patient_sk": attr.patient_sk, - ":imms_resource_val": json.dumps(attr.resource, use_decimal=True), - ":operation": "UPDATE", - ":version": updated_version, - ":supplier_system": supplier_system, - ":respawn": "reinstated", - } - else: - expression_attribute_values = { - ":timestamp": attr.timestamp, - ":patient_pk": attr.patient_pk, - ":patient_sk": attr.patient_sk, - ":imms_resource_val": json.dumps(attr.resource, use_decimal=True), - ":operation": "UPDATE", - ":version": updated_version, - ":supplier_system": supplier_system, - } + expression_attribute_values.update( + { + ":respawn": "reinstated", + } + ) try: self.table.update_item( diff --git a/backend/src/search_imms_handler.py b/lambdas/backend/src/search_imms_handler.py similarity index 99% rename from backend/src/search_imms_handler.py rename to lambdas/backend/src/search_imms_handler.py index 2a8e50197..40a47bab3 100644 --- a/backend/src/search_imms_handler.py +++ b/lambdas/backend/src/search_imms_handler.py @@ -88,7 +88,7 @@ def search_imms(event: events.APIGatewayProxyEventV1, controller: FhirController ) parser.add_argument( "--immunization.target", - help="http://hl7.org/fhir/ValueSet/immunization-target-disease", + help="http://hl7.org/fhir/ValueSet/immunization-target-disease", # NOSONAR(S5332) type=str, required=True, nargs="+", diff --git a/backend/src/models/__init__.py b/lambdas/backend/src/service/__init__.py similarity index 100% rename from backend/src/models/__init__.py rename to lambdas/backend/src/service/__init__.py diff --git a/backend/src/service/fhir_service.py b/lambdas/backend/src/service/fhir_service.py similarity index 89% rename from backend/src/service/fhir_service.py rename to lambdas/backend/src/service/fhir_service.py index 520b078dc..075e9cb8f 100644 --- a/backend/src/service/fhir_service.py +++ b/lambdas/backend/src/service/fhir_service.py @@ -16,26 +16,29 @@ from fhir.resources.R4B.fhirtypes import Id from fhir.resources.R4B.identifier import Identifier from fhir.resources.R4B.immunization import Immunization -from pydantic import ValidationError import parameter_parser from authorisation.api_operation_code import ApiOperationCode from authorisation.authoriser import Authoriser -from filter import Filter -from models.errors import ( +from common.models.errors import ( CustomValidationError, IdentifierDuplicationError, MandatoryError, ResourceNotFoundError, - UnauthorizedVaxError, ) -from models.fhir_immunization import ImmunizationValidator -from models.utils.generic_utils import ( +from common.models.fhir_immunization import ImmunizationValidator +from common.models.utils.generic_utils import ( form_json, get_contained_patient, get_occurrence_datetime, ) -from models.utils.validation_utils import get_vaccine_type, validate_identifiers_match, validate_resource_versions_match +from common.models.utils.validation_utils import ( + get_vaccine_type, + validate_identifiers_match, + validate_resource_versions_match, +) +from filter import Filter +from models.errors import UnauthorizedVaxError from repository.fhir_repository import ImmunizationRepository logging.basicConfig(level="INFO") @@ -102,7 +105,7 @@ def get_immunization_and_version_by_id(self, imms_id: str, supplier_system: str) """ Get an Immunization by its ID. Returns the immunization entity and version number. """ - resource, resource_metadata = self.immunization_repo.get_immunization_and_resource_meta_by_id(imms_id) + resource, immunization_metadata = self.immunization_repo.get_immunization_resource_and_metadata_by_id(imms_id) if resource is None: raise ResourceNotFoundError(resource_type="Immunization", resource_id=imms_id) @@ -112,7 +115,7 @@ def get_immunization_and_version_by_id(self, imms_id: str, supplier_system: str) if not self.authoriser.authorise(supplier_system, ApiOperationCode.READ, {vaccination_type}): raise UnauthorizedVaxError() - return Immunization.parse_obj(resource), str(resource_metadata.resource_version) + return Immunization.parse_obj(resource), str(immunization_metadata.resource_version) def create_immunization(self, immunization: dict, supplier_system: str) -> Id: if immunization.get("id") is not None: @@ -120,7 +123,7 @@ def create_immunization(self, immunization: dict, supplier_system: str) -> Id: try: self.validator.validate(immunization) - except (ValidationError, ValueError, MandatoryError) as error: + except (ValueError, MandatoryError) as error: raise CustomValidationError(message=str(error)) from error vaccination_type = get_vaccine_type(immunization) @@ -142,14 +145,14 @@ def create_immunization(self, immunization: dict, supplier_system: str) -> Id: def update_immunization(self, imms_id: str, immunization: dict, supplier_system: str, resource_version: int) -> int: try: self.validator.validate(immunization) - except (ValidationError, ValueError, MandatoryError) as error: + except (ValueError, MandatoryError) as error: raise CustomValidationError(message=str(error)) from error - existing_immunization, existing_resource_meta = self.immunization_repo.get_immunization_and_resource_meta_by_id( - imms_id, include_deleted=True + existing_immunization_resource, existing_immunization_meta = ( + self.immunization_repo.get_immunization_resource_and_metadata_by_id(imms_id, include_deleted=True) ) - if not existing_immunization: + if not existing_immunization_resource: raise ResourceNotFoundError(resource_type="Immunization", resource_id=imms_id) # If the user is updating the resource vaccination_type, they must have permissions for both the existing and @@ -157,23 +160,30 @@ def update_immunization(self, imms_id: str, immunization: dict, supplier_system: if not self.authoriser.authorise( supplier_system, ApiOperationCode.UPDATE, - {get_vaccine_type(immunization), get_vaccine_type(existing_immunization)}, + {get_vaccine_type(immunization), get_vaccine_type(existing_immunization_resource)}, ): raise UnauthorizedVaxError() - validate_identifiers_match(immunization, existing_immunization) + identifier = Identifier.construct( + system=immunization["identifier"][0]["system"], + value=immunization["identifier"][0]["value"], + ) + + validate_identifiers_match(identifier, existing_immunization_meta.identifier) - if not existing_resource_meta.is_deleted: - validate_resource_versions_match(resource_version, existing_resource_meta.resource_version, imms_id) + if not existing_immunization_meta.is_deleted: + validate_resource_versions_match(resource_version, existing_immunization_meta.resource_version, imms_id) - return self.immunization_repo.update_immunization(imms_id, immunization, existing_resource_meta, supplier_system) + return self.immunization_repo.update_immunization( + imms_id, immunization, existing_immunization_meta, supplier_system + ) def delete_immunization(self, imms_id: str, supplier_system: str) -> None: """ Delete an Immunization if it exists and return the ID back if successful. An exception will be raised if the resource does not exist. """ - existing_immunisation, _ = self.immunization_repo.get_immunization_and_resource_meta_by_id(imms_id) + existing_immunisation, _ = self.immunization_repo.get_immunization_resource_and_metadata_by_id(imms_id) if not existing_immunisation: raise ResourceNotFoundError(resource_type="Immunization", resource_id=imms_id) diff --git a/backend/src/timer.py b/lambdas/backend/src/timer.py similarity index 100% rename from backend/src/timer.py rename to lambdas/backend/src/timer.py diff --git a/backend/src/update_imms_handler.py b/lambdas/backend/src/update_imms_handler.py similarity index 100% rename from backend/src/update_imms_handler.py rename to lambdas/backend/src/update_imms_handler.py diff --git a/backend/src/models/utils/__init__.py b/lambdas/backend/src/utils/__init__.py similarity index 100% rename from backend/src/models/utils/__init__.py rename to lambdas/backend/src/utils/__init__.py diff --git a/backend/src/utils/dict_utils.py b/lambdas/backend/src/utils/dict_utils.py similarity index 100% rename from backend/src/utils/dict_utils.py rename to lambdas/backend/src/utils/dict_utils.py diff --git a/backend/src/repository/__init__.py b/lambdas/backend/tests/__init__.py similarity index 100% rename from backend/src/repository/__init__.py rename to lambdas/backend/tests/__init__.py diff --git a/backend/src/service/__init__.py b/lambdas/backend/tests/authorisation/__init__.py similarity index 100% rename from backend/src/service/__init__.py rename to lambdas/backend/tests/authorisation/__init__.py diff --git a/backend/tests/authorisation/test_authoriser.py b/lambdas/backend/tests/authorisation/test_authoriser.py similarity index 70% rename from backend/tests/authorisation/test_authoriser.py rename to lambdas/backend/tests/authorisation/test_authoriser.py index b8707eed9..541ee5773 100644 --- a/backend/tests/authorisation/test_authoriser.py +++ b/lambdas/backend/tests/authorisation/test_authoriser.py @@ -1,5 +1,5 @@ import unittest -from unittest.mock import patch +from unittest.mock import Mock, patch from authorisation.api_operation_code import ApiOperationCode from authorisation.authoriser import Authoriser @@ -9,7 +9,8 @@ class TestAuthoriser(unittest.TestCase): MOCK_SUPPLIER_NAME = "TestSupplier" def setUp(self): - self.cache_client_patcher = patch("authorisation.authoriser.redis_client") + self.mock_redis = Mock() + self.cache_client_patcher = patch("authorisation.authoriser.get_redis_client") self.mock_cache_client = self.cache_client_patcher.start() self.logger_patcher = patch("authorisation.authoriser.logger") @@ -22,36 +23,39 @@ def tearDown(self): def test_authorise_returns_true_if_supplier_has_permissions(self): """Authoriser().authorise should return true if the supplier has the required permissions""" - self.mock_cache_client.hget.return_value = '["COVID.RS"]' + self.mock_redis.hget.return_value = '["COVID.RS"]' + self.mock_cache_client.return_value = self.mock_redis result = self.test_authoriser.authorise(self.MOCK_SUPPLIER_NAME, ApiOperationCode.READ, {"COVID"}) self.assertTrue(result) - self.mock_cache_client.hget.assert_called_once_with("supplier_permissions", self.MOCK_SUPPLIER_NAME) + self.mock_redis.hget.assert_called_once_with("supplier_permissions", self.MOCK_SUPPLIER_NAME) self.mock_logger.info.assert_called_once_with( "operation: r, supplier_permissions: {'covid': ['r', 's']}, vaccine_types: {'COVID'}" ) def test_authorise_returns_false_if_supplier_does_not_have_any_permissions(self): """Authoriser().authorise should return false if the supplier does not have any permissions in the cache""" - self.mock_cache_client.hget.return_value = "" + self.mock_redis.hget.return_value = "" + self.mock_cache_client.return_value = self.mock_redis result = self.test_authoriser.authorise(self.MOCK_SUPPLIER_NAME, ApiOperationCode.CREATE, {"COVID"}) self.assertFalse(result) - self.mock_cache_client.hget.assert_called_once_with("supplier_permissions", self.MOCK_SUPPLIER_NAME) + self.mock_redis.hget.assert_called_once_with("supplier_permissions", self.MOCK_SUPPLIER_NAME) self.mock_logger.info.assert_called_once_with("operation: c, supplier_permissions: {}, vaccine_types: {'COVID'}") def test_authorise_returns_false_if_supplier_does_not_have_permission_for_operation( self, ): """Authoriser().authorise should return false if the supplier does not have permission for the operation""" - self.mock_cache_client.hget.return_value = '["COVID.RS"]' + self.mock_redis.hget.return_value = '["COVID.RS"]' + self.mock_cache_client.return_value = self.mock_redis result = self.test_authoriser.authorise(self.MOCK_SUPPLIER_NAME, ApiOperationCode.CREATE, {"COVID"}) self.assertFalse(result) - self.mock_cache_client.hget.assert_called_once_with("supplier_permissions", self.MOCK_SUPPLIER_NAME) + self.mock_redis.hget.assert_called_once_with("supplier_permissions", self.MOCK_SUPPLIER_NAME) self.mock_logger.info.assert_called_once_with( "operation: c, supplier_permissions: {'covid': ['r', 's']}, vaccine_types: {'COVID'}" ) @@ -59,12 +63,13 @@ def test_authorise_returns_false_if_supplier_does_not_have_permission_for_operat def test_authorise_returns_false_if_no_permission_for_vaccination_type(self): """Authoriser().authorise should return false if the supplier does not have permission for the vaccination type""" - self.mock_cache_client.hget.return_value = '["COVID.RS"]' + self.mock_redis.hget.return_value = '["COVID.RS"]' + self.mock_cache_client.return_value = self.mock_redis result = self.test_authoriser.authorise(self.MOCK_SUPPLIER_NAME, ApiOperationCode.READ, {"FLU"}) self.assertFalse(result) - self.mock_cache_client.hget.assert_called_once_with("supplier_permissions", self.MOCK_SUPPLIER_NAME) + self.mock_redis.hget.assert_called_once_with("supplier_permissions", self.MOCK_SUPPLIER_NAME) self.mock_logger.info.assert_called_once_with( "operation: r, supplier_permissions: {'covid': ['r', 's']}, vaccine_types: {'FLU'}" ) @@ -72,18 +77,20 @@ def test_authorise_returns_false_if_no_permission_for_vaccination_type(self): def test_authorise_returns_false_multiple_vaccs_scenario(self): """Authoriser().authorise should return false if the supplier is missing a permission for any of the vaccs in the list provided""" - self.mock_cache_client.hget.return_value = '["COVID.RS", "FLU.CRUDS"]' + self.mock_redis.hget.return_value = '["COVID.RS", "FLU.CRUDS"]' + self.mock_cache_client.return_value = self.mock_redis result = self.test_authoriser.authorise(self.MOCK_SUPPLIER_NAME, ApiOperationCode.READ, {"FLU", "COVID", "RSV"}) self.assertFalse(result) - self.mock_cache_client.hget.assert_called_once_with("supplier_permissions", self.MOCK_SUPPLIER_NAME) + self.mock_redis.hget.assert_called_once_with("supplier_permissions", self.MOCK_SUPPLIER_NAME) def test_filter_permitted_vacc_types_returns_all_if_supplier_has_perms_for_all( self, ): """The same set of vaccination types will be returned if the supplier has the required permissions""" - self.mock_cache_client.hget.return_value = '["COVID.RS", "FLU.CRUDS", "RSV.CRUDS"]' + self.mock_redis.hget.return_value = '["COVID.RS", "FLU.CRUDS", "RSV.CRUDS"]' + self.mock_cache_client.return_value = self.mock_redis requested_vacc_types = {"FLU", "COVID", "RSV"} result = self.test_authoriser.filter_permitted_vacc_types( @@ -91,7 +98,7 @@ def test_filter_permitted_vacc_types_returns_all_if_supplier_has_perms_for_all( ) self.assertSetEqual(result, requested_vacc_types) - self.mock_cache_client.hget.assert_called_once_with("supplier_permissions", self.MOCK_SUPPLIER_NAME) + self.mock_redis.hget.assert_called_once_with("supplier_permissions", self.MOCK_SUPPLIER_NAME) self.assertNotEqual(id(requested_vacc_types), id(result)) def test_filter_permitted_vacc_types_removes_any_vacc_types_that_the_supplier_cannot_interact_with( @@ -99,11 +106,12 @@ def test_filter_permitted_vacc_types_removes_any_vacc_types_that_the_supplier_ca ): """Filter permitted vacc types method will filter out any vaccination types that the user cannot interact with""" - self.mock_cache_client.hget.return_value = '["COVID.RS", "FLU.CRUDS", "RSV.R"]' + self.mock_redis.hget.return_value = '["COVID.RS", "FLU.CRUDS", "RSV.R"]' + self.mock_cache_client.return_value = self.mock_redis result = self.test_authoriser.filter_permitted_vacc_types( self.MOCK_SUPPLIER_NAME, ApiOperationCode.SEARCH, {"FLU", "COVID", "RSV"} ) self.assertSetEqual(result, {"FLU", "COVID"}) - self.mock_cache_client.hget.assert_called_once_with("supplier_permissions", self.MOCK_SUPPLIER_NAME) + self.mock_redis.hget.assert_called_once_with("supplier_permissions", self.MOCK_SUPPLIER_NAME) diff --git a/backend/src/utils/__init__.py b/lambdas/backend/tests/controller/__init__.py similarity index 100% rename from backend/src/utils/__init__.py rename to lambdas/backend/tests/controller/__init__.py diff --git a/backend/tests/controller/test_fhir_api_exception_handler.py b/lambdas/backend/tests/controller/test_fhir_api_exception_handler.py similarity index 88% rename from backend/tests/controller/test_fhir_api_exception_handler.py rename to lambdas/backend/tests/controller/test_fhir_api_exception_handler.py index 654aef254..b0b6718c6 100644 --- a/backend/tests/controller/test_fhir_api_exception_handler.py +++ b/lambdas/backend/tests/controller/test_fhir_api_exception_handler.py @@ -2,17 +2,20 @@ import unittest from unittest.mock import patch -from controller.fhir_api_exception_handler import fhir_api_exception_handler -from models.errors import ( +from common.models.errors import ( CustomValidationError, IdentifierDuplicationError, InconsistentIdentifierError, + InconsistentResourceVersionError, + ResourceNotFoundError, +) +from controller.fhir_api_exception_handler import fhir_api_exception_handler +from models.errors import ( InconsistentIdError, - InconsistentResourceVersion, InvalidJsonError, - InvalidResourceVersion, - ResourceNotFoundError, - ResourceVersionNotProvided, + InvalidResourceVersionError, + InvalidStoredDataError, + ResourceVersionNotProvidedError, UnauthorizedError, UnauthorizedVaxError, UnhandledResponseError, @@ -41,7 +44,7 @@ def test_exception_handler_handles_custom_exception_and_returns_fhir_response(se """Test that custom exceptions are handled by the wrapper and a valid response is returned to the client""" test_cases = [ ( - InconsistentResourceVersion("Resource versions do not match"), + InconsistentResourceVersionError("Resource versions do not match"), 400, "invariant", "Resource versions do not match", @@ -56,13 +59,13 @@ def test_exception_handler_handles_custom_exception_and_returns_fhir_response(se (InvalidJsonError("Invalid JSON provided"), 400, "invalid", "Invalid JSON provided"), (CustomValidationError("This field was invalid"), 400, "invariant", "This field was invalid"), ( - ResourceVersionNotProvided(resource_type="Immunization"), + ResourceVersionNotProvidedError(resource_type="Immunization"), 400, "invariant", "Validation errors: Immunization resource version not specified in the request headers", ), ( - InvalidResourceVersion(resource_version="badVersion"), + InvalidResourceVersionError(resource_version="badVersion"), 400, "invariant", "Validation errors: Immunization resource version:badVersion in the request headers is invalid.", @@ -92,6 +95,12 @@ def test_exception_handler_handles_custom_exception_and_returns_fhir_response(se "exception", "Critical error\n{'outcome': 'critical error'}", ), + ( + InvalidStoredDataError("data_type"), + 500, + "exception", + "Invalid data stored for immunization record: data_type", + ), ] for error, expected_status, expected_code, expected_message in test_cases: diff --git a/backend/tests/controller/test_fhir_controller.py b/lambdas/backend/tests/controller/test_fhir_controller.py similarity index 98% rename from backend/tests/controller/test_fhir_controller.py rename to lambdas/backend/tests/controller/test_fhir_controller.py index 7557adedb..a445efe5e 100644 --- a/backend/tests/controller/test_fhir_controller.py +++ b/lambdas/backend/tests/controller/test_fhir_controller.py @@ -10,20 +10,22 @@ from fhir.resources.R4B.bundle import Bundle from fhir.resources.R4B.immunization import Immunization +from common.models.errors import ( + CustomValidationError, + ResourceNotFoundError, +) from controller.aws_apig_response_utils import create_response from controller.fhir_controller import FhirController from models.errors import ( - CustomValidationError, - ParameterException, - ResourceNotFoundError, + ParameterExceptionError, UnauthorizedVaxError, UnhandledResponseError, ) from parameter_parser import patient_identifier_system, process_search_params from repository.fhir_repository import ImmunizationRepository from service.fhir_service import FhirService -from testing_utils.generic_utils import load_json_data -from testing_utils.immunization_utils import create_covid_immunization +from test_common.testing_utils.generic_utils import load_json_data +from test_common.testing_utils.immunization_utils import create_covid_immunization class TestFhirControllerBase(unittest.TestCase): @@ -31,13 +33,14 @@ class TestFhirControllerBase(unittest.TestCase): def setUp(self): super().setUp() - self.redis_patcher = patch("parameter_parser.redis_client") - self.mock_redis_client = self.redis_patcher.start() + self.mock_redis = Mock() + self.redis_getter_patcher = patch("parameter_parser.get_redis_client") + self.mock_redis_getter = self.redis_getter_patcher.start() self.logger_info_patcher = patch("logging.Logger.info") self.mock_logger_info = self.logger_info_patcher.start() def tearDown(self): - self.redis_patcher.stop() + self.redis_getter_patcher.stop() self.logger_info_patcher.stop() super().tearDown() @@ -1270,7 +1273,8 @@ def setUp(self): self.date_to_key = "-date.to" self.nhs_number_valid_value = "9000000009" self.patient_identifier_valid_value = f"{patient_identifier_system}|{self.nhs_number_valid_value}" - self.mock_redis_client.hkeys.return_value = self.MOCK_REDIS_V2D_HKEYS + self.mock_redis.hkeys.return_value = self.MOCK_REDIS_V2D_HKEYS + self.mock_redis_getter.return_value = self.mock_redis def test_get_search_immunizations(self): """it should search based on patient_identifier and immunization_target""" @@ -1537,7 +1541,8 @@ def test_post_search_immunizations_for_unauthorized_vaccine_type_search_403(self @patch("controller.fhir_controller.process_search_params", wraps=process_search_params) def test_uses_parameter_parser(self, process_search_params: Mock): - self.mock_redis_client.hkeys.return_value = self.MOCK_REDIS_V2D_HKEYS + self.mock_redis.hkeys.return_value = self.MOCK_REDIS_V2D_HKEYS + self.mock_redis_getter.return_value = self.mock_redis lambda_event = { "multiValueQueryStringParameters": { self.patient_identifier_key: ["https://fhir.nhs.uk/Id/nhs-number|9000000009"], @@ -1565,7 +1570,7 @@ def test_search_immunizations_returns_400_on_ParameterException_from_parameter_p } } - process_search_params.side_effect = ParameterException("Test") + process_search_params.side_effect = ParameterExceptionError("Test") response = self.controller.search_immunizations(lambda_event) # Then diff --git a/backend/tests/__init__.py b/lambdas/backend/tests/repository/__init__.py similarity index 100% rename from backend/tests/__init__.py rename to lambdas/backend/tests/repository/__init__.py diff --git a/backend/tests/repository/test_fhir_repository.py b/lambdas/backend/tests/repository/test_fhir_repository.py similarity index 80% rename from backend/tests/repository/test_fhir_repository.py rename to lambdas/backend/tests/repository/test_fhir_repository.py index 07bea3a4d..2258ea810 100644 --- a/backend/tests/repository/test_fhir_repository.py +++ b/lambdas/backend/tests/repository/test_fhir_repository.py @@ -1,22 +1,21 @@ import time import unittest import uuid -from unittest.mock import ANY, MagicMock, patch +from unittest.mock import ANY, MagicMock, Mock, patch import botocore.exceptions import simplejson as json from boto3.dynamodb.conditions import Attr, Key +from fhir.resources.R4B.identifier import Identifier from fhir.resources.R4B.immunization import Immunization -from models.errors import ( - ResourceNotFoundError, - UnhandledResponseError, -) -from models.immunization_record_metadata import ImmunizationRecordMetadata -from models.utils.validation_utils import get_vaccine_type +from common.models.errors import ResourceNotFoundError +from common.models.immunization_record_metadata import ImmunizationRecordMetadata +from common.models.utils.validation_utils import get_vaccine_type +from models.errors import InvalidStoredDataError, UnhandledResponseError from repository.fhir_repository import ImmunizationRepository -from testing_utils.generic_utils import update_target_disease_code -from testing_utils.immunization_utils import VALID_NHS_NUMBER, create_covid_immunization_dict +from test_common.testing_utils.generic_utils import update_target_disease_code +from test_common.testing_utils.immunization_utils import VALID_NHS_NUMBER, create_covid_immunization_dict def _make_immunization_pk(_id): @@ -32,8 +31,9 @@ class TestFhirRepositoryBase(unittest.TestCase): def setUp(self): super().setUp() - self.redis_patcher = patch("models.utils.validation_utils.redis_client") - self.mock_redis_client = self.redis_patcher.start() + self.mock_redis = Mock() + self.redis_getter_patcher = patch("common.models.utils.validation_utils.get_redis_client") + self.mock_redis_getter = self.redis_getter_patcher.start() self.logger_info_patcher = patch("logging.Logger.info") self.mock_logger_info = self.logger_info_patcher.start() @@ -138,37 +138,42 @@ def setUp(self): def tearDown(self): patch.stopall() - def test_get_immunization_by_id(self): - """it should find an Immunization by id""" + def test_get_immunization_resource_and_metadata_by_id(self): + """it should find an Immunization resource and metadata by id""" imms_id = "an-id" expected_resource = {"foo": "bar"} + expected_identifier = Identifier(system="supplier", value="uid") expected_version = 1 self.table.get_item = MagicMock( return_value={ "Item": { + "IdentifierPK": "supplier#uid", "Resource": json.dumps(expected_resource), "Version": expected_version, "PatientSK": "COVID#2516525251", } } ) - immunisation, resource_meta = self.repository.get_immunization_and_resource_meta_by_id(imms_id) + actual_resource, actual_metadata = self.repository.get_immunization_resource_and_metadata_by_id(imms_id) # Validate the results - self.assertDictEqual(expected_resource, immunisation) - self.assertEqual(resource_meta.resource_version, expected_version) - self.assertEqual(resource_meta.is_deleted, False) - self.assertEqual(resource_meta.is_reinstated, False) + self.assertDictEqual(expected_resource, actual_resource) + self.assertEqual(actual_metadata.identifier, expected_identifier) + self.assertEqual(actual_metadata.resource_version, expected_version) + self.assertEqual(actual_metadata.is_deleted, False) + self.assertEqual(actual_metadata.is_reinstated, False) self.table.get_item.assert_called_once_with(Key={"PK": _make_immunization_pk(imms_id)}) - def test_get_immunization_by_id_returns_reinstated_records(self): - """it should find an Immunization by id, including reinstated records by default""" + def test_get_immunization_resource_and_metadata_by_id_returns_reinstated_records(self): + """it should find an Immunization resource and metadata by id, including reinstated records by default""" imms_id = "an-id" expected_resource = {"foo": "bar"} + expected_identifier = Identifier(system="supplier", value="uid") expected_version = 1 self.table.get_item = MagicMock( return_value={ "Item": { + "IdentifierPK": "supplier#uid", "Resource": json.dumps(expected_resource), "Version": expected_version, "DeletedAt": "reinstated", @@ -176,23 +181,26 @@ def test_get_immunization_by_id_returns_reinstated_records(self): } } ) - immunisation, resource_meta = self.repository.get_immunization_and_resource_meta_by_id(imms_id) + actual_resource, actual_metadata = self.repository.get_immunization_resource_and_metadata_by_id(imms_id) # Validate the results - self.assertDictEqual(expected_resource, immunisation) - self.assertEqual(resource_meta.resource_version, expected_version) - self.assertEqual(resource_meta.is_deleted, False) - self.assertEqual(resource_meta.is_reinstated, True) + self.assertDictEqual(expected_resource, actual_resource) + self.assertEqual(actual_metadata.identifier, expected_identifier) + self.assertEqual(actual_metadata.resource_version, expected_version) + self.assertEqual(actual_metadata.is_deleted, False) + self.assertEqual(actual_metadata.is_reinstated, True) self.table.get_item.assert_called_once_with(Key={"PK": _make_immunization_pk(imms_id)}) - def test_get_immunization_by_id_returns_deleted_records_when_flag_is_set(self): - """it should find an Immunization by id, including deleted records when the include_deleted flag is set True""" + def test_get_immunization_resource_and_metadata_by_id_returns_deleted_records_when_flag_is_set(self): + """it should find an Immunization resource and metadata by id, including deleted records when the include_deleted flag is set True""" imms_id = "an-id" expected_resource = {"foo": "bar"} + expected_identifier = Identifier(system="supplier", value="uid") expected_version = 4 self.table.get_item = MagicMock( return_value={ "Item": { + "IdentifierPK": "supplier#uid", "Resource": json.dumps(expected_resource), "Version": expected_version, "DeletedAt": time.time(), @@ -200,23 +208,67 @@ def test_get_immunization_by_id_returns_deleted_records_when_flag_is_set(self): } } ) - immunisation, resource_meta = self.repository.get_immunization_and_resource_meta_by_id( + actual_resource, actual_metadata = self.repository.get_immunization_resource_and_metadata_by_id( imms_id, include_deleted=True ) # Validate the results - self.assertDictEqual(expected_resource, immunisation) - self.assertEqual(resource_meta.resource_version, expected_version) - self.assertEqual(resource_meta.is_deleted, True) - self.assertEqual(resource_meta.is_reinstated, False) + self.assertDictEqual(expected_resource, actual_resource) + self.assertEqual(actual_metadata.identifier, expected_identifier) + self.assertEqual(actual_metadata.resource_version, expected_version) + self.assertEqual(actual_metadata.is_deleted, True) + self.assertEqual(actual_metadata.is_reinstated, False) self.table.get_item.assert_called_once_with(Key={"PK": _make_immunization_pk(imms_id)}) + def test_get_immunization_resource_and_metadata_by_id_raises_invalid_stored_data_error_when_idpk_is_none(self): + """it should raise an InvalidStoredData error when stored IdentifierPK is none""" + imms_id = "an-id" + expected_resource = {"foo": "bar"} + expected_version = 1 + self.table.get_item = MagicMock( + return_value={ + "Item": { + "Resource": json.dumps(expected_resource), + "Version": expected_version, + "PatientSK": "COVID19#2516525251", + } + } + ) + + with self.assertRaises(InvalidStoredDataError) as error: + # When + _, _ = self.repository.get_immunization_resource_and_metadata_by_id(imms_id) + + self.assertEqual(str(error.exception), "Invalid data stored for immunization record: identifier") + + def test_get_immunization_resource_and_metadata_by_id_raises_invalid_stored_data_error_when_idpk_is_invalid(self): + """it should raise an InvalidStoredData error when stored IdentifierPK is invalid""" + imms_id = "an-id" + expected_resource = {"foo": "bar"} + expected_version = 1 + self.table.get_item = MagicMock( + return_value={ + "Item": { + "IdentifierPK": "bad-data", + "Resource": json.dumps(expected_resource), + "Version": expected_version, + "PatientSK": "COVID19#2516525251", + } + } + ) + + with self.assertRaises(InvalidStoredDataError) as error: + # When + _, _ = self.repository.get_immunization_resource_and_metadata_by_id(imms_id) + + self.assertEqual(str(error.exception), "Invalid data stored for immunization record: identifier") + def test_immunization_not_found(self): """it should return None if Immunization doesn't exist""" imms_id = "non-existent-id" self.table.get_item = MagicMock(return_value={}) - imms, version = self.repository.get_immunization_and_resource_meta_by_id(imms_id) + imms, version = self.repository.get_immunization_resource_and_metadata_by_id(imms_id) self.assertIsNone(imms) self.assertIsNone(version) @@ -226,7 +278,7 @@ def test_immunization_not_found_when_record_is_logically_deleted(self): imms_id = "a-deleted-id" self.table.get_item = MagicMock(return_value={"Item": {"Resource": "{}", "DeletedAt": time.time()}}) - imms, version = self.repository.get_immunization_and_resource_meta_by_id(imms_id, include_deleted=False) + imms, version = self.repository.get_immunization_resource_and_metadata_by_id(imms_id, include_deleted=False) self.assertIsNone(imms) self.assertIsNone(version) @@ -252,7 +304,8 @@ def test_create_immunization(self): imms = Immunization.parse_obj(create_covid_immunization_dict(imms_id=self._MOCK_CREATED_UUID)) self.table.put_item = MagicMock(return_value={"ResponseMetadata": {"HTTPStatusCode": 200}}) - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis created_id = self.repository.create_immunization(imms, "Test") @@ -339,7 +392,10 @@ def test_update_immunisation_is_successful(self): """it should update the immunisation record""" imms_id = "an-imms-id" imms = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER) - existing_record_metadata = ImmunizationRecordMetadata(resource_version=1, is_deleted=False, is_reinstated=False) + identifier = Identifier(system=imms["identifier"][0]["system"], value=imms["identifier"][0]["value"]) + existing_record_metadata = ImmunizationRecordMetadata( + identifier=identifier, resource_version=1, is_deleted=False, is_reinstated=False + ) dynamo_response = {"ResponseMetadata": {"HTTPStatusCode": 200}} self.table.update_item = MagicMock(return_value=dynamo_response) @@ -378,7 +434,10 @@ def test_update_immunisation_is_successful_when_record_needs_to_be_reinstated(se """it should reinstate a deleted record when requested via the update operation""" imms_id = "an-imms-id" imms = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER) - existing_record_metadata = ImmunizationRecordMetadata(resource_version=2, is_deleted=True, is_reinstated=False) + identifier = Identifier(system=imms["identifier"][0]["system"], value=imms["identifier"][0]["value"]) + existing_record_metadata = ImmunizationRecordMetadata( + identifier=identifier, resource_version=2, is_deleted=True, is_reinstated=False + ) dynamo_response = {"ResponseMetadata": {"HTTPStatusCode": 200}} self.table.update_item = MagicMock(return_value=dynamo_response) @@ -419,7 +478,10 @@ def test_update_throws_error_when_response_can_not_be_handled(self): condition, as a check is made first to retrieve the record.""" imms_id = "an-id" imms = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER) - existing_record_metadata = ImmunizationRecordMetadata(resource_version=2, is_deleted=True, is_reinstated=False) + identifier = Identifier(system=imms["identifier"][0]["system"], value=imms["identifier"][0]["value"]) + existing_record_metadata = ImmunizationRecordMetadata( + identifier=identifier, resource_version=2, is_deleted=True, is_reinstated=False + ) error_res = {"Error": {"Code": "ConditionalCheckFailedException"}} self.table.update_item.side_effect = botocore.exceptions.ClientError( @@ -647,7 +709,10 @@ def test_decimal_on_create(self): def run_update_immunization_test(self, imms_id, imms, updated_dose_quantity=None): dynamo_response = {"ResponseMetadata": {"HTTPStatusCode": 200}} self.table.update_item = MagicMock(return_value=dynamo_response) - existing_record_metadata = ImmunizationRecordMetadata(resource_version=1, is_deleted=False, is_reinstated=False) + identifier = Identifier(system="supplier", value="uid") + existing_record_metadata = ImmunizationRecordMetadata( + identifier=identifier, resource_version=1, is_deleted=False, is_reinstated=False + ) # When updated_version = self.repository.update_immunization(imms_id, imms, existing_record_metadata, "Test") diff --git a/backend/tests/authorisation/__init__.py b/lambdas/backend/tests/service/__init__.py similarity index 100% rename from backend/tests/authorisation/__init__.py rename to lambdas/backend/tests/service/__init__.py diff --git a/backend/tests/service/test_fhir_service.py b/lambdas/backend/tests/service/test_fhir_service.py similarity index 85% rename from backend/tests/service/test_fhir_service.py rename to lambdas/backend/tests/service/test_fhir_service.py index 3d99418d9..c0eb4bb23 100644 --- a/backend/tests/service/test_fhir_service.py +++ b/lambdas/backend/tests/service/test_fhir_service.py @@ -5,43 +5,47 @@ import uuid from copy import deepcopy from decimal import Decimal -from unittest.mock import create_autospec, patch +from unittest.mock import Mock, create_autospec, patch from fhir.resources.R4B.bundle import Bundle as FhirBundle from fhir.resources.R4B.bundle import BundleEntry +from fhir.resources.R4B.identifier import Identifier from fhir.resources.R4B.immunization import Immunization from authorisation.api_operation_code import ApiOperationCode from authorisation.authoriser import Authoriser -from constants import NHS_NUMBER_USED_IN_SAMPLE_DATA -from models.errors import ( +from common.models.errors import ( CustomValidationError, IdentifierDuplicationError, InconsistentIdentifierError, - InconsistentResourceVersion, + InconsistentResourceVersionError, ResourceNotFoundError, - UnauthorizedVaxError, ) -from models.fhir_immunization import ImmunizationValidator -from models.immunization_record_metadata import ImmunizationRecordMetadata +from common.models.fhir_immunization import ImmunizationValidator +from common.models.immunization_record_metadata import ImmunizationRecordMetadata +from models.errors import UnauthorizedVaxError from repository.fhir_repository import ImmunizationRepository from service.fhir_service import FhirService, get_service_url -from testing_utils.generic_utils import load_json_data -from testing_utils.immunization_utils import ( +from test_common.testing_utils.generic_utils import load_json_data +from test_common.testing_utils.immunization_utils import ( VALID_NHS_NUMBER, create_covid_immunization, create_covid_immunization_dict, create_covid_immunization_dict_no_id, ) +# Constants for use within the tests +NHS_NUMBER_USED_IN_SAMPLE_DATA = "9000000009" + class TestFhirServiceBase(unittest.TestCase): """Base class for all tests to set up common fixtures""" def setUp(self): super().setUp() - self.redis_patcher = patch("models.utils.validation_utils.redis_client") - self.mock_redis_client = self.redis_patcher.start() + self.mock_redis = Mock() + self.redis_getter_patcher = patch("common.models.utils.validation_utils.get_redis_client") + self.mock_redis_getter = self.redis_getter_patcher.start() self.logger_info_patcher = patch("logging.Logger.info") self.mock_logger_info = self.logger_info_patcher.start() @@ -99,11 +103,17 @@ def tearDown(self): def test_get_immunization_by_id(self): """it should find an Immunization by id""" imms_id = "an-id" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis + immunisation_resource = create_covid_immunization(imms_id).dict() + identifier = Identifier( + system=immunisation_resource["identifier"][0]["system"], + value=immunisation_resource["identifier"][0]["value"], + ) self.authoriser.authorise.return_value = True - self.imms_repo.get_immunization_and_resource_meta_by_id.return_value = ( - create_covid_immunization(imms_id).dict(), - ImmunizationRecordMetadata(resource_version=1, is_deleted=False, is_reinstated=False), + self.imms_repo.get_immunization_resource_and_metadata_by_id.return_value = ( + immunisation_resource, + ImmunizationRecordMetadata(identifier=identifier, resource_version=1, is_deleted=False, is_reinstated=False), ) # When @@ -111,7 +121,7 @@ def test_get_immunization_by_id(self): # Then self.authoriser.authorise.assert_called_once_with("Test Supplier", ApiOperationCode.READ, {"COVID"}) - self.imms_repo.get_immunization_and_resource_meta_by_id.assert_called_once_with(imms_id) + self.imms_repo.get_immunization_resource_and_metadata_by_id.assert_called_once_with(imms_id) self.assertEqual(immunisation.id, imms_id) self.assertEqual(version, "1") @@ -119,14 +129,14 @@ def test_get_immunization_by_id(self): def test_immunization_not_found(self): """it should return None if Immunization doesn't exist""" imms_id = "non-existent-id" - self.imms_repo.get_immunization_and_resource_meta_by_id.return_value = None, None + self.imms_repo.get_immunization_resource_and_metadata_by_id.return_value = None, None # When with self.assertRaises(ResourceNotFoundError) as error: self.fhir_service.get_immunization_and_version_by_id(imms_id, "Test Supplier") # Then - self.imms_repo.get_immunization_and_resource_meta_by_id.assert_called_once_with(imms_id) + self.imms_repo.get_immunization_resource_and_metadata_by_id.assert_called_once_with(imms_id) self.assertEqual( "Immunization resource does not exist. ID: non-existent-id", str(error.exception), @@ -140,11 +150,16 @@ def test_get_immunization_by_id_patient_not_restricted(self): imms_id = "non_restricted_id" immunization_data = load_json_data("completed_covid_immunization_event.json") - self.mock_redis_client.hget.return_value = "COVID" + identifier = Identifier( + system=immunization_data["identifier"][0]["system"], value=immunization_data["identifier"][0]["value"] + ) + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis + self.authoriser.authorise.return_value = True - self.imms_repo.get_immunization_and_resource_meta_by_id.return_value = ( + self.imms_repo.get_immunization_resource_and_metadata_by_id.return_value = ( immunization_data, - ImmunizationRecordMetadata(resource_version=2, is_deleted=False, is_reinstated=False), + ImmunizationRecordMetadata(identifier=identifier, resource_version=2, is_deleted=False, is_reinstated=False), ) expected_imms = load_json_data("completed_covid_immunization_event_for_read.json") @@ -155,18 +170,24 @@ def test_get_immunization_by_id_patient_not_restricted(self): # Then self.authoriser.authorise.assert_called_once_with("Test Supplier", ApiOperationCode.READ, {"COVID"}) - self.imms_repo.get_immunization_and_resource_meta_by_id.assert_called_once_with(imms_id) + self.imms_repo.get_immunization_resource_and_metadata_by_id.assert_called_once_with(imms_id) self.assertEqual(actual_output, expected_output) self.assertEqual(version, "2") def test_unauthorised_error_raised_when_user_lacks_permissions(self): """it should throw an exception when user lacks permissions""" imms_id = "an-id" - self.mock_redis_client.hget.return_value = "COVID" + immunisation_resource = create_covid_immunization(imms_id).dict() + identifier = Identifier( + system=immunisation_resource["identifier"][0]["system"], + value=immunisation_resource["identifier"][0]["value"], + ) + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis self.authoriser.authorise.return_value = False - self.imms_repo.get_immunization_and_resource_meta_by_id.return_value = ( - create_covid_immunization(imms_id).dict(), - ImmunizationRecordMetadata(resource_version=1, is_deleted=False, is_reinstated=False), + self.imms_repo.get_immunization_resource_and_metadata_by_id.return_value = ( + immunisation_resource, + ImmunizationRecordMetadata(identifier=identifier, resource_version=1, is_deleted=False, is_reinstated=False), ) with self.assertRaises(UnauthorizedVaxError): @@ -175,7 +196,7 @@ def test_unauthorised_error_raised_when_user_lacks_permissions(self): # Then self.authoriser.authorise.assert_called_once_with("Test Supplier", ApiOperationCode.READ, {"COVID"}) - self.imms_repo.get_immunization_and_resource_meta_by_id.assert_called_once_with(imms_id) + self.imms_repo.get_immunization_resource_and_metadata_by_id.assert_called_once_with(imms_id) class TestGetImmunizationIdentifier(unittest.TestCase): @@ -288,7 +309,8 @@ def setUp(self): def test_create_immunization(self): """it should create Immunization and validate it""" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis self.authoriser.authorise.return_value = True self.imms_repo.check_immunization_identifier_exists.return_value = False self.imms_repo.create_immunization.return_value = self._MOCK_NEW_UUID @@ -338,7 +360,8 @@ def test_pre_validation_failed(self): def test_post_validation_failed_create_invalid_target_disease(self): """it should raise CustomValidationError for invalid target disease code on create""" - self.mock_redis_client.hget.return_value = None + self.mock_redis.hget.return_value = None + self.mock_redis_getter.return_value = self.mock_redis valid_imms = create_covid_immunization_dict_no_id(VALID_NHS_NUMBER) bad_target_disease_imms = deepcopy(valid_imms) @@ -358,7 +381,8 @@ def test_post_validation_failed_create_invalid_target_disease(self): def test_post_validation_failed_create_missing_patient_name(self): """it should raise CustomValidationError for missing patient name on create""" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis valid_imms = create_covid_immunization_dict_no_id(VALID_NHS_NUMBER) bad_patient_name_imms = deepcopy(valid_imms) @@ -391,7 +415,8 @@ def test_patient_error(self): def test_unauthorised_error_raised_when_user_lacks_permissions(self): """it should raise error when user lacks permissions""" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis self.authoriser.authorise.return_value = False self.imms_repo.create_immunization.return_value = create_covid_immunization_dict_no_id() @@ -409,7 +434,8 @@ def test_unauthorised_error_raised_when_user_lacks_permissions(self): def test_raises_duplicate_error_if_identifier_already_exits(self): """it should raise a duplicate error if the immunisation identifier (system + local ID) already exists""" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis self.authoriser.authorise.return_value = True self.imms_repo.check_immunization_identifier_exists.return_value = True @@ -441,16 +467,23 @@ def setUp(self): self.authoriser = create_autospec(Authoriser) self.imms_repo = create_autospec(ImmunizationRepository) self.fhir_service = FhirService(self.imms_repo, self.authoriser) - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis def test_update_immunization(self): """it should update Immunization and validate NHS number""" imms_id = "an-id" original_immunisation = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER) + identifier = Identifier( + system=original_immunisation["identifier"][0]["system"], + value=original_immunisation["identifier"][0]["value"], + ) updated_immunisation = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER, "2021-02-07T13:28:00+00:00") - existing_resource_meta = ImmunizationRecordMetadata(resource_version=1, is_deleted=False, is_reinstated=False) + existing_resource_meta = ImmunizationRecordMetadata( + identifier=identifier, resource_version=1, is_deleted=False, is_reinstated=False + ) - self.imms_repo.get_immunization_and_resource_meta_by_id.return_value = ( + self.imms_repo.get_immunization_resource_and_metadata_by_id.return_value = ( original_immunisation, existing_resource_meta, ) @@ -462,7 +495,9 @@ def test_update_immunization(self): # Then self.assertEqual(updated_version, 2) - self.imms_repo.get_immunization_and_resource_meta_by_id.assert_called_once_with(imms_id, include_deleted=True) + self.imms_repo.get_immunization_resource_and_metadata_by_id.assert_called_once_with( + imms_id, include_deleted=True + ) self.imms_repo.update_immunization.assert_called_once_with( imms_id, updated_immunisation, existing_resource_meta, "Test" ) @@ -478,7 +513,7 @@ def test_update_immunization_raises_validation_exception_when_nhs_number_invalid self.fhir_service.update_immunization(imms_id, invalid_imms, "Test", 1) # Then - self.imms_repo.get_immunization_and_resource_meta_by_id.assert_not_called() + self.imms_repo.get_immunization_resource_and_metadata_by_id.assert_not_called() self.imms_repo.update_immunization.assert_not_called() self.assertEqual( error.exception.message, @@ -490,14 +525,16 @@ def test_update_immunization_raises_not_found_error_when_no_existing_immunisatio imms_id = "non-existent-id-123" requested_imms = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER) - self.imms_repo.get_immunization_and_resource_meta_by_id.return_value = (None, None) + self.imms_repo.get_immunization_resource_and_metadata_by_id.return_value = (None, None) # When with self.assertRaises(ResourceNotFoundError) as error: self.fhir_service.update_immunization(imms_id, requested_imms, "Test", 1) # Then - self.imms_repo.get_immunization_and_resource_meta_by_id.assert_called_once_with(imms_id, include_deleted=True) + self.imms_repo.get_immunization_resource_and_metadata_by_id.assert_called_once_with( + imms_id, include_deleted=True + ) self.imms_repo.update_immunization.assert_not_called() self.assertEqual(str(error.exception), "Immunization resource does not exist. ID: non-existent-id-123") @@ -506,11 +543,15 @@ def test_update_immunization_raises_unauthorized_exception_when_user_lacks_permi interaction with the target vaccination""" imms_id = "test-id" original_immunisation = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER) + identifier = Identifier( + system=original_immunisation["identifier"][0]["system"], + value=original_immunisation["identifier"][0]["value"], + ) updated_immunisation = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER, "2021-02-07T13:28:00+00:00") - self.imms_repo.get_immunization_and_resource_meta_by_id.return_value = ( + self.imms_repo.get_immunization_resource_and_metadata_by_id.return_value = ( original_immunisation, - ImmunizationRecordMetadata(resource_version=1, is_deleted=False, is_reinstated=True), + ImmunizationRecordMetadata(identifier=identifier, resource_version=1, is_deleted=False, is_reinstated=True), ) self.authoriser.authorise.return_value = False @@ -519,29 +560,35 @@ def test_update_immunization_raises_unauthorized_exception_when_user_lacks_permi self.fhir_service.update_immunization(imms_id, updated_immunisation, "Test", 1) # Then - self.imms_repo.get_immunization_and_resource_meta_by_id.assert_called_once_with(imms_id, include_deleted=True) + self.imms_repo.get_immunization_resource_and_metadata_by_id.assert_called_once_with( + imms_id, include_deleted=True + ) self.imms_repo.update_immunization.assert_not_called() def test_update_immunization_raises_invalid_error_if_identifiers_do_not_match(self): - """it should raise an InconsistentIdentifierError if the local identifier in the update does not match that in - the stored resource""" + """it should raise an InconsistentIdentifierError if the local identifier in the update does not match the + IdentifierPK stored in the database""" imms_id = "test-id" - original_immunisation = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER) - original_immunisation["identifier"][0]["system"] = "legacyUri.com" - updated_immunisation = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER, "2021-02-07T13:28:00+00:00") + immunisation_resource = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER) + identifier = Identifier( + system="legacyUri.com", + value=immunisation_resource["identifier"][0]["value"], + ) - self.imms_repo.get_immunization_and_resource_meta_by_id.return_value = ( - original_immunisation, - ImmunizationRecordMetadata(resource_version=1, is_deleted=False, is_reinstated=False), + self.imms_repo.get_immunization_resource_and_metadata_by_id.return_value = ( + immunisation_resource, + ImmunizationRecordMetadata(identifier=identifier, resource_version=1, is_deleted=False, is_reinstated=False), ) self.authoriser.authorise.return_value = True # When with self.assertRaises(InconsistentIdentifierError) as error: - self.fhir_service.update_immunization(imms_id, updated_immunisation, "Test", 1) + self.fhir_service.update_immunization(imms_id, immunisation_resource, "Test", 1) # Then - self.imms_repo.get_immunization_and_resource_meta_by_id.assert_called_once_with(imms_id, include_deleted=True) + self.imms_repo.get_immunization_resource_and_metadata_by_id.assert_called_once_with( + imms_id, include_deleted=True + ) self.imms_repo.update_immunization.assert_not_called() self.assertEqual( str(error.exception), "Validation errors: identifier[0].system doesn't match with the stored content" @@ -552,20 +599,26 @@ def test_update_immunization_raises_invalid_error_if_resource_version_does_not_m the current version of the stored resource""" imms_id = "test-id" original_immunisation = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER) + identifier = Identifier( + system=original_immunisation["identifier"][0]["system"], + value=original_immunisation["identifier"][0]["value"], + ) updated_immunisation = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER, "2021-02-07T13:28:00+00:00") - self.imms_repo.get_immunization_and_resource_meta_by_id.return_value = ( + self.imms_repo.get_immunization_resource_and_metadata_by_id.return_value = ( original_immunisation, - ImmunizationRecordMetadata(resource_version=4, is_deleted=False, is_reinstated=False), + ImmunizationRecordMetadata(identifier=identifier, resource_version=4, is_deleted=False, is_reinstated=False), ) self.authoriser.authorise.return_value = True # When - with self.assertRaises(InconsistentResourceVersion) as error: + with self.assertRaises(InconsistentResourceVersionError) as error: self.fhir_service.update_immunization(imms_id, updated_immunisation, "Test", 2) # Then - self.imms_repo.get_immunization_and_resource_meta_by_id.assert_called_once_with(imms_id, include_deleted=True) + self.imms_repo.get_immunization_resource_and_metadata_by_id.assert_called_once_with( + imms_id, include_deleted=True + ) self.imms_repo.update_immunization.assert_not_called() self.assertEqual( str(error.exception), @@ -588,11 +641,13 @@ def setUp(self): def test_delete_immunization(self): """it should delete Immunization record""" imms = json.loads(create_covid_immunization(self.TEST_IMMUNISATION_ID).json()) - self.mock_redis_client.hget.return_value = "COVID" + identifier = Identifier(system=imms["identifier"][0]["system"], value=imms["identifier"][0]["value"]) + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis self.authoriser.authorise.return_value = True - self.imms_repo.get_immunization_and_resource_meta_by_id.return_value = ( + self.imms_repo.get_immunization_resource_and_metadata_by_id.return_value = ( imms, - ImmunizationRecordMetadata(resource_version=1, is_deleted=False, is_reinstated=False), + ImmunizationRecordMetadata(identifier=identifier, resource_version=1, is_deleted=False, is_reinstated=False), ) self.imms_repo.delete_immunization.return_value = None @@ -600,20 +655,20 @@ def test_delete_immunization(self): self.fhir_service.delete_immunization(self.TEST_IMMUNISATION_ID, "Test") # Then - self.imms_repo.get_immunization_and_resource_meta_by_id.assert_called_once_with(self.TEST_IMMUNISATION_ID) + self.imms_repo.get_immunization_resource_and_metadata_by_id.assert_called_once_with(self.TEST_IMMUNISATION_ID) self.imms_repo.delete_immunization.assert_called_once_with(self.TEST_IMMUNISATION_ID, "Test") self.authoriser.authorise.assert_called_once_with("Test", ApiOperationCode.DELETE, {"COVID"}) def test_delete_immunization_throws_not_found_exception_if_does_not_exist(self): """it should raise a ResourceNotFound exception if the immunisation does not exist""" - self.imms_repo.get_immunization_and_resource_meta_by_id.return_value = (None, None) + self.imms_repo.get_immunization_resource_and_metadata_by_id.return_value = (None, None) # When with self.assertRaises(ResourceNotFoundError): self.fhir_service.delete_immunization(self.TEST_IMMUNISATION_ID, "Test") # Then - self.imms_repo.get_immunization_and_resource_meta_by_id.assert_called_once_with(self.TEST_IMMUNISATION_ID) + self.imms_repo.get_immunization_resource_and_metadata_by_id.assert_called_once_with(self.TEST_IMMUNISATION_ID) self.imms_repo.delete_immunization.assert_not_called() def test_delete_immunization_throws_authorisation_exception_if_does_not_have_required_permissions( @@ -621,11 +676,13 @@ def test_delete_immunization_throws_authorisation_exception_if_does_not_have_req ): """it should raise an UnauthorizedVaxError when the client does not have permissions for the given vacc type""" imms = json.loads(create_covid_immunization(self.TEST_IMMUNISATION_ID).json()) - self.mock_redis_client.hget.return_value = "FLU" + identifier = Identifier(system=imms["identifier"][0]["system"], value=imms["identifier"][0]["value"]) + self.mock_redis.hget.return_value = "FLU" + self.mock_redis_getter.return_value = self.mock_redis self.authoriser.authorise.return_value = False - self.imms_repo.get_immunization_and_resource_meta_by_id.return_value = ( + self.imms_repo.get_immunization_resource_and_metadata_by_id.return_value = ( imms, - ImmunizationRecordMetadata(resource_version=2, is_deleted=False, is_reinstated=False), + ImmunizationRecordMetadata(identifier=identifier, resource_version=2, is_deleted=False, is_reinstated=False), ) # When @@ -633,7 +690,7 @@ def test_delete_immunization_throws_authorisation_exception_if_does_not_have_req self.fhir_service.delete_immunization(self.TEST_IMMUNISATION_ID, "Test") # Then - self.imms_repo.get_immunization_and_resource_meta_by_id.assert_called_once_with(self.TEST_IMMUNISATION_ID) + self.imms_repo.get_immunization_resource_and_metadata_by_id.assert_called_once_with(self.TEST_IMMUNISATION_ID) self.imms_repo.delete_immunization.assert_not_called() self.authoriser.authorise.assert_called_once_with("Test", ApiOperationCode.DELETE, {"FLU"}) diff --git a/backend/tests/test_create_imms.py b/lambdas/backend/tests/test_create_imms.py similarity index 100% rename from backend/tests/test_create_imms.py rename to lambdas/backend/tests/test_create_imms.py diff --git a/backend/tests/test_delete_imms.py b/lambdas/backend/tests/test_delete_imms.py similarity index 100% rename from backend/tests/test_delete_imms.py rename to lambdas/backend/tests/test_delete_imms.py diff --git a/lambdas/backend/tests/test_errors.py b/lambdas/backend/tests/test_errors.py new file mode 100644 index 000000000..d06d2a0e6 --- /dev/null +++ b/lambdas/backend/tests/test_errors.py @@ -0,0 +1,194 @@ +import unittest +from unittest.mock import patch + +import models.errors as errors +from models.errors import Code, Severity, create_operation_outcome + + +class TestApiErrors(unittest.TestCase): + def test_error_to_uk_core2(self): + code = Code.not_found + + severity = Severity.error + diag = "a-diagnostic" + error_id = "a-id" + + error = create_operation_outcome(resource_id=error_id, severity=severity, code=code, diagnostics=diag) + + issue = error["issue"][0] + self.assertEqual(error["id"], error_id) + self.assertEqual(issue["code"], "not-found") + self.assertEqual(issue["severity"], "error") + self.assertEqual(issue["diagnostics"], diag) + + +class TestErrors(unittest.TestCase): + def setUp(self): + TEST_UUID = "01234567-89ab-cdef-0123-4567890abcde" + # Patch uuid4 + self.uuid4_patch = patch("uuid.uuid4", return_value=TEST_UUID) + self.mock_uuid4 = self.uuid4_patch.start() + self.addCleanup(self.uuid4_patch.stop) + + def assert_response_message(self, context, response, message): + self.assertEqual(context.exception.response, response) + self.assertEqual(context.exception.message, message) + + def assert_resource_type_and_id(self, context, resource_type, resource_id): + self.assertEqual(context.exception.resource_type, resource_type) + self.assertEqual(context.exception.resource_id, resource_id) + + def assert_operation_outcome(self, outcome): + self.assertEqual(outcome.get("resourceType"), "OperationOutcome") + + def test_errors_unauthorized_error(self): + """Test correct operation of UnauthorizedError""" + + with self.assertRaises(errors.UnauthorizedError) as context: + raise errors.UnauthorizedError() + + outcome = context.exception.to_operation_outcome() + self.assert_operation_outcome(outcome) + issue = outcome.get("issue")[0] + self.assertEqual(issue.get("severity"), errors.Severity.error) + self.assertEqual(issue.get("code"), errors.Code.forbidden) + self.assertEqual(issue.get("diagnostics"), "Unauthorized request") + + def test_errors_unauthorized_vax_error(self): + """Test correct operation of UnauthorizedVaxError""" + + with self.assertRaises(errors.UnauthorizedVaxError) as context: + raise errors.UnauthorizedVaxError() + + outcome = context.exception.to_operation_outcome() + self.assert_operation_outcome(outcome) + issue = outcome.get("issue")[0] + self.assertEqual(issue.get("severity"), errors.Severity.error) + self.assertEqual(issue.get("code"), errors.Code.forbidden) + self.assertEqual(issue.get("diagnostics"), "Unauthorized request for vaccine type") + + def test_errors_resource_version_not_provided(self): + """Test correct operation of ResourceVersionNotProvided""" + test_resource_type = "test_resource_type" + + with self.assertRaises(errors.ResourceVersionNotProvidedError) as context: + raise errors.ResourceVersionNotProvidedError(test_resource_type) + self.assertEqual(context.exception.resource_type, test_resource_type) + outcome = context.exception.to_operation_outcome() + self.assert_operation_outcome(outcome) + issue = outcome.get("issue")[0] + self.assertEqual(issue.get("severity"), errors.Severity.error) + self.assertEqual(issue.get("code"), errors.Code.invariant) + self.assertEqual( + issue.get("diagnostics"), + f"Validation errors: {test_resource_type} resource version not specified in the request headers", + ) + + def test_errors_parameter_exception(self): + """Test correct operation of ParameterException""" + test_message = "test_message" + + with self.assertRaises(errors.ParameterExceptionError) as context: + raise errors.ParameterExceptionError(test_message) + self.assertEqual(context.exception.message, test_message) + self.assertEqual(str(context.exception), test_message) + + def test_errors_invalid_immunization_id(self): + """Test correct operation of InvalidImmunizationId""" + + with self.assertRaises(errors.InvalidImmunizationIdError) as context: + raise errors.InvalidImmunizationIdError() + + outcome = context.exception.to_operation_outcome() + self.assert_operation_outcome(outcome) + issue = outcome.get("issue")[0] + self.assertEqual(issue.get("severity"), errors.Severity.error) + self.assertEqual(issue.get("code"), errors.Code.invalid) + self.assertEqual( + issue.get("diagnostics"), + "Validation errors: the provided event ID is either missing or not in the expected format.", + ) + + def test_errors_invalid_resource_version(self): + """Test correct operation of InvalidResourceVersion""" + test_resource_version = "test_resource_version" + + with self.assertRaises(errors.InvalidResourceVersionError) as context: + raise errors.InvalidResourceVersionError(test_resource_version) + self.assertEqual(context.exception.resource_version, test_resource_version) + outcome = context.exception.to_operation_outcome() + self.assert_operation_outcome(outcome) + issue = outcome.get("issue")[0] + self.assertEqual(issue.get("severity"), errors.Severity.error) + self.assertEqual(issue.get("code"), errors.Code.invariant) + self.assertEqual( + issue.get("diagnostics"), + f"Validation errors: Immunization resource version:{test_resource_version} in the request headers is invalid.", + ) + + def test_errors_inconsistent_id_error(self): + """Test correct operation of InconsistentIdError""" + test_imms_id = "test_imms_id" + + with self.assertRaises(errors.InconsistentIdError) as context: + raise errors.InconsistentIdError(test_imms_id) + self.assertEqual(context.exception.imms_id, test_imms_id) + self.assertEqual( + str(context.exception), + f"Validation errors: The provided immunization id:{test_imms_id} doesn't match with the content of the request body", + ) + outcome = context.exception.to_operation_outcome() + self.assert_operation_outcome(outcome) + issue = outcome.get("issue")[0] + self.assertEqual(issue.get("severity"), errors.Severity.error) + self.assertEqual(issue.get("code"), errors.Code.invariant) + self.assertEqual( + issue.get("diagnostics"), + f"Validation errors: The provided immunization id:{test_imms_id} doesn't match with the content of the request body", + ) + + def test_errors_invalid_json_error(self): + """Test correct operation of InvalidJsonError""" + test_message = "test_message" + + with self.assertRaises(errors.InvalidJsonError) as context: + raise errors.InvalidJsonError(test_message) + self.assertEqual(context.exception.message, test_message) + + outcome = context.exception.to_operation_outcome() + self.assert_operation_outcome(outcome) + issue = outcome.get("issue")[0] + self.assertEqual(issue.get("severity"), errors.Severity.error) + self.assertEqual(issue.get("code"), errors.Code.invalid) + self.assertEqual(issue.get("diagnostics"), test_message) + + def test_errors_unhandled_response_error(self): + """Test correct operation of UnhandledResponseError""" + test_response = "test_response" + test_message = "test_message" + + with self.assertRaises(errors.UnhandledResponseError) as context: + raise errors.UnhandledResponseError(test_response, test_message) + self.assert_response_message(context, test_response, test_message) + self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") + outcome = context.exception.to_operation_outcome() + self.assert_operation_outcome(outcome) + issue = outcome.get("issue")[0] + self.assertEqual(issue.get("severity"), errors.Severity.error) + self.assertEqual(issue.get("code"), errors.Code.server_error) + self.assertEqual(issue.get("diagnostics"), f"{test_message}\n{test_response}") + + def test_errors_invalid_stored_data_error(self): + """Test correct operation of UnhandledResponseError""" + test_data_type = "test_data_type" + + with self.assertRaises(errors.InvalidStoredDataError) as context: + raise errors.InvalidStoredDataError(test_data_type) + + self.assertEqual(str(context.exception), f"Invalid data stored for immunization record: {test_data_type}") + outcome = context.exception.to_operation_outcome() + self.assert_operation_outcome(outcome) + issue = outcome.get("issue")[0] + self.assertEqual(issue.get("severity"), errors.Severity.error) + self.assertEqual(issue.get("code"), errors.Code.server_error) + self.assertEqual(issue.get("diagnostics"), f"Invalid data stored for immunization record: {test_data_type}") diff --git a/backend/tests/test_filter.py b/lambdas/backend/tests/test_filter.py similarity index 98% rename from backend/tests/test_filter.py rename to lambdas/backend/tests/test_filter.py index de19c6f8b..758f5adff 100644 --- a/backend/tests/test_filter.py +++ b/lambdas/backend/tests/test_filter.py @@ -4,7 +4,7 @@ from copy import deepcopy from uuid import uuid4 -from constants import Urls +from common.models.constants import Urls from filter import ( Filter, add_use_to_identifier, @@ -13,7 +13,7 @@ replace_address_postal_codes, replace_organization_values, ) -from testing_utils.generic_utils import load_json_data +from test_common.testing_utils.generic_utils import load_json_data class TestFilter(unittest.TestCase): diff --git a/backend/tests/test_get_imms.py b/lambdas/backend/tests/test_get_imms.py similarity index 100% rename from backend/tests/test_get_imms.py rename to lambdas/backend/tests/test_get_imms.py diff --git a/backend/tests/test_lambda_handler.py b/lambdas/backend/tests/test_lambda_handler.py similarity index 100% rename from backend/tests/test_lambda_handler.py rename to lambdas/backend/tests/test_lambda_handler.py diff --git a/backend/tests/test_log_structure_wrapper.py b/lambdas/backend/tests/test_log_structure_wrapper.py similarity index 89% rename from backend/tests/test_log_structure_wrapper.py rename to lambdas/backend/tests/test_log_structure_wrapper.py index f0e4afd66..3a4887054 100644 --- a/backend/tests/test_log_structure_wrapper.py +++ b/lambdas/backend/tests/test_log_structure_wrapper.py @@ -1,16 +1,17 @@ import json import unittest -from unittest.mock import patch +from unittest.mock import Mock, patch from log_structure import function_info -@patch("log_structure.firehose_logger") +@patch("log_structure.send_log_to_firehose") @patch("log_structure.logger") class TestFunctionInfoWrapper(unittest.TestCase): def setUp(self): - self.redis_patcher = patch("models.utils.validation_utils.redis_client") - self.mock_redis_client = self.redis_patcher.start() + self.mock_redis = Mock() + self.redis_getter_patcher = patch("common.models.utils.validation_utils.get_redis_client") + self.mock_redis_getter = self.redis_getter_patcher.start() def tearDown(self): patch.stopall() @@ -31,7 +32,7 @@ def extract_all_call_args_for_logger(self, mock_logger) -> list: + [args[0] for args, _ in mock_logger.error.call_args_list] ) - def test_successful_execution(self, mock_logger, mock_firehose_logger): + def test_successful_execution(self, mock_logger, mock_send_log_to_firehose): # Arrange test_correlation = "test_correlation" test_request = "test_request" @@ -39,7 +40,8 @@ def test_successful_execution(self, mock_logger, mock_firehose_logger): test_actual_path = "/test" test_resource_path = "/test" - self.mock_redis_client.hget.return_value = "FLU" + self.mock_redis.hget.return_value = "FLU" + self.mock_redis_getter.return_value = self.mock_redis wrapped_function = function_info(self.mock_success_function) event = { "headers": { @@ -58,7 +60,7 @@ def test_successful_execution(self, mock_logger, mock_firehose_logger): # Assert self.assertEqual(result, "Success") mock_logger.info.assert_called() - mock_firehose_logger.send_log.assert_called() + mock_send_log_to_firehose.assert_called() args, kwargs = mock_logger.info.call_args logged_info = json.loads(args[0]) @@ -73,7 +75,7 @@ def test_successful_execution(self, mock_logger, mock_firehose_logger): self.assertEqual(logged_info["local_id"], "12345^http://test") self.assertEqual(logged_info["vaccine_type"], "FLU") - def test_successful_execution_pii(self, mock_logger, mock_firehose_logger): + def test_successful_execution_pii(self, mock_logger, mock_send_log_to_firehose): """Pass personally identifiable information in an event, and ensure that it is not logged anywhere.""" # Arrange test_correlation = "test_correlation" @@ -82,7 +84,8 @@ def test_successful_execution_pii(self, mock_logger, mock_firehose_logger): test_actual_path = "/test" test_resource_path = "/test" - self.mock_redis_client.hget.return_value = "FLU" + self.mock_redis.hget.return_value = "FLU" + self.mock_redis_getter.return_value = self.mock_redis wrapped_function = function_info(self.mock_success_function) event = { "headers": { @@ -104,7 +107,7 @@ def test_successful_execution_pii(self, mock_logger, mock_firehose_logger): for logger_info in self.extract_all_call_args_for_logger(mock_logger): self.assertNotIn("9693632109", str(logger_info)) - def test_exception_handling(self, mock_logger, mock_firehose_logger): + def test_exception_handling(self, mock_logger, mock_send_log_to_firehose): # Arrange test_correlation = "failed_test_correlation" test_request = "failed_test_request" @@ -112,7 +115,8 @@ def test_exception_handling(self, mock_logger, mock_firehose_logger): test_actual_path = "/failed_test" test_resource_path = "/failed_test" - self.mock_redis_client.hget.return_value = "FLU" + self.mock_redis.hget.return_value = "FLU" + self.mock_redis_getter.return_value = self.mock_redis # Act decorated_function_raises = function_info(self.mock_function_raises) @@ -135,7 +139,7 @@ def test_exception_handling(self, mock_logger, mock_firehose_logger): # Assert mock_logger.exception.assert_called() - mock_firehose_logger.send_log.assert_called() + mock_send_log_to_firehose.assert_called() args, kwargs = mock_logger.exception.call_args logged_info = json.loads(args[0]) @@ -151,7 +155,7 @@ def test_exception_handling(self, mock_logger, mock_firehose_logger): self.assertEqual(logged_info["local_id"], "12345^http://test") self.assertEqual(logged_info["vaccine_type"], "FLU") - def test_body_missing(self, mock_logger, mock_firehose_logger): + def test_body_missing(self, mock_logger, mock_send_log_to_firehose): # Arrange test_correlation = "failed_test_correlation_body_missing" test_request = "failed_test_request_body_missing" @@ -185,7 +189,7 @@ def test_body_missing(self, mock_logger, mock_firehose_logger): self.assertNotIn("local_id", logged_info) self.assertNotIn("vaccine_type", logged_info) - def test_body_not_json(self, mock_logger, mock_firehose_logger): + def test_body_not_json(self, mock_logger, mock_send_log_to_firehose): # Arrange test_correlation = "failed_test_correlation_body_not_json" test_request = "failed_test_request_body_not_json" @@ -224,7 +228,7 @@ def test_body_not_json(self, mock_logger, mock_firehose_logger): self.assertNotIn("local_id", logged_info) self.assertNotIn("vaccine_type", logged_info) - def test_body_invalid_identifier(self, mock_logger, mock_firehose_logger): + def test_body_invalid_identifier(self, mock_logger, mock_send_log_to_firehose): # Arrange test_correlation = "failed_test_correlation_invalid_identifier" test_request = "failed_test_request_invalid_identifier" @@ -232,7 +236,8 @@ def test_body_invalid_identifier(self, mock_logger, mock_firehose_logger): test_actual_path = "/failed_test_invalid_identifier" test_resource_path = "/failed_test_invalid_identifier" - self.mock_redis_client.hget.return_value = "FLU" + self.mock_redis.hget.return_value = "FLU" + self.mock_redis_getter.return_value = self.mock_redis # Act decorated_function_raises = function_info(self.mock_function_raises) @@ -265,7 +270,7 @@ def test_body_invalid_identifier(self, mock_logger, mock_firehose_logger): self.assertNotIn("local_id", logged_info) self.assertEqual(logged_info["vaccine_type"], "FLU") - def test_body_invalid_protocol_applied(self, mock_logger, mock_firehose_logger): + def test_body_invalid_protocol_applied(self, mock_logger, mock_send_log_to_firehose): # Arrange test_correlation = "failed_test_correlation_invalid_protocol" test_request = "failed_test_request_invalid_protocol" @@ -273,7 +278,8 @@ def test_body_invalid_protocol_applied(self, mock_logger, mock_firehose_logger): test_actual_path = "/failed_test_invalid_protocol" test_resource_path = "/failed_test_invalid_protocol" - self.mock_redis_client.hget.return_value = "FLU" + self.mock_redis.hget.return_value = "FLU" + self.mock_redis_getter.return_value = self.mock_redis # Act decorated_function_raises = function_info(self.mock_function_raises) diff --git a/backend/tests/test_parameter_parser.py b/lambdas/backend/tests/test_parameter_parser.py similarity index 85% rename from backend/tests/test_parameter_parser.py rename to lambdas/backend/tests/test_parameter_parser.py index 2f265981c..9dbb1c4f3 100644 --- a/backend/tests/test_parameter_parser.py +++ b/lambdas/backend/tests/test_parameter_parser.py @@ -1,9 +1,9 @@ import base64 import datetime import unittest -from unittest.mock import create_autospec, patch +from unittest.mock import Mock, create_autospec, patch -from models.errors import ParameterException +from models.errors import ParameterExceptionError from parameter_parser import ( SearchParams, create_query_string, @@ -25,8 +25,9 @@ def setUp(self): self.date_to_key = "-date.to" self.logger_info_patcher = patch("logging.Logger.info") self.mock_logger_info = self.logger_info_patcher.start() - self.redis_patcher = patch("parameter_parser.redis_client") - self.mock_redis_client = self.redis_patcher.start() + self.mock_redis = Mock() + self.redis_getter_patcher = patch("parameter_parser.get_redis_client") + self.mock_redis_getter = self.redis_getter_patcher.start() def tearDown(self): patch.stopall() @@ -90,13 +91,13 @@ def test_process_params_does_not_allow_anded_params(self): "httpMethod": "POST", } - with self.assertRaises(ParameterException) as e: + with self.assertRaises(ParameterExceptionError) as e: process_params(lambda_event) self.assertEqual(str(e.exception), 'Parameters may not be duplicated. Use commas for "or".') def test_process_search_params_checks_patient_identifier_format(self): - with self.assertRaises(ParameterException) as e: + with self.assertRaises(ParameterExceptionError) as e: _ = process_search_params({self.patient_identifier_key: ["9000000009"]}) self.assertEqual( str(e.exception), @@ -104,7 +105,8 @@ def test_process_search_params_checks_patient_identifier_format(self): '"https://fhir.nhs.uk/Id/nhs-number|{NHS number}" ' 'e.g. "https://fhir.nhs.uk/Id/nhs-number|9000000009"', ) - self.mock_redis_client.hkeys.return_value = ["RSV"] + self.mock_redis.hkeys.return_value = ["RSV"] + self.mock_redis_getter.return_value = self.mock_redis process_search_params( { self.patient_identifier_key: ["https://fhir.nhs.uk/Id/nhs-number|9000000009"], @@ -114,8 +116,9 @@ def test_process_search_params_checks_patient_identifier_format(self): def test_process_search_params_whitelists_immunization_target(self): mock_redis_key = "RSV" - self.mock_redis_client.hkeys.return_value = [mock_redis_key] - with self.assertRaises(ParameterException) as e: + self.mock_redis.hkeys.return_value = [mock_redis_key] + self.mock_redis_getter.return_value = self.mock_redis + with self.assertRaises(ParameterExceptionError) as e: process_search_params( { self.patient_identifier_key: ["https://fhir.nhs.uk/Id/nhs-number|9000000009"], @@ -133,8 +136,8 @@ def test_process_search_params_whitelists_immunization_target(self): ) def test_process_search_params_immunization_target(self): - mock_redis_key = "RSV" - self.mock_redis_client.hkeys.return_value = [mock_redis_key] + self.mock_redis.hkeys.return_value = ["RSV"] + self.mock_redis_getter.return_value = self.mock_redis params = process_search_params( { self.patient_identifier_key: ["https://fhir.nhs.uk/Id/nhs-number|9000000009"], @@ -145,7 +148,8 @@ def test_process_search_params_immunization_target(self): self.assertIsNotNone(params) def test_search_params_date_from_must_be_before_date_to(self): - self.mock_redis_client.hkeys.return_value = ["RSV"] + self.mock_redis.hkeys.return_value = ["RSV"] + self.mock_redis_getter.return_value = self.mock_redis params = process_search_params( { self.patient_identifier_key: ["https://fhir.nhs.uk/Id/nhs-number|9000000009"], @@ -168,7 +172,7 @@ def test_search_params_date_from_must_be_before_date_to(self): self.assertIsNotNone(params) - with self.assertRaises(ParameterException) as e: + with self.assertRaises(ParameterExceptionError) as e: _ = process_search_params( { self.patient_identifier_key: ["https://fhir.nhs.uk/Id/nhs-number|9000000009"], @@ -184,8 +188,9 @@ def test_search_params_date_from_must_be_before_date_to(self): ) def test_process_search_params_immunization_target_is_mandatory(self): - self.mock_redis_client.hkeys.return_value = ["RSV"] - with self.assertRaises(ParameterException) as e: + self.mock_redis.hkeys.return_value = ["RSV"] + self.mock_redis_getter.return_value = self.mock_redis + with self.assertRaises(ParameterExceptionError) as e: _ = process_search_params( { self.patient_identifier_key: ["https://fhir.nhs.uk/Id/nhs-number|9000000009"], @@ -197,7 +202,7 @@ def test_process_search_params_immunization_target_is_mandatory(self): ) def test_process_search_params_patient_identifier_is_mandatory(self): - with self.assertRaises(ParameterException) as e: + with self.assertRaises(ParameterExceptionError) as e: _ = process_search_params( { self.immunization_target_key: ["a-disease-type"], @@ -238,7 +243,8 @@ def test_process_search_params_dedupes_immunization_targets_and_respects_include self, ): """Ensure duplicate immunization targets are deduped and include is preserved.""" - self.mock_redis_client.hkeys.return_value = ["RSV", "FLU"] + self.mock_redis.hkeys.return_value = ["RSV", "FLU"] + self.mock_redis_getter.return_value = self.mock_redis params = process_search_params( { @@ -257,9 +263,10 @@ def test_process_search_params_dedupes_immunization_targets_and_respects_include def test_process_search_params_aggregates_date_errors(self): """When multiple date-related errors occur they should be returned together.""" - self.mock_redis_client.hkeys.return_value = ["RSV"] + self.mock_redis.hkeys.return_value = ["RSV"] + self.mock_redis_getter.return_value = self.mock_redis - with self.assertRaises(ParameterException) as e: + with self.assertRaises(ParameterExceptionError) as e: process_search_params( { self.patient_identifier_key: ["https://fhir.nhs.uk/Id/nhs-number|9000000009"], @@ -278,9 +285,10 @@ def test_process_search_params_aggregates_date_errors(self): def test_process_search_params_invalid_nhs_number_is_rejected(self): """If the NHS number fails mod11 check a ParameterException is raised.""" # redis returns a valid vaccine type - self.mock_redis_client.hkeys.return_value = ["RSV"] + self.mock_redis.hkeys.return_value = ["RSV"] + self.mock_redis_getter.return_value = self.mock_redis - with self.assertRaises(ParameterException) as e: + with self.assertRaises(ParameterExceptionError) as e: process_search_params( { self.patient_identifier_key: ["https://fhir.nhs.uk/Id/nhs-number|1234567890"], # invalid mod11 @@ -295,9 +303,10 @@ def test_process_search_params_invalid_nhs_number_is_rejected(self): def test_process_search_params_invalid_include_value_is_rejected(self): """_include may only be 'Immunization:patient' if provided.""" - self.mock_redis_client.hkeys.return_value = ["RSV"] + self.mock_redis.hkeys.return_value = ["RSV"] + self.mock_redis_getter.return_value = self.mock_redis - with self.assertRaises(ParameterException) as e: + with self.assertRaises(ParameterExceptionError) as e: process_search_params( { self.patient_identifier_key: ["https://fhir.nhs.uk/Id/nhs-number|9000000009"], diff --git a/backend/tests/test_search_imms.py b/lambdas/backend/tests/test_search_imms.py similarity index 100% rename from backend/tests/test_search_imms.py rename to lambdas/backend/tests/test_search_imms.py diff --git a/backend/tests/test_update_imms.py b/lambdas/backend/tests/test_update_imms.py similarity index 100% rename from backend/tests/test_update_imms.py rename to lambdas/backend/tests/test_update_imms.py diff --git a/backend/tests/batch/__init__.py b/lambdas/backend/tests/utils/__init__.py similarity index 100% rename from backend/tests/batch/__init__.py rename to lambdas/backend/tests/utils/__init__.py diff --git a/backend/tests/utils/test_dict_utils.py b/lambdas/backend/tests/utils/test_dict_utils.py similarity index 100% rename from backend/tests/utils/test_dict_utils.py rename to lambdas/backend/tests/utils/test_dict_utils.py diff --git a/lambdas/batch_processor_filter/Makefile b/lambdas/batch_processor_filter/Makefile index 505dbb711..e70113cf2 100644 --- a/lambdas/batch_processor_filter/Makefile +++ b/lambdas/batch_processor_filter/Makefile @@ -1,11 +1,11 @@ TEST_ENV := @PYTHONPATH=src:tests:../shared/src build: - docker build -t imms-lambda-build -f Dockerfile . + docker build -t batchprocessor-lambda-build -f Dockerfile . package: build mkdir -p build - docker run --rm -v $(shell pwd)/build:/build imms-lambda-build + docker run --rm -v $(shell pwd)/build:/build batchprocessor-lambda-build test: $(TEST_ENV) python -m unittest diff --git a/lambdas/batch_processor_filter/poetry.lock b/lambdas/batch_processor_filter/poetry.lock index c1eece0a2..6c5a8a147 100644 --- a/lambdas/batch_processor_filter/poetry.lock +++ b/lambdas/batch_processor_filter/poetry.lock @@ -14,18 +14,18 @@ files = [ [[package]] name = "boto3" -version = "1.40.68" +version = "1.40.72" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.40.68-py3-none-any.whl", hash = "sha256:4f08115e3a4d1e1056003e433d393e78c20da6af7753409992bb33fb69f04186"}, - {file = "boto3-1.40.68.tar.gz", hash = "sha256:c7994989e5bbba071b7c742adfba35773cf03e87f5d3f9f2b0a18c1664417b61"}, + {file = "boto3-1.40.72-py3-none-any.whl", hash = "sha256:1063a295712f2605d3e463e4dc1fe32fce17cf77a0f4d3bb14249d68533ee856"}, + {file = "boto3-1.40.72.tar.gz", hash = "sha256:58d30dd5e046789a760db7a49f817650b8ff08d8d169e127976a61f44b7c59ad"}, ] [package.dependencies] -botocore = ">=1.40.68,<1.41.0" +botocore = ">=1.40.72,<1.41.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.14.0,<0.15.0" @@ -34,14 +34,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.40.68" +version = "1.40.72" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.40.68-py3-none-any.whl", hash = "sha256:9d514f9c9054e1af055f2cbe9e0d6771d407a600206d45a01b54d5f09538fecb"}, - {file = "botocore-1.40.68.tar.gz", hash = "sha256:28f41b463d9f012a711ee8b61d4e26cd14ee3b450b816d5dee849aa79155e856"}, + {file = "botocore-1.40.72-py3-none-any.whl", hash = "sha256:4f859e5aaf871fe59aac431d6bba59cc0c8ed8a38da2a6a5345700bdc5c74b32"}, + {file = "botocore-1.40.72.tar.gz", hash = "sha256:f69199ff6570695556e733fa052f2739e01e0c592c9b60f843f84c77ba3bcdf3"}, ] [package.dependencies] @@ -287,104 +287,104 @@ files = [ [[package]] name = "coverage" -version = "7.11.0" +version = "7.11.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, - {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, - {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, - {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, - {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, - {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, - {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, - {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, - {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, - {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, - {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, - {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, - {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, - {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, - {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, - {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, - {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, - {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, - {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, - {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, - {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, - {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, - {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, - {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e"}, + {file = "coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb"}, + {file = "coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060"}, + {file = "coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7"}, + {file = "coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55"}, + {file = "coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405"}, + {file = "coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e"}, + {file = "coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055"}, + {file = "coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094"}, + {file = "coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c"}, + {file = "coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2"}, + {file = "coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76"}, + {file = "coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c"}, + {file = "coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac"}, + {file = "coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131"}, + {file = "coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a"}, + {file = "coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86"}, + {file = "coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820"}, + {file = "coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237"}, + {file = "coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9"}, + {file = "coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd"}, + {file = "coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe"}, + {file = "coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b"}, ] [package.extras] @@ -894,4 +894,4 @@ test = ["pytest", "pytest-cov"] [metadata] lock-version = "2.1" python-versions = "~3.11" -content-hash = "0489c643ec24b827a3b15cba0df11a922cb31a3729e8ac5c40ca24f9d749907c" +content-hash = "c38a9514f87143927f04704e6e8e66b80e7159d18dcab9d168e61b7c293ed8bf" diff --git a/lambdas/batch_processor_filter/pyproject.toml b/lambdas/batch_processor_filter/pyproject.toml index c462b18a9..5a7da36d9 100644 --- a/lambdas/batch_processor_filter/pyproject.toml +++ b/lambdas/batch_processor_filter/pyproject.toml @@ -11,9 +11,9 @@ packages = [ [tool.poetry.dependencies] python = "~3.11" -coverage = "^7.10.7" +coverage = "^7.11.3" aws-lambda-typing = "~2.20.0" -boto3 = "~1.40.68" +boto3 = "~1.40.72" moto = "^5.1.16" [build-system] diff --git a/lambdas/delta_backend/poetry.lock b/lambdas/delta_backend/poetry.lock index 3c1ce4869..32db24592 100644 --- a/lambdas/delta_backend/poetry.lock +++ b/lambdas/delta_backend/poetry.lock @@ -2,18 +2,18 @@ [[package]] name = "boto3" -version = "1.40.68" +version = "1.40.72" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.40.68-py3-none-any.whl", hash = "sha256:4f08115e3a4d1e1056003e433d393e78c20da6af7753409992bb33fb69f04186"}, - {file = "boto3-1.40.68.tar.gz", hash = "sha256:c7994989e5bbba071b7c742adfba35773cf03e87f5d3f9f2b0a18c1664417b61"}, + {file = "boto3-1.40.72-py3-none-any.whl", hash = "sha256:1063a295712f2605d3e463e4dc1fe32fce17cf77a0f4d3bb14249d68533ee856"}, + {file = "boto3-1.40.72.tar.gz", hash = "sha256:58d30dd5e046789a760db7a49f817650b8ff08d8d169e127976a61f44b7c59ad"}, ] [package.dependencies] -botocore = ">=1.40.68,<1.41.0" +botocore = ">=1.40.72,<1.41.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.14.0,<0.15.0" @@ -22,14 +22,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.40.68" +version = "1.40.72" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.40.68-py3-none-any.whl", hash = "sha256:9d514f9c9054e1af055f2cbe9e0d6771d407a600206d45a01b54d5f09538fecb"}, - {file = "botocore-1.40.68.tar.gz", hash = "sha256:28f41b463d9f012a711ee8b61d4e26cd14ee3b450b816d5dee849aa79155e856"}, + {file = "botocore-1.40.72-py3-none-any.whl", hash = "sha256:4f859e5aaf871fe59aac431d6bba59cc0c8ed8a38da2a6a5345700bdc5c74b32"}, + {file = "botocore-1.40.72.tar.gz", hash = "sha256:f69199ff6570695556e733fa052f2739e01e0c592c9b60f843f84c77ba3bcdf3"}, ] [package.dependencies] @@ -275,104 +275,104 @@ files = [ [[package]] name = "coverage" -version = "7.11.0" +version = "7.11.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, - {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, - {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, - {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, - {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, - {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, - {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, - {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, - {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, - {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, - {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, - {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, - {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, - {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, - {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, - {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, - {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, - {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, - {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, - {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, - {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, - {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, - {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, - {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e"}, + {file = "coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb"}, + {file = "coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060"}, + {file = "coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7"}, + {file = "coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55"}, + {file = "coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405"}, + {file = "coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e"}, + {file = "coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055"}, + {file = "coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094"}, + {file = "coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c"}, + {file = "coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2"}, + {file = "coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76"}, + {file = "coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c"}, + {file = "coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac"}, + {file = "coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131"}, + {file = "coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a"}, + {file = "coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86"}, + {file = "coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820"}, + {file = "coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237"}, + {file = "coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9"}, + {file = "coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd"}, + {file = "coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe"}, + {file = "coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b"}, ] [package.extras] @@ -924,4 +924,4 @@ test = ["pytest", "pytest-cov"] [metadata] lock-version = "2.1" python-versions = "~3.11" -content-hash = "a4f5fd3297a18c75d4eaff8bcf813334a50832b9a752219b28d327a10b9eb847" +content-hash = "2685319e382e69e2d6badcc6b6240f3831ff053fc097690d86dfa8593d2b226e" diff --git a/lambdas/delta_backend/pyproject.toml b/lambdas/delta_backend/pyproject.toml index 2831664a5..f3ecb4c91 100644 --- a/lambdas/delta_backend/pyproject.toml +++ b/lambdas/delta_backend/pyproject.toml @@ -11,14 +11,14 @@ packages = [ [tool.poetry.dependencies] python = "~3.11" -boto3 = "~1.40.68" +boto3 = "~1.40.72" mypy-boto3-dynamodb = "^1.40.44" moto = "~5.1.16" python-stdnum = "^2.1" -coverage = "^7.10.7" +coverage = "^7.11.3" [tool.poetry.group.dev.dependencies] -coverage = "^7.10.7" +coverage = "^7.11.3" [build-system] requires = ["poetry-core"] diff --git a/lambdas/filenameprocessor/poetry.lock b/lambdas/filenameprocessor/poetry.lock index cc018d438..8d2cf57a3 100644 --- a/lambdas/filenameprocessor/poetry.lock +++ b/lambdas/filenameprocessor/poetry.lock @@ -27,18 +27,18 @@ files = [ [[package]] name = "boto3" -version = "1.40.68" +version = "1.40.72" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.40.68-py3-none-any.whl", hash = "sha256:4f08115e3a4d1e1056003e433d393e78c20da6af7753409992bb33fb69f04186"}, - {file = "boto3-1.40.68.tar.gz", hash = "sha256:c7994989e5bbba071b7c742adfba35773cf03e87f5d3f9f2b0a18c1664417b61"}, + {file = "boto3-1.40.72-py3-none-any.whl", hash = "sha256:1063a295712f2605d3e463e4dc1fe32fce17cf77a0f4d3bb14249d68533ee856"}, + {file = "boto3-1.40.72.tar.gz", hash = "sha256:58d30dd5e046789a760db7a49f817650b8ff08d8d169e127976a61f44b7c59ad"}, ] [package.dependencies] -botocore = ">=1.40.68,<1.41.0" +botocore = ">=1.40.72,<1.41.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.14.0,<0.15.0" @@ -47,14 +47,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "boto3-stubs-lite" -version = "1.40.64" -description = "Lite type annotations for boto3 1.40.64 generated with mypy-boto3-builder 8.11.0" +version = "1.40.72" +description = "Lite type annotations for boto3 1.40.72 generated with mypy-boto3-builder 8.12.0" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3_stubs_lite-1.40.64-py3-none-any.whl", hash = "sha256:21f8d859edfafb98bf9fb4c529349c40b92ef39c7f9f16eb741b993fde31530c"}, - {file = "boto3_stubs_lite-1.40.64.tar.gz", hash = "sha256:8c5a019bea442ba436675b61c76fe99d1d0faf846bea920768bf574b1df4f1eb"}, + {file = "boto3_stubs_lite-1.40.72-py3-none-any.whl", hash = "sha256:94713bd1ddffdde61f4252c7842476ea0072ad74dc9ca9d9eed012922ca92645"}, + {file = "boto3_stubs_lite-1.40.72.tar.gz", hash = "sha256:fcef6d54d945fccabf89c32db0d3d2a00b1727a02f7185ae162777e3387a099d"}, ] [package.dependencies] @@ -116,7 +116,7 @@ bedrock-data-automation-runtime = ["mypy-boto3-bedrock-data-automation-runtime ( bedrock-runtime = ["mypy-boto3-bedrock-runtime (>=1.40.0,<1.41.0)"] billing = ["mypy-boto3-billing (>=1.40.0,<1.41.0)"] billingconductor = ["mypy-boto3-billingconductor (>=1.40.0,<1.41.0)"] -boto3 = ["boto3 (==1.40.64)"] +boto3 = ["boto3 (==1.40.72)"] braket = ["mypy-boto3-braket (>=1.40.0,<1.41.0)"] budgets = ["mypy-boto3-budgets (>=1.40.0,<1.41.0)"] ce = ["mypy-boto3-ce (>=1.40.0,<1.41.0)"] @@ -477,14 +477,14 @@ xray = ["mypy-boto3-xray (>=1.40.0,<1.41.0)"] [[package]] name = "botocore" -version = "1.40.68" +version = "1.40.72" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.40.68-py3-none-any.whl", hash = "sha256:9d514f9c9054e1af055f2cbe9e0d6771d407a600206d45a01b54d5f09538fecb"}, - {file = "botocore-1.40.68.tar.gz", hash = "sha256:28f41b463d9f012a711ee8b61d4e26cd14ee3b450b816d5dee849aa79155e856"}, + {file = "botocore-1.40.72-py3-none-any.whl", hash = "sha256:4f859e5aaf871fe59aac431d6bba59cc0c8ed8a38da2a6a5345700bdc5c74b32"}, + {file = "botocore-1.40.72.tar.gz", hash = "sha256:f69199ff6570695556e733fa052f2739e01e0c592c9b60f843f84c77ba3bcdf3"}, ] [package.dependencies] @@ -730,104 +730,104 @@ files = [ [[package]] name = "coverage" -version = "7.11.0" +version = "7.11.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, - {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, - {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, - {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, - {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, - {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, - {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, - {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, - {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, - {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, - {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, - {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, - {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, - {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, - {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, - {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, - {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, - {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, - {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, - {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, - {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, - {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, - {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, - {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e"}, + {file = "coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb"}, + {file = "coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060"}, + {file = "coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7"}, + {file = "coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55"}, + {file = "coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405"}, + {file = "coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e"}, + {file = "coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055"}, + {file = "coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094"}, + {file = "coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c"}, + {file = "coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2"}, + {file = "coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76"}, + {file = "coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c"}, + {file = "coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac"}, + {file = "coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131"}, + {file = "coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a"}, + {file = "coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86"}, + {file = "coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820"}, + {file = "coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237"}, + {file = "coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9"}, + {file = "coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd"}, + {file = "coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe"}, + {file = "coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b"}, ] [package.extras] @@ -1769,4 +1769,4 @@ test = ["pytest", "pytest-cov"] [metadata] lock-version = "2.1" python-versions = "~3.11" -content-hash = "5d230f3cf1fbf1f15ab959eabb0568bab595324f40abca88a324cc56f3796940" +content-hash = "8a8a64d1b6b8adafe8fb5d4153f5f479761821fb3caab64d338b6a3ce83e4a7f" diff --git a/lambdas/filenameprocessor/pyproject.toml b/lambdas/filenameprocessor/pyproject.toml index fbc899e19..a9ddc2ae6 100644 --- a/lambdas/filenameprocessor/pyproject.toml +++ b/lambdas/filenameprocessor/pyproject.toml @@ -12,8 +12,8 @@ packages = [ [tool.poetry.dependencies] python = "~3.11" "fhir.resources" = "~7.0.2" -boto3 = "~1.40.68" -boto3-stubs-lite = { extras = ["dynamodb"], version = "~1.40.64" } +boto3 = "~1.40.72" +boto3-stubs-lite = { extras = ["dynamodb"], version = "~1.40.72" } aws-lambda-typing = "~2.20.0" moto = "^4" requests = "~2.32.5" @@ -26,7 +26,7 @@ jsonpath-ng = "^1.6.0" simplejson = "^3.20.2" structlog = "^24.1.0" redis = "^5.1.1" -coverage = "^7.10.7" +coverage = "^7.11.3" freezegun = "^1.5.5" fakeredis = "^2.32.1" diff --git a/lambdas/filenameprocessor/src/constants.py b/lambdas/filenameprocessor/src/constants.py index f3add0a91..7388b0c3e 100644 --- a/lambdas/filenameprocessor/src/constants.py +++ b/lambdas/filenameprocessor/src/constants.py @@ -3,9 +3,9 @@ import os from enum import StrEnum -from common.models.errors import ( +from common.models.errors import UnhandledAuditTableError +from models.errors import ( InvalidFileKeyError, - UnhandledAuditTableError, UnhandledSqsError, VaccineTypePermissionsError, ) @@ -15,7 +15,6 @@ AUDIT_TABLE_TTL_DAYS = os.getenv("AUDIT_TABLE_TTL_DAYS") VALID_VERSIONS = ["V5"] -SUPPLIER_PERMISSIONS_HASH_KEY = "supplier_permissions" VACCINE_TYPE_TO_DISEASES_HASH_KEY = "vacc_to_diseases" ODS_CODE_TO_SUPPLIER_SYSTEM_HASH_KEY = "ods_code_to_supplier" diff --git a/lambdas/filenameprocessor/src/elasticache.py b/lambdas/filenameprocessor/src/elasticache.py index 3b83a521f..6dd14e357 100644 --- a/lambdas/filenameprocessor/src/elasticache.py +++ b/lambdas/filenameprocessor/src/elasticache.py @@ -1,9 +1,9 @@ import json +from common.models.constants import SUPPLIER_PERMISSIONS_HASH_KEY from common.redis_client import get_redis_client from constants import ( ODS_CODE_TO_SUPPLIER_SYSTEM_HASH_KEY, - SUPPLIER_PERMISSIONS_HASH_KEY, VACCINE_TYPE_TO_DISEASES_HASH_KEY, ) diff --git a/lambdas/filenameprocessor/src/file_name_processor.py b/lambdas/filenameprocessor/src/file_name_processor.py index f0d21bdc8..5588f81eb 100644 --- a/lambdas/filenameprocessor/src/file_name_processor.py +++ b/lambdas/filenameprocessor/src/file_name_processor.py @@ -10,14 +10,10 @@ from uuid import uuid4 from audit_table import upsert_audit_table -from common.clients import STREAM_NAME, logger, s3_client +from common.aws_s3_utils import move_file +from common.clients import STREAM_NAME, get_s3_client, logger from common.log_decorator import logging_decorator -from common.models.errors import ( - InvalidFileKeyError, - UnhandledAuditTableError, - UnhandledSqsError, - VaccineTypePermissionsError, -) +from common.models.errors import UnhandledAuditTableError from constants import ( ERROR_TYPE_TO_STATUS_CODE_MAP, SOURCE_BUCKET_NAME, @@ -26,9 +22,14 @@ ) from file_validation import is_file_in_directory_root, validate_file_key from make_and_upload_ack_file import make_and_upload_the_ack_file +from models.errors import ( + InvalidFileKeyError, + UnhandledSqsError, + VaccineTypePermissionsError, +) from send_sqs_message import make_and_send_sqs_message from supplier_permissions import validate_vaccine_type_permissions -from utils_for_filenameprocessor import get_creation_and_expiry_times, move_file +from utils_for_filenameprocessor import get_creation_and_expiry_times # NOTE: logging_decorator is applied to handle_record function, rather than lambda_handler, because @@ -73,7 +74,7 @@ def handle_record(record) -> dict: try: message_id = str(uuid4()) - s3_response = s3_client.get_object(Bucket=bucket_name, Key=file_key) + s3_response = get_s3_client().get_object(Bucket=bucket_name, Key=file_key) created_at_formatted_string, expiry_timestamp = get_creation_and_expiry_times(s3_response) vaccine_type, supplier = validate_file_key(file_key) diff --git a/lambdas/filenameprocessor/src/file_validation.py b/lambdas/filenameprocessor/src/file_validation.py index fab6bfa2f..b13a23c43 100644 --- a/lambdas/filenameprocessor/src/file_validation.py +++ b/lambdas/filenameprocessor/src/file_validation.py @@ -3,12 +3,12 @@ from datetime import datetime from re import match -from common.models.errors import InvalidFileKeyError from constants import VALID_VERSIONS from elasticache import ( get_supplier_system_from_cache, get_valid_vaccine_types_from_cache, ) +from models.errors import InvalidFileKeyError def is_file_in_directory_root(file_key: str) -> bool: diff --git a/lambdas/filenameprocessor/src/make_and_upload_ack_file.py b/lambdas/filenameprocessor/src/make_and_upload_ack_file.py index 8b9ee1942..2ca5c1da5 100644 --- a/lambdas/filenameprocessor/src/make_and_upload_ack_file.py +++ b/lambdas/filenameprocessor/src/make_and_upload_ack_file.py @@ -4,7 +4,7 @@ from csv import writer from io import BytesIO, StringIO -from common.clients import s3_client +from common.clients import get_s3_client def make_the_ack_data(message_id: str, message_delivered: bool, created_at_formatted_string: str) -> dict: @@ -43,7 +43,7 @@ def upload_ack_file(file_key: str, ack_data: dict, created_at_formatted_string: csv_buffer.seek(0) csv_bytes = BytesIO(csv_buffer.getvalue().encode("utf-8")) ack_bucket_name = os.getenv("ACK_BUCKET_NAME") - s3_client.upload_fileobj(csv_bytes, ack_bucket_name, ack_filename) + get_s3_client().upload_fileobj(csv_bytes, ack_bucket_name, ack_filename) def make_and_upload_the_ack_file( diff --git a/lambdas/filenameprocessor/src/models/errors.py b/lambdas/filenameprocessor/src/models/errors.py new file mode 100644 index 000000000..eae58985a --- /dev/null +++ b/lambdas/filenameprocessor/src/models/errors.py @@ -0,0 +1,10 @@ +class VaccineTypePermissionsError(Exception): + """A custom exception for when the supplier does not have the necessary vaccine type permissions.""" + + +class InvalidFileKeyError(Exception): + """A custom exception for when the file key is invalid.""" + + +class UnhandledSqsError(Exception): + """A custom exception for when an unexpected error occurs whilst sending a message to SQS.""" diff --git a/lambdas/filenameprocessor/src/send_sqs_message.py b/lambdas/filenameprocessor/src/send_sqs_message.py index 6f42dd506..b3aeaa34d 100644 --- a/lambdas/filenameprocessor/src/send_sqs_message.py +++ b/lambdas/filenameprocessor/src/send_sqs_message.py @@ -4,7 +4,7 @@ from json import dumps as json_dumps from common.clients import logger, sqs_client -from common.models.errors import UnhandledSqsError +from models.errors import UnhandledSqsError def send_to_supplier_queue(message_body: dict, vaccine_type: str, supplier: str) -> None: diff --git a/lambdas/filenameprocessor/src/supplier_permissions.py b/lambdas/filenameprocessor/src/supplier_permissions.py index 048091c3f..03d7d934a 100644 --- a/lambdas/filenameprocessor/src/supplier_permissions.py +++ b/lambdas/filenameprocessor/src/supplier_permissions.py @@ -1,8 +1,8 @@ """Functions for fetching supplier permissions""" from common.clients import logger -from common.models.errors import VaccineTypePermissionsError from elasticache import get_supplier_permissions_from_cache +from models.errors import VaccineTypePermissionsError def validate_vaccine_type_permissions(vaccine_type: str, supplier: str) -> list: diff --git a/lambdas/filenameprocessor/src/utils_for_filenameprocessor.py b/lambdas/filenameprocessor/src/utils_for_filenameprocessor.py index 0123ffcd6..d84dd401f 100644 --- a/lambdas/filenameprocessor/src/utils_for_filenameprocessor.py +++ b/lambdas/filenameprocessor/src/utils_for_filenameprocessor.py @@ -2,7 +2,6 @@ from datetime import timedelta -from common.clients import logger, s3_client from constants import AUDIT_TABLE_TTL_DAYS @@ -12,14 +11,3 @@ def get_creation_and_expiry_times(s3_response: dict) -> (str, int): expiry_datetime = creation_datetime + timedelta(days=int(AUDIT_TABLE_TTL_DAYS)) expiry_timestamp = int(expiry_datetime.timestamp()) return creation_datetime.strftime("%Y%m%dT%H%M%S00"), expiry_timestamp - - -def move_file(bucket_name: str, source_file_key: str, destination_file_key: str) -> None: - """Moves a file from one location to another within a single S3 bucket by copying and then deleting the file.""" - s3_client.copy_object( - Bucket=bucket_name, - CopySource={"Bucket": bucket_name, "Key": source_file_key}, - Key=destination_file_key, - ) - s3_client.delete_object(Bucket=bucket_name, Key=source_file_key) - logger.info("File moved from %s to %s", source_file_key, destination_file_key) diff --git a/lambdas/filenameprocessor/tests/test_file_key_validation.py b/lambdas/filenameprocessor/tests/test_file_key_validation.py index 7b1e09530..a5b2e7de9 100644 --- a/lambdas/filenameprocessor/tests/test_file_key_validation.py +++ b/lambdas/filenameprocessor/tests/test_file_key_validation.py @@ -12,12 +12,12 @@ # Ensure environment variables are mocked before importing from src files with patch.dict("os.environ", MOCK_ENVIRONMENT_DICT): - from common.models.errors import InvalidFileKeyError from file_validation import ( is_file_in_directory_root, is_valid_datetime, validate_file_key, ) + from models.errors import InvalidFileKeyError VALID_FLU_EMIS_FILE_KEY = MockFileDetails.emis_flu.file_key VALID_RSV_RAVS_FILE_KEY = MockFileDetails.ravs_rsv_1.file_key diff --git a/lambdas/filenameprocessor/tests/test_send_sqs_message.py b/lambdas/filenameprocessor/tests/test_send_sqs_message.py index 7f268191f..3c2a442b1 100644 --- a/lambdas/filenameprocessor/tests/test_send_sqs_message.py +++ b/lambdas/filenameprocessor/tests/test_send_sqs_message.py @@ -14,7 +14,7 @@ # Ensure environment variables are mocked before importing from src files with patch.dict("os.environ", MOCK_ENVIRONMENT_DICT): from common.clients import REGION_NAME - from common.models.errors import UnhandledSqsError + from models.errors import UnhandledSqsError from send_sqs_message import make_and_send_sqs_message, send_to_supplier_queue sqs_client = boto3_client("sqs", region_name=REGION_NAME) diff --git a/lambdas/filenameprocessor/tests/test_supplier_permissions.py b/lambdas/filenameprocessor/tests/test_supplier_permissions.py index 7cba55adf..2b9968c0b 100644 --- a/lambdas/filenameprocessor/tests/test_supplier_permissions.py +++ b/lambdas/filenameprocessor/tests/test_supplier_permissions.py @@ -7,7 +7,7 @@ # Ensure environment variables are mocked before importing from src files with patch.dict("os.environ", MOCK_ENVIRONMENT_DICT): - from common.models.errors import VaccineTypePermissionsError + from models.errors import VaccineTypePermissionsError from supplier_permissions import validate_vaccine_type_permissions diff --git a/lambdas/filenameprocessor/tests/test_utils_for_filenameprocessor.py b/lambdas/filenameprocessor/tests/test_utils_for_filenameprocessor.py index d580f4eed..f77917515 100644 --- a/lambdas/filenameprocessor/tests/test_utils_for_filenameprocessor.py +++ b/lambdas/filenameprocessor/tests/test_utils_for_filenameprocessor.py @@ -9,7 +9,6 @@ from utils_for_tests.mock_environment_variables import ( MOCK_ENVIRONMENT_DICT, - BucketNames, ) from utils_for_tests.utils_for_filenameprocessor_tests import ( GenericSetUp, @@ -20,7 +19,7 @@ with patch.dict("os.environ", MOCK_ENVIRONMENT_DICT): from common.clients import REGION_NAME from constants import AUDIT_TABLE_TTL_DAYS - from utils_for_filenameprocessor import get_creation_and_expiry_times, move_file + from utils_for_filenameprocessor import get_creation_and_expiry_times s3_client = boto3_client("s3", region_name=REGION_NAME) @@ -50,20 +49,3 @@ def test_get_creation_and_expiry_times(self): self.assertEqual(created_at_formatted_string, expected_result_created_at) self.assertEqual(expires_at, expected_result_expires_at) - - def test_move_file(self): - """Tests that move_file correctly moves a file from one location to another within a single S3 bucket""" - source_file_key = "test_file_key" - destination_file_key = "destination/test_file_key" - source_file_content = "test_content" - s3_client.put_object(Bucket=BucketNames.SOURCE, Key=source_file_key, Body=source_file_content) - - move_file(BucketNames.SOURCE, source_file_key, destination_file_key) - - keys_of_objects_in_bucket = [ - obj["Key"] for obj in s3_client.list_objects_v2(Bucket=BucketNames.SOURCE).get("Contents") - ] - self.assertNotIn(source_file_key, keys_of_objects_in_bucket) - self.assertIn(destination_file_key, keys_of_objects_in_bucket) - destination_file_content = s3_client.get_object(Bucket=BucketNames.SOURCE, Key=destination_file_key) - self.assertEqual(destination_file_content["Body"].read().decode("utf-8"), source_file_content) diff --git a/lambdas/filenameprocessor/tests/utils_for_tests/utils_for_filenameprocessor_tests.py b/lambdas/filenameprocessor/tests/utils_for_tests/utils_for_filenameprocessor_tests.py index ef416dffb..65a17c58e 100644 --- a/lambdas/filenameprocessor/tests/utils_for_tests/utils_for_filenameprocessor_tests.py +++ b/lambdas/filenameprocessor/tests/utils_for_tests/utils_for_filenameprocessor_tests.py @@ -18,10 +18,10 @@ from csv import DictReader from common.clients import REGION_NAME + from common.models.constants import SUPPLIER_PERMISSIONS_HASH_KEY from constants import ( AUDIT_TABLE_NAME, ODS_CODE_TO_SUPPLIER_SYSTEM_HASH_KEY, - SUPPLIER_PERMISSIONS_HASH_KEY, AuditTableKeys, FileStatus, ) diff --git a/lambdas/id_sync/poetry.lock b/lambdas/id_sync/poetry.lock index b70c0eb71..b3cb68e4e 100644 --- a/lambdas/id_sync/poetry.lock +++ b/lambdas/id_sync/poetry.lock @@ -15,18 +15,18 @@ files = [ [[package]] name = "boto3" -version = "1.40.68" +version = "1.40.72" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.40.68-py3-none-any.whl", hash = "sha256:4f08115e3a4d1e1056003e433d393e78c20da6af7753409992bb33fb69f04186"}, - {file = "boto3-1.40.68.tar.gz", hash = "sha256:c7994989e5bbba071b7c742adfba35773cf03e87f5d3f9f2b0a18c1664417b61"}, + {file = "boto3-1.40.72-py3-none-any.whl", hash = "sha256:1063a295712f2605d3e463e4dc1fe32fce17cf77a0f4d3bb14249d68533ee856"}, + {file = "boto3-1.40.72.tar.gz", hash = "sha256:58d30dd5e046789a760db7a49f817650b8ff08d8d169e127976a61f44b7c59ad"}, ] [package.dependencies] -botocore = ">=1.40.68,<1.41.0" +botocore = ">=1.40.72,<1.41.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.14.0,<0.15.0" @@ -35,14 +35,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.40.68" +version = "1.40.72" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.40.68-py3-none-any.whl", hash = "sha256:9d514f9c9054e1af055f2cbe9e0d6771d407a600206d45a01b54d5f09538fecb"}, - {file = "botocore-1.40.68.tar.gz", hash = "sha256:28f41b463d9f012a711ee8b61d4e26cd14ee3b450b816d5dee849aa79155e856"}, + {file = "botocore-1.40.72-py3-none-any.whl", hash = "sha256:4f859e5aaf871fe59aac431d6bba59cc0c8ed8a38da2a6a5345700bdc5c74b32"}, + {file = "botocore-1.40.72.tar.gz", hash = "sha256:f69199ff6570695556e733fa052f2739e01e0c592c9b60f843f84c77ba3bcdf3"}, ] [package.dependencies] @@ -299,104 +299,104 @@ files = [ [[package]] name = "coverage" -version = "7.11.0" +version = "7.11.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, - {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, - {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, - {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, - {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, - {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, - {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, - {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, - {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, - {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, - {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, - {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, - {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, - {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, - {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, - {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, - {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, - {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, - {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, - {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, - {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, - {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, - {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, - {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e"}, + {file = "coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb"}, + {file = "coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060"}, + {file = "coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7"}, + {file = "coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55"}, + {file = "coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405"}, + {file = "coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e"}, + {file = "coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055"}, + {file = "coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094"}, + {file = "coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c"}, + {file = "coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2"}, + {file = "coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76"}, + {file = "coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c"}, + {file = "coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac"}, + {file = "coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131"}, + {file = "coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a"}, + {file = "coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86"}, + {file = "coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820"}, + {file = "coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237"}, + {file = "coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9"}, + {file = "coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd"}, + {file = "coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe"}, + {file = "coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b"}, ] [package.extras] @@ -985,4 +985,4 @@ test = ["pytest", "pytest-cov"] [metadata] lock-version = "2.1" python-versions = "~3.11" -content-hash = "05ddcbb69688c90cda60cb89cf75c48a2f67e5ed6e66cb57065af10bbb019666" +content-hash = "37bde3e95cb98aed26a35c5e056d2e495361c43310f307e14dcc679dd92b3133" diff --git a/lambdas/id_sync/pyproject.toml b/lambdas/id_sync/pyproject.toml index a4df02f15..839c9737c 100644 --- a/lambdas/id_sync/pyproject.toml +++ b/lambdas/id_sync/pyproject.toml @@ -19,17 +19,17 @@ packages = [ [tool.poetry.dependencies] python = "~3.11" -boto3 = "~1.40.68" +boto3 = "~1.40.72" mypy-boto3-dynamodb = "^1.40.44" moto = "~5.1.16" python-stdnum = "^2.1" -coverage = "^7.10.7" +coverage = "^7.11.3" redis = "^4.6.0" cache = "^1.0.3" pyjwt = "^2.10.1" [tool.poetry.group.dev.dependencies] -coverage = "^7.10.7" +coverage = "^7.11.3" [build-system] requires = ["poetry-core"] diff --git a/lambdas/mesh_processor/Makefile b/lambdas/mesh_processor/Makefile index e0e4d4283..045e979c8 100644 --- a/lambdas/mesh_processor/Makefile +++ b/lambdas/mesh_processor/Makefile @@ -1,3 +1,5 @@ +TEST_ENV := @PYTHONPATH=src:tests:../shared/src + build: docker build -t mesh-lambda-build . @@ -6,6 +8,15 @@ package: build docker run --rm -v $(shell pwd)/build:/build mesh-lambda-build test: - python -m unittest + $(TEST_ENV) python -m unittest + +coverage-run: + $(TEST_ENV) coverage run --source=src -m unittest discover + +coverage-report: + $(TEST_ENV) coverage report -m + +coverage-html: + $(TEST_ENV) coverage html .PHONY: build package test diff --git a/lambdas/mesh_processor/poetry.lock b/lambdas/mesh_processor/poetry.lock index 3a7a62f11..b46ce655e 100644 --- a/lambdas/mesh_processor/poetry.lock +++ b/lambdas/mesh_processor/poetry.lock @@ -2,18 +2,18 @@ [[package]] name = "boto3" -version = "1.40.68" +version = "1.40.72" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.40.68-py3-none-any.whl", hash = "sha256:4f08115e3a4d1e1056003e433d393e78c20da6af7753409992bb33fb69f04186"}, - {file = "boto3-1.40.68.tar.gz", hash = "sha256:c7994989e5bbba071b7c742adfba35773cf03e87f5d3f9f2b0a18c1664417b61"}, + {file = "boto3-1.40.72-py3-none-any.whl", hash = "sha256:1063a295712f2605d3e463e4dc1fe32fce17cf77a0f4d3bb14249d68533ee856"}, + {file = "boto3-1.40.72.tar.gz", hash = "sha256:58d30dd5e046789a760db7a49f817650b8ff08d8d169e127976a61f44b7c59ad"}, ] [package.dependencies] -botocore = ">=1.40.68,<1.41.0" +botocore = ">=1.40.72,<1.41.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.14.0,<0.15.0" @@ -22,14 +22,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.40.68" +version = "1.40.72" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.40.68-py3-none-any.whl", hash = "sha256:9d514f9c9054e1af055f2cbe9e0d6771d407a600206d45a01b54d5f09538fecb"}, - {file = "botocore-1.40.68.tar.gz", hash = "sha256:28f41b463d9f012a711ee8b61d4e26cd14ee3b450b816d5dee849aa79155e856"}, + {file = "botocore-1.40.72-py3-none-any.whl", hash = "sha256:4f859e5aaf871fe59aac431d6bba59cc0c8ed8a38da2a6a5345700bdc5c74b32"}, + {file = "botocore-1.40.72.tar.gz", hash = "sha256:f69199ff6570695556e733fa052f2739e01e0c592c9b60f843f84c77ba3bcdf3"}, ] [package.dependencies] @@ -275,104 +275,104 @@ files = [ [[package]] name = "coverage" -version = "7.11.0" +version = "7.11.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, - {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, - {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, - {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, - {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, - {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, - {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, - {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, - {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, - {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, - {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, - {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, - {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, - {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, - {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, - {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, - {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, - {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, - {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, - {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, - {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, - {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, - {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, - {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e"}, + {file = "coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb"}, + {file = "coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060"}, + {file = "coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7"}, + {file = "coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55"}, + {file = "coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405"}, + {file = "coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e"}, + {file = "coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055"}, + {file = "coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094"}, + {file = "coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c"}, + {file = "coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2"}, + {file = "coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76"}, + {file = "coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c"}, + {file = "coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac"}, + {file = "coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131"}, + {file = "coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a"}, + {file = "coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86"}, + {file = "coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820"}, + {file = "coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237"}, + {file = "coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9"}, + {file = "coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd"}, + {file = "coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe"}, + {file = "coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b"}, ] [package.extras] @@ -862,14 +862,14 @@ files = [ [[package]] name = "smart-open" -version = "7.4.4" +version = "7.5.0" description = "Utils for streaming large files (S3, HDFS, GCS, SFTP, Azure Blob Storage, gzip, bz2, zst...)" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] files = [ - {file = "smart_open-7.4.4-py3-none-any.whl", hash = "sha256:47077ed486a7e66d0bb928c284a8e5775c705092c6ea3e3bc6979d5b561c7bbf"}, - {file = "smart_open-7.4.4.tar.gz", hash = "sha256:2c264f43c55c2fcdea37b1752dcd06bb152afd514490a0aee5d21db0424b0669"}, + {file = "smart_open-7.5.0-py3-none-any.whl", hash = "sha256:87e695c5148bbb988f15cec00971602765874163be85acb1c9fb8abc012e6599"}, + {file = "smart_open-7.5.0.tar.gz", hash = "sha256:f394b143851d8091011832ac8113ea4aba6b92e6c35f6e677ddaaccb169d7cb9"}, ] [package.dependencies] @@ -1073,4 +1073,4 @@ test = ["pytest", "pytest-cov"] [metadata] lock-version = "2.1" python-versions = "~3.11" -content-hash = "48396f36332cbf7c02a669a356c478589041682ce41701e2e845d1c15a0f243b" +content-hash = "c3d29d4910ea02d12d9943a14ae9fe48a9e86b04180092b1eaa0c8ac8dfc8a65" diff --git a/lambdas/mesh_processor/pyproject.toml b/lambdas/mesh_processor/pyproject.toml index 2a157fe76..09b87ef29 100644 --- a/lambdas/mesh_processor/pyproject.toml +++ b/lambdas/mesh_processor/pyproject.toml @@ -8,11 +8,11 @@ packages = [{include = "src"}] [tool.poetry.dependencies] python = "~3.11" -boto3 = "~1.40.68" +boto3 = "~1.40.72" mypy-boto3-dynamodb = "^1.40.44" moto = {extras = ["s3"], version = "^5.1.16"} -coverage = "^7.10.7" -smart-open = {extras = ["s3"], version = "^7.4.4"} +coverage = "^7.11.3" +smart-open = {extras = ["s3"], version = "^7.5.0"} [build-system] requires = ["poetry-core"] diff --git a/lambdas/mesh_processor/src/converter.py b/lambdas/mesh_processor/src/converter.py index 2577aea7a..df86773cb 100644 --- a/lambdas/mesh_processor/src/converter.py +++ b/lambdas/mesh_processor/src/converter.py @@ -1,19 +1,14 @@ -import logging import os from typing import BinaryIO -import boto3 from smart_open import open +from common.clients import get_s3_client, logger + EXPECTED_BUCKET_OWNER_ACCOUNT = os.getenv("ACCOUNT_ID") DESTINATION_BUCKET_NAME = os.getenv("DESTINATION_BUCKET_NAME") UNEXPECTED_EOF_ERROR = "Unexpected EOF" -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger() - -s3_client = boto3.client("s3") - def parse_headers(headers_str: str) -> dict[str, str]: headers = dict( # NOSONAR(S7494) force this to dict @@ -74,6 +69,7 @@ def stream_part_body(input_file: BinaryIO, boundary: bytes, output_file: BinaryI def move_file(source_bucket: str, source_key: str, destination_bucket: str, destination_key: str) -> None: + s3_client = get_s3_client() s3_client.copy_object( CopySource={"Bucket": source_bucket, "Key": source_key}, Bucket=destination_bucket, @@ -89,6 +85,7 @@ def move_file(source_bucket: str, source_key: str, destination_bucket: str, dest def transfer_multipart_content(bucket_name: str, file_key: str, boundary: bytes, filename: str) -> None: + s3_client = get_s3_client() with open(f"s3://{bucket_name}/{file_key}", "rb", transport_params={"client": s3_client}) as input_file: read_until_part_start(input_file, boundary) @@ -122,6 +119,7 @@ def process_record(record: dict) -> None: file_key = record["s3"]["object"]["key"] logger.info(f"Processing {file_key}") + s3_client = get_s3_client() response = s3_client.head_object( Bucket=bucket_name, Key=file_key, diff --git a/lambdas/mns_subscription/poetry.lock b/lambdas/mns_subscription/poetry.lock index f64352f68..2285e1d22 100644 --- a/lambdas/mns_subscription/poetry.lock +++ b/lambdas/mns_subscription/poetry.lock @@ -2,18 +2,18 @@ [[package]] name = "boto3" -version = "1.40.68" +version = "1.40.72" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.40.68-py3-none-any.whl", hash = "sha256:4f08115e3a4d1e1056003e433d393e78c20da6af7753409992bb33fb69f04186"}, - {file = "boto3-1.40.68.tar.gz", hash = "sha256:c7994989e5bbba071b7c742adfba35773cf03e87f5d3f9f2b0a18c1664417b61"}, + {file = "boto3-1.40.72-py3-none-any.whl", hash = "sha256:1063a295712f2605d3e463e4dc1fe32fce17cf77a0f4d3bb14249d68533ee856"}, + {file = "boto3-1.40.72.tar.gz", hash = "sha256:58d30dd5e046789a760db7a49f817650b8ff08d8d169e127976a61f44b7c59ad"}, ] [package.dependencies] -botocore = ">=1.40.68,<1.41.0" +botocore = ">=1.40.72,<1.41.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.14.0,<0.15.0" @@ -22,14 +22,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.40.68" +version = "1.40.72" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.40.68-py3-none-any.whl", hash = "sha256:9d514f9c9054e1af055f2cbe9e0d6771d407a600206d45a01b54d5f09538fecb"}, - {file = "botocore-1.40.68.tar.gz", hash = "sha256:28f41b463d9f012a711ee8b61d4e26cd14ee3b450b816d5dee849aa79155e856"}, + {file = "botocore-1.40.72-py3-none-any.whl", hash = "sha256:4f859e5aaf871fe59aac431d6bba59cc0c8ed8a38da2a6a5345700bdc5c74b32"}, + {file = "botocore-1.40.72.tar.gz", hash = "sha256:f69199ff6570695556e733fa052f2739e01e0c592c9b60f843f84c77ba3bcdf3"}, ] [package.dependencies] @@ -275,104 +275,104 @@ files = [ [[package]] name = "coverage" -version = "7.11.0" +version = "7.11.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, - {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, - {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, - {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, - {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, - {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, - {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, - {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, - {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, - {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, - {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, - {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, - {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, - {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, - {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, - {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, - {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, - {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, - {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, - {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, - {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, - {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, - {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, - {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e"}, + {file = "coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb"}, + {file = "coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060"}, + {file = "coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7"}, + {file = "coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55"}, + {file = "coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405"}, + {file = "coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e"}, + {file = "coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055"}, + {file = "coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094"}, + {file = "coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c"}, + {file = "coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2"}, + {file = "coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76"}, + {file = "coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c"}, + {file = "coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac"}, + {file = "coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131"}, + {file = "coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a"}, + {file = "coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86"}, + {file = "coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820"}, + {file = "coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237"}, + {file = "coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9"}, + {file = "coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd"}, + {file = "coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe"}, + {file = "coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b"}, ] [package.extras] @@ -900,4 +900,4 @@ test = ["pytest", "pytest-cov"] [metadata] lock-version = "2.1" python-versions = "~3.11" -content-hash = "add441f77209c6d7501727bcc14b497ca11ec4d4d7c32669033544e819b2ef83" +content-hash = "780c1439cbaab4b2eb6ef7622925fbc7a4540893239d27ef82fb6db330edcb19" diff --git a/lambdas/mns_subscription/pyproject.toml b/lambdas/mns_subscription/pyproject.toml index 79021bff2..ab50e6e70 100644 --- a/lambdas/mns_subscription/pyproject.toml +++ b/lambdas/mns_subscription/pyproject.toml @@ -10,10 +10,10 @@ packages = [ [tool.poetry.dependencies] python = "~3.11" -boto3 = "~1.40.68" +boto3 = "~1.40.72" pyjwt = "~2.10.1" moto = "^5.1.16" -coverage = "^7.10.7" +coverage = "^7.11.3" requests = "~2.32.5" responses = "~0.25.8" diff --git a/lambdas/mns_subscription/src/mns_service.py b/lambdas/mns_subscription/src/mns_service.py index 3ffa38298..309d44898 100644 --- a/lambdas/mns_subscription/src/mns_service.py +++ b/lambdas/mns_subscription/src/mns_service.py @@ -7,13 +7,15 @@ from common.authentication import AppRestrictedAuth from common.models.errors import ( + ResourceNotFoundError, + UnhandledResponseError, +) +from models.errors import ( BadRequestError, ConflictError, - ResourceNotFoundError, ServerError, TokenValidationError, UnauthorizedError, - UnhandledResponseError, ) SQS_ARN = os.getenv("SQS_ARN") diff --git a/lambdas/mns_subscription/src/models/errors.py b/lambdas/mns_subscription/src/models/errors.py new file mode 100644 index 000000000..f85b7286e --- /dev/null +++ b/lambdas/mns_subscription/src/models/errors.py @@ -0,0 +1,99 @@ +import uuid +from dataclasses import dataclass + +from common.models.errors import Code, Severity, create_operation_outcome + + +@dataclass +class UnauthorizedError(RuntimeError): + response: dict | str + message: str + + def __str__(self): + return f"{self.message}\n{self.response}" + + @staticmethod + def to_operation_outcome() -> dict: + msg = "Unauthorized request" + return create_operation_outcome( + resource_id=str(uuid.uuid4()), + severity=Severity.error, + code=Code.forbidden, + diagnostics=msg, + ) + + +@dataclass +class TokenValidationError(RuntimeError): + response: dict | str + message: str + + def __str__(self): + return f"{self.message}\n{self.response}" + + @staticmethod + def to_operation_outcome() -> dict: + msg = "Missing/Invalid Token" + return create_operation_outcome( + resource_id=str(uuid.uuid4()), + severity=Severity.error, + code=Code.invalid, + diagnostics=msg, + ) + + +@dataclass +class ConflictError(RuntimeError): + response: dict | str + message: str + + def __str__(self): + return f"{self.message}\n{self.response}" + + @staticmethod + def to_operation_outcome() -> dict: + msg = "Conflict" + return create_operation_outcome( + resource_id=str(uuid.uuid4()), + severity=Severity.error, + code=Code.duplicate, + diagnostics=msg, + ) + + +@dataclass +class BadRequestError(RuntimeError): + """Use when payload is missing required parameters""" + + response: dict | str + message: str + + def __str__(self): + return f"{self.message}\n{self.response}" + + def to_operation_outcome(self) -> dict: + return create_operation_outcome( + resource_id=str(uuid.uuid4()), + severity=Severity.error, + code=Code.incomplete, + diagnostics=self.__str__(), + ) + + +@dataclass +class ServerError(RuntimeError): + """Use when there is a server error""" + + response: dict | str + message: str + + def __str__(self): + return f"{self.message}\n{self.response}" + + def to_operation_outcome(self) -> dict: + return create_operation_outcome( + resource_id=str(uuid.uuid4()), + severity=Severity.error, + code=Code.server_error, + diagnostics=self.__str__(), + ) diff --git a/lambdas/mns_subscription/tests/test_errors.py b/lambdas/mns_subscription/tests/test_errors.py new file mode 100644 index 000000000..888019641 --- /dev/null +++ b/lambdas/mns_subscription/tests/test_errors.py @@ -0,0 +1,100 @@ +import unittest +from unittest.mock import patch + +import models.errors as errors + + +class TestErrors(unittest.TestCase): + def setUp(self): + TEST_UUID = "01234567-89ab-cdef-0123-4567890abcde" + # Patch uuid4 + self.uuid4_patch = patch("uuid.uuid4", return_value=TEST_UUID) + self.mock_uuid4 = self.uuid4_patch.start() + self.addCleanup(self.uuid4_patch.stop) + + def assert_response_message(self, context, response, message): + self.assertEqual(context.exception.response, response) + self.assertEqual(context.exception.message, message) + + def assert_operation_outcome(self, outcome): + self.assertEqual(outcome.get("resourceType"), "OperationOutcome") + + def test_errors_unauthorized_error(self): + """Test correct operation of UnauthorizedError""" + test_response = "test_response" + test_message = "test_message" + + with self.assertRaises(errors.UnauthorizedError) as context: + raise errors.UnauthorizedError(test_response, test_message) + self.assert_response_message(context, test_response, test_message) + self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") + outcome = context.exception.to_operation_outcome() + self.assert_operation_outcome(outcome) + issue = outcome.get("issue")[0] + self.assertEqual(issue.get("severity"), errors.Severity.error) + self.assertEqual(issue.get("code"), errors.Code.forbidden) + self.assertEqual(issue.get("diagnostics"), "Unauthorized request") + + def test_errors_token_validation_error(self): + """Test correct operation of TokenValidationError""" + test_response = "test_response" + test_message = "test_message" + + with self.assertRaises(errors.TokenValidationError) as context: + raise errors.TokenValidationError(test_response, test_message) + self.assert_response_message(context, test_response, test_message) + self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") + outcome = context.exception.to_operation_outcome() + self.assert_operation_outcome(outcome) + issue = outcome.get("issue")[0] + self.assertEqual(issue.get("severity"), errors.Severity.error) + self.assertEqual(issue.get("code"), errors.Code.invalid) + self.assertEqual(issue.get("diagnostics"), "Missing/Invalid Token") + + def test_errors_conflict_error(self): + """Test correct operation of ConflictError""" + test_response = "test_response" + test_message = "test_message" + + with self.assertRaises(errors.ConflictError) as context: + raise errors.ConflictError(test_response, test_message) + self.assert_response_message(context, test_response, test_message) + self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") + outcome = context.exception.to_operation_outcome() + self.assert_operation_outcome(outcome) + issue = outcome.get("issue")[0] + self.assertEqual(issue.get("severity"), errors.Severity.error) + self.assertEqual(issue.get("code"), errors.Code.duplicate) + self.assertEqual(issue.get("diagnostics"), "Conflict") + + def test_errors_bad_request_error(self): + """Test correct operation of BadRequestError""" + test_response = "test_response" + test_message = "test_message" + + with self.assertRaises(errors.BadRequestError) as context: + raise errors.BadRequestError(test_response, test_message) + self.assert_response_message(context, test_response, test_message) + self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") + outcome = context.exception.to_operation_outcome() + self.assert_operation_outcome(outcome) + issue = outcome.get("issue")[0] + self.assertEqual(issue.get("severity"), errors.Severity.error) + self.assertEqual(issue.get("code"), errors.Code.incomplete) + self.assertEqual(issue.get("diagnostics"), f"{test_message}\n{test_response}") + + def test_errors_server_error(self): + """Test correct operation of ServerError""" + test_response = "test_response" + test_message = "test_message" + + with self.assertRaises(errors.ServerError) as context: + raise errors.ServerError(test_response, test_message) + self.assert_response_message(context, test_response, test_message) + self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") + outcome = context.exception.to_operation_outcome() + self.assert_operation_outcome(outcome) + issue = outcome.get("issue")[0] + self.assertEqual(issue.get("severity"), errors.Severity.error) + self.assertEqual(issue.get("code"), errors.Code.server_error) + self.assertEqual(issue.get("diagnostics"), f"{test_message}\n{test_response}") diff --git a/lambdas/mns_subscription/tests/test_mns_service.py b/lambdas/mns_subscription/tests/test_mns_service.py index a9fe6d998..66bb34134 100644 --- a/lambdas/mns_subscription/tests/test_mns_service.py +++ b/lambdas/mns_subscription/tests/test_mns_service.py @@ -4,14 +4,16 @@ from common.authentication import AppRestrictedAuth from common.models.errors import ( - BadRequestError, ResourceNotFoundError, + UnhandledResponseError, +) +from mns_service import MNS_URL, MnsService +from models.errors import ( + BadRequestError, ServerError, TokenValidationError, UnauthorizedError, - UnhandledResponseError, ) -from mns_service import MNS_URL, MnsService SQS_ARN = "arn:aws:sqs:eu-west-2:123456789012:my-queue" diff --git a/lambdas/recordforwarder/Dockerfile b/lambdas/recordforwarder/Dockerfile new file mode 100644 index 000000000..904c3d46f --- /dev/null +++ b/lambdas/recordforwarder/Dockerfile @@ -0,0 +1,35 @@ +FROM public.ecr.aws/lambda/python:3.11 AS base + +RUN mkdir -p /home/appuser && \ + echo 'appuser:x:1001:1001::/home/appuser:/sbin/nologin' >> /etc/passwd && \ + echo 'appuser:x:1001:' >> /etc/group && \ + chown -R 1001:1001 /home/appuser && pip install "poetry~=2.1.4" + +# Install Poetry dependencies +# Copy recordforwarder Poetry files +COPY ./recordforwarder/poetry.lock ./recordforwarder/pyproject.toml ./ + +# Install recordforwarder dependencies +WORKDIR /var/task +RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi --no-root --only main + +# ----------------------------- +FROM base AS build + +# Set working directory back to Lambda task root +WORKDIR /var/task + +# Copy shared source code +COPY ./shared/src/common ./common + +# Copy recordforwarder source code +COPY ./recordforwarder/src . + +# Set correct permissions +RUN chmod 644 $(find . -type f) && chmod 755 $(find . -type d) + +# Switch to the non-root user for running the container +USER 1001:1001 + +# Set the Lambda handler +CMD ["forwarding_batch_lambda.forward_lambda_handler"] diff --git a/lambdas/recordforwarder/Makefile b/lambdas/recordforwarder/Makefile new file mode 100644 index 000000000..1078bd551 --- /dev/null +++ b/lambdas/recordforwarder/Makefile @@ -0,0 +1,22 @@ +TEST_ENV := @PYTHONPATH=src:tests:../shared/src:../shared/tests + +build: + docker build -t recordforwarder-lambda-build -f Dockerfile . + +package: build + mkdir -p build + docker run --rm -v $(shell pwd)/build:/build recordforwarder-lambda-build + +test: + $(TEST_ENV) python -m unittest + +coverage-run: + $(TEST_ENV) coverage run --source=src -m unittest discover + +coverage-report: + $(TEST_ENV) coverage report -m + +coverage-html: + $(TEST_ENV) coverage html + +.PHONY: build package test diff --git a/lambdas/recordforwarder/README.md b/lambdas/recordforwarder/README.md new file mode 100644 index 000000000..d537ac695 --- /dev/null +++ b/lambdas/recordforwarder/README.md @@ -0,0 +1,24 @@ +# About + +This document describes the environment setup for the recordforwarder Lambda. + +## Setting up the recordforwarder lambda + +Note: Paths are relative to this directory, `recordforwarder`. + +1. Follow the instructions in the root level README.md to setup the [dependencies](../README.md#environment-setup) and create a [virtual environment](../README.md#) for this folder. + +2. Replace the `.env` file in the recordforwarder folder. Note the variables might change in the future. These environment variables will be loaded automatically when using `direnv`. + + ``` + AWS_PROFILE={your-profile} + IMMUNIZATION_ENV={environment} + ``` + +3. Run `poetry install --no-root` to install dependencies. + +4. Run `make test` to run unit tests or individual tests by running: + ``` + python -m unittest tests.test_fhir_batch_controller.TestCreateImmunizationBatchController + python -m unittest tests.test_fhir_batch_controller.TestCreateImmunizationBatchController.test_send_request_to_dynamo_create_success + ``` diff --git a/lambdas/recordforwarder/poetry.lock b/lambdas/recordforwarder/poetry.lock new file mode 100644 index 000000000..ea0cfe895 --- /dev/null +++ b/lambdas/recordforwarder/poetry.lock @@ -0,0 +1,1751 @@ +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. + +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_full_version <= \"3.11.2\"" +files = [ + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, +] + +[[package]] +name = "aws-lambda-typing" +version = "2.20.0" +description = "A package that provides type hints for AWS Lambda event, context and response objects" +optional = false +python-versions = "<4.0,>=3.6" +groups = ["main"] +files = [ + {file = "aws-lambda-typing-2.20.0.tar.gz", hash = "sha256:78b0d8ebab73b3a6b0da98a7969f4e9c4bb497298ec50f3217da8a8dfba17154"}, + {file = "aws_lambda_typing-2.20.0-py3-none-any.whl", hash = "sha256:1d44264cabfeab5ac38e67ddd0c874e677b2cbbae77a42d0519df470e6bbb49b"}, +] + +[[package]] +name = "boto3" +version = "1.40.67" +description = "The AWS SDK for Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "boto3-1.40.67-py3-none-any.whl", hash = "sha256:3d06e9b3c7abedb8253c7d75b9ab27005480ca1e6e448d1f3c3cc3e209673ca0"}, + {file = "boto3-1.40.67.tar.gz", hash = "sha256:3e4317139ace6d44658b8e1f2b5b6612f05b45720721841c90cdee45b02aa514"}, +] + +[package.dependencies] +botocore = ">=1.40.67,<1.41.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.14.0,<0.15.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "boto3-stubs-lite" +version = "1.40.64" +description = "Lite type annotations for boto3 1.40.64 generated with mypy-boto3-builder 8.11.0" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "boto3_stubs_lite-1.40.64-py3-none-any.whl", hash = "sha256:21f8d859edfafb98bf9fb4c529349c40b92ef39c7f9f16eb741b993fde31530c"}, + {file = "boto3_stubs_lite-1.40.64.tar.gz", hash = "sha256:8c5a019bea442ba436675b61c76fe99d1d0faf846bea920768bf574b1df4f1eb"}, +] + +[package.dependencies] +botocore-stubs = "*" +mypy-boto3-dynamodb = {version = ">=1.40.0,<1.41.0", optional = true, markers = "extra == \"dynamodb\""} +types-s3transfer = "*" +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""} + +[package.extras] +accessanalyzer = ["mypy-boto3-accessanalyzer (>=1.40.0,<1.41.0)"] +account = ["mypy-boto3-account (>=1.40.0,<1.41.0)"] +acm = ["mypy-boto3-acm (>=1.40.0,<1.41.0)"] +acm-pca = ["mypy-boto3-acm-pca (>=1.40.0,<1.41.0)"] +aiops = ["mypy-boto3-aiops (>=1.40.0,<1.41.0)"] +all = ["mypy-boto3-accessanalyzer (>=1.40.0,<1.41.0)", "mypy-boto3-account (>=1.40.0,<1.41.0)", "mypy-boto3-acm (>=1.40.0,<1.41.0)", "mypy-boto3-acm-pca (>=1.40.0,<1.41.0)", "mypy-boto3-aiops (>=1.40.0,<1.41.0)", "mypy-boto3-amp (>=1.40.0,<1.41.0)", "mypy-boto3-amplify (>=1.40.0,<1.41.0)", "mypy-boto3-amplifybackend (>=1.40.0,<1.41.0)", "mypy-boto3-amplifyuibuilder (>=1.40.0,<1.41.0)", "mypy-boto3-apigateway (>=1.40.0,<1.41.0)", "mypy-boto3-apigatewaymanagementapi (>=1.40.0,<1.41.0)", "mypy-boto3-apigatewayv2 (>=1.40.0,<1.41.0)", "mypy-boto3-appconfig (>=1.40.0,<1.41.0)", "mypy-boto3-appconfigdata (>=1.40.0,<1.41.0)", "mypy-boto3-appfabric (>=1.40.0,<1.41.0)", "mypy-boto3-appflow (>=1.40.0,<1.41.0)", "mypy-boto3-appintegrations (>=1.40.0,<1.41.0)", "mypy-boto3-application-autoscaling (>=1.40.0,<1.41.0)", "mypy-boto3-application-insights (>=1.40.0,<1.41.0)", "mypy-boto3-application-signals (>=1.40.0,<1.41.0)", "mypy-boto3-applicationcostprofiler (>=1.40.0,<1.41.0)", "mypy-boto3-appmesh (>=1.40.0,<1.41.0)", "mypy-boto3-apprunner (>=1.40.0,<1.41.0)", "mypy-boto3-appstream (>=1.40.0,<1.41.0)", "mypy-boto3-appsync (>=1.40.0,<1.41.0)", "mypy-boto3-arc-region-switch (>=1.40.0,<1.41.0)", "mypy-boto3-arc-zonal-shift (>=1.40.0,<1.41.0)", "mypy-boto3-artifact (>=1.40.0,<1.41.0)", "mypy-boto3-athena (>=1.40.0,<1.41.0)", "mypy-boto3-auditmanager (>=1.40.0,<1.41.0)", "mypy-boto3-autoscaling (>=1.40.0,<1.41.0)", "mypy-boto3-autoscaling-plans (>=1.40.0,<1.41.0)", "mypy-boto3-b2bi (>=1.40.0,<1.41.0)", "mypy-boto3-backup (>=1.40.0,<1.41.0)", "mypy-boto3-backup-gateway (>=1.40.0,<1.41.0)", "mypy-boto3-backupsearch (>=1.40.0,<1.41.0)", "mypy-boto3-batch (>=1.40.0,<1.41.0)", "mypy-boto3-bcm-dashboards (>=1.40.0,<1.41.0)", "mypy-boto3-bcm-data-exports (>=1.40.0,<1.41.0)", "mypy-boto3-bcm-pricing-calculator (>=1.40.0,<1.41.0)", "mypy-boto3-bcm-recommended-actions (>=1.40.0,<1.41.0)", "mypy-boto3-bedrock (>=1.40.0,<1.41.0)", "mypy-boto3-bedrock-agent (>=1.40.0,<1.41.0)", "mypy-boto3-bedrock-agent-runtime (>=1.40.0,<1.41.0)", "mypy-boto3-bedrock-agentcore (>=1.40.0,<1.41.0)", "mypy-boto3-bedrock-agentcore-control (>=1.40.0,<1.41.0)", "mypy-boto3-bedrock-data-automation (>=1.40.0,<1.41.0)", "mypy-boto3-bedrock-data-automation-runtime (>=1.40.0,<1.41.0)", "mypy-boto3-bedrock-runtime (>=1.40.0,<1.41.0)", "mypy-boto3-billing (>=1.40.0,<1.41.0)", "mypy-boto3-billingconductor (>=1.40.0,<1.41.0)", "mypy-boto3-braket (>=1.40.0,<1.41.0)", "mypy-boto3-budgets (>=1.40.0,<1.41.0)", "mypy-boto3-ce (>=1.40.0,<1.41.0)", "mypy-boto3-chatbot (>=1.40.0,<1.41.0)", "mypy-boto3-chime (>=1.40.0,<1.41.0)", "mypy-boto3-chime-sdk-identity (>=1.40.0,<1.41.0)", "mypy-boto3-chime-sdk-media-pipelines (>=1.40.0,<1.41.0)", "mypy-boto3-chime-sdk-meetings (>=1.40.0,<1.41.0)", "mypy-boto3-chime-sdk-messaging (>=1.40.0,<1.41.0)", "mypy-boto3-chime-sdk-voice (>=1.40.0,<1.41.0)", "mypy-boto3-cleanrooms (>=1.40.0,<1.41.0)", "mypy-boto3-cleanroomsml (>=1.40.0,<1.41.0)", "mypy-boto3-cloud9 (>=1.40.0,<1.41.0)", "mypy-boto3-cloudcontrol (>=1.40.0,<1.41.0)", "mypy-boto3-clouddirectory (>=1.40.0,<1.41.0)", "mypy-boto3-cloudformation (>=1.40.0,<1.41.0)", "mypy-boto3-cloudfront (>=1.40.0,<1.41.0)", "mypy-boto3-cloudfront-keyvaluestore (>=1.40.0,<1.41.0)", "mypy-boto3-cloudhsm (>=1.40.0,<1.41.0)", "mypy-boto3-cloudhsmv2 (>=1.40.0,<1.41.0)", "mypy-boto3-cloudsearch (>=1.40.0,<1.41.0)", "mypy-boto3-cloudsearchdomain (>=1.40.0,<1.41.0)", "mypy-boto3-cloudtrail (>=1.40.0,<1.41.0)", "mypy-boto3-cloudtrail-data (>=1.40.0,<1.41.0)", "mypy-boto3-cloudwatch (>=1.40.0,<1.41.0)", "mypy-boto3-codeartifact (>=1.40.0,<1.41.0)", "mypy-boto3-codebuild (>=1.40.0,<1.41.0)", "mypy-boto3-codecatalyst (>=1.40.0,<1.41.0)", "mypy-boto3-codecommit (>=1.40.0,<1.41.0)", "mypy-boto3-codeconnections (>=1.40.0,<1.41.0)", "mypy-boto3-codedeploy (>=1.40.0,<1.41.0)", "mypy-boto3-codeguru-reviewer (>=1.40.0,<1.41.0)", "mypy-boto3-codeguru-security (>=1.40.0,<1.41.0)", "mypy-boto3-codeguruprofiler (>=1.40.0,<1.41.0)", "mypy-boto3-codepipeline (>=1.40.0,<1.41.0)", "mypy-boto3-codestar-connections (>=1.40.0,<1.41.0)", "mypy-boto3-codestar-notifications (>=1.40.0,<1.41.0)", "mypy-boto3-cognito-identity (>=1.40.0,<1.41.0)", "mypy-boto3-cognito-idp (>=1.40.0,<1.41.0)", "mypy-boto3-cognito-sync (>=1.40.0,<1.41.0)", "mypy-boto3-comprehend (>=1.40.0,<1.41.0)", "mypy-boto3-comprehendmedical (>=1.40.0,<1.41.0)", "mypy-boto3-compute-optimizer (>=1.40.0,<1.41.0)", "mypy-boto3-config (>=1.40.0,<1.41.0)", "mypy-boto3-connect (>=1.40.0,<1.41.0)", "mypy-boto3-connect-contact-lens (>=1.40.0,<1.41.0)", "mypy-boto3-connectcampaigns (>=1.40.0,<1.41.0)", "mypy-boto3-connectcampaignsv2 (>=1.40.0,<1.41.0)", "mypy-boto3-connectcases (>=1.40.0,<1.41.0)", "mypy-boto3-connectparticipant (>=1.40.0,<1.41.0)", "mypy-boto3-controlcatalog (>=1.40.0,<1.41.0)", "mypy-boto3-controltower (>=1.40.0,<1.41.0)", "mypy-boto3-cost-optimization-hub (>=1.40.0,<1.41.0)", "mypy-boto3-cur (>=1.40.0,<1.41.0)", "mypy-boto3-customer-profiles (>=1.40.0,<1.41.0)", "mypy-boto3-databrew (>=1.40.0,<1.41.0)", "mypy-boto3-dataexchange (>=1.40.0,<1.41.0)", "mypy-boto3-datapipeline (>=1.40.0,<1.41.0)", "mypy-boto3-datasync (>=1.40.0,<1.41.0)", "mypy-boto3-datazone (>=1.40.0,<1.41.0)", "mypy-boto3-dax (>=1.40.0,<1.41.0)", "mypy-boto3-deadline (>=1.40.0,<1.41.0)", "mypy-boto3-detective (>=1.40.0,<1.41.0)", "mypy-boto3-devicefarm (>=1.40.0,<1.41.0)", "mypy-boto3-devops-guru (>=1.40.0,<1.41.0)", "mypy-boto3-directconnect (>=1.40.0,<1.41.0)", "mypy-boto3-discovery (>=1.40.0,<1.41.0)", "mypy-boto3-dlm (>=1.40.0,<1.41.0)", "mypy-boto3-dms (>=1.40.0,<1.41.0)", "mypy-boto3-docdb (>=1.40.0,<1.41.0)", "mypy-boto3-docdb-elastic (>=1.40.0,<1.41.0)", "mypy-boto3-drs (>=1.40.0,<1.41.0)", "mypy-boto3-ds (>=1.40.0,<1.41.0)", "mypy-boto3-ds-data (>=1.40.0,<1.41.0)", "mypy-boto3-dsql (>=1.40.0,<1.41.0)", "mypy-boto3-dynamodb (>=1.40.0,<1.41.0)", "mypy-boto3-dynamodbstreams (>=1.40.0,<1.41.0)", "mypy-boto3-ebs (>=1.40.0,<1.41.0)", "mypy-boto3-ec2 (>=1.40.0,<1.41.0)", "mypy-boto3-ec2-instance-connect (>=1.40.0,<1.41.0)", "mypy-boto3-ecr (>=1.40.0,<1.41.0)", "mypy-boto3-ecr-public (>=1.40.0,<1.41.0)", "mypy-boto3-ecs (>=1.40.0,<1.41.0)", "mypy-boto3-efs (>=1.40.0,<1.41.0)", "mypy-boto3-eks (>=1.40.0,<1.41.0)", "mypy-boto3-eks-auth (>=1.40.0,<1.41.0)", "mypy-boto3-elasticache (>=1.40.0,<1.41.0)", "mypy-boto3-elasticbeanstalk (>=1.40.0,<1.41.0)", "mypy-boto3-elastictranscoder (>=1.40.0,<1.41.0)", "mypy-boto3-elb (>=1.40.0,<1.41.0)", "mypy-boto3-elbv2 (>=1.40.0,<1.41.0)", "mypy-boto3-emr (>=1.40.0,<1.41.0)", "mypy-boto3-emr-containers (>=1.40.0,<1.41.0)", "mypy-boto3-emr-serverless (>=1.40.0,<1.41.0)", "mypy-boto3-entityresolution (>=1.40.0,<1.41.0)", "mypy-boto3-es (>=1.40.0,<1.41.0)", "mypy-boto3-events (>=1.40.0,<1.41.0)", "mypy-boto3-evidently (>=1.40.0,<1.41.0)", "mypy-boto3-evs (>=1.40.0,<1.41.0)", "mypy-boto3-finspace (>=1.40.0,<1.41.0)", "mypy-boto3-finspace-data (>=1.40.0,<1.41.0)", "mypy-boto3-firehose (>=1.40.0,<1.41.0)", "mypy-boto3-fis (>=1.40.0,<1.41.0)", "mypy-boto3-fms (>=1.40.0,<1.41.0)", "mypy-boto3-forecast (>=1.40.0,<1.41.0)", "mypy-boto3-forecastquery (>=1.40.0,<1.41.0)", "mypy-boto3-frauddetector (>=1.40.0,<1.41.0)", "mypy-boto3-freetier (>=1.40.0,<1.41.0)", "mypy-boto3-fsx (>=1.40.0,<1.41.0)", "mypy-boto3-gamelift (>=1.40.0,<1.41.0)", "mypy-boto3-gameliftstreams (>=1.40.0,<1.41.0)", "mypy-boto3-geo-maps (>=1.40.0,<1.41.0)", "mypy-boto3-geo-places (>=1.40.0,<1.41.0)", "mypy-boto3-geo-routes (>=1.40.0,<1.41.0)", "mypy-boto3-glacier (>=1.40.0,<1.41.0)", "mypy-boto3-globalaccelerator (>=1.40.0,<1.41.0)", "mypy-boto3-glue (>=1.40.0,<1.41.0)", "mypy-boto3-grafana (>=1.40.0,<1.41.0)", "mypy-boto3-greengrass (>=1.40.0,<1.41.0)", "mypy-boto3-greengrassv2 (>=1.40.0,<1.41.0)", "mypy-boto3-groundstation (>=1.40.0,<1.41.0)", "mypy-boto3-guardduty (>=1.40.0,<1.41.0)", "mypy-boto3-health (>=1.40.0,<1.41.0)", "mypy-boto3-healthlake (>=1.40.0,<1.41.0)", "mypy-boto3-iam (>=1.40.0,<1.41.0)", "mypy-boto3-identitystore (>=1.40.0,<1.41.0)", "mypy-boto3-imagebuilder (>=1.40.0,<1.41.0)", "mypy-boto3-importexport (>=1.40.0,<1.41.0)", "mypy-boto3-inspector (>=1.40.0,<1.41.0)", "mypy-boto3-inspector-scan (>=1.40.0,<1.41.0)", "mypy-boto3-inspector2 (>=1.40.0,<1.41.0)", "mypy-boto3-internetmonitor (>=1.40.0,<1.41.0)", "mypy-boto3-invoicing (>=1.40.0,<1.41.0)", "mypy-boto3-iot (>=1.40.0,<1.41.0)", "mypy-boto3-iot-data (>=1.40.0,<1.41.0)", "mypy-boto3-iot-jobs-data (>=1.40.0,<1.41.0)", "mypy-boto3-iot-managed-integrations (>=1.40.0,<1.41.0)", "mypy-boto3-iotanalytics (>=1.40.0,<1.41.0)", "mypy-boto3-iotdeviceadvisor (>=1.40.0,<1.41.0)", "mypy-boto3-iotevents (>=1.40.0,<1.41.0)", "mypy-boto3-iotevents-data (>=1.40.0,<1.41.0)", "mypy-boto3-iotfleetwise (>=1.40.0,<1.41.0)", "mypy-boto3-iotsecuretunneling (>=1.40.0,<1.41.0)", "mypy-boto3-iotsitewise (>=1.40.0,<1.41.0)", "mypy-boto3-iotthingsgraph (>=1.40.0,<1.41.0)", "mypy-boto3-iottwinmaker (>=1.40.0,<1.41.0)", "mypy-boto3-iotwireless (>=1.40.0,<1.41.0)", "mypy-boto3-ivs (>=1.40.0,<1.41.0)", "mypy-boto3-ivs-realtime (>=1.40.0,<1.41.0)", "mypy-boto3-ivschat (>=1.40.0,<1.41.0)", "mypy-boto3-kafka (>=1.40.0,<1.41.0)", "mypy-boto3-kafkaconnect (>=1.40.0,<1.41.0)", "mypy-boto3-kendra (>=1.40.0,<1.41.0)", "mypy-boto3-kendra-ranking (>=1.40.0,<1.41.0)", "mypy-boto3-keyspaces (>=1.40.0,<1.41.0)", "mypy-boto3-keyspacesstreams (>=1.40.0,<1.41.0)", "mypy-boto3-kinesis (>=1.40.0,<1.41.0)", "mypy-boto3-kinesis-video-archived-media (>=1.40.0,<1.41.0)", "mypy-boto3-kinesis-video-media (>=1.40.0,<1.41.0)", "mypy-boto3-kinesis-video-signaling (>=1.40.0,<1.41.0)", "mypy-boto3-kinesis-video-webrtc-storage (>=1.40.0,<1.41.0)", "mypy-boto3-kinesisanalytics (>=1.40.0,<1.41.0)", "mypy-boto3-kinesisanalyticsv2 (>=1.40.0,<1.41.0)", "mypy-boto3-kinesisvideo (>=1.40.0,<1.41.0)", "mypy-boto3-kms (>=1.40.0,<1.41.0)", "mypy-boto3-lakeformation (>=1.40.0,<1.41.0)", "mypy-boto3-lambda (>=1.40.0,<1.41.0)", "mypy-boto3-launch-wizard (>=1.40.0,<1.41.0)", "mypy-boto3-lex-models (>=1.40.0,<1.41.0)", "mypy-boto3-lex-runtime (>=1.40.0,<1.41.0)", "mypy-boto3-lexv2-models (>=1.40.0,<1.41.0)", "mypy-boto3-lexv2-runtime (>=1.40.0,<1.41.0)", "mypy-boto3-license-manager (>=1.40.0,<1.41.0)", "mypy-boto3-license-manager-linux-subscriptions (>=1.40.0,<1.41.0)", "mypy-boto3-license-manager-user-subscriptions (>=1.40.0,<1.41.0)", "mypy-boto3-lightsail (>=1.40.0,<1.41.0)", "mypy-boto3-location (>=1.40.0,<1.41.0)", "mypy-boto3-logs (>=1.40.0,<1.41.0)", "mypy-boto3-lookoutequipment (>=1.40.0,<1.41.0)", "mypy-boto3-m2 (>=1.40.0,<1.41.0)", "mypy-boto3-machinelearning (>=1.40.0,<1.41.0)", "mypy-boto3-macie2 (>=1.40.0,<1.41.0)", "mypy-boto3-mailmanager (>=1.40.0,<1.41.0)", "mypy-boto3-managedblockchain (>=1.40.0,<1.41.0)", "mypy-boto3-managedblockchain-query (>=1.40.0,<1.41.0)", "mypy-boto3-marketplace-agreement (>=1.40.0,<1.41.0)", "mypy-boto3-marketplace-catalog (>=1.40.0,<1.41.0)", "mypy-boto3-marketplace-deployment (>=1.40.0,<1.41.0)", "mypy-boto3-marketplace-entitlement (>=1.40.0,<1.41.0)", "mypy-boto3-marketplace-reporting (>=1.40.0,<1.41.0)", "mypy-boto3-marketplacecommerceanalytics (>=1.40.0,<1.41.0)", "mypy-boto3-mediaconnect (>=1.40.0,<1.41.0)", "mypy-boto3-mediaconvert (>=1.40.0,<1.41.0)", "mypy-boto3-medialive (>=1.40.0,<1.41.0)", "mypy-boto3-mediapackage (>=1.40.0,<1.41.0)", "mypy-boto3-mediapackage-vod (>=1.40.0,<1.41.0)", "mypy-boto3-mediapackagev2 (>=1.40.0,<1.41.0)", "mypy-boto3-mediastore (>=1.40.0,<1.41.0)", "mypy-boto3-mediastore-data (>=1.40.0,<1.41.0)", "mypy-boto3-mediatailor (>=1.40.0,<1.41.0)", "mypy-boto3-medical-imaging (>=1.40.0,<1.41.0)", "mypy-boto3-memorydb (>=1.40.0,<1.41.0)", "mypy-boto3-meteringmarketplace (>=1.40.0,<1.41.0)", "mypy-boto3-mgh (>=1.40.0,<1.41.0)", "mypy-boto3-mgn (>=1.40.0,<1.41.0)", "mypy-boto3-migration-hub-refactor-spaces (>=1.40.0,<1.41.0)", "mypy-boto3-migrationhub-config (>=1.40.0,<1.41.0)", "mypy-boto3-migrationhuborchestrator (>=1.40.0,<1.41.0)", "mypy-boto3-migrationhubstrategy (>=1.40.0,<1.41.0)", "mypy-boto3-mpa (>=1.40.0,<1.41.0)", "mypy-boto3-mq (>=1.40.0,<1.41.0)", "mypy-boto3-mturk (>=1.40.0,<1.41.0)", "mypy-boto3-mwaa (>=1.40.0,<1.41.0)", "mypy-boto3-neptune (>=1.40.0,<1.41.0)", "mypy-boto3-neptune-graph (>=1.40.0,<1.41.0)", "mypy-boto3-neptunedata (>=1.40.0,<1.41.0)", "mypy-boto3-network-firewall (>=1.40.0,<1.41.0)", "mypy-boto3-networkflowmonitor (>=1.40.0,<1.41.0)", "mypy-boto3-networkmanager (>=1.40.0,<1.41.0)", "mypy-boto3-networkmonitor (>=1.40.0,<1.41.0)", "mypy-boto3-notifications (>=1.40.0,<1.41.0)", "mypy-boto3-notificationscontacts (>=1.40.0,<1.41.0)", "mypy-boto3-oam (>=1.40.0,<1.41.0)", "mypy-boto3-observabilityadmin (>=1.40.0,<1.41.0)", "mypy-boto3-odb (>=1.40.0,<1.41.0)", "mypy-boto3-omics (>=1.40.0,<1.41.0)", "mypy-boto3-opensearch (>=1.40.0,<1.41.0)", "mypy-boto3-opensearchserverless (>=1.40.0,<1.41.0)", "mypy-boto3-organizations (>=1.40.0,<1.41.0)", "mypy-boto3-osis (>=1.40.0,<1.41.0)", "mypy-boto3-outposts (>=1.40.0,<1.41.0)", "mypy-boto3-panorama (>=1.40.0,<1.41.0)", "mypy-boto3-partnercentral-selling (>=1.40.0,<1.41.0)", "mypy-boto3-payment-cryptography (>=1.40.0,<1.41.0)", "mypy-boto3-payment-cryptography-data (>=1.40.0,<1.41.0)", "mypy-boto3-pca-connector-ad (>=1.40.0,<1.41.0)", "mypy-boto3-pca-connector-scep (>=1.40.0,<1.41.0)", "mypy-boto3-pcs (>=1.40.0,<1.41.0)", "mypy-boto3-personalize (>=1.40.0,<1.41.0)", "mypy-boto3-personalize-events (>=1.40.0,<1.41.0)", "mypy-boto3-personalize-runtime (>=1.40.0,<1.41.0)", "mypy-boto3-pi (>=1.40.0,<1.41.0)", "mypy-boto3-pinpoint (>=1.40.0,<1.41.0)", "mypy-boto3-pinpoint-email (>=1.40.0,<1.41.0)", "mypy-boto3-pinpoint-sms-voice (>=1.40.0,<1.41.0)", "mypy-boto3-pinpoint-sms-voice-v2 (>=1.40.0,<1.41.0)", "mypy-boto3-pipes (>=1.40.0,<1.41.0)", "mypy-boto3-polly (>=1.40.0,<1.41.0)", "mypy-boto3-pricing (>=1.40.0,<1.41.0)", "mypy-boto3-proton (>=1.40.0,<1.41.0)", "mypy-boto3-qapps (>=1.40.0,<1.41.0)", "mypy-boto3-qbusiness (>=1.40.0,<1.41.0)", "mypy-boto3-qconnect (>=1.40.0,<1.41.0)", "mypy-boto3-quicksight (>=1.40.0,<1.41.0)", "mypy-boto3-ram (>=1.40.0,<1.41.0)", "mypy-boto3-rbin (>=1.40.0,<1.41.0)", "mypy-boto3-rds (>=1.40.0,<1.41.0)", "mypy-boto3-rds-data (>=1.40.0,<1.41.0)", "mypy-boto3-redshift (>=1.40.0,<1.41.0)", "mypy-boto3-redshift-data (>=1.40.0,<1.41.0)", "mypy-boto3-redshift-serverless (>=1.40.0,<1.41.0)", "mypy-boto3-rekognition (>=1.40.0,<1.41.0)", "mypy-boto3-repostspace (>=1.40.0,<1.41.0)", "mypy-boto3-resiliencehub (>=1.40.0,<1.41.0)", "mypy-boto3-resource-explorer-2 (>=1.40.0,<1.41.0)", "mypy-boto3-resource-groups (>=1.40.0,<1.41.0)", "mypy-boto3-resourcegroupstaggingapi (>=1.40.0,<1.41.0)", "mypy-boto3-rolesanywhere (>=1.40.0,<1.41.0)", "mypy-boto3-route53 (>=1.40.0,<1.41.0)", "mypy-boto3-route53-recovery-cluster (>=1.40.0,<1.41.0)", "mypy-boto3-route53-recovery-control-config (>=1.40.0,<1.41.0)", "mypy-boto3-route53-recovery-readiness (>=1.40.0,<1.41.0)", "mypy-boto3-route53domains (>=1.40.0,<1.41.0)", "mypy-boto3-route53profiles (>=1.40.0,<1.41.0)", "mypy-boto3-route53resolver (>=1.40.0,<1.41.0)", "mypy-boto3-rtbfabric (>=1.40.0,<1.41.0)", "mypy-boto3-rum (>=1.40.0,<1.41.0)", "mypy-boto3-s3 (>=1.40.0,<1.41.0)", "mypy-boto3-s3control (>=1.40.0,<1.41.0)", "mypy-boto3-s3outposts (>=1.40.0,<1.41.0)", "mypy-boto3-s3tables (>=1.40.0,<1.41.0)", "mypy-boto3-s3vectors (>=1.40.0,<1.41.0)", "mypy-boto3-sagemaker (>=1.40.0,<1.41.0)", "mypy-boto3-sagemaker-a2i-runtime (>=1.40.0,<1.41.0)", "mypy-boto3-sagemaker-edge (>=1.40.0,<1.41.0)", "mypy-boto3-sagemaker-featurestore-runtime (>=1.40.0,<1.41.0)", "mypy-boto3-sagemaker-geospatial (>=1.40.0,<1.41.0)", "mypy-boto3-sagemaker-metrics (>=1.40.0,<1.41.0)", "mypy-boto3-sagemaker-runtime (>=1.40.0,<1.41.0)", "mypy-boto3-savingsplans (>=1.40.0,<1.41.0)", "mypy-boto3-scheduler (>=1.40.0,<1.41.0)", "mypy-boto3-schemas (>=1.40.0,<1.41.0)", "mypy-boto3-sdb (>=1.40.0,<1.41.0)", "mypy-boto3-secretsmanager (>=1.40.0,<1.41.0)", "mypy-boto3-security-ir (>=1.40.0,<1.41.0)", "mypy-boto3-securityhub (>=1.40.0,<1.41.0)", "mypy-boto3-securitylake (>=1.40.0,<1.41.0)", "mypy-boto3-serverlessrepo (>=1.40.0,<1.41.0)", "mypy-boto3-service-quotas (>=1.40.0,<1.41.0)", "mypy-boto3-servicecatalog (>=1.40.0,<1.41.0)", "mypy-boto3-servicecatalog-appregistry (>=1.40.0,<1.41.0)", "mypy-boto3-servicediscovery (>=1.40.0,<1.41.0)", "mypy-boto3-ses (>=1.40.0,<1.41.0)", "mypy-boto3-sesv2 (>=1.40.0,<1.41.0)", "mypy-boto3-shield (>=1.40.0,<1.41.0)", "mypy-boto3-signer (>=1.40.0,<1.41.0)", "mypy-boto3-simspaceweaver (>=1.40.0,<1.41.0)", "mypy-boto3-snow-device-management (>=1.40.0,<1.41.0)", "mypy-boto3-snowball (>=1.40.0,<1.41.0)", "mypy-boto3-sns (>=1.40.0,<1.41.0)", "mypy-boto3-socialmessaging (>=1.40.0,<1.41.0)", "mypy-boto3-sqs (>=1.40.0,<1.41.0)", "mypy-boto3-ssm (>=1.40.0,<1.41.0)", "mypy-boto3-ssm-contacts (>=1.40.0,<1.41.0)", "mypy-boto3-ssm-guiconnect (>=1.40.0,<1.41.0)", "mypy-boto3-ssm-incidents (>=1.40.0,<1.41.0)", "mypy-boto3-ssm-quicksetup (>=1.40.0,<1.41.0)", "mypy-boto3-ssm-sap (>=1.40.0,<1.41.0)", "mypy-boto3-sso (>=1.40.0,<1.41.0)", "mypy-boto3-sso-admin (>=1.40.0,<1.41.0)", "mypy-boto3-sso-oidc (>=1.40.0,<1.41.0)", "mypy-boto3-stepfunctions (>=1.40.0,<1.41.0)", "mypy-boto3-storagegateway (>=1.40.0,<1.41.0)", "mypy-boto3-sts (>=1.40.0,<1.41.0)", "mypy-boto3-supplychain (>=1.40.0,<1.41.0)", "mypy-boto3-support (>=1.40.0,<1.41.0)", "mypy-boto3-support-app (>=1.40.0,<1.41.0)", "mypy-boto3-swf (>=1.40.0,<1.41.0)", "mypy-boto3-synthetics (>=1.40.0,<1.41.0)", "mypy-boto3-taxsettings (>=1.40.0,<1.41.0)", "mypy-boto3-textract (>=1.40.0,<1.41.0)", "mypy-boto3-timestream-influxdb (>=1.40.0,<1.41.0)", "mypy-boto3-timestream-query (>=1.40.0,<1.41.0)", "mypy-boto3-timestream-write (>=1.40.0,<1.41.0)", "mypy-boto3-tnb (>=1.40.0,<1.41.0)", "mypy-boto3-transcribe (>=1.40.0,<1.41.0)", "mypy-boto3-transfer (>=1.40.0,<1.41.0)", "mypy-boto3-translate (>=1.40.0,<1.41.0)", "mypy-boto3-trustedadvisor (>=1.40.0,<1.41.0)", "mypy-boto3-verifiedpermissions (>=1.40.0,<1.41.0)", "mypy-boto3-voice-id (>=1.40.0,<1.41.0)", "mypy-boto3-vpc-lattice (>=1.40.0,<1.41.0)", "mypy-boto3-waf (>=1.40.0,<1.41.0)", "mypy-boto3-waf-regional (>=1.40.0,<1.41.0)", "mypy-boto3-wafv2 (>=1.40.0,<1.41.0)", "mypy-boto3-wellarchitected (>=1.40.0,<1.41.0)", "mypy-boto3-wisdom (>=1.40.0,<1.41.0)", "mypy-boto3-workdocs (>=1.40.0,<1.41.0)", "mypy-boto3-workmail (>=1.40.0,<1.41.0)", "mypy-boto3-workmailmessageflow (>=1.40.0,<1.41.0)", "mypy-boto3-workspaces (>=1.40.0,<1.41.0)", "mypy-boto3-workspaces-instances (>=1.40.0,<1.41.0)", "mypy-boto3-workspaces-thin-client (>=1.40.0,<1.41.0)", "mypy-boto3-workspaces-web (>=1.40.0,<1.41.0)", "mypy-boto3-xray (>=1.40.0,<1.41.0)"] +amp = ["mypy-boto3-amp (>=1.40.0,<1.41.0)"] +amplify = ["mypy-boto3-amplify (>=1.40.0,<1.41.0)"] +amplifybackend = ["mypy-boto3-amplifybackend (>=1.40.0,<1.41.0)"] +amplifyuibuilder = ["mypy-boto3-amplifyuibuilder (>=1.40.0,<1.41.0)"] +apigateway = ["mypy-boto3-apigateway (>=1.40.0,<1.41.0)"] +apigatewaymanagementapi = ["mypy-boto3-apigatewaymanagementapi (>=1.40.0,<1.41.0)"] +apigatewayv2 = ["mypy-boto3-apigatewayv2 (>=1.40.0,<1.41.0)"] +appconfig = ["mypy-boto3-appconfig (>=1.40.0,<1.41.0)"] +appconfigdata = ["mypy-boto3-appconfigdata (>=1.40.0,<1.41.0)"] +appfabric = ["mypy-boto3-appfabric (>=1.40.0,<1.41.0)"] +appflow = ["mypy-boto3-appflow (>=1.40.0,<1.41.0)"] +appintegrations = ["mypy-boto3-appintegrations (>=1.40.0,<1.41.0)"] +application-autoscaling = ["mypy-boto3-application-autoscaling (>=1.40.0,<1.41.0)"] +application-insights = ["mypy-boto3-application-insights (>=1.40.0,<1.41.0)"] +application-signals = ["mypy-boto3-application-signals (>=1.40.0,<1.41.0)"] +applicationcostprofiler = ["mypy-boto3-applicationcostprofiler (>=1.40.0,<1.41.0)"] +appmesh = ["mypy-boto3-appmesh (>=1.40.0,<1.41.0)"] +apprunner = ["mypy-boto3-apprunner (>=1.40.0,<1.41.0)"] +appstream = ["mypy-boto3-appstream (>=1.40.0,<1.41.0)"] +appsync = ["mypy-boto3-appsync (>=1.40.0,<1.41.0)"] +arc-region-switch = ["mypy-boto3-arc-region-switch (>=1.40.0,<1.41.0)"] +arc-zonal-shift = ["mypy-boto3-arc-zonal-shift (>=1.40.0,<1.41.0)"] +artifact = ["mypy-boto3-artifact (>=1.40.0,<1.41.0)"] +athena = ["mypy-boto3-athena (>=1.40.0,<1.41.0)"] +auditmanager = ["mypy-boto3-auditmanager (>=1.40.0,<1.41.0)"] +autoscaling = ["mypy-boto3-autoscaling (>=1.40.0,<1.41.0)"] +autoscaling-plans = ["mypy-boto3-autoscaling-plans (>=1.40.0,<1.41.0)"] +b2bi = ["mypy-boto3-b2bi (>=1.40.0,<1.41.0)"] +backup = ["mypy-boto3-backup (>=1.40.0,<1.41.0)"] +backup-gateway = ["mypy-boto3-backup-gateway (>=1.40.0,<1.41.0)"] +backupsearch = ["mypy-boto3-backupsearch (>=1.40.0,<1.41.0)"] +batch = ["mypy-boto3-batch (>=1.40.0,<1.41.0)"] +bcm-dashboards = ["mypy-boto3-bcm-dashboards (>=1.40.0,<1.41.0)"] +bcm-data-exports = ["mypy-boto3-bcm-data-exports (>=1.40.0,<1.41.0)"] +bcm-pricing-calculator = ["mypy-boto3-bcm-pricing-calculator (>=1.40.0,<1.41.0)"] +bcm-recommended-actions = ["mypy-boto3-bcm-recommended-actions (>=1.40.0,<1.41.0)"] +bedrock = ["mypy-boto3-bedrock (>=1.40.0,<1.41.0)"] +bedrock-agent = ["mypy-boto3-bedrock-agent (>=1.40.0,<1.41.0)"] +bedrock-agent-runtime = ["mypy-boto3-bedrock-agent-runtime (>=1.40.0,<1.41.0)"] +bedrock-agentcore = ["mypy-boto3-bedrock-agentcore (>=1.40.0,<1.41.0)"] +bedrock-agentcore-control = ["mypy-boto3-bedrock-agentcore-control (>=1.40.0,<1.41.0)"] +bedrock-data-automation = ["mypy-boto3-bedrock-data-automation (>=1.40.0,<1.41.0)"] +bedrock-data-automation-runtime = ["mypy-boto3-bedrock-data-automation-runtime (>=1.40.0,<1.41.0)"] +bedrock-runtime = ["mypy-boto3-bedrock-runtime (>=1.40.0,<1.41.0)"] +billing = ["mypy-boto3-billing (>=1.40.0,<1.41.0)"] +billingconductor = ["mypy-boto3-billingconductor (>=1.40.0,<1.41.0)"] +boto3 = ["boto3 (==1.40.64)"] +braket = ["mypy-boto3-braket (>=1.40.0,<1.41.0)"] +budgets = ["mypy-boto3-budgets (>=1.40.0,<1.41.0)"] +ce = ["mypy-boto3-ce (>=1.40.0,<1.41.0)"] +chatbot = ["mypy-boto3-chatbot (>=1.40.0,<1.41.0)"] +chime = ["mypy-boto3-chime (>=1.40.0,<1.41.0)"] +chime-sdk-identity = ["mypy-boto3-chime-sdk-identity (>=1.40.0,<1.41.0)"] +chime-sdk-media-pipelines = ["mypy-boto3-chime-sdk-media-pipelines (>=1.40.0,<1.41.0)"] +chime-sdk-meetings = ["mypy-boto3-chime-sdk-meetings (>=1.40.0,<1.41.0)"] +chime-sdk-messaging = ["mypy-boto3-chime-sdk-messaging (>=1.40.0,<1.41.0)"] +chime-sdk-voice = ["mypy-boto3-chime-sdk-voice (>=1.40.0,<1.41.0)"] +cleanrooms = ["mypy-boto3-cleanrooms (>=1.40.0,<1.41.0)"] +cleanroomsml = ["mypy-boto3-cleanroomsml (>=1.40.0,<1.41.0)"] +cloud9 = ["mypy-boto3-cloud9 (>=1.40.0,<1.41.0)"] +cloudcontrol = ["mypy-boto3-cloudcontrol (>=1.40.0,<1.41.0)"] +clouddirectory = ["mypy-boto3-clouddirectory (>=1.40.0,<1.41.0)"] +cloudformation = ["mypy-boto3-cloudformation (>=1.40.0,<1.41.0)"] +cloudfront = ["mypy-boto3-cloudfront (>=1.40.0,<1.41.0)"] +cloudfront-keyvaluestore = ["mypy-boto3-cloudfront-keyvaluestore (>=1.40.0,<1.41.0)"] +cloudhsm = ["mypy-boto3-cloudhsm (>=1.40.0,<1.41.0)"] +cloudhsmv2 = ["mypy-boto3-cloudhsmv2 (>=1.40.0,<1.41.0)"] +cloudsearch = ["mypy-boto3-cloudsearch (>=1.40.0,<1.41.0)"] +cloudsearchdomain = ["mypy-boto3-cloudsearchdomain (>=1.40.0,<1.41.0)"] +cloudtrail = ["mypy-boto3-cloudtrail (>=1.40.0,<1.41.0)"] +cloudtrail-data = ["mypy-boto3-cloudtrail-data (>=1.40.0,<1.41.0)"] +cloudwatch = ["mypy-boto3-cloudwatch (>=1.40.0,<1.41.0)"] +codeartifact = ["mypy-boto3-codeartifact (>=1.40.0,<1.41.0)"] +codebuild = ["mypy-boto3-codebuild (>=1.40.0,<1.41.0)"] +codecatalyst = ["mypy-boto3-codecatalyst (>=1.40.0,<1.41.0)"] +codecommit = ["mypy-boto3-codecommit (>=1.40.0,<1.41.0)"] +codeconnections = ["mypy-boto3-codeconnections (>=1.40.0,<1.41.0)"] +codedeploy = ["mypy-boto3-codedeploy (>=1.40.0,<1.41.0)"] +codeguru-reviewer = ["mypy-boto3-codeguru-reviewer (>=1.40.0,<1.41.0)"] +codeguru-security = ["mypy-boto3-codeguru-security (>=1.40.0,<1.41.0)"] +codeguruprofiler = ["mypy-boto3-codeguruprofiler (>=1.40.0,<1.41.0)"] +codepipeline = ["mypy-boto3-codepipeline (>=1.40.0,<1.41.0)"] +codestar-connections = ["mypy-boto3-codestar-connections (>=1.40.0,<1.41.0)"] +codestar-notifications = ["mypy-boto3-codestar-notifications (>=1.40.0,<1.41.0)"] +cognito-identity = ["mypy-boto3-cognito-identity (>=1.40.0,<1.41.0)"] +cognito-idp = ["mypy-boto3-cognito-idp (>=1.40.0,<1.41.0)"] +cognito-sync = ["mypy-boto3-cognito-sync (>=1.40.0,<1.41.0)"] +comprehend = ["mypy-boto3-comprehend (>=1.40.0,<1.41.0)"] +comprehendmedical = ["mypy-boto3-comprehendmedical (>=1.40.0,<1.41.0)"] +compute-optimizer = ["mypy-boto3-compute-optimizer (>=1.40.0,<1.41.0)"] +config = ["mypy-boto3-config (>=1.40.0,<1.41.0)"] +connect = ["mypy-boto3-connect (>=1.40.0,<1.41.0)"] +connect-contact-lens = ["mypy-boto3-connect-contact-lens (>=1.40.0,<1.41.0)"] +connectcampaigns = ["mypy-boto3-connectcampaigns (>=1.40.0,<1.41.0)"] +connectcampaignsv2 = ["mypy-boto3-connectcampaignsv2 (>=1.40.0,<1.41.0)"] +connectcases = ["mypy-boto3-connectcases (>=1.40.0,<1.41.0)"] +connectparticipant = ["mypy-boto3-connectparticipant (>=1.40.0,<1.41.0)"] +controlcatalog = ["mypy-boto3-controlcatalog (>=1.40.0,<1.41.0)"] +controltower = ["mypy-boto3-controltower (>=1.40.0,<1.41.0)"] +cost-optimization-hub = ["mypy-boto3-cost-optimization-hub (>=1.40.0,<1.41.0)"] +cur = ["mypy-boto3-cur (>=1.40.0,<1.41.0)"] +customer-profiles = ["mypy-boto3-customer-profiles (>=1.40.0,<1.41.0)"] +databrew = ["mypy-boto3-databrew (>=1.40.0,<1.41.0)"] +dataexchange = ["mypy-boto3-dataexchange (>=1.40.0,<1.41.0)"] +datapipeline = ["mypy-boto3-datapipeline (>=1.40.0,<1.41.0)"] +datasync = ["mypy-boto3-datasync (>=1.40.0,<1.41.0)"] +datazone = ["mypy-boto3-datazone (>=1.40.0,<1.41.0)"] +dax = ["mypy-boto3-dax (>=1.40.0,<1.41.0)"] +deadline = ["mypy-boto3-deadline (>=1.40.0,<1.41.0)"] +detective = ["mypy-boto3-detective (>=1.40.0,<1.41.0)"] +devicefarm = ["mypy-boto3-devicefarm (>=1.40.0,<1.41.0)"] +devops-guru = ["mypy-boto3-devops-guru (>=1.40.0,<1.41.0)"] +directconnect = ["mypy-boto3-directconnect (>=1.40.0,<1.41.0)"] +discovery = ["mypy-boto3-discovery (>=1.40.0,<1.41.0)"] +dlm = ["mypy-boto3-dlm (>=1.40.0,<1.41.0)"] +dms = ["mypy-boto3-dms (>=1.40.0,<1.41.0)"] +docdb = ["mypy-boto3-docdb (>=1.40.0,<1.41.0)"] +docdb-elastic = ["mypy-boto3-docdb-elastic (>=1.40.0,<1.41.0)"] +drs = ["mypy-boto3-drs (>=1.40.0,<1.41.0)"] +ds = ["mypy-boto3-ds (>=1.40.0,<1.41.0)"] +ds-data = ["mypy-boto3-ds-data (>=1.40.0,<1.41.0)"] +dsql = ["mypy-boto3-dsql (>=1.40.0,<1.41.0)"] +dynamodb = ["mypy-boto3-dynamodb (>=1.40.0,<1.41.0)"] +dynamodbstreams = ["mypy-boto3-dynamodbstreams (>=1.40.0,<1.41.0)"] +ebs = ["mypy-boto3-ebs (>=1.40.0,<1.41.0)"] +ec2 = ["mypy-boto3-ec2 (>=1.40.0,<1.41.0)"] +ec2-instance-connect = ["mypy-boto3-ec2-instance-connect (>=1.40.0,<1.41.0)"] +ecr = ["mypy-boto3-ecr (>=1.40.0,<1.41.0)"] +ecr-public = ["mypy-boto3-ecr-public (>=1.40.0,<1.41.0)"] +ecs = ["mypy-boto3-ecs (>=1.40.0,<1.41.0)"] +efs = ["mypy-boto3-efs (>=1.40.0,<1.41.0)"] +eks = ["mypy-boto3-eks (>=1.40.0,<1.41.0)"] +eks-auth = ["mypy-boto3-eks-auth (>=1.40.0,<1.41.0)"] +elasticache = ["mypy-boto3-elasticache (>=1.40.0,<1.41.0)"] +elasticbeanstalk = ["mypy-boto3-elasticbeanstalk (>=1.40.0,<1.41.0)"] +elastictranscoder = ["mypy-boto3-elastictranscoder (>=1.40.0,<1.41.0)"] +elb = ["mypy-boto3-elb (>=1.40.0,<1.41.0)"] +elbv2 = ["mypy-boto3-elbv2 (>=1.40.0,<1.41.0)"] +emr = ["mypy-boto3-emr (>=1.40.0,<1.41.0)"] +emr-containers = ["mypy-boto3-emr-containers (>=1.40.0,<1.41.0)"] +emr-serverless = ["mypy-boto3-emr-serverless (>=1.40.0,<1.41.0)"] +entityresolution = ["mypy-boto3-entityresolution (>=1.40.0,<1.41.0)"] +es = ["mypy-boto3-es (>=1.40.0,<1.41.0)"] +essential = ["mypy-boto3-cloudformation (>=1.40.0,<1.41.0)", "mypy-boto3-dynamodb (>=1.40.0,<1.41.0)", "mypy-boto3-ec2 (>=1.40.0,<1.41.0)", "mypy-boto3-lambda (>=1.40.0,<1.41.0)", "mypy-boto3-rds (>=1.40.0,<1.41.0)", "mypy-boto3-s3 (>=1.40.0,<1.41.0)", "mypy-boto3-sqs (>=1.40.0,<1.41.0)"] +events = ["mypy-boto3-events (>=1.40.0,<1.41.0)"] +evidently = ["mypy-boto3-evidently (>=1.40.0,<1.41.0)"] +evs = ["mypy-boto3-evs (>=1.40.0,<1.41.0)"] +finspace = ["mypy-boto3-finspace (>=1.40.0,<1.41.0)"] +finspace-data = ["mypy-boto3-finspace-data (>=1.40.0,<1.41.0)"] +firehose = ["mypy-boto3-firehose (>=1.40.0,<1.41.0)"] +fis = ["mypy-boto3-fis (>=1.40.0,<1.41.0)"] +fms = ["mypy-boto3-fms (>=1.40.0,<1.41.0)"] +forecast = ["mypy-boto3-forecast (>=1.40.0,<1.41.0)"] +forecastquery = ["mypy-boto3-forecastquery (>=1.40.0,<1.41.0)"] +frauddetector = ["mypy-boto3-frauddetector (>=1.40.0,<1.41.0)"] +freetier = ["mypy-boto3-freetier (>=1.40.0,<1.41.0)"] +fsx = ["mypy-boto3-fsx (>=1.40.0,<1.41.0)"] +full = ["boto3-stubs-full (>=1.40.0,<1.41.0)"] +gamelift = ["mypy-boto3-gamelift (>=1.40.0,<1.41.0)"] +gameliftstreams = ["mypy-boto3-gameliftstreams (>=1.40.0,<1.41.0)"] +geo-maps = ["mypy-boto3-geo-maps (>=1.40.0,<1.41.0)"] +geo-places = ["mypy-boto3-geo-places (>=1.40.0,<1.41.0)"] +geo-routes = ["mypy-boto3-geo-routes (>=1.40.0,<1.41.0)"] +glacier = ["mypy-boto3-glacier (>=1.40.0,<1.41.0)"] +globalaccelerator = ["mypy-boto3-globalaccelerator (>=1.40.0,<1.41.0)"] +glue = ["mypy-boto3-glue (>=1.40.0,<1.41.0)"] +grafana = ["mypy-boto3-grafana (>=1.40.0,<1.41.0)"] +greengrass = ["mypy-boto3-greengrass (>=1.40.0,<1.41.0)"] +greengrassv2 = ["mypy-boto3-greengrassv2 (>=1.40.0,<1.41.0)"] +groundstation = ["mypy-boto3-groundstation (>=1.40.0,<1.41.0)"] +guardduty = ["mypy-boto3-guardduty (>=1.40.0,<1.41.0)"] +health = ["mypy-boto3-health (>=1.40.0,<1.41.0)"] +healthlake = ["mypy-boto3-healthlake (>=1.40.0,<1.41.0)"] +iam = ["mypy-boto3-iam (>=1.40.0,<1.41.0)"] +identitystore = ["mypy-boto3-identitystore (>=1.40.0,<1.41.0)"] +imagebuilder = ["mypy-boto3-imagebuilder (>=1.40.0,<1.41.0)"] +importexport = ["mypy-boto3-importexport (>=1.40.0,<1.41.0)"] +inspector = ["mypy-boto3-inspector (>=1.40.0,<1.41.0)"] +inspector-scan = ["mypy-boto3-inspector-scan (>=1.40.0,<1.41.0)"] +inspector2 = ["mypy-boto3-inspector2 (>=1.40.0,<1.41.0)"] +internetmonitor = ["mypy-boto3-internetmonitor (>=1.40.0,<1.41.0)"] +invoicing = ["mypy-boto3-invoicing (>=1.40.0,<1.41.0)"] +iot = ["mypy-boto3-iot (>=1.40.0,<1.41.0)"] +iot-data = ["mypy-boto3-iot-data (>=1.40.0,<1.41.0)"] +iot-jobs-data = ["mypy-boto3-iot-jobs-data (>=1.40.0,<1.41.0)"] +iot-managed-integrations = ["mypy-boto3-iot-managed-integrations (>=1.40.0,<1.41.0)"] +iotanalytics = ["mypy-boto3-iotanalytics (>=1.40.0,<1.41.0)"] +iotdeviceadvisor = ["mypy-boto3-iotdeviceadvisor (>=1.40.0,<1.41.0)"] +iotevents = ["mypy-boto3-iotevents (>=1.40.0,<1.41.0)"] +iotevents-data = ["mypy-boto3-iotevents-data (>=1.40.0,<1.41.0)"] +iotfleetwise = ["mypy-boto3-iotfleetwise (>=1.40.0,<1.41.0)"] +iotsecuretunneling = ["mypy-boto3-iotsecuretunneling (>=1.40.0,<1.41.0)"] +iotsitewise = ["mypy-boto3-iotsitewise (>=1.40.0,<1.41.0)"] +iotthingsgraph = ["mypy-boto3-iotthingsgraph (>=1.40.0,<1.41.0)"] +iottwinmaker = ["mypy-boto3-iottwinmaker (>=1.40.0,<1.41.0)"] +iotwireless = ["mypy-boto3-iotwireless (>=1.40.0,<1.41.0)"] +ivs = ["mypy-boto3-ivs (>=1.40.0,<1.41.0)"] +ivs-realtime = ["mypy-boto3-ivs-realtime (>=1.40.0,<1.41.0)"] +ivschat = ["mypy-boto3-ivschat (>=1.40.0,<1.41.0)"] +kafka = ["mypy-boto3-kafka (>=1.40.0,<1.41.0)"] +kafkaconnect = ["mypy-boto3-kafkaconnect (>=1.40.0,<1.41.0)"] +kendra = ["mypy-boto3-kendra (>=1.40.0,<1.41.0)"] +kendra-ranking = ["mypy-boto3-kendra-ranking (>=1.40.0,<1.41.0)"] +keyspaces = ["mypy-boto3-keyspaces (>=1.40.0,<1.41.0)"] +keyspacesstreams = ["mypy-boto3-keyspacesstreams (>=1.40.0,<1.41.0)"] +kinesis = ["mypy-boto3-kinesis (>=1.40.0,<1.41.0)"] +kinesis-video-archived-media = ["mypy-boto3-kinesis-video-archived-media (>=1.40.0,<1.41.0)"] +kinesis-video-media = ["mypy-boto3-kinesis-video-media (>=1.40.0,<1.41.0)"] +kinesis-video-signaling = ["mypy-boto3-kinesis-video-signaling (>=1.40.0,<1.41.0)"] +kinesis-video-webrtc-storage = ["mypy-boto3-kinesis-video-webrtc-storage (>=1.40.0,<1.41.0)"] +kinesisanalytics = ["mypy-boto3-kinesisanalytics (>=1.40.0,<1.41.0)"] +kinesisanalyticsv2 = ["mypy-boto3-kinesisanalyticsv2 (>=1.40.0,<1.41.0)"] +kinesisvideo = ["mypy-boto3-kinesisvideo (>=1.40.0,<1.41.0)"] +kms = ["mypy-boto3-kms (>=1.40.0,<1.41.0)"] +lakeformation = ["mypy-boto3-lakeformation (>=1.40.0,<1.41.0)"] +lambda = ["mypy-boto3-lambda (>=1.40.0,<1.41.0)"] +launch-wizard = ["mypy-boto3-launch-wizard (>=1.40.0,<1.41.0)"] +lex-models = ["mypy-boto3-lex-models (>=1.40.0,<1.41.0)"] +lex-runtime = ["mypy-boto3-lex-runtime (>=1.40.0,<1.41.0)"] +lexv2-models = ["mypy-boto3-lexv2-models (>=1.40.0,<1.41.0)"] +lexv2-runtime = ["mypy-boto3-lexv2-runtime (>=1.40.0,<1.41.0)"] +license-manager = ["mypy-boto3-license-manager (>=1.40.0,<1.41.0)"] +license-manager-linux-subscriptions = ["mypy-boto3-license-manager-linux-subscriptions (>=1.40.0,<1.41.0)"] +license-manager-user-subscriptions = ["mypy-boto3-license-manager-user-subscriptions (>=1.40.0,<1.41.0)"] +lightsail = ["mypy-boto3-lightsail (>=1.40.0,<1.41.0)"] +location = ["mypy-boto3-location (>=1.40.0,<1.41.0)"] +logs = ["mypy-boto3-logs (>=1.40.0,<1.41.0)"] +lookoutequipment = ["mypy-boto3-lookoutequipment (>=1.40.0,<1.41.0)"] +m2 = ["mypy-boto3-m2 (>=1.40.0,<1.41.0)"] +machinelearning = ["mypy-boto3-machinelearning (>=1.40.0,<1.41.0)"] +macie2 = ["mypy-boto3-macie2 (>=1.40.0,<1.41.0)"] +mailmanager = ["mypy-boto3-mailmanager (>=1.40.0,<1.41.0)"] +managedblockchain = ["mypy-boto3-managedblockchain (>=1.40.0,<1.41.0)"] +managedblockchain-query = ["mypy-boto3-managedblockchain-query (>=1.40.0,<1.41.0)"] +marketplace-agreement = ["mypy-boto3-marketplace-agreement (>=1.40.0,<1.41.0)"] +marketplace-catalog = ["mypy-boto3-marketplace-catalog (>=1.40.0,<1.41.0)"] +marketplace-deployment = ["mypy-boto3-marketplace-deployment (>=1.40.0,<1.41.0)"] +marketplace-entitlement = ["mypy-boto3-marketplace-entitlement (>=1.40.0,<1.41.0)"] +marketplace-reporting = ["mypy-boto3-marketplace-reporting (>=1.40.0,<1.41.0)"] +marketplacecommerceanalytics = ["mypy-boto3-marketplacecommerceanalytics (>=1.40.0,<1.41.0)"] +mediaconnect = ["mypy-boto3-mediaconnect (>=1.40.0,<1.41.0)"] +mediaconvert = ["mypy-boto3-mediaconvert (>=1.40.0,<1.41.0)"] +medialive = ["mypy-boto3-medialive (>=1.40.0,<1.41.0)"] +mediapackage = ["mypy-boto3-mediapackage (>=1.40.0,<1.41.0)"] +mediapackage-vod = ["mypy-boto3-mediapackage-vod (>=1.40.0,<1.41.0)"] +mediapackagev2 = ["mypy-boto3-mediapackagev2 (>=1.40.0,<1.41.0)"] +mediastore = ["mypy-boto3-mediastore (>=1.40.0,<1.41.0)"] +mediastore-data = ["mypy-boto3-mediastore-data (>=1.40.0,<1.41.0)"] +mediatailor = ["mypy-boto3-mediatailor (>=1.40.0,<1.41.0)"] +medical-imaging = ["mypy-boto3-medical-imaging (>=1.40.0,<1.41.0)"] +memorydb = ["mypy-boto3-memorydb (>=1.40.0,<1.41.0)"] +meteringmarketplace = ["mypy-boto3-meteringmarketplace (>=1.40.0,<1.41.0)"] +mgh = ["mypy-boto3-mgh (>=1.40.0,<1.41.0)"] +mgn = ["mypy-boto3-mgn (>=1.40.0,<1.41.0)"] +migration-hub-refactor-spaces = ["mypy-boto3-migration-hub-refactor-spaces (>=1.40.0,<1.41.0)"] +migrationhub-config = ["mypy-boto3-migrationhub-config (>=1.40.0,<1.41.0)"] +migrationhuborchestrator = ["mypy-boto3-migrationhuborchestrator (>=1.40.0,<1.41.0)"] +migrationhubstrategy = ["mypy-boto3-migrationhubstrategy (>=1.40.0,<1.41.0)"] +mpa = ["mypy-boto3-mpa (>=1.40.0,<1.41.0)"] +mq = ["mypy-boto3-mq (>=1.40.0,<1.41.0)"] +mturk = ["mypy-boto3-mturk (>=1.40.0,<1.41.0)"] +mwaa = ["mypy-boto3-mwaa (>=1.40.0,<1.41.0)"] +neptune = ["mypy-boto3-neptune (>=1.40.0,<1.41.0)"] +neptune-graph = ["mypy-boto3-neptune-graph (>=1.40.0,<1.41.0)"] +neptunedata = ["mypy-boto3-neptunedata (>=1.40.0,<1.41.0)"] +network-firewall = ["mypy-boto3-network-firewall (>=1.40.0,<1.41.0)"] +networkflowmonitor = ["mypy-boto3-networkflowmonitor (>=1.40.0,<1.41.0)"] +networkmanager = ["mypy-boto3-networkmanager (>=1.40.0,<1.41.0)"] +networkmonitor = ["mypy-boto3-networkmonitor (>=1.40.0,<1.41.0)"] +notifications = ["mypy-boto3-notifications (>=1.40.0,<1.41.0)"] +notificationscontacts = ["mypy-boto3-notificationscontacts (>=1.40.0,<1.41.0)"] +oam = ["mypy-boto3-oam (>=1.40.0,<1.41.0)"] +observabilityadmin = ["mypy-boto3-observabilityadmin (>=1.40.0,<1.41.0)"] +odb = ["mypy-boto3-odb (>=1.40.0,<1.41.0)"] +omics = ["mypy-boto3-omics (>=1.40.0,<1.41.0)"] +opensearch = ["mypy-boto3-opensearch (>=1.40.0,<1.41.0)"] +opensearchserverless = ["mypy-boto3-opensearchserverless (>=1.40.0,<1.41.0)"] +organizations = ["mypy-boto3-organizations (>=1.40.0,<1.41.0)"] +osis = ["mypy-boto3-osis (>=1.40.0,<1.41.0)"] +outposts = ["mypy-boto3-outposts (>=1.40.0,<1.41.0)"] +panorama = ["mypy-boto3-panorama (>=1.40.0,<1.41.0)"] +partnercentral-selling = ["mypy-boto3-partnercentral-selling (>=1.40.0,<1.41.0)"] +payment-cryptography = ["mypy-boto3-payment-cryptography (>=1.40.0,<1.41.0)"] +payment-cryptography-data = ["mypy-boto3-payment-cryptography-data (>=1.40.0,<1.41.0)"] +pca-connector-ad = ["mypy-boto3-pca-connector-ad (>=1.40.0,<1.41.0)"] +pca-connector-scep = ["mypy-boto3-pca-connector-scep (>=1.40.0,<1.41.0)"] +pcs = ["mypy-boto3-pcs (>=1.40.0,<1.41.0)"] +personalize = ["mypy-boto3-personalize (>=1.40.0,<1.41.0)"] +personalize-events = ["mypy-boto3-personalize-events (>=1.40.0,<1.41.0)"] +personalize-runtime = ["mypy-boto3-personalize-runtime (>=1.40.0,<1.41.0)"] +pi = ["mypy-boto3-pi (>=1.40.0,<1.41.0)"] +pinpoint = ["mypy-boto3-pinpoint (>=1.40.0,<1.41.0)"] +pinpoint-email = ["mypy-boto3-pinpoint-email (>=1.40.0,<1.41.0)"] +pinpoint-sms-voice = ["mypy-boto3-pinpoint-sms-voice (>=1.40.0,<1.41.0)"] +pinpoint-sms-voice-v2 = ["mypy-boto3-pinpoint-sms-voice-v2 (>=1.40.0,<1.41.0)"] +pipes = ["mypy-boto3-pipes (>=1.40.0,<1.41.0)"] +polly = ["mypy-boto3-polly (>=1.40.0,<1.41.0)"] +pricing = ["mypy-boto3-pricing (>=1.40.0,<1.41.0)"] +proton = ["mypy-boto3-proton (>=1.40.0,<1.41.0)"] +qapps = ["mypy-boto3-qapps (>=1.40.0,<1.41.0)"] +qbusiness = ["mypy-boto3-qbusiness (>=1.40.0,<1.41.0)"] +qconnect = ["mypy-boto3-qconnect (>=1.40.0,<1.41.0)"] +quicksight = ["mypy-boto3-quicksight (>=1.40.0,<1.41.0)"] +ram = ["mypy-boto3-ram (>=1.40.0,<1.41.0)"] +rbin = ["mypy-boto3-rbin (>=1.40.0,<1.41.0)"] +rds = ["mypy-boto3-rds (>=1.40.0,<1.41.0)"] +rds-data = ["mypy-boto3-rds-data (>=1.40.0,<1.41.0)"] +redshift = ["mypy-boto3-redshift (>=1.40.0,<1.41.0)"] +redshift-data = ["mypy-boto3-redshift-data (>=1.40.0,<1.41.0)"] +redshift-serverless = ["mypy-boto3-redshift-serverless (>=1.40.0,<1.41.0)"] +rekognition = ["mypy-boto3-rekognition (>=1.40.0,<1.41.0)"] +repostspace = ["mypy-boto3-repostspace (>=1.40.0,<1.41.0)"] +resiliencehub = ["mypy-boto3-resiliencehub (>=1.40.0,<1.41.0)"] +resource-explorer-2 = ["mypy-boto3-resource-explorer-2 (>=1.40.0,<1.41.0)"] +resource-groups = ["mypy-boto3-resource-groups (>=1.40.0,<1.41.0)"] +resourcegroupstaggingapi = ["mypy-boto3-resourcegroupstaggingapi (>=1.40.0,<1.41.0)"] +rolesanywhere = ["mypy-boto3-rolesanywhere (>=1.40.0,<1.41.0)"] +route53 = ["mypy-boto3-route53 (>=1.40.0,<1.41.0)"] +route53-recovery-cluster = ["mypy-boto3-route53-recovery-cluster (>=1.40.0,<1.41.0)"] +route53-recovery-control-config = ["mypy-boto3-route53-recovery-control-config (>=1.40.0,<1.41.0)"] +route53-recovery-readiness = ["mypy-boto3-route53-recovery-readiness (>=1.40.0,<1.41.0)"] +route53domains = ["mypy-boto3-route53domains (>=1.40.0,<1.41.0)"] +route53profiles = ["mypy-boto3-route53profiles (>=1.40.0,<1.41.0)"] +route53resolver = ["mypy-boto3-route53resolver (>=1.40.0,<1.41.0)"] +rtbfabric = ["mypy-boto3-rtbfabric (>=1.40.0,<1.41.0)"] +rum = ["mypy-boto3-rum (>=1.40.0,<1.41.0)"] +s3 = ["mypy-boto3-s3 (>=1.40.0,<1.41.0)"] +s3control = ["mypy-boto3-s3control (>=1.40.0,<1.41.0)"] +s3outposts = ["mypy-boto3-s3outposts (>=1.40.0,<1.41.0)"] +s3tables = ["mypy-boto3-s3tables (>=1.40.0,<1.41.0)"] +s3vectors = ["mypy-boto3-s3vectors (>=1.40.0,<1.41.0)"] +sagemaker = ["mypy-boto3-sagemaker (>=1.40.0,<1.41.0)"] +sagemaker-a2i-runtime = ["mypy-boto3-sagemaker-a2i-runtime (>=1.40.0,<1.41.0)"] +sagemaker-edge = ["mypy-boto3-sagemaker-edge (>=1.40.0,<1.41.0)"] +sagemaker-featurestore-runtime = ["mypy-boto3-sagemaker-featurestore-runtime (>=1.40.0,<1.41.0)"] +sagemaker-geospatial = ["mypy-boto3-sagemaker-geospatial (>=1.40.0,<1.41.0)"] +sagemaker-metrics = ["mypy-boto3-sagemaker-metrics (>=1.40.0,<1.41.0)"] +sagemaker-runtime = ["mypy-boto3-sagemaker-runtime (>=1.40.0,<1.41.0)"] +savingsplans = ["mypy-boto3-savingsplans (>=1.40.0,<1.41.0)"] +scheduler = ["mypy-boto3-scheduler (>=1.40.0,<1.41.0)"] +schemas = ["mypy-boto3-schemas (>=1.40.0,<1.41.0)"] +sdb = ["mypy-boto3-sdb (>=1.40.0,<1.41.0)"] +secretsmanager = ["mypy-boto3-secretsmanager (>=1.40.0,<1.41.0)"] +security-ir = ["mypy-boto3-security-ir (>=1.40.0,<1.41.0)"] +securityhub = ["mypy-boto3-securityhub (>=1.40.0,<1.41.0)"] +securitylake = ["mypy-boto3-securitylake (>=1.40.0,<1.41.0)"] +serverlessrepo = ["mypy-boto3-serverlessrepo (>=1.40.0,<1.41.0)"] +service-quotas = ["mypy-boto3-service-quotas (>=1.40.0,<1.41.0)"] +servicecatalog = ["mypy-boto3-servicecatalog (>=1.40.0,<1.41.0)"] +servicecatalog-appregistry = ["mypy-boto3-servicecatalog-appregistry (>=1.40.0,<1.41.0)"] +servicediscovery = ["mypy-boto3-servicediscovery (>=1.40.0,<1.41.0)"] +ses = ["mypy-boto3-ses (>=1.40.0,<1.41.0)"] +sesv2 = ["mypy-boto3-sesv2 (>=1.40.0,<1.41.0)"] +shield = ["mypy-boto3-shield (>=1.40.0,<1.41.0)"] +signer = ["mypy-boto3-signer (>=1.40.0,<1.41.0)"] +simspaceweaver = ["mypy-boto3-simspaceweaver (>=1.40.0,<1.41.0)"] +snow-device-management = ["mypy-boto3-snow-device-management (>=1.40.0,<1.41.0)"] +snowball = ["mypy-boto3-snowball (>=1.40.0,<1.41.0)"] +sns = ["mypy-boto3-sns (>=1.40.0,<1.41.0)"] +socialmessaging = ["mypy-boto3-socialmessaging (>=1.40.0,<1.41.0)"] +sqs = ["mypy-boto3-sqs (>=1.40.0,<1.41.0)"] +ssm = ["mypy-boto3-ssm (>=1.40.0,<1.41.0)"] +ssm-contacts = ["mypy-boto3-ssm-contacts (>=1.40.0,<1.41.0)"] +ssm-guiconnect = ["mypy-boto3-ssm-guiconnect (>=1.40.0,<1.41.0)"] +ssm-incidents = ["mypy-boto3-ssm-incidents (>=1.40.0,<1.41.0)"] +ssm-quicksetup = ["mypy-boto3-ssm-quicksetup (>=1.40.0,<1.41.0)"] +ssm-sap = ["mypy-boto3-ssm-sap (>=1.40.0,<1.41.0)"] +sso = ["mypy-boto3-sso (>=1.40.0,<1.41.0)"] +sso-admin = ["mypy-boto3-sso-admin (>=1.40.0,<1.41.0)"] +sso-oidc = ["mypy-boto3-sso-oidc (>=1.40.0,<1.41.0)"] +stepfunctions = ["mypy-boto3-stepfunctions (>=1.40.0,<1.41.0)"] +storagegateway = ["mypy-boto3-storagegateway (>=1.40.0,<1.41.0)"] +sts = ["mypy-boto3-sts (>=1.40.0,<1.41.0)"] +supplychain = ["mypy-boto3-supplychain (>=1.40.0,<1.41.0)"] +support = ["mypy-boto3-support (>=1.40.0,<1.41.0)"] +support-app = ["mypy-boto3-support-app (>=1.40.0,<1.41.0)"] +swf = ["mypy-boto3-swf (>=1.40.0,<1.41.0)"] +synthetics = ["mypy-boto3-synthetics (>=1.40.0,<1.41.0)"] +taxsettings = ["mypy-boto3-taxsettings (>=1.40.0,<1.41.0)"] +textract = ["mypy-boto3-textract (>=1.40.0,<1.41.0)"] +timestream-influxdb = ["mypy-boto3-timestream-influxdb (>=1.40.0,<1.41.0)"] +timestream-query = ["mypy-boto3-timestream-query (>=1.40.0,<1.41.0)"] +timestream-write = ["mypy-boto3-timestream-write (>=1.40.0,<1.41.0)"] +tnb = ["mypy-boto3-tnb (>=1.40.0,<1.41.0)"] +transcribe = ["mypy-boto3-transcribe (>=1.40.0,<1.41.0)"] +transfer = ["mypy-boto3-transfer (>=1.40.0,<1.41.0)"] +translate = ["mypy-boto3-translate (>=1.40.0,<1.41.0)"] +trustedadvisor = ["mypy-boto3-trustedadvisor (>=1.40.0,<1.41.0)"] +verifiedpermissions = ["mypy-boto3-verifiedpermissions (>=1.40.0,<1.41.0)"] +voice-id = ["mypy-boto3-voice-id (>=1.40.0,<1.41.0)"] +vpc-lattice = ["mypy-boto3-vpc-lattice (>=1.40.0,<1.41.0)"] +waf = ["mypy-boto3-waf (>=1.40.0,<1.41.0)"] +waf-regional = ["mypy-boto3-waf-regional (>=1.40.0,<1.41.0)"] +wafv2 = ["mypy-boto3-wafv2 (>=1.40.0,<1.41.0)"] +wellarchitected = ["mypy-boto3-wellarchitected (>=1.40.0,<1.41.0)"] +wisdom = ["mypy-boto3-wisdom (>=1.40.0,<1.41.0)"] +workdocs = ["mypy-boto3-workdocs (>=1.40.0,<1.41.0)"] +workmail = ["mypy-boto3-workmail (>=1.40.0,<1.41.0)"] +workmailmessageflow = ["mypy-boto3-workmailmessageflow (>=1.40.0,<1.41.0)"] +workspaces = ["mypy-boto3-workspaces (>=1.40.0,<1.41.0)"] +workspaces-instances = ["mypy-boto3-workspaces-instances (>=1.40.0,<1.41.0)"] +workspaces-thin-client = ["mypy-boto3-workspaces-thin-client (>=1.40.0,<1.41.0)"] +workspaces-web = ["mypy-boto3-workspaces-web (>=1.40.0,<1.41.0)"] +xray = ["mypy-boto3-xray (>=1.40.0,<1.41.0)"] + +[[package]] +name = "botocore" +version = "1.40.67" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "botocore-1.40.67-py3-none-any.whl", hash = "sha256:e49e61f6718e8bc8b34e9bb8a97f16c8dc560485faef4981b55d76f825c9d78a"}, + {file = "botocore-1.40.67.tar.gz", hash = "sha256:cc086f39c877aee0ea8dc88ef69062c9f395b9d30d49bfcfac7b8b7e61864b3a"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} + +[package.extras] +crt = ["awscrt (==0.27.6)"] + +[[package]] +name = "botocore-stubs" +version = "1.40.56" +description = "Type annotations and code completion for botocore" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "botocore_stubs-1.40.56-py3-none-any.whl", hash = "sha256:e6494eef30a5979e8f270f058bd81bc803641fa0ea97414b53c7f0e52d82d227"}, + {file = "botocore_stubs-1.40.56.tar.gz", hash = "sha256:aa9535b8a0f7135b062504e39e7cbc83fb3f00b2d4dc2baba6170436b494b696"}, +] + +[package.dependencies] +types-awscrt = "*" + +[package.extras] +botocore = ["botocore"] + +[[package]] +name = "certifi" +version = "2025.10.5" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, + {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, + {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, + {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, +] + +[[package]] +name = "coverage" +version = "7.11.0" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, + {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, + {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, + {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, + {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, + {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, + {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, + {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, + {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, + {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, + {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, + {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, + {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, + {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, + {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, + {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, + {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, + {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, + {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, + {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, + {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, + {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, + {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, + {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, + {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, + {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, + {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, + {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, + {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, + {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, + {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, + {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, + {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, + {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, + {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, + {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, + {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, + {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, + {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, + {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, + {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, + {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, + {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, + {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, + {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, + {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, + {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, + {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, + {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, + {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, + {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, + {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, + {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, + {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, + {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, + {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, + {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, + {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, + {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, + {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, + {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, + {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, + {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, + {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, + {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, + {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, + {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, + {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, + {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, + {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, + {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, + {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, + {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, + {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, + {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, + {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, + {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, + {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, + {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, + {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, + {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, + {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, + {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, + {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, + {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, + {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, + {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, + {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, + {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, + {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, + {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, + {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, +] + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + +[[package]] +name = "cryptography" +version = "46.0.0" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = "!=3.9.0,!=3.9.1,>=3.8" +groups = ["main"] +files = [ + {file = "cryptography-46.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:c9c4121f9a41cc3d02164541d986f59be31548ad355a5c96ac50703003c50fb7"}, + {file = "cryptography-46.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4f70cbade61a16f5e238c4b0eb4e258d177a2fcb59aa0aae1236594f7b0ae338"}, + {file = "cryptography-46.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1eccae15d5c28c74b2bea228775c63ac5b6c36eedb574e002440c0bc28750d3"}, + {file = "cryptography-46.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1b4fba84166d906a22027f0d958e42f3a4dbbb19c28ea71f0fb7812380b04e3c"}, + {file = "cryptography-46.0.0-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:523153480d7575a169933f083eb47b1edd5fef45d87b026737de74ffeb300f69"}, + {file = "cryptography-46.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:f09a3a108223e319168b7557810596631a8cb864657b0c16ed7a6017f0be9433"}, + {file = "cryptography-46.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c1f6ccd6f2eef3b2eb52837f0463e853501e45a916b3fc42e5d93cf244a4b97b"}, + {file = "cryptography-46.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:80a548a5862d6912a45557a101092cd6c64ae1475b82cef50ee305d14a75f598"}, + {file = "cryptography-46.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6c39fd5cd9b7526afa69d64b5e5645a06e1b904f342584b3885254400b63f1b3"}, + {file = "cryptography-46.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d5c0cbb2fb522f7e39b59a5482a1c9c5923b7c506cfe96a1b8e7368c31617ac0"}, + {file = "cryptography-46.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6d8945bc120dcd90ae39aa841afddaeafc5f2e832809dc54fb906e3db829dfdc"}, + {file = "cryptography-46.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:88c09da8a94ac27798f6b62de6968ac78bb94805b5d272dbcfd5fdc8c566999f"}, + {file = "cryptography-46.0.0-cp311-abi3-win32.whl", hash = "sha256:3738f50215211cee1974193a1809348d33893696ce119968932ea117bcbc9b1d"}, + {file = "cryptography-46.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bbaa5eef3c19c66613317dc61e211b48d5f550db009c45e1c28b59d5a9b7812a"}, + {file = "cryptography-46.0.0-cp311-abi3-win_arm64.whl", hash = "sha256:16b5ac72a965ec9d1e34d9417dbce235d45fa04dac28634384e3ce40dfc66495"}, + {file = "cryptography-46.0.0-cp314-abi3-macosx_10_9_universal2.whl", hash = "sha256:91585fc9e696abd7b3e48a463a20dda1a5c0eeeca4ba60fa4205a79527694390"}, + {file = "cryptography-46.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:65e9117ebed5b16b28154ed36b164c20021f3a480e9cbb4b4a2a59b95e74c25d"}, + {file = "cryptography-46.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:da7f93551d39d462263b6b5c9056c49f780b9200bf9fc2656d7c88c7bdb9b363"}, + {file = "cryptography-46.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:be7479f9504bfb46628544ec7cb4637fe6af8b70445d4455fbb9c395ad9b7290"}, + {file = "cryptography-46.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f85e6a7d42ad60024fa1347b1d4ef82c4df517a4deb7f829d301f1a92ded038c"}, + {file = "cryptography-46.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:d349af4d76a93562f1dce4d983a4a34d01cb22b48635b0d2a0b8372cdb4a8136"}, + {file = "cryptography-46.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:35aa1a44bd3e0efc3ef09cf924b3a0e2a57eda84074556f4506af2d294076685"}, + {file = "cryptography-46.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c457ad3f151d5fb380be99425b286167b358f76d97ad18b188b68097193ed95a"}, + {file = "cryptography-46.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:399ef4c9be67f3902e5ca1d80e64b04498f8b56c19e1bc8d0825050ea5290410"}, + {file = "cryptography-46.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:378eff89b040cbce6169528f130ee75dceeb97eef396a801daec03b696434f06"}, + {file = "cryptography-46.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c3648d6a5878fd1c9a22b1d43fa75efc069d5f54de12df95c638ae7ba88701d0"}, + {file = "cryptography-46.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fc30be952dd4334801d345d134c9ef0e9ccbaa8c3e1bc18925cbc4247b3e29c"}, + {file = "cryptography-46.0.0-cp314-cp314t-win32.whl", hash = "sha256:b8e7db4ce0b7297e88f3d02e6ee9a39382e0efaf1e8974ad353120a2b5a57ef7"}, + {file = "cryptography-46.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:40ee4ce3c34acaa5bc347615ec452c74ae8ff7db973a98c97c62293120f668c6"}, + {file = "cryptography-46.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:07a1be54f995ce14740bf8bbe1cc35f7a37760f992f73cf9f98a2a60b9b97419"}, + {file = "cryptography-46.0.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:1d2073313324226fd846e6b5fc340ed02d43fd7478f584741bd6b791c33c9fee"}, + {file = "cryptography-46.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83af84ebe7b6e9b6de05050c79f8cc0173c864ce747b53abce6a11e940efdc0d"}, + {file = "cryptography-46.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c3cd09b1490c1509bf3892bde9cef729795fae4a2fee0621f19be3321beca7e4"}, + {file = "cryptography-46.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d14eaf1569d6252280516bedaffdd65267428cdbc3a8c2d6de63753cf0863d5e"}, + {file = "cryptography-46.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ab3a14cecc741c8c03ad0ad46dfbf18de25218551931a23bca2731d46c706d83"}, + {file = "cryptography-46.0.0-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8e8b222eb54e3e7d3743a7c2b1f7fa7df7a9add790307bb34327c88ec85fe087"}, + {file = "cryptography-46.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7f3f88df0c9b248dcc2e76124f9140621aca187ccc396b87bc363f890acf3a30"}, + {file = "cryptography-46.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9aa85222f03fdb30defabc7a9e1e3d4ec76eb74ea9fe1504b2800844f9c98440"}, + {file = "cryptography-46.0.0-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f9aaf2a91302e1490c068d2f3af7df4137ac2b36600f5bd26e53d9ec320412d3"}, + {file = "cryptography-46.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:32670ca085150ff36b438c17f2dfc54146fe4a074ebf0a76d72fb1b419a974bc"}, + {file = "cryptography-46.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0f58183453032727a65e6605240e7a3824fd1d6a7e75d2b537e280286ab79a52"}, + {file = "cryptography-46.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4bc257c2d5d865ed37d0bd7c500baa71f939a7952c424f28632298d80ccd5ec1"}, + {file = "cryptography-46.0.0-cp38-abi3-win32.whl", hash = "sha256:df932ac70388be034b2e046e34d636245d5eeb8140db24a6b4c2268cd2073270"}, + {file = "cryptography-46.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:274f8b2eb3616709f437326185eb563eb4e5813d01ebe2029b61bfe7d9995fbb"}, + {file = "cryptography-46.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:249c41f2bbfa026615e7bdca47e4a66135baa81b08509ab240a2e666f6af5966"}, + {file = "cryptography-46.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fe9ff1139b2b1f59a5a0b538bbd950f8660a39624bbe10cf3640d17574f973bb"}, + {file = "cryptography-46.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:77e3bd53c9c189cea361bc18ceb173959f8b2dd8f8d984ae118e9ac641410252"}, + {file = "cryptography-46.0.0-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:75d2ddde8f1766ab2db48ed7f2aa3797aeb491ea8dfe9b4c074201aec00f5c16"}, + {file = "cryptography-46.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f9f85d9cf88e3ba2b2b6da3c2310d1cf75bdf04a5bc1a2e972603054f82c4dd5"}, + {file = "cryptography-46.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:834af45296083d892e23430e3b11df77e2ac5c042caede1da29c9bf59016f4d2"}, + {file = "cryptography-46.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:c39f0947d50f74b1b3523cec3931315072646286fb462995eb998f8136779319"}, + {file = "cryptography-46.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:6460866a92143a24e3ed68eaeb6e98d0cedd85d7d9a8ab1fc293ec91850b1b38"}, + {file = "cryptography-46.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bf1961037309ee0bdf874ccba9820b1c2f720c2016895c44d8eb2316226c1ad5"}, + {file = "cryptography-46.0.0.tar.gz", hash = "sha256:99f64a6d15f19f3afd78720ad2978f6d8d4c68cd4eb600fab82ab1a7c2071dca"}, +] + +[package.dependencies] +cffi = {version = ">=1.14", markers = "python_full_version < \"3.14.0\" and platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox[uv] (>=2024.4.15)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"] +sdist = ["build (>=1.0.0)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi (>=2024)", "cryptography-vectors (==46.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "dnspython" +version = "2.8.0" +description = "DNS toolkit" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af"}, + {file = "dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f"}, +] + +[package.extras] +dev = ["black (>=25.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.17.0)", "mypy (>=1.17)", "pylint (>=3)", "pytest (>=8.4)", "pytest-cov (>=6.2.0)", "quart-trio (>=0.12.0)", "sphinx (>=8.2.0)", "sphinx-rtd-theme (>=3.0.0)", "twine (>=6.1.0)", "wheel (>=0.45.0)"] +dnssec = ["cryptography (>=45)"] +doh = ["h2 (>=4.2.0)", "httpcore (>=1.0.0)", "httpx (>=0.28.0)"] +doq = ["aioquic (>=1.2.0)"] +idna = ["idna (>=3.10)"] +trio = ["trio (>=0.30)"] +wmi = ["wmi (>=1.5.1) ; platform_system == \"Windows\""] + +[[package]] +name = "email-validator" +version = "2.3.0" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4"}, + {file = "email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + +[[package]] +name = "fhir-resources" +version = "7.0.2" +description = "FHIR Resources as Model Class" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "fhir.resources-7.0.2-py2.py3-none-any.whl", hash = "sha256:ba16b3348821614650dd2e7f04db170210ce4406f65a5cc873beb10317d595ff"}, + {file = "fhir.resources-7.0.2.tar.gz", hash = "sha256:1e6392f7bc3b143463b07ada87a3b6a92dd8fe97947670423317fea69226f6de"}, +] + +[package.dependencies] +pydantic = {version = ">=1.7.2,<2.0.0", extras = ["email"]} + +[package.extras] +all = ["PyYAML (>=5.4.1)", "lxml", "orjson (>=3.4.3)"] +dev = ["Jinja2 (==2.11.1)", "MarkupSafe (==1.1.1)", "black", "certifi", "colorlog (==2.10.0)", "coverage", "fhirspec", "flake8 (==5.0.4) ; python_version < \"3.10\"", "flake8-bugbear (==20.1.4) ; python_version < \"3.10\"", "flake8-isort (==4.2.0) ; python_version < \"3.10\"", "isort (==4.3.21)", "mypy (==0.812)", "pytest (>5.4.0) ; python_version >= \"3.6\"", "pytest-cov (>=2.10.0) ; python_version >= \"3.6\"", "requests (==2.23.0) ; python_version < \"3.10\"", "setuptools (==65.6.3) ; python_version >= \"3.7\"", "zest-releaser[recommended]"] +orjson = ["orjson (>=3.4.3)"] +test = ["PyYAML (>=5.4.1)", "black", "coverage", "flake8 (==5.0.4) ; python_version < \"3.10\"", "flake8-bugbear (==20.1.4) ; python_version < \"3.10\"", "flake8-isort (==4.2.0) ; python_version < \"3.10\"", "isort (==4.3.21)", "lxml", "mypy (==0.812)", "orjson (>=3.4.3)", "pytest (>5.4.0) ; python_version >= \"3.6\"", "pytest-cov (>=2.10.0) ; python_version >= \"3.6\"", "pytest-runner", "requests (==2.23.0) ; python_version < \"3.10\"", "setuptools (==65.6.3) ; python_version >= \"3.7\""] +xml = ["lxml"] +yaml = ["PyYAML (>=5.4.1)"] + +[[package]] +name = "freezegun" +version = "1.5.5" +description = "Let your Python tests travel through time" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "freezegun-1.5.5-py3-none-any.whl", hash = "sha256:cd557f4a75cf074e84bc374249b9dd491eaeacd61376b9eb3c423282211619d2"}, + {file = "freezegun-1.5.5.tar.gz", hash = "sha256:ac7742a6cc6c25a2c35e9292dfd554b897b517d2dec26891a2e8debf205cb94a"}, +] + +[package.dependencies] +python-dateutil = ">=2.7" + +[[package]] +name = "idna" +version = "3.11" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, + {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=3.7" +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"}, +] + +[[package]] +name = "jsonpath-ng" +version = "1.7.0" +description = "A final implementation of JSONPath for Python that aims to be standard compliant, including arithmetic and binary comparison operators and providing clear AST for metaprogramming." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"}, + {file = "jsonpath_ng-1.7.0-py2-none-any.whl", hash = "sha256:898c93fc173f0c336784a3fa63d7434297544b7198124a68f9a3ef9597b0ae6e"}, + {file = "jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6"}, +] + +[package.dependencies] +ply = "*" + +[[package]] +name = "markupsafe" +version = "3.0.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, + {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, + {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, + {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, + {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, + {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, + {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, + {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, +] + +[[package]] +name = "moto" +version = "5.1.16" +description = "A library that allows you to easily mock out tests based on AWS infrastructure" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "moto-5.1.16-py3-none-any.whl", hash = "sha256:8e6186f20b3aa91755d186e47701fe7e47f74e625c36fdf3bd7747da68468b19"}, + {file = "moto-5.1.16.tar.gz", hash = "sha256:792045b345d16a8aa09068ad4a7656894e707c796f0799b438fffb738e8fae7c"}, +] + +[package.dependencies] +boto3 = ">=1.9.201" +botocore = ">=1.20.88,<1.35.45 || >1.35.45,<1.35.46 || >1.35.46" +cryptography = ">=35.0.0" +Jinja2 = ">=2.10.1" +python-dateutil = ">=2.1,<3.0.0" +requests = ">=2.5" +responses = ">=0.15.0,<0.25.5 || >0.25.5" +werkzeug = ">=0.5,<2.2.0 || >2.2.0,<2.2.1 || >2.2.1" +xmltodict = "*" + +[package.extras] +all = ["PyYAML (>=5.1)", "antlr4-python3-runtime", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "graphql-core", "joserfc (>=0.9.0)", "jsonpath_ng", "jsonschema", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.6.3)", "pyparsing (>=3.0.7)", "setuptools"] +apigateway = ["PyYAML (>=5.1)", "joserfc (>=0.9.0)", "openapi-spec-validator (>=0.5.0)"] +apigatewayv2 = ["PyYAML (>=5.1)", "openapi-spec-validator (>=0.5.0)"] +appsync = ["graphql-core"] +awslambda = ["docker (>=3.0.0)"] +batch = ["docker (>=3.0.0)"] +cloudformation = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "graphql-core", "joserfc (>=0.9.0)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.6.3)", "pyparsing (>=3.0.7)", "setuptools"] +cognitoidp = ["joserfc (>=0.9.0)"] +dynamodb = ["docker (>=3.0.0)", "py-partiql-parser (==0.6.3)"] +dynamodbstreams = ["docker (>=3.0.0)", "py-partiql-parser (==0.6.3)"] +events = ["jsonpath_ng"] +glue = ["pyparsing (>=3.0.7)"] +proxy = ["PyYAML (>=5.1)", "antlr4-python3-runtime", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=2.5.1)", "graphql-core", "joserfc (>=0.9.0)", "jsonpath_ng", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.6.3)", "pyparsing (>=3.0.7)", "setuptools"] +quicksight = ["jsonschema"] +resourcegroupstaggingapi = ["PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "graphql-core", "joserfc (>=0.9.0)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.6.3)", "pyparsing (>=3.0.7)"] +s3 = ["PyYAML (>=5.1)", "py-partiql-parser (==0.6.3)"] +s3crc32c = ["PyYAML (>=5.1)", "crc32c", "py-partiql-parser (==0.6.3)"] +server = ["PyYAML (>=5.1)", "antlr4-python3-runtime", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "flask (!=2.2.0,!=2.2.1)", "flask-cors", "graphql-core", "joserfc (>=0.9.0)", "jsonpath_ng", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.6.3)", "pyparsing (>=3.0.7)", "setuptools"] +ssm = ["PyYAML (>=5.1)"] +stepfunctions = ["antlr4-python3-runtime", "jsonpath_ng"] +xray = ["aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] + +[[package]] +name = "mypy-boto3-dynamodb" +version = "1.40.56" +description = "Type annotations for boto3 DynamoDB 1.40.56 service generated with mypy-boto3-builder 8.11.0" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "mypy_boto3_dynamodb-1.40.56-py3-none-any.whl", hash = "sha256:3bf3f541a0d21c249109dd65f18c61b3e6a0fe7124b3afe989877d5cca42b65a"}, + {file = "mypy_boto3_dynamodb-1.40.56.tar.gz", hash = "sha256:576dd12fe1125754066e7fa480f92c123220970a9d69f7663a56d701f2978ac5"}, +] + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.12\""} + +[[package]] +name = "ply" +version = "3.11" +description = "Python Lex & Yacc" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"}, + {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, +] + +[[package]] +name = "pycparser" +version = "2.23" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, + {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, +] + +[[package]] +name = "pydantic" +version = "1.10.24" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "pydantic-1.10.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eef07ea2fba12f9188cfa2c50cb3eaa6516b56c33e2a8cc3cd288b4190ee6c0c"}, + {file = "pydantic-1.10.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a42033fac69b9f1f867ecc3a2159f0e94dceb1abfc509ad57e9e88d49774683"}, + {file = "pydantic-1.10.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c626596c1b95dc6d45f7129f10b6743fbb50f29d942d25a22b2ceead670c067d"}, + {file = "pydantic-1.10.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8057172868b0d98f95e6fcddcc5f75d01570e85c6308702dd2c50ea673bc197b"}, + {file = "pydantic-1.10.24-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:82f951210ebcdb778b1d93075af43adcd04e9ebfd4f44b1baa8eeb21fbd71e36"}, + {file = "pydantic-1.10.24-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b66e4892d8ae005f436a5c5f1519ecf837574d8414b1c93860fb3c13943d9b37"}, + {file = "pydantic-1.10.24-cp310-cp310-win_amd64.whl", hash = "sha256:50d9f8a207c07f347d4b34806dc576872000d9a60fd481ed9eb78ea8512e0666"}, + {file = "pydantic-1.10.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:70152291488f8d2bbcf2027b5c28c27724c78a7949c91b466d28ad75d6d12702"}, + {file = "pydantic-1.10.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:956b30638272c51c85caaff76851b60db4b339022c0ee6eca677c41e3646255b"}, + {file = "pydantic-1.10.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bed9d6eea5fabbc6978c42e947190c7bd628ddaff3b56fc963fe696c3710ccd6"}, + {file = "pydantic-1.10.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af8e2b3648128b8cadb1a71e2f8092a6f42d4ca123fad7a8d7ce6db8938b1db3"}, + {file = "pydantic-1.10.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:076fff9da02ca716e4c8299c68512fdfbeac32fdefc9c160e6f80bdadca0993d"}, + {file = "pydantic-1.10.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8f2447ca88a7e14fd4d268857521fb37535c53a367b594fa2d7c2551af905993"}, + {file = "pydantic-1.10.24-cp311-cp311-win_amd64.whl", hash = "sha256:58d42a7c344882c00e3bb7c6c8c6f62db2e3aafa671f307271c45ad96e8ccf7a"}, + {file = "pydantic-1.10.24-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:17e7610119483f03954569c18d4de16f4e92f1585f20975414033ac2d4a96624"}, + {file = "pydantic-1.10.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e24435a9970dcb2b35648f2cf57505d4bd414fcca1a404c82e28d948183fe0a6"}, + {file = "pydantic-1.10.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a9e92b9c78d7f3cfa085c21c110e7000894446e24a836d006aabfc6ae3f1813"}, + {file = "pydantic-1.10.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef14dfa7c98b314a3e449e92df6f1479cafe74c626952f353ff0176b075070de"}, + {file = "pydantic-1.10.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52219b4e70c1db185cfd103a804e416384e1c8950168a2d4f385664c7c35d21a"}, + {file = "pydantic-1.10.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ce0986799248082e9a5a026c9b5d2f9fa2e24d2afb9b0eace9104334a58fdc1"}, + {file = "pydantic-1.10.24-cp312-cp312-win_amd64.whl", hash = "sha256:874a78e4ed821258295a472e325eee7de3d91ba7a61d0639ce1b0367a3c63d4c"}, + {file = "pydantic-1.10.24-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:265788a1120285c4955f8b3d52b3ea6a52c7a74db097c4c13a4d3567f0c6df3c"}, + {file = "pydantic-1.10.24-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d255bebd927e5f1e026b32605684f7b6fc36a13e62b07cb97b29027b91657def"}, + {file = "pydantic-1.10.24-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6e45dbc79a44e34c2c83ef1fcb56ff663040474dcf4dfc452db24a1de0f7574"}, + {file = "pydantic-1.10.24-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af31565b12a7db5bfa5fe8c3a4f8fda4d32f5c2929998b1b241f1c22e9ab6e69"}, + {file = "pydantic-1.10.24-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9c377fc30d9ca40dbff5fd79c5a5e1f0d6fff040fa47a18851bb6b0bd040a5d8"}, + {file = "pydantic-1.10.24-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b644d6f14b2ce617d6def21622f9ba73961a16b7dffdba7f6692e2f66fa05d00"}, + {file = "pydantic-1.10.24-cp313-cp313-win_amd64.whl", hash = "sha256:0cbbf306124ae41cc153fdc2559b37faa1bec9a23ef7b082c1756d1315ceffe6"}, + {file = "pydantic-1.10.24-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7c8bbad6037a87effe9f3739bdf39851add6e0f7e101d103a601c504892ffa70"}, + {file = "pydantic-1.10.24-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f154a8a46a0d950c055254f8f010ba07e742ac4404a3b6e281a31913ac45ccd0"}, + {file = "pydantic-1.10.24-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f25d2f792afcd874cc8339c1da1cc52739f4f3d52993ed1f6c263ef2afadc47"}, + {file = "pydantic-1.10.24-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:49a6f0178063f15eaea6cbcb2dba04db0b73db9834bc7b1e1c4dbea28c7cd22f"}, + {file = "pydantic-1.10.24-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:bb3df10be3c7d264947180615819aeec0916f19650f2ba7309ed1fe546ead0d2"}, + {file = "pydantic-1.10.24-cp37-cp37m-win_amd64.whl", hash = "sha256:fa0ebefc169439267e4b4147c7d458908788367640509ed32c90a91a63ebb579"}, + {file = "pydantic-1.10.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1a5ef77efeb54def2695f2b8f4301aae8c7aa2b334bd15f61c18ef54317621"}, + {file = "pydantic-1.10.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02f7a25e8949d8ca568e4bcef2ffed7881d7843286e7c3488bdd3b67f092059c"}, + {file = "pydantic-1.10.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da2775712dda8b89e701ed2a72d5d81d23dbc6af84089da8a0f61a0be439c8c"}, + {file = "pydantic-1.10.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75259be0558ca3af09192ad7b18557f2e9033ad4cbd48c252131f5292f6374fd"}, + {file = "pydantic-1.10.24-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:1a1ae996daa3d43c530b8d0bacc7e2d9cb55e3991f0e6b7cc2cb61a0fb9f6667"}, + {file = "pydantic-1.10.24-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:34109b0afa63b36eec2f2b115694e48ae5ee52f7d3c1baa0be36f80e586bda52"}, + {file = "pydantic-1.10.24-cp38-cp38-win_amd64.whl", hash = "sha256:4d7336bfcdb8cb58411e6b498772ba2cff84a2ce92f389bae3a8f1bb2c840c49"}, + {file = "pydantic-1.10.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25fb9a69a21d711deb5acefdab9ff8fb49e6cc77fdd46d38217d433bff2e3de2"}, + {file = "pydantic-1.10.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6af36a8fb3072526b5b38d3f341b12d8f423188e7d185f130c0079fe02cdec7f"}, + {file = "pydantic-1.10.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fc35569dfd15d3b3fc06a22abee0a45fdde0784be644e650a8769cd0b2abd94"}, + {file = "pydantic-1.10.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fac7fbcb65171959973f3136d0792c3d1668bc01fd414738f0898b01f692f1b4"}, + {file = "pydantic-1.10.24-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fc3f4a6544517380658b63b144c7d43d5276a343012913b7e5d18d9fba2f12bb"}, + {file = "pydantic-1.10.24-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:415c638ca5fd57b915a62dd38c18c8e0afe5adf5527be6f8ce16b4636b616816"}, + {file = "pydantic-1.10.24-cp39-cp39-win_amd64.whl", hash = "sha256:a5bf94042efbc6ab56b18a5921f426ebbeefc04f554a911d76029e7be9057d01"}, + {file = "pydantic-1.10.24-py3-none-any.whl", hash = "sha256:093768eba26db55a88b12f3073017e3fdee319ef60d3aef5c6c04a4e484db193"}, + {file = "pydantic-1.10.24.tar.gz", hash = "sha256:7e6d1af1bd3d2312079f28c9baf2aafb4a452a06b50717526e5ac562e37baa53"}, +] + +[package.dependencies] +email-validator = {version = ">=1.0.3", optional = true, markers = "extra == \"email\""} +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pyjwt" +version = "2.10.1" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-stdnum" +version = "2.1" +description = "Python module to handle standardized numbers and codes" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "python_stdnum-2.1-py3-none-any.whl", hash = "sha256:25eabcf5f307dd4150fd8f1c03f4512a6caeb84c9f09be1448711f5803373c58"}, + {file = "python_stdnum-2.1.tar.gz", hash = "sha256:6b01645969eb3dfd55061a0114d593753cd9e653cea9083198b7eea12644397a"}, +] + +[package.extras] +soap = ["zeep"] + +[[package]] +name = "pyyaml" +version = "6.0.3" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, + {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, + {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, + {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, + {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, + {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, + {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, + {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, + {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, + {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, + {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, + {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, + {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, +] + +[[package]] +name = "redis" +version = "4.6.0" +description = "Python client for Redis database and key-value store" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "redis-4.6.0-py3-none-any.whl", hash = "sha256:e2b03db868160ee4591de3cb90d40ebb50a90dd302138775937f6a42b7ed183c"}, + {file = "redis-4.6.0.tar.gz", hash = "sha256:585dc516b9eb042a619ef0a39c3d7d55fe81bdb4df09a52c9cdde0d07bf1aa7d"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + +[[package]] +name = "requests" +version = "2.32.5" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "responses" +version = "0.25.8" +description = "A utility library for mocking out the `requests` Python library." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "responses-0.25.8-py3-none-any.whl", hash = "sha256:0c710af92def29c8352ceadff0c3fe340ace27cf5af1bbe46fb71275bcd2831c"}, + {file = "responses-0.25.8.tar.gz", hash = "sha256:9374d047a575c8f781b94454db5cab590b6029505f488d12899ddb10a4af1cf4"}, +] + +[package.dependencies] +pyyaml = "*" +requests = ">=2.30.0,<3.0" +urllib3 = ">=1.25.10,<3.0" + +[package.extras] +tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli ; python_version < \"3.11\"", "tomli-w", "types-PyYAML", "types-requests"] + +[[package]] +name = "s3transfer" +version = "0.14.0" +description = "An Amazon S3 Transfer Manager" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "s3transfer-0.14.0-py3-none-any.whl", hash = "sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456"}, + {file = "s3transfer-0.14.0.tar.gz", hash = "sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125"}, +] + +[package.dependencies] +botocore = ">=1.37.4,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.37.4,<2.0a.0)"] + +[[package]] +name = "simplejson" +version = "3.20.2" +description = "Simple, fast, extensible JSON encoder/decoder for Python" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.5" +groups = ["main"] +files = [ + {file = "simplejson-3.20.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:11847093fd36e3f5a4f595ff0506286c54885f8ad2d921dfb64a85bce67f72c4"}, + {file = "simplejson-3.20.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d291911d23b1ab8eb3241204dd54e3ec60ddcd74dfcb576939d3df327205865"}, + {file = "simplejson-3.20.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:da6d16d7108d366bbbf1c1f3274662294859c03266e80dd899fc432598115ea4"}, + {file = "simplejson-3.20.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9ddf9a07694c5bbb4856271cbc4247cc6cf48f224a7d128a280482a2f78bae3d"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:3a0d2337e490e6ab42d65a082e69473717f5cc75c3c3fb530504d3681c4cb40c"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8ba88696351ed26a8648f8378a1431223f02438f8036f006d23b4f5b572778fa"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:00bcd408a4430af99d1f8b2b103bb2f5133bb688596a511fcfa7db865fbb845e"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4fc62feb76f590ccaff6f903f52a01c58ba6423171aa117b96508afda9c210f0"}, + {file = "simplejson-3.20.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6d7286dc11af60a2f76eafb0c2acde2d997e87890e37e24590bb513bec9f1bc5"}, + {file = "simplejson-3.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c01379b4861c3b0aa40cba8d44f2b448f5743999aa68aaa5d3ef7049d4a28a2d"}, + {file = "simplejson-3.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a16b029ca25645b3bc44e84a4f941efa51bf93c180b31bd704ce6349d1fc77c1"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e22a5fb7b1437ffb057e02e1936a3bfb19084ae9d221ec5e9f4cf85f69946b6"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8b6ff02fc7b8555c906c24735908854819b0d0dc85883d453e23ca4c0445d01"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bfc1c396ad972ba4431130b42307b2321dba14d988580c1ac421ec6a6b7cee3"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a97249ee1aee005d891b5a211faf58092a309f3d9d440bc269043b08f662eda"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f1036be00b5edaddbddbb89c0f80ed229714a941cfd21e51386dc69c237201c2"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5d6f5bacb8cdee64946b45f2680afa3f54cd38e62471ceda89f777693aeca4e4"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8db6841fb796ec5af632f677abf21c6425a1ebea0d9ac3ef1a340b8dc69f52b8"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0a341f7cc2aae82ee2b31f8a827fd2e51d09626f8b3accc441a6907c88aedb7"}, + {file = "simplejson-3.20.2-cp310-cp310-win32.whl", hash = "sha256:27f9c01a6bc581d32ab026f515226864576da05ef322d7fc141cd8a15a95ce53"}, + {file = "simplejson-3.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0a63ec98a4547ff366871bf832a7367ee43d047bcec0b07b66c794e2137b476"}, + {file = "simplejson-3.20.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:06190b33cd7849efc413a5738d3da00b90e4a5382fd3d584c841ac20fb828c6f"}, + {file = "simplejson-3.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4ad4eac7d858947a30d2c404e61f16b84d16be79eb6fb316341885bdde864fa8"}, + {file = "simplejson-3.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b392e11c6165d4a0fde41754a0e13e1d88a5ad782b245a973dd4b2bdb4e5076a"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51eccc4e353eed3c50e0ea2326173acdc05e58f0c110405920b989d481287e51"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:306e83d7c331ad833d2d43c76a67f476c4b80c4a13334f6e34bb110e6105b3bd"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f820a6ac2ef0bc338ae4963f4f82ccebdb0824fe9caf6d660670c578abe01013"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e7a066528a5451433eb3418184f05682ea0493d14e9aae690499b7e1eb6b81"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:438680ddde57ea87161a4824e8de04387b328ad51cfdf1eaf723623a3014b7aa"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cac78470ae68b8d8c41b6fca97f5bf8e024ca80d5878c7724e024540f5cdaadb"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7524e19c2da5ef281860a3d74668050c6986be15c9dd99966034ba47c68828c2"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e9b6d845a603b2eef3394eb5e21edb8626cd9ae9a8361d14e267eb969dbe413"}, + {file = "simplejson-3.20.2-cp311-cp311-win32.whl", hash = "sha256:47d8927e5ac927fdd34c99cc617938abb3624b06ff86e8e219740a86507eb961"}, + {file = "simplejson-3.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:ba4edf3be8e97e4713d06c3d302cba1ff5c49d16e9d24c209884ac1b8455520c"}, + {file = "simplejson-3.20.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4376d5acae0d1e91e78baeba4ee3cf22fbf6509d81539d01b94e0951d28ec2b6"}, + {file = "simplejson-3.20.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f8fe6de652fcddae6dec8f281cc1e77e4e8f3575249e1800090aab48f73b4259"}, + {file = "simplejson-3.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25ca2663d99328d51e5a138f22018e54c9162438d831e26cfc3458688616eca8"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12a6b2816b6cab6c3fd273d43b1948bc9acf708272074c8858f579c394f4cbc9"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac20dc3fcdfc7b8415bfc3d7d51beccd8695c3f4acb7f74e3a3b538e76672868"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db0804d04564e70862ef807f3e1ace2cc212ef0e22deb1b3d6f80c45e5882c6b"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:979ce23ea663895ae39106946ef3d78527822d918a136dbc77b9e2b7f006237e"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a2ba921b047bb029805726800819675249ef25d2f65fd0edb90639c5b1c3033c"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:12d3d4dc33770069b780cc8f5abef909fe4a3f071f18f55f6d896a370fd0f970"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aff032a59a201b3683a34be1169e71ddda683d9c3b43b261599c12055349251e"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30e590e133b06773f0dc9c3f82e567463df40598b660b5adf53eb1c488202544"}, + {file = "simplejson-3.20.2-cp312-cp312-win32.whl", hash = "sha256:8d7be7c99939cc58e7c5bcf6bb52a842a58e6c65e1e9cdd2a94b697b24cddb54"}, + {file = "simplejson-3.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:2c0b4a67e75b945489052af6590e7dca0ed473ead5d0f3aad61fa584afe814ab"}, + {file = "simplejson-3.20.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90d311ba8fcd733a3677e0be21804827226a57144130ba01c3c6a325e887dd86"}, + {file = "simplejson-3.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:feed6806f614bdf7f5cb6d0123cb0c1c5f40407ef103aa935cffaa694e2e0c74"}, + {file = "simplejson-3.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b1d8d7c3e1a205c49e1aee6ba907dcb8ccea83651e6c3e2cb2062f1e52b0726"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:552f55745044a24c3cb7ec67e54234be56d5d6d0e054f2e4cf4fb3e297429be5"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2da97ac65165d66b0570c9e545786f0ac7b5de5854d3711a16cacbcaa8c472d"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f59a12966daa356bf68927fca5a67bebac0033cd18b96de9c2d426cd11756cd0"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133ae2098a8e162c71da97cdab1f383afdd91373b7ff5fe65169b04167da976b"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7977640af7b7d5e6a852d26622057d428706a550f7f5083e7c4dd010a84d941f"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b530ad6d55e71fa9e93e1109cf8182f427a6355848a4ffa09f69cc44e1512522"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bd96a7d981bf64f0e42345584768da4435c05b24fd3c364663f5fbc8fabf82e3"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f28ee755fadb426ba2e464d6fcf25d3f152a05eb6b38e0b4f790352f5540c769"}, + {file = "simplejson-3.20.2-cp313-cp313-win32.whl", hash = "sha256:472785b52e48e3eed9b78b95e26a256f59bb1ee38339be3075dad799e2e1e661"}, + {file = "simplejson-3.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:a1a85013eb33e4820286139540accbe2c98d2da894b2dcefd280209db508e608"}, + {file = "simplejson-3.20.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a135941a50795c934bdc9acc74e172b126e3694fe26de3c0c1bc0b33ea17e6ce"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ba488decb18738f5d6bd082018409689ed8e74bc6c4d33a0b81af6edf1c9f4"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d81f8e982923d5e9841622ff6568be89756428f98a82c16e4158ac32b92a3787"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdad497ccb1edc5020bef209e9c3e062a923e8e6fca5b8a39f0fb34380c8a66c"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a3f1db97bcd9fb592928159af7a405b18df7e847cbcc5682a209c5b2ad5d6b1"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:215b65b0dc2c432ab79c430aa4f1e595f37b07a83c1e4c4928d7e22e6b49a748"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:ece4863171ba53f086a3bfd87f02ec3d6abc586f413babfc6cf4de4d84894620"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:4a76d7c47d959afe6c41c88005f3041f583a4b9a1783cf341887a3628a77baa0"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:e9b0523582a57d9ea74f83ecefdffe18b2b0a907df1a9cef06955883341930d8"}, + {file = "simplejson-3.20.2-cp36-cp36m-win32.whl", hash = "sha256:16366591c8e08a4ac76b81d76a3fc97bf2bcc234c9c097b48d32ea6bfe2be2fe"}, + {file = "simplejson-3.20.2-cp36-cp36m-win_amd64.whl", hash = "sha256:732cf4c4ac1a258b4e9334e1e40a38303689f432497d3caeb491428b7547e782"}, + {file = "simplejson-3.20.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6c3a98e21e5f098e4f982ef302ebb1e681ff16a5d530cfce36296bea58fe2396"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10cf9ca1363dc3711c72f4ec7c1caed2bbd9aaa29a8d9122e31106022dc175c6"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:106762f8aedf3fc3364649bfe8dc9a40bf5104f872a4d2d86bae001b1af30d30"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b21659898b7496322e99674739193f81052e588afa8b31b6a1c7733d8829b925"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fa1db6a02bca88829f2b2057c76a1d2dc2fccb8c5ff1199e352f213e9ec719"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:156139d94b660448ec8a4ea89f77ec476597f752c2ff66432d3656704c66b40e"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:b2620ac40be04dff08854baf6f4df10272f67079f61ed1b6274c0e840f2e2ae1"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:9ccef5b5d3e3ac5d9da0a0ca1d2de8cf2b0fb56b06aa0ab79325fa4bcc5a1d60"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:f526304c2cc9fd8b8d18afacb75bc171650f83a7097b2c92ad6a431b5d7c1b72"}, + {file = "simplejson-3.20.2-cp37-cp37m-win32.whl", hash = "sha256:e0f661105398121dd48d9987a2a8f7825b8297b3b2a7fe5b0d247370396119d5"}, + {file = "simplejson-3.20.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dab98625b3d6821e77ea59c4d0e71059f8063825a0885b50ed410e5c8bd5cb66"}, + {file = "simplejson-3.20.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b8205f113082e7d8f667d6cd37d019a7ee5ef30b48463f9de48e1853726c6127"}, + {file = "simplejson-3.20.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fc8da64929ef0ff16448b602394a76fd9968a39afff0692e5ab53669df1f047f"}, + {file = "simplejson-3.20.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfe704864b5fead4f21c8d448a89ee101c9b0fc92a5f40b674111da9272b3a90"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40ca7cbe7d2f423b97ed4e70989ef357f027a7e487606628c11b79667639dc84"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cec1868b237fe9fb2d466d6ce0c7b772e005aadeeda582d867f6f1ec9710cad"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:792debfba68d8dd61085ffb332d72b9f5b38269cda0c99f92c7a054382f55246"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e022b2c4c54cb4855e555f64aa3377e3e5ca912c372fa9e3edcc90ebbad93dce"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5de26f11d5aca575d3825dddc65f69fdcba18f6ca2b4db5cef16f41f969cef15"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:e2162b2a43614727ec3df75baeda8881ab129824aa1b49410d4b6c64f55a45b4"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e11a1d6b2f7e72ca546bdb4e6374b237ebae9220e764051b867111df83acbd13"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:daf7cd18fe99eb427fa6ddb6b437cfde65125a96dc27b93a8969b6fe90a1dbea"}, + {file = "simplejson-3.20.2-cp38-cp38-win32.whl", hash = "sha256:da795ea5f440052f4f497b496010e2c4e05940d449ea7b5c417794ec1be55d01"}, + {file = "simplejson-3.20.2-cp38-cp38-win_amd64.whl", hash = "sha256:6a4b5e7864f952fcce4244a70166797d7b8fd6069b4286d3e8403c14b88656b6"}, + {file = "simplejson-3.20.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b3bf76512ccb07d47944ebdca44c65b781612d38b9098566b4bb40f713fc4047"}, + {file = "simplejson-3.20.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:214e26acf2dfb9ff3314e65c4e168a6b125bced0e2d99a65ea7b0f169db1e562"}, + {file = "simplejson-3.20.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2fb1259ca9c385b0395bad59cdbf79535a5a84fb1988f339a49bfbc57455a35a"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34e028a2ba8553a208ded1da5fa8501833875078c4c00a50dffc33622057881"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b538f9d9e503b0dd43af60496780cb50755e4d8e5b34e5647b887675c1ae9fee"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab998e416ded6c58f549a22b6a8847e75a9e1ef98eb9fbb2863e1f9e61a4105b"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a8f1c307edf5fbf0c6db3396c5d3471409c4a40c7a2a466fbc762f20d46601a"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5a7bbac80bdb82a44303f5630baee140aee208e5a4618e8b9fde3fc400a42671"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5ef70ec8fe1569872e5a3e4720c1e1dcb823879a3c78bc02589eb88fab920b1f"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:cb11c09c99253a74c36925d461c86ea25f0140f3b98ff678322734ddc0f038d7"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:66f7c78c6ef776f8bd9afaad455e88b8197a51e95617bcc44b50dd974a7825ba"}, + {file = "simplejson-3.20.2-cp39-cp39-win32.whl", hash = "sha256:619ada86bfe3a5aa02b8222ca6bfc5aa3e1075c1fb5b3263d24ba579382df472"}, + {file = "simplejson-3.20.2-cp39-cp39-win_amd64.whl", hash = "sha256:44a6235e09ca5cc41aa5870a952489c06aa4aee3361ae46daa947d8398e57502"}, + {file = "simplejson-3.20.2-py3-none-any.whl", hash = "sha256:3b6bb7fb96efd673eac2e4235200bfffdc2353ad12c54117e1e4e2fc485ac017"}, + {file = "simplejson-3.20.2.tar.gz", hash = "sha256:5fe7a6ce14d1c300d80d08695b7f7e633de6cd72c80644021874d985b3393649"}, +] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "structlog" +version = "24.4.0" +description = "Structured Logging for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "structlog-24.4.0-py3-none-any.whl", hash = "sha256:597f61e80a91cc0749a9fd2a098ed76715a1c8a01f73e336b746504d1aad7610"}, + {file = "structlog-24.4.0.tar.gz", hash = "sha256:b27bfecede327a6d2da5fbc96bd859f114ecc398a6389d664f62085ee7ae6fc4"}, +] + +[package.extras] +dev = ["freezegun (>=0.2.8)", "mypy (>=1.4)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "rich", "simplejson", "twisted"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "sphinxext-opengraph", "twisted"] +tests = ["freezegun (>=0.2.8)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "simplejson"] +typing = ["mypy (>=1.4)", "rich", "twisted"] + +[[package]] +name = "types-awscrt" +version = "0.28.2" +description = "Type annotations and code completion for awscrt" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "types_awscrt-0.28.2-py3-none-any.whl", hash = "sha256:d08916fa735cfc032e6a8cfdac92785f1c4e88623999b224ea4e6267d5de5fcb"}, + {file = "types_awscrt-0.28.2.tar.gz", hash = "sha256:4349b6fc7b1cd9c9eb782701fb213875db89ab1781219c0e947dd7c4d9dcd65e"}, +] + +[[package]] +name = "types-s3transfer" +version = "0.14.0" +description = "Type annotations and code completion for s3transfer" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "types_s3transfer-0.14.0-py3-none-any.whl", hash = "sha256:108134854069a38b048e9b710b9b35904d22a9d0f37e4e1889c2e6b58e5b3253"}, + {file = "types_s3transfer-0.14.0.tar.gz", hash = "sha256:17f800a87c7eafab0434e9d87452c809c290ae906c2024c24261c564479e9c95"}, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "werkzeug" +version = "3.1.3" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, + {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[[package]] +name = "xmltodict" +version = "1.0.2" +description = "Makes working with XML feel like you are working with JSON" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "xmltodict-1.0.2-py3-none-any.whl", hash = "sha256:62d0fddb0dcbc9f642745d8bbf4d81fd17d6dfaec5a15b5c1876300aad92af0d"}, + {file = "xmltodict-1.0.2.tar.gz", hash = "sha256:54306780b7c2175a3967cad1db92f218207e5bc1aba697d887807c0fb68b7649"}, +] + +[package.extras] +test = ["pytest", "pytest-cov"] + +[metadata] +lock-version = "2.1" +python-versions = "~3.11" +content-hash = "5aef78edcccd98109d7bb30d40174cd9aace0eefebcc326ed6ca8a355be6d4ae" diff --git a/lambdas/recordforwarder/pyproject.toml b/lambdas/recordforwarder/pyproject.toml new file mode 100644 index 000000000..fd533a894 --- /dev/null +++ b/lambdas/recordforwarder/pyproject.toml @@ -0,0 +1,35 @@ +[tool.poetry] +name = "recordforwarder" +version = "0.1.0" +description = "" +authors = ["Your Name "] +readme = "README.md" +packages = [ + {include = "src"}, + {include = "common", from = "../shared/src"} +] + +[tool.poetry.dependencies] +python = "~3.11" +"fhir.resources" = "~7.0.2" +boto3 = "~1.40.64" +boto3-stubs-lite = {extras = ["dynamodb"], version = "~1.40.64"} +aws-lambda-typing = "~2.20.0" +redis = "^4.6.0" +moto = "^5.1.16" +requests = "~2.32.5" +responses = "~0.25.7" +pydantic = "~1.10.13" +pyjwt = "~2.10.1" +cryptography = "~46.0.0" +cffi = "~1.17.1" +jsonpath-ng = "^1.6.0" +simplejson = "^3.20.2" +structlog = "^24.1.0" +python-stdnum = "^2.1" +freezegun = "^1.5.1" +coverage = "^7.10.7" + +[build-system] +requires = ["poetry-core ~= 1.5.0"] +build-backend = "poetry.core.masonry.api" diff --git a/backend/tests/controller/__init__.py b/lambdas/recordforwarder/src/__init__.py similarity index 100% rename from backend/tests/controller/__init__.py rename to lambdas/recordforwarder/src/__init__.py diff --git a/backend/tests/models/__init__.py b/lambdas/recordforwarder/src/batch/__init__.py similarity index 100% rename from backend/tests/models/__init__.py rename to lambdas/recordforwarder/src/batch/__init__.py diff --git a/backend/src/batch/batch_filename_to_events_mapper.py b/lambdas/recordforwarder/src/batch/batch_filename_to_events_mapper.py similarity index 100% rename from backend/src/batch/batch_filename_to_events_mapper.py rename to lambdas/recordforwarder/src/batch/batch_filename_to_events_mapper.py diff --git a/backend/tests/models/utils/__init__.py b/lambdas/recordforwarder/src/controller/__init__.py similarity index 100% rename from backend/tests/models/utils/__init__.py rename to lambdas/recordforwarder/src/controller/__init__.py diff --git a/backend/src/controller/fhir_batch_controller.py b/lambdas/recordforwarder/src/controller/fhir_batch_controller.py similarity index 100% rename from backend/src/controller/fhir_batch_controller.py rename to lambdas/recordforwarder/src/controller/fhir_batch_controller.py diff --git a/backend/src/forwarding_batch_lambda.py b/lambdas/recordforwarder/src/forwarding_batch_lambda.py similarity index 98% rename from backend/src/forwarding_batch_lambda.py rename to lambdas/recordforwarder/src/forwarding_batch_lambda.py index 952879da2..1ba298b63 100644 --- a/backend/src/forwarding_batch_lambda.py +++ b/lambdas/recordforwarder/src/forwarding_batch_lambda.py @@ -9,18 +9,20 @@ import simplejson as json from batch.batch_filename_to_events_mapper import BatchFilenameToEventsMapper -from clients import sqs_client +from common.clients import sqs_client +from common.models.errors import ( + CustomValidationError, + IdentifierDuplicationError, + ResourceFoundError, + ResourceNotFoundError, +) from controller.fhir_batch_controller import ( ImmunizationBatchController, make_batch_controller, ) from models.errors import ( - CustomValidationError, - IdentifierDuplicationError, MessageNotSuccessfulError, RecordProcessorError, - ResourceFoundError, - ResourceNotFoundError, ) from repository.fhir_batch_repository import create_table diff --git a/lambdas/recordforwarder/src/models/errors.py b/lambdas/recordforwarder/src/models/errors.py new file mode 100644 index 000000000..06369a57a --- /dev/null +++ b/lambdas/recordforwarder/src/models/errors.py @@ -0,0 +1,24 @@ +from dataclasses import dataclass + + +@dataclass +class MessageNotSuccessfulError(Exception): + """ + Generic error message for any scenario which either prevents sending to the Imms API, or which results in a + non-successful response from the Imms API + """ + + def __init__(self, message=None): + self.message = message + + +@dataclass +class RecordProcessorError(Exception): + """ + Exception for re-raising exceptions which have already occurred in the Record Processor. + The diagnostics dictionary received from the Record Processor is passed to the exception as an argument + and is stored as an attribute. + """ + + def __init__(self, diagnostics_dictionary: dict): + self.diagnostics_dictionary = diagnostics_dictionary diff --git a/backend/tests/repository/__init__.py b/lambdas/recordforwarder/src/repository/__init__.py similarity index 100% rename from backend/tests/repository/__init__.py rename to lambdas/recordforwarder/src/repository/__init__.py diff --git a/backend/src/repository/fhir_batch_repository.py b/lambdas/recordforwarder/src/repository/fhir_batch_repository.py similarity index 89% rename from backend/src/repository/fhir_batch_repository.py rename to lambdas/recordforwarder/src/repository/fhir_batch_repository.py index f0216c95f..f1a2cae0a 100644 --- a/backend/src/repository/fhir_batch_repository.py +++ b/lambdas/recordforwarder/src/repository/fhir_batch_repository.py @@ -8,13 +8,14 @@ import simplejson as json from boto3.dynamodb.conditions import Attr, Key -from clients import logger -from models.errors import ( +from common.clients import logger +from common.models.errors import ( IdentifierDuplicationError, ResourceFoundError, ResourceNotFoundError, UnhandledResponseError, ) +from common.models.utils.generic_utils import get_nhs_number def create_table(region_name="eu-west-2"): @@ -56,14 +57,6 @@ def _query_identifier(table, index, pk, identifier, is_present): return queryresponse -def get_nhs_number(imms): - try: - nhs_number = [x for x in imms["contained"] if x["resourceType"] == "Patient"][0]["identifier"][0]["value"] - except (KeyError, IndexError): - nhs_number = "TBC" - return nhs_number - - @dataclass class RecordAttributes: pk: str @@ -171,7 +164,7 @@ def delete_immunization( self, immunization: any, supplier_system: str, - vax_type: str, + _: str, # vax_type not used table: any, is_present: bool, ) -> dict: @@ -274,27 +267,21 @@ def _perform_dynamo_update( if deleted_at_required else Attr("PK").eq(attr.pk) & Attr("DeletedAt").not_exists() ) + expression_attribute_values = { + ":timestamp": attr.timestamp, + ":patient_pk": attr.patient_pk, + ":patient_sk": attr.patient_sk, + ":imms_resource_val": json.dumps(attr.resource, use_decimal=True), + ":operation": "UPDATE", + ":version": attr.version, + ":supplier_system": attr.supplier, + } if deleted_at_required and update_reinstated is False: - expression_attribute_values = { - ":timestamp": attr.timestamp, - ":patient_pk": attr.patient_pk, - ":patient_sk": attr.patient_sk, - ":imms_resource_val": json.dumps(attr.resource, use_decimal=True), - ":operation": "UPDATE", - ":version": attr.version, - ":supplier_system": attr.supplier, - ":respawn": "reinstated", - } - else: - expression_attribute_values = { - ":timestamp": attr.timestamp, - ":patient_pk": attr.patient_pk, - ":patient_sk": attr.patient_sk, - ":imms_resource_val": json.dumps(attr.resource, use_decimal=True), - ":operation": "UPDATE", - ":version": attr.version, - ":supplier_system": attr.supplier, - } + expression_attribute_values.update( + { + ":respawn": "reinstated", + } + ) response = table.update_item( Key={"PK": attr.pk}, diff --git a/backend/tests/service/__init__.py b/lambdas/recordforwarder/src/service/__init__.py similarity index 100% rename from backend/tests/service/__init__.py rename to lambdas/recordforwarder/src/service/__init__.py diff --git a/backend/src/service/fhir_batch_service.py b/lambdas/recordforwarder/src/service/fhir_batch_service.py similarity index 88% rename from backend/src/service/fhir_batch_service.py rename to lambdas/recordforwarder/src/service/fhir_batch_service.py index da0da55d9..70cbc1ba8 100644 --- a/backend/src/service/fhir_batch_service.py +++ b/lambdas/recordforwarder/src/service/fhir_batch_service.py @@ -1,7 +1,5 @@ -from pydantic import ValidationError - -from models.errors import CustomValidationError, MandatoryError -from models.fhir_immunization import ImmunizationValidator +from common.models.errors import CustomValidationError, MandatoryError +from common.models.fhir_immunization import ImmunizationValidator from repository.fhir_batch_repository import ImmunizationBatchRepository IMMUNIZATION_VALIDATOR = ImmunizationValidator() @@ -31,7 +29,7 @@ def create_immunization( """ try: self.validator.validate(immunization) - except (ValidationError, ValueError, MandatoryError) as error: + except (ValueError, MandatoryError) as error: raise CustomValidationError(message=str(error)) from error return self.immunization_repo.create_immunization(immunization, supplier_system, vax_type, table, is_present) @@ -51,7 +49,7 @@ def update_immunization( """ try: self.validator.validate(immunization) - except (ValidationError, ValueError, MandatoryError) as error: + except (ValueError, MandatoryError) as error: raise CustomValidationError(message=str(error)) from error return self.immunization_repo.update_immunization(immunization, supplier_system, vax_type, table, is_present) diff --git a/backend/tests/testing_utils/__init__.py b/lambdas/recordforwarder/tests/__init__.py similarity index 100% rename from backend/tests/testing_utils/__init__.py rename to lambdas/recordforwarder/tests/__init__.py diff --git a/backend/tests/utils/__init__.py b/lambdas/recordforwarder/tests/batch/__init__.py similarity index 100% rename from backend/tests/utils/__init__.py rename to lambdas/recordforwarder/tests/batch/__init__.py diff --git a/backend/tests/batch/test_batch_filename_to_events_mapper.py b/lambdas/recordforwarder/tests/batch/test_batch_filename_to_events_mapper.py similarity index 100% rename from backend/tests/batch/test_batch_filename_to_events_mapper.py rename to lambdas/recordforwarder/tests/batch/test_batch_filename_to_events_mapper.py diff --git a/lambdas/recordforwarder/tests/controller/__init__.py b/lambdas/recordforwarder/tests/controller/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/tests/controller/test_fhir_batch_controller.py b/lambdas/recordforwarder/tests/controller/test_fhir_batch_controller.py similarity index 99% rename from backend/tests/controller/test_fhir_batch_controller.py rename to lambdas/recordforwarder/tests/controller/test_fhir_batch_controller.py index 5166cf85d..8fd818bdc 100644 --- a/backend/tests/controller/test_fhir_batch_controller.py +++ b/lambdas/recordforwarder/tests/controller/test_fhir_batch_controller.py @@ -2,16 +2,16 @@ import uuid from unittest.mock import Mock, create_autospec -from controller.fhir_batch_controller import ImmunizationBatchController -from models.errors import ( +from common.models.errors import ( CustomValidationError, IdentifierDuplicationError, ResourceNotFoundError, UnhandledResponseError, ) +from controller.fhir_batch_controller import ImmunizationBatchController from repository.fhir_batch_repository import ImmunizationBatchRepository from service.fhir_batch_service import ImmunizationBatchService -from testing_utils.immunization_utils import create_covid_immunization +from test_common.testing_utils.immunization_utils import create_covid_immunization class TestCreateImmunizationBatchController(unittest.TestCase): diff --git a/lambdas/recordforwarder/tests/repository/__init__.py b/lambdas/recordforwarder/tests/repository/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/tests/repository/test_fhir_batch_repository.py b/lambdas/recordforwarder/tests/repository/test_fhir_batch_repository.py similarity index 97% rename from backend/tests/repository/test_fhir_batch_repository.py rename to lambdas/recordforwarder/tests/repository/test_fhir_batch_repository.py index 8deecd846..1f31a0516 100644 --- a/backend/tests/repository/test_fhir_batch_repository.py +++ b/lambdas/recordforwarder/tests/repository/test_fhir_batch_repository.py @@ -1,6 +1,6 @@ import os import unittest -from unittest.mock import ANY, MagicMock, patch +from unittest.mock import ANY, MagicMock, Mock, patch from uuid import uuid4 import boto3 @@ -8,14 +8,14 @@ import simplejson as json from moto import mock_aws -from models.errors import ( +from common.models.errors import ( IdentifierDuplicationError, ResourceFoundError, ResourceNotFoundError, UnhandledResponseError, ) from repository.fhir_batch_repository import ImmunizationBatchRepository, create_table -from testing_utils.immunization_utils import create_covid_immunization_dict +from test_common.testing_utils.immunization_utils import create_covid_immunization_dict imms_id = str(uuid4()) @@ -36,8 +36,9 @@ def setUp(self): self.table.query = MagicMock(return_value={}) self.immunization = create_covid_immunization_dict(imms_id) self.table.update_item = MagicMock(return_value={"ResponseMetadata": {"HTTPStatusCode": 200}}) - self.redis_patcher = patch("models.utils.validation_utils.redis_client") - self.mock_redis_client = self.redis_patcher.start() + self.mock_redis = Mock() + self.redis_getter_patcher = patch("common.models.utils.validation_utils.get_redis_client") + self.mock_redis_getter = self.redis_getter_patcher.start() self.logger_info_patcher = patch("logging.Logger.info") self.mock_logger_info = self.logger_info_patcher.start() @@ -56,7 +57,8 @@ def modify_immunization(self, remove_nhs): def create_immunization_test_logic(self, is_present, remove_nhs): """Common logic for testing immunization creation.""" - self.mock_redis_client.hget.side_effect = ["COVID"] + self.mock_redis.hget.side_effect = ["COVID"] + self.mock_redis_getter.return_value = self.mock_redis self.modify_immunization(remove_nhs) self.repository.create_immunization(self.immunization, "supplier", "vax-type", self.table, is_present) diff --git a/lambdas/recordforwarder/tests/service/__init__.py b/lambdas/recordforwarder/tests/service/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/tests/service/test_fhir_batch_service.py b/lambdas/recordforwarder/tests/service/test_fhir_batch_service.py similarity index 91% rename from backend/tests/service/test_fhir_batch_service.py rename to lambdas/recordforwarder/tests/service/test_fhir_batch_service.py index a17b6e6d6..fc3837df6 100644 --- a/backend/tests/service/test_fhir_batch_service.py +++ b/lambdas/recordforwarder/tests/service/test_fhir_batch_service.py @@ -3,11 +3,11 @@ from copy import deepcopy from unittest.mock import Mock, create_autospec, patch -from models.errors import CustomValidationError -from models.fhir_immunization import ImmunizationValidator +from common.models.errors import CustomValidationError +from common.models.fhir_immunization import ImmunizationValidator from repository.fhir_batch_repository import ImmunizationBatchRepository from service.fhir_batch_service import ImmunizationBatchService -from testing_utils.immunization_utils import create_covid_immunization_dict_no_id +from test_common.testing_utils.immunization_utils import create_covid_immunization_dict_no_id class TestFhirBatchServiceBase(unittest.TestCase): @@ -15,13 +15,14 @@ class TestFhirBatchServiceBase(unittest.TestCase): def setUp(self): super().setUp() - self.redis_patcher = patch("models.utils.validation_utils.redis_client") - self.mock_redis_client = self.redis_patcher.start() + self.mock_redis = Mock() + self.redis_getter_patcher = patch("common.models.utils.validation_utils.get_redis_client") + self.mock_redis_getter = self.redis_getter_patcher.start() self.logger_info_patcher = patch("logging.Logger.info") self.mock_logger_info = self.logger_info_patcher.start() def tearDown(self): - self.redis_patcher.stop() + self.redis_getter_patcher.stop() self.logger_info_patcher.stop() super().tearDown() @@ -83,7 +84,8 @@ def test_create_immunization_post_validation_error(self): bad_target_disease_imms = deepcopy(valid_imms) bad_target_disease_imms["protocolApplied"][0]["targetDisease"][0]["coding"][0]["code"] = "bad-code" expected_msg = "protocolApplied[0].targetDisease[*].coding[?(@.system=='http://snomed.info/sct')].code - ['bad-code'] is not a valid combination of disease codes for this service" - self.mock_redis_client.hget.return_value = None # Reset mock for invalid cases + self.mock_redis.hget.return_value = None # Reset mock for invalid cases + self.mock_redis_getter.return_value = self.mock_redis with self.assertRaises(CustomValidationError) as error: self.pre_validate_fhir_service.create_immunization( immunization=bad_target_disease_imms, @@ -149,7 +151,8 @@ def test_update_immunization_pre_validation_error(self): def test_update_immunization_post_validation_error(self): """it should return error since it got failed in initial validation""" - self.mock_redis_client.hget.return_value = None # Reset mock for invalid cases + self.mock_redis.hget.return_value = None # Reset mock for invalid cases + self.mock_redis_getter.return_value = self.mock_redis valid_imms = create_covid_immunization_dict_no_id() bad_target_disease_imms = deepcopy(valid_imms) diff --git a/lambdas/recordforwarder/tests/test_errors.py b/lambdas/recordforwarder/tests/test_errors.py new file mode 100644 index 000000000..aaa34e7f2 --- /dev/null +++ b/lambdas/recordforwarder/tests/test_errors.py @@ -0,0 +1,28 @@ +import unittest + +import models.errors as errors + + +class TestErrors(unittest.TestCase): + def test_errors_message_not_successful_error(self): + """Test correct operation of MessageNotSuccessfulError""" + test_message = "test_message" + + with self.assertRaises(errors.MessageNotSuccessfulError) as context: + raise errors.MessageNotSuccessfulError(test_message) + self.assertEqual(str(context.exception.message), test_message) + + def test_errors_message_not_successful_error_no_message(self): + """Test correct operation of MessageNotSuccessfulError with no message""" + + with self.assertRaises(errors.MessageNotSuccessfulError) as context: + raise errors.MessageNotSuccessfulError() + self.assertIsNone(context.exception.message) + + def test_errors_record_processor_error(self): + """Test correct operation of RecordProcessorError""" + test_diagnostics = {"test_diagnostic": "test_value"} + + with self.assertRaises(errors.RecordProcessorError) as context: + raise errors.RecordProcessorError(test_diagnostics) + self.assertEqual(context.exception.diagnostics_dictionary, test_diagnostics) diff --git a/backend/tests/test_forwarding_batch_lambda.py b/lambdas/recordforwarder/tests/test_forwarding_batch_lambda.py similarity index 97% rename from backend/tests/test_forwarding_batch_lambda.py rename to lambdas/recordforwarder/tests/test_forwarding_batch_lambda.py index 8647cfb31..29524dbb6 100644 --- a/backend/tests/test_forwarding_batch_lambda.py +++ b/lambdas/recordforwarder/tests/test_forwarding_batch_lambda.py @@ -5,19 +5,21 @@ import unittest from typing import Optional from unittest import TestCase -from unittest.mock import ANY, MagicMock, patch +from unittest.mock import ANY, MagicMock, Mock, patch from boto3 import resource as boto3_resource from moto import mock_aws -from models.errors import ( +from common.models.errors import ( CustomValidationError, IdentifierDuplicationError, - MessageNotSuccessfulError, - RecordProcessorError, ResourceFoundError, ResourceNotFoundError, ) +from models.errors import ( + MessageNotSuccessfulError, + RecordProcessorError, +) from testing_utils.test_utils_for_batch import ForwarderValues, MockFhirImmsResources with patch.dict("os.environ", ForwarderValues.MOCK_ENVIRONMENT_DICT): @@ -67,13 +69,16 @@ def setUp(self): }, ], ) - self.redis_patcher = patch("models.utils.validation_utils.redis_client") - self.mock_redis_client = self.redis_patcher.start() + self.logger_info_patcher = patch("logging.Logger.info") self.mock_logger_info = self.logger_info_patcher.start() self.logger_error_patcher = patch("logging.Logger.error") self.mock_logger_error = self.logger_error_patcher.start() + self.mock_redis = Mock() + self.redis_getter_patcher = patch("common.models.utils.validation_utils.get_redis_client") + self.mock_redis_getter = self.redis_getter_patcher.start() + def tearDown(self): """Tear down after each test. This runs after every test""" patch.stopall() @@ -208,7 +213,8 @@ def test_forward_lambda_handler_single_operations(self, mock_send_message): "PatientSK": "RSV#4d2ac1eb-080f-4e54-9598-f2d53334681c", } ) - self.mock_redis_client.hget.return_value = "RSV" + self.mock_redis.hget.return_value = "RSV" + self.mock_redis_getter.return_value = self.mock_redis test_cases = [ { @@ -443,7 +449,9 @@ def test_forward_lambda_handler_multiple_scenarios(self, mock_send_message): mock_send_message.reset_mock() event = self.generate_event(test_cases) - self.mock_redis_client.hget.return_value = "RSV" + self.mock_redis.hget.return_value = "RSV" + self.mock_redis_getter.return_value = self.mock_redis + forward_lambda_handler(event, {}) self.assert_dynamo_item(table_item) @@ -474,7 +482,9 @@ def test_forward_lambda_handler_groups_and_sends_events_by_filename(self, mock_s }, ] mock_kinesis_event = self.generate_event(mock_records) - self.mock_redis_client.hget.return_value = "RSV" + + self.mock_redis.hget.return_value = "RSV" + self.mock_redis_getter.return_value = self.mock_redis forward_lambda_handler(mock_kinesis_event, {}) @@ -532,7 +542,9 @@ def test_forward_lambda_handler_update_scenarios(self, mock_send_message): input: generates the kinesis row data for the event, expected_keys (list): expected output dictionary keys, expected_values (dict): expected output dictionary values""" - self.mock_redis_client.hget.return_value = "RSV" + self.mock_redis.hget.return_value = "RSV" + self.mock_redis_getter.return_value = self.mock_redis + pk_test_update = "Immunization#4d2ac1eb-080f-4e54-9598-f2d53334687r" self.table.put_item( Item={ diff --git a/lambdas/recordforwarder/tests/testing_utils/__init__.py b/lambdas/recordforwarder/tests/testing_utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/tests/testing_utils/test_utils_for_batch.py b/lambdas/recordforwarder/tests/testing_utils/test_utils_for_batch.py similarity index 100% rename from backend/tests/testing_utils/test_utils_for_batch.py rename to lambdas/recordforwarder/tests/testing_utils/test_utils_for_batch.py diff --git a/lambdas/recordprocessor/poetry.lock b/lambdas/recordprocessor/poetry.lock index a3972f8bf..42ae895a5 100644 --- a/lambdas/recordprocessor/poetry.lock +++ b/lambdas/recordprocessor/poetry.lock @@ -27,18 +27,18 @@ files = [ [[package]] name = "boto3" -version = "1.40.68" +version = "1.40.72" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.40.68-py3-none-any.whl", hash = "sha256:4f08115e3a4d1e1056003e433d393e78c20da6af7753409992bb33fb69f04186"}, - {file = "boto3-1.40.68.tar.gz", hash = "sha256:c7994989e5bbba071b7c742adfba35773cf03e87f5d3f9f2b0a18c1664417b61"}, + {file = "boto3-1.40.72-py3-none-any.whl", hash = "sha256:1063a295712f2605d3e463e4dc1fe32fce17cf77a0f4d3bb14249d68533ee856"}, + {file = "boto3-1.40.72.tar.gz", hash = "sha256:58d30dd5e046789a760db7a49f817650b8ff08d8d169e127976a61f44b7c59ad"}, ] [package.dependencies] -botocore = ">=1.40.68,<1.41.0" +botocore = ">=1.40.72,<1.41.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.14.0,<0.15.0" @@ -47,14 +47,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "boto3-stubs-lite" -version = "1.40.64" -description = "Lite type annotations for boto3 1.40.64 generated with mypy-boto3-builder 8.11.0" +version = "1.40.72" +description = "Lite type annotations for boto3 1.40.72 generated with mypy-boto3-builder 8.12.0" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3_stubs_lite-1.40.64-py3-none-any.whl", hash = "sha256:21f8d859edfafb98bf9fb4c529349c40b92ef39c7f9f16eb741b993fde31530c"}, - {file = "boto3_stubs_lite-1.40.64.tar.gz", hash = "sha256:8c5a019bea442ba436675b61c76fe99d1d0faf846bea920768bf574b1df4f1eb"}, + {file = "boto3_stubs_lite-1.40.72-py3-none-any.whl", hash = "sha256:94713bd1ddffdde61f4252c7842476ea0072ad74dc9ca9d9eed012922ca92645"}, + {file = "boto3_stubs_lite-1.40.72.tar.gz", hash = "sha256:fcef6d54d945fccabf89c32db0d3d2a00b1727a02f7185ae162777e3387a099d"}, ] [package.dependencies] @@ -116,7 +116,7 @@ bedrock-data-automation-runtime = ["mypy-boto3-bedrock-data-automation-runtime ( bedrock-runtime = ["mypy-boto3-bedrock-runtime (>=1.40.0,<1.41.0)"] billing = ["mypy-boto3-billing (>=1.40.0,<1.41.0)"] billingconductor = ["mypy-boto3-billingconductor (>=1.40.0,<1.41.0)"] -boto3 = ["boto3 (==1.40.64)"] +boto3 = ["boto3 (==1.40.72)"] braket = ["mypy-boto3-braket (>=1.40.0,<1.41.0)"] budgets = ["mypy-boto3-budgets (>=1.40.0,<1.41.0)"] ce = ["mypy-boto3-ce (>=1.40.0,<1.41.0)"] @@ -477,14 +477,14 @@ xray = ["mypy-boto3-xray (>=1.40.0,<1.41.0)"] [[package]] name = "botocore" -version = "1.40.68" +version = "1.40.72" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.40.68-py3-none-any.whl", hash = "sha256:9d514f9c9054e1af055f2cbe9e0d6771d407a600206d45a01b54d5f09538fecb"}, - {file = "botocore-1.40.68.tar.gz", hash = "sha256:28f41b463d9f012a711ee8b61d4e26cd14ee3b450b816d5dee849aa79155e856"}, + {file = "botocore-1.40.72-py3-none-any.whl", hash = "sha256:4f859e5aaf871fe59aac431d6bba59cc0c8ed8a38da2a6a5345700bdc5c74b32"}, + {file = "botocore-1.40.72.tar.gz", hash = "sha256:f69199ff6570695556e733fa052f2739e01e0c592c9b60f843f84c77ba3bcdf3"}, ] [package.dependencies] @@ -748,104 +748,104 @@ files = [ [[package]] name = "coverage" -version = "7.11.0" +version = "7.11.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, - {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, - {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, - {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, - {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, - {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, - {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, - {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, - {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, - {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, - {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, - {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, - {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, - {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, - {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, - {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, - {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, - {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, - {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, - {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, - {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, - {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, - {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, - {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e"}, + {file = "coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb"}, + {file = "coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060"}, + {file = "coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7"}, + {file = "coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55"}, + {file = "coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405"}, + {file = "coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e"}, + {file = "coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055"}, + {file = "coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094"}, + {file = "coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c"}, + {file = "coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2"}, + {file = "coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76"}, + {file = "coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c"}, + {file = "coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac"}, + {file = "coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131"}, + {file = "coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a"}, + {file = "coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86"}, + {file = "coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820"}, + {file = "coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237"}, + {file = "coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9"}, + {file = "coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd"}, + {file = "coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe"}, + {file = "coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b"}, ] [package.extras] @@ -1673,4 +1673,4 @@ test = ["pytest", "pytest-cov"] [metadata] lock-version = "2.1" python-versions = "~3.11" -content-hash = "a8f88fc5258c032f215b4bd20ffe30d4425df99054d1e6e9e9bf83f6e3c31248" +content-hash = "de58b6f2b1803fdf7873949e357ea809301d980bb95fef01c7803b24bfc098e6" diff --git a/lambdas/recordprocessor/pyproject.toml b/lambdas/recordprocessor/pyproject.toml index 667dfc4ea..a6c844631 100644 --- a/lambdas/recordprocessor/pyproject.toml +++ b/lambdas/recordprocessor/pyproject.toml @@ -12,12 +12,12 @@ packages = [ [tool.poetry.dependencies] python = "~3.11" "fhir.resources" = "~7.0.2" -boto3 = "~1.40.68" -boto3-stubs-lite = {extras = ["dynamodb"], version = "~1.40.64"} +boto3 = "~1.40.72" +boto3-stubs-lite = {extras = ["dynamodb"], version = "~1.40.72"} aws-lambda-typing = "~2.20.0" moto = "^4" simplejson = "^3.20.2" -coverage = "^7.10.7" +coverage = "^7.11.3" redis = "^6.4.0" [build-system] diff --git a/lambdas/recordprocessor/src/batch_processor.py b/lambdas/recordprocessor/src/batch_processor.py index 3a7312b90..4f1cf89d7 100644 --- a/lambdas/recordprocessor/src/batch_processor.py +++ b/lambdas/recordprocessor/src/batch_processor.py @@ -8,6 +8,7 @@ from typing import Optional from audit_table import update_audit_table_status +from common.aws_s3_utils import move_file from common.clients import logger from constants import ( ARCHIVE_DIR_NAME, @@ -16,7 +17,7 @@ FileNotProcessedReason, FileStatus, ) -from file_level_validation import file_is_empty, file_level_validation, move_file +from file_level_validation import file_is_empty, file_level_validation from mappings import map_target_disease from process_row import process_row from send_to_kinesis import send_to_kinesis @@ -142,7 +143,6 @@ def process_rows( send_to_kinesis(supplier, outgoing_message_body, vaccine) total_rows_processed_count += 1 except UnicodeDecodeError as error: # pylint: disable=broad-exception-caught - logger.error("Error processing row %s: %s", row_count, error) return total_rows_processed_count, error return total_rows_processed_count, None diff --git a/lambdas/recordprocessor/src/file_level_validation.py b/lambdas/recordprocessor/src/file_level_validation.py index c9eabeceb..cd71e237a 100644 --- a/lambdas/recordprocessor/src/file_level_validation.py +++ b/lambdas/recordprocessor/src/file_level_validation.py @@ -6,7 +6,8 @@ from csv import DictReader from audit_table import update_audit_table_status -from common.clients import logger, s3_client +from common.aws_s3_utils import move_file +from common.clients import logger from constants import ( ARCHIVE_DIR_NAME, EXPECTED_CSV_HEADERS, @@ -17,9 +18,9 @@ Permission, permission_to_operation_map, ) -from errors import InvalidHeaders, NoOperationPermissions from logging_decorator import file_level_validation_logging_decorator from make_and_upload_ack_file import make_and_upload_ack_file +from models.errors import InvalidHeaders, NoOperationPermissions from utils_for_recordprocessor import get_csv_content_dict_reader @@ -61,17 +62,6 @@ def get_permitted_operations(supplier: str, vaccine_type: str, allowed_permissio return permitted_operations_for_vaccine_type -def move_file(bucket_name: str, source_file_key: str, destination_file_key: str) -> None: - """Moves a file from one location to another within a single S3 bucket by copying and then deleting the file.""" - s3_client.copy_object( - Bucket=bucket_name, - CopySource={"Bucket": bucket_name, "Key": source_file_key}, - Key=destination_file_key, - ) - s3_client.delete_object(Bucket=bucket_name, Key=source_file_key) - logger.info("File moved from %s to %s", source_file_key, destination_file_key) - - @file_level_validation_logging_decorator def file_level_validation(incoming_message_body: dict) -> dict: """ diff --git a/lambdas/recordprocessor/src/logging_decorator.py b/lambdas/recordprocessor/src/logging_decorator.py index 0eb7fc9d2..a9c863dd5 100644 --- a/lambdas/recordprocessor/src/logging_decorator.py +++ b/lambdas/recordprocessor/src/logging_decorator.py @@ -6,7 +6,7 @@ from functools import wraps from common.log_decorator import generate_and_send_logs -from errors import InvalidHeaders, NoOperationPermissions +from models.errors import InvalidHeaders, NoOperationPermissions STREAM_NAME = os.getenv("SPLUNK_FIREHOSE_NAME", "immunisation-fhir-api-internal-dev-splunk-firehose") diff --git a/lambdas/recordprocessor/src/make_and_upload_ack_file.py b/lambdas/recordprocessor/src/make_and_upload_ack_file.py index 7cf3795f6..95c549f24 100644 --- a/lambdas/recordprocessor/src/make_and_upload_ack_file.py +++ b/lambdas/recordprocessor/src/make_and_upload_ack_file.py @@ -3,7 +3,7 @@ from csv import writer from io import BytesIO, StringIO -from common.clients import s3_client +from common.clients import get_s3_client from constants import ACK_BUCKET_NAME @@ -46,7 +46,7 @@ def upload_ack_file(file_key: str, ack_data: dict, created_at_formatted_string: # Upload the CSV file to S3 csv_buffer.seek(0) csv_bytes = BytesIO(csv_buffer.getvalue().encode("utf-8")) - s3_client.upload_fileobj(csv_bytes, ACK_BUCKET_NAME, ack_filename) + get_s3_client().upload_fileobj(csv_bytes, ACK_BUCKET_NAME, ack_filename) def make_and_upload_ack_file( diff --git a/lambdas/recordprocessor/src/errors.py b/lambdas/recordprocessor/src/models/errors.py similarity index 100% rename from lambdas/recordprocessor/src/errors.py rename to lambdas/recordprocessor/src/models/errors.py diff --git a/lambdas/recordprocessor/src/models/utils.py b/lambdas/recordprocessor/src/models/utils.py deleted file mode 100644 index ae2d9620f..000000000 --- a/lambdas/recordprocessor/src/models/utils.py +++ /dev/null @@ -1,79 +0,0 @@ -import uuid -from dataclasses import dataclass -from enum import Enum - - -class Severity(str, Enum): - error = "error" - warning = "warning" - - -class Code(str, Enum): - forbidden = "forbidden" - not_found = "not-found" - invalid = "invalid" - server_error = "exception" - invariant = "invariant" - not_supported = "not-supported" - duplicate = "duplicate" - # Added an unauthorized code its used when returning a response for an unauthorized vaccine type search. - unauthorized = "unauthorized" - - -@dataclass -class UnhandledResponseError(RuntimeError): - """Use this error when the response from an external service (ex: dynamodb) can't be handled""" - - response: dict | str - message: str - - def __str__(self): - return f"{self.message}\n{self.response}" - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.server_error, - diagnostics=self.__str__(), - ) - - -@dataclass -class ImmunizationApiUnhandledError(RuntimeError): - """An error that occurs when the ImmunizationApi throws an unhandled error.""" - - request: dict - - -@dataclass -class ImmunizationApiError(RuntimeError): - """An error that occurs when the ImmunizationApi returns a non-200 status code.""" - - status_code: int - request: dict - response: dict | str - - -def create_operation_outcome(resource_id: str, severity: Severity, code: Code, diagnostics: str) -> dict: - """Create an OperationOutcome object. Do not use `fhir.resource` library since it adds unnecessary validations""" - return { - "resourceType": "OperationOutcome", - "id": resource_id, - "meta": {"profile": ["https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome"]}, - "issue": [ - { - "severity": severity, - "code": code, - "details": { - "coding": [ - { - "system": "https://fhir.nhs.uk/Codesystem/http-error-codes", - "code": code.upper(), - } - ] - }, - "diagnostics": diagnostics, - } - ], - } diff --git a/lambdas/recordprocessor/src/utils_for_recordprocessor.py b/lambdas/recordprocessor/src/utils_for_recordprocessor.py index d0cf81681..5fef8ca57 100644 --- a/lambdas/recordprocessor/src/utils_for_recordprocessor.py +++ b/lambdas/recordprocessor/src/utils_for_recordprocessor.py @@ -4,7 +4,7 @@ from csv import DictReader from io import TextIOWrapper -from common.clients import s3_client +from common.clients import get_s3_client def get_environment() -> str: @@ -16,7 +16,7 @@ def get_environment() -> str: def get_csv_content_dict_reader(file_key: str, encoder="utf-8") -> DictReader: """Returns the requested file contents from the source bucket in the form of a DictReader""" - response = s3_client.get_object(Bucket=os.getenv("SOURCE_BUCKET_NAME"), Key=file_key) + response = get_s3_client().get_object(Bucket=os.getenv("SOURCE_BUCKET_NAME"), Key=file_key) binary_io = response["Body"] text_io = TextIOWrapper(binary_io, encoding=encoder, newline="") return DictReader(text_io, delimiter="|") diff --git a/lambdas/recordprocessor/tests/test_file_level_validation.py b/lambdas/recordprocessor/tests/test_file_level_validation.py index 4ea12cbf9..ad25d2c7f 100644 --- a/lambdas/recordprocessor/tests/test_file_level_validation.py +++ b/lambdas/recordprocessor/tests/test_file_level_validation.py @@ -16,8 +16,8 @@ ) with patch("os.environ", MOCK_ENVIRONMENT_DICT): - from errors import InvalidHeaders, NoOperationPermissions from file_level_validation import get_permitted_operations, validate_content_headers + from models.errors import InvalidHeaders, NoOperationPermissions test_file = MockFileDetails.rsv_emis diff --git a/lambdas/recordprocessor/tests/test_logging_decorator.py b/lambdas/recordprocessor/tests/test_logging_decorator.py index 3916c0d35..5290b5486 100644 --- a/lambdas/recordprocessor/tests/test_logging_decorator.py +++ b/lambdas/recordprocessor/tests/test_logging_decorator.py @@ -22,8 +22,8 @@ with patch.dict("os.environ", MOCK_ENVIRONMENT_DICT): from common.clients import REGION_NAME - from errors import InvalidHeaders, NoOperationPermissions from file_level_validation import file_level_validation + from models.errors import InvalidHeaders, NoOperationPermissions from utils_for_recordprocessor_tests.utils_for_recordprocessor_tests import ( diff --git a/lambdas/recordprocessor/tests/test_recordprocessor_edge_cases.py b/lambdas/recordprocessor/tests/test_recordprocessor_edge_cases.py index e055efe13..d69eb1733 100644 --- a/lambdas/recordprocessor/tests/test_recordprocessor_edge_cases.py +++ b/lambdas/recordprocessor/tests/test_recordprocessor_edge_cases.py @@ -1,14 +1,17 @@ import os import unittest from io import BytesIO -from unittest.mock import call, patch +from unittest.mock import Mock, call, patch from batch_processor import process_csv_to_fhir from utils_for_recordprocessor_tests.utils_for_recordprocessor_tests import ( + MOCK_ENVIRONMENT_DICT, + BucketNames, create_patch, ) +@patch.dict("os.environ", MOCK_ENVIRONMENT_DICT) class TestProcessorEdgeCases(unittest.TestCase): def setUp(self): self.mock_logger_info = create_patch("logging.Logger.info") @@ -16,8 +19,7 @@ def setUp(self): self.mock_logger_error = create_patch("logging.Logger.error") self.mock_send_to_kinesis = create_patch("batch_processor.send_to_kinesis") self.mock_map_target_disease = create_patch("batch_processor.map_target_disease") - self.mock_s3_get_object = create_patch("utils_for_recordprocessor.s3_client.get_object") - self.mock_s3_put_object = create_patch("utils_for_recordprocessor.s3_client.put_object") + self.mock_get_s3_client = create_patch("utils_for_recordprocessor.get_s3_client") self.mock_make_and_move = create_patch("file_level_validation.make_and_upload_ack_file") self.mock_move_file = create_patch("file_level_validation.move_file") self.mock_get_permitted_operations = create_patch("file_level_validation.get_permitted_operations") @@ -63,7 +65,9 @@ def test_process_large_file_cp1252(self): data = self.insert_cp1252_at_end(data, b"D\xe9cembre", 2) ret1 = {"Body": BytesIO(b"".join(data))} ret2 = {"Body": BytesIO(b"".join(data))} - self.mock_s3_get_object.side_effect = [ret1, ret2] + mock_s3 = Mock() + mock_s3.get_object.side_effect = [ret1, ret2] + self.mock_get_s3_client.return_value = mock_s3 self.mock_map_target_disease.return_value = "some disease" message_body = { @@ -80,10 +84,10 @@ def test_process_large_file_cp1252(self): self.mock_logger_warning.assert_called() warning_call_args = self.mock_logger_warning.call_args[0][0] self.assertTrue(warning_call_args.startswith("Encoding Error: 'utf-8' codec can't decode byte 0xe9")) - self.mock_s3_get_object.assert_has_calls( + mock_s3.get_object.assert_has_calls( [ - call(Bucket=None, Key="test-filename"), - call(Bucket=None, Key="processing/test-filename"), + call(Bucket=BucketNames.SOURCE, Key="test-filename"), + call(Bucket=BucketNames.SOURCE, Key="processing/test-filename"), ] ) @@ -94,7 +98,9 @@ def test_process_large_file_utf8(self): data = self.expand_test_data(data, n_rows) ret1 = {"Body": BytesIO(b"".join(data))} ret2 = {"Body": BytesIO(b"".join(data))} - self.mock_s3_get_object.side_effect = [ret1, ret2] + mock_s3 = Mock() + mock_s3.get_object.side_effect = [ret1, ret2] + self.mock_get_s3_client.return_value = mock_s3 self.mock_map_target_disease.return_value = "some disease" message_body = { @@ -118,7 +124,9 @@ def test_process_small_file_cp1252(self): ret1 = {"Body": BytesIO(b"".join(data))} ret2 = {"Body": BytesIO(b"".join(data))} - self.mock_s3_get_object.side_effect = [ret1, ret2] + mock_s3 = Mock() + mock_s3.get_object.side_effect = [ret1, ret2] + self.mock_get_s3_client.return_value = mock_s3 self.mock_map_target_disease.return_value = "some disease" message_body = { @@ -143,7 +151,9 @@ def test_process_small_file_utf8(self): ret1 = {"Body": BytesIO(b"".join(data))} ret2 = {"Body": BytesIO(b"".join(data))} - self.mock_s3_get_object.side_effect = [ret1, ret2] + mock_s3 = Mock() + mock_s3.get_object.side_effect = [ret1, ret2] + self.mock_get_s3_client.return_value = mock_s3 self.mock_map_target_disease.return_value = "some disease" message_body = { diff --git a/lambdas/recordprocessor/tests/test_utils_for_recordprocessor.py b/lambdas/recordprocessor/tests/test_utils_for_recordprocessor.py index ab3ba099b..6ebe2f54f 100644 --- a/lambdas/recordprocessor/tests/test_utils_for_recordprocessor.py +++ b/lambdas/recordprocessor/tests/test_utils_for_recordprocessor.py @@ -23,7 +23,6 @@ ) with patch("os.environ", MOCK_ENVIRONMENT_DICT): - from file_level_validation import move_file from utils_for_recordprocessor import ( create_diagnostics_dictionary, get_csv_content_dict_reader, @@ -87,23 +86,6 @@ def test_create_diagnostics_dictionary(self): }, ) - def test_move_file(self): - """Tests that move_file correctly moves a file from one location to another within a single S3 bucket""" - source_file_key = "test_file_key" - destination_file_key = "archive/test_file_key" - source_file_content = "test_content" - s3_client.put_object(Bucket=BucketNames.SOURCE, Key=source_file_key, Body=source_file_content) - - move_file(BucketNames.SOURCE, source_file_key, destination_file_key) - - keys_of_objects_in_bucket = [ - obj["Key"] for obj in s3_client.list_objects_v2(Bucket=BucketNames.SOURCE).get("Contents") - ] - self.assertNotIn(source_file_key, keys_of_objects_in_bucket) - self.assertIn(destination_file_key, keys_of_objects_in_bucket) - destination_file_content = s3_client.get_object(Bucket=BucketNames.SOURCE, Key=destination_file_key) - self.assertEqual(destination_file_content["Body"].read().decode("utf-8"), source_file_content) - if __name__ == "__main__": unittest.main() diff --git a/lambdas/redis_sync/poetry.lock b/lambdas/redis_sync/poetry.lock index 1bd78a707..b487e6834 100644 --- a/lambdas/redis_sync/poetry.lock +++ b/lambdas/redis_sync/poetry.lock @@ -15,18 +15,18 @@ files = [ [[package]] name = "boto3" -version = "1.40.68" +version = "1.40.72" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.40.68-py3-none-any.whl", hash = "sha256:4f08115e3a4d1e1056003e433d393e78c20da6af7753409992bb33fb69f04186"}, - {file = "boto3-1.40.68.tar.gz", hash = "sha256:c7994989e5bbba071b7c742adfba35773cf03e87f5d3f9f2b0a18c1664417b61"}, + {file = "boto3-1.40.72-py3-none-any.whl", hash = "sha256:1063a295712f2605d3e463e4dc1fe32fce17cf77a0f4d3bb14249d68533ee856"}, + {file = "boto3-1.40.72.tar.gz", hash = "sha256:58d30dd5e046789a760db7a49f817650b8ff08d8d169e127976a61f44b7c59ad"}, ] [package.dependencies] -botocore = ">=1.40.68,<1.41.0" +botocore = ">=1.40.72,<1.41.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.14.0,<0.15.0" @@ -35,14 +35,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.40.68" +version = "1.40.72" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.40.68-py3-none-any.whl", hash = "sha256:9d514f9c9054e1af055f2cbe9e0d6771d407a600206d45a01b54d5f09538fecb"}, - {file = "botocore-1.40.68.tar.gz", hash = "sha256:28f41b463d9f012a711ee8b61d4e26cd14ee3b450b816d5dee849aa79155e856"}, + {file = "botocore-1.40.72-py3-none-any.whl", hash = "sha256:4f859e5aaf871fe59aac431d6bba59cc0c8ed8a38da2a6a5345700bdc5c74b32"}, + {file = "botocore-1.40.72.tar.gz", hash = "sha256:f69199ff6570695556e733fa052f2739e01e0c592c9b60f843f84c77ba3bcdf3"}, ] [package.dependencies] @@ -288,104 +288,104 @@ files = [ [[package]] name = "coverage" -version = "7.11.0" +version = "7.11.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, - {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, - {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, - {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, - {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, - {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, - {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, - {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, - {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, - {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, - {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, - {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, - {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, - {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, - {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, - {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, - {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, - {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, - {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, - {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, - {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, - {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, - {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, - {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e"}, + {file = "coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb"}, + {file = "coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060"}, + {file = "coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7"}, + {file = "coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55"}, + {file = "coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405"}, + {file = "coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e"}, + {file = "coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055"}, + {file = "coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094"}, + {file = "coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c"}, + {file = "coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2"}, + {file = "coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76"}, + {file = "coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c"}, + {file = "coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac"}, + {file = "coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131"}, + {file = "coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a"}, + {file = "coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86"}, + {file = "coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820"}, + {file = "coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237"}, + {file = "coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9"}, + {file = "coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd"}, + {file = "coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe"}, + {file = "coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b"}, ] [package.extras] @@ -956,4 +956,4 @@ test = ["pytest", "pytest-cov"] [metadata] lock-version = "2.1" python-versions = "~3.11" -content-hash = "a677c0f1cbe470ed13cde507cf54915f631b75d810aeaa736af7a3a4ee2591f5" +content-hash = "481d4fe85a720a17e15a1f36feca0ab76e08a7ff6ef8226c0e57de65d3b3a95d" diff --git a/lambdas/redis_sync/pyproject.toml b/lambdas/redis_sync/pyproject.toml index 6d3488371..de93ed648 100644 --- a/lambdas/redis_sync/pyproject.toml +++ b/lambdas/redis_sync/pyproject.toml @@ -19,15 +19,15 @@ packages = [ [tool.poetry.dependencies] python = "~3.11" -boto3 = "~1.40.68" +boto3 = "~1.40.72" mypy-boto3-dynamodb = "^1.40.44" moto = "~5.1.16" python-stdnum = "^2.1" -coverage = "^7.10.7" +coverage = "^7.11.3" redis = "^4.6.0" [tool.poetry.group.dev.dependencies] -coverage = "^7.10.7" +coverage = "^7.11.3" [build-system] requires = ["poetry-core"] diff --git a/lambdas/shared/poetry.lock b/lambdas/shared/poetry.lock index b70c0eb71..a121ab45a 100644 --- a/lambdas/shared/poetry.lock +++ b/lambdas/shared/poetry.lock @@ -15,18 +15,18 @@ files = [ [[package]] name = "boto3" -version = "1.40.68" +version = "1.40.72" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.40.68-py3-none-any.whl", hash = "sha256:4f08115e3a4d1e1056003e433d393e78c20da6af7753409992bb33fb69f04186"}, - {file = "boto3-1.40.68.tar.gz", hash = "sha256:c7994989e5bbba071b7c742adfba35773cf03e87f5d3f9f2b0a18c1664417b61"}, + {file = "boto3-1.40.72-py3-none-any.whl", hash = "sha256:1063a295712f2605d3e463e4dc1fe32fce17cf77a0f4d3bb14249d68533ee856"}, + {file = "boto3-1.40.72.tar.gz", hash = "sha256:58d30dd5e046789a760db7a49f817650b8ff08d8d169e127976a61f44b7c59ad"}, ] [package.dependencies] -botocore = ">=1.40.68,<1.41.0" +botocore = ">=1.40.72,<1.41.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.14.0,<0.15.0" @@ -35,14 +35,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.40.68" +version = "1.40.72" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.40.68-py3-none-any.whl", hash = "sha256:9d514f9c9054e1af055f2cbe9e0d6771d407a600206d45a01b54d5f09538fecb"}, - {file = "botocore-1.40.68.tar.gz", hash = "sha256:28f41b463d9f012a711ee8b61d4e26cd14ee3b450b816d5dee849aa79155e856"}, + {file = "botocore-1.40.72-py3-none-any.whl", hash = "sha256:4f859e5aaf871fe59aac431d6bba59cc0c8ed8a38da2a6a5345700bdc5c74b32"}, + {file = "botocore-1.40.72.tar.gz", hash = "sha256:f69199ff6570695556e733fa052f2739e01e0c592c9b60f843f84c77ba3bcdf3"}, ] [package.dependencies] @@ -299,104 +299,104 @@ files = [ [[package]] name = "coverage" -version = "7.11.0" +version = "7.11.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, - {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, - {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, - {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, - {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, - {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, - {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, - {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, - {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, - {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, - {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, - {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, - {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, - {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, - {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, - {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, - {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, - {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, - {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, - {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, - {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, - {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, - {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, - {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e"}, + {file = "coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb"}, + {file = "coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060"}, + {file = "coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7"}, + {file = "coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55"}, + {file = "coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405"}, + {file = "coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e"}, + {file = "coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055"}, + {file = "coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094"}, + {file = "coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c"}, + {file = "coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2"}, + {file = "coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76"}, + {file = "coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c"}, + {file = "coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac"}, + {file = "coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131"}, + {file = "coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a"}, + {file = "coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86"}, + {file = "coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820"}, + {file = "coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237"}, + {file = "coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9"}, + {file = "coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd"}, + {file = "coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe"}, + {file = "coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b"}, ] [package.extras] @@ -479,6 +479,66 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==46.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "dnspython" +version = "2.8.0" +description = "DNS toolkit" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af"}, + {file = "dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f"}, +] + +[package.extras] +dev = ["black (>=25.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.17.0)", "mypy (>=1.17)", "pylint (>=3)", "pytest (>=8.4)", "pytest-cov (>=6.2.0)", "quart-trio (>=0.12.0)", "sphinx (>=8.2.0)", "sphinx-rtd-theme (>=3.0.0)", "twine (>=6.1.0)", "wheel (>=0.45.0)"] +dnssec = ["cryptography (>=45)"] +doh = ["h2 (>=4.2.0)", "httpcore (>=1.0.0)", "httpx (>=0.28.0)"] +doq = ["aioquic (>=1.2.0)"] +idna = ["idna (>=3.10)"] +trio = ["trio (>=0.30)"] +wmi = ["wmi (>=1.5.1) ; platform_system == \"Windows\""] + +[[package]] +name = "email-validator" +version = "2.3.0" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4"}, + {file = "email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + +[[package]] +name = "fhir-resources" +version = "7.0.2" +description = "FHIR Resources as Model Class" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "fhir.resources-7.0.2-py2.py3-none-any.whl", hash = "sha256:ba16b3348821614650dd2e7f04db170210ce4406f65a5cc873beb10317d595ff"}, + {file = "fhir.resources-7.0.2.tar.gz", hash = "sha256:1e6392f7bc3b143463b07ada87a3b6a92dd8fe97947670423317fea69226f6de"}, +] + +[package.dependencies] +pydantic = {version = ">=1.7.2,<2.0.0", extras = ["email"]} + +[package.extras] +all = ["PyYAML (>=5.4.1)", "lxml", "orjson (>=3.4.3)"] +dev = ["Jinja2 (==2.11.1)", "MarkupSafe (==1.1.1)", "black", "certifi", "colorlog (==2.10.0)", "coverage", "fhirspec", "flake8 (==5.0.4) ; python_version < \"3.10\"", "flake8-bugbear (==20.1.4) ; python_version < \"3.10\"", "flake8-isort (==4.2.0) ; python_version < \"3.10\"", "isort (==4.3.21)", "mypy (==0.812)", "pytest (>5.4.0) ; python_version >= \"3.6\"", "pytest-cov (>=2.10.0) ; python_version >= \"3.6\"", "requests (==2.23.0) ; python_version < \"3.10\"", "setuptools (==65.6.3) ; python_version >= \"3.7\"", "zest-releaser[recommended]"] +orjson = ["orjson (>=3.4.3)"] +test = ["PyYAML (>=5.4.1)", "black", "coverage", "flake8 (==5.0.4) ; python_version < \"3.10\"", "flake8-bugbear (==20.1.4) ; python_version < \"3.10\"", "flake8-isort (==4.2.0) ; python_version < \"3.10\"", "isort (==4.3.21)", "lxml", "mypy (==0.812)", "orjson (>=3.4.3)", "pytest (>5.4.0) ; python_version >= \"3.6\"", "pytest-cov (>=2.10.0) ; python_version >= \"3.6\"", "pytest-runner", "requests (==2.23.0) ; python_version < \"3.10\"", "setuptools (==65.6.3) ; python_version >= \"3.7\""] +xml = ["lxml"] +yaml = ["PyYAML (>=5.4.1)"] + [[package]] name = "idna" version = "3.11" @@ -524,6 +584,22 @@ files = [ {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, ] +[[package]] +name = "jsonpath-ng" +version = "1.7.0" +description = "A final implementation of JSONPath for Python that aims to be standard compliant, including arithmetic and binary comparison operators and providing clear AST for metaprogramming." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"}, + {file = "jsonpath_ng-1.7.0-py2-none-any.whl", hash = "sha256:898c93fc173f0c336784a3fa63d7434297544b7198124a68f9a3ef9597b0ae6e"}, + {file = "jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6"}, +] + +[package.dependencies] +ply = "*" + [[package]] name = "markupsafe" version = "3.0.3" @@ -684,6 +760,18 @@ files = [ [package.dependencies] typing-extensions = {version = "*", markers = "python_version < \"3.12\""} +[[package]] +name = "ply" +version = "3.11" +description = "Python Lex & Yacc" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"}, + {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, +] + [[package]] name = "pycparser" version = "2.23" @@ -697,6 +785,74 @@ files = [ {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] +[[package]] +name = "pydantic" +version = "1.10.24" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "pydantic-1.10.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eef07ea2fba12f9188cfa2c50cb3eaa6516b56c33e2a8cc3cd288b4190ee6c0c"}, + {file = "pydantic-1.10.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a42033fac69b9f1f867ecc3a2159f0e94dceb1abfc509ad57e9e88d49774683"}, + {file = "pydantic-1.10.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c626596c1b95dc6d45f7129f10b6743fbb50f29d942d25a22b2ceead670c067d"}, + {file = "pydantic-1.10.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8057172868b0d98f95e6fcddcc5f75d01570e85c6308702dd2c50ea673bc197b"}, + {file = "pydantic-1.10.24-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:82f951210ebcdb778b1d93075af43adcd04e9ebfd4f44b1baa8eeb21fbd71e36"}, + {file = "pydantic-1.10.24-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b66e4892d8ae005f436a5c5f1519ecf837574d8414b1c93860fb3c13943d9b37"}, + {file = "pydantic-1.10.24-cp310-cp310-win_amd64.whl", hash = "sha256:50d9f8a207c07f347d4b34806dc576872000d9a60fd481ed9eb78ea8512e0666"}, + {file = "pydantic-1.10.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:70152291488f8d2bbcf2027b5c28c27724c78a7949c91b466d28ad75d6d12702"}, + {file = "pydantic-1.10.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:956b30638272c51c85caaff76851b60db4b339022c0ee6eca677c41e3646255b"}, + {file = "pydantic-1.10.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bed9d6eea5fabbc6978c42e947190c7bd628ddaff3b56fc963fe696c3710ccd6"}, + {file = "pydantic-1.10.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af8e2b3648128b8cadb1a71e2f8092a6f42d4ca123fad7a8d7ce6db8938b1db3"}, + {file = "pydantic-1.10.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:076fff9da02ca716e4c8299c68512fdfbeac32fdefc9c160e6f80bdadca0993d"}, + {file = "pydantic-1.10.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8f2447ca88a7e14fd4d268857521fb37535c53a367b594fa2d7c2551af905993"}, + {file = "pydantic-1.10.24-cp311-cp311-win_amd64.whl", hash = "sha256:58d42a7c344882c00e3bb7c6c8c6f62db2e3aafa671f307271c45ad96e8ccf7a"}, + {file = "pydantic-1.10.24-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:17e7610119483f03954569c18d4de16f4e92f1585f20975414033ac2d4a96624"}, + {file = "pydantic-1.10.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e24435a9970dcb2b35648f2cf57505d4bd414fcca1a404c82e28d948183fe0a6"}, + {file = "pydantic-1.10.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a9e92b9c78d7f3cfa085c21c110e7000894446e24a836d006aabfc6ae3f1813"}, + {file = "pydantic-1.10.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef14dfa7c98b314a3e449e92df6f1479cafe74c626952f353ff0176b075070de"}, + {file = "pydantic-1.10.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52219b4e70c1db185cfd103a804e416384e1c8950168a2d4f385664c7c35d21a"}, + {file = "pydantic-1.10.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ce0986799248082e9a5a026c9b5d2f9fa2e24d2afb9b0eace9104334a58fdc1"}, + {file = "pydantic-1.10.24-cp312-cp312-win_amd64.whl", hash = "sha256:874a78e4ed821258295a472e325eee7de3d91ba7a61d0639ce1b0367a3c63d4c"}, + {file = "pydantic-1.10.24-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:265788a1120285c4955f8b3d52b3ea6a52c7a74db097c4c13a4d3567f0c6df3c"}, + {file = "pydantic-1.10.24-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d255bebd927e5f1e026b32605684f7b6fc36a13e62b07cb97b29027b91657def"}, + {file = "pydantic-1.10.24-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6e45dbc79a44e34c2c83ef1fcb56ff663040474dcf4dfc452db24a1de0f7574"}, + {file = "pydantic-1.10.24-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af31565b12a7db5bfa5fe8c3a4f8fda4d32f5c2929998b1b241f1c22e9ab6e69"}, + {file = "pydantic-1.10.24-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9c377fc30d9ca40dbff5fd79c5a5e1f0d6fff040fa47a18851bb6b0bd040a5d8"}, + {file = "pydantic-1.10.24-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b644d6f14b2ce617d6def21622f9ba73961a16b7dffdba7f6692e2f66fa05d00"}, + {file = "pydantic-1.10.24-cp313-cp313-win_amd64.whl", hash = "sha256:0cbbf306124ae41cc153fdc2559b37faa1bec9a23ef7b082c1756d1315ceffe6"}, + {file = "pydantic-1.10.24-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7c8bbad6037a87effe9f3739bdf39851add6e0f7e101d103a601c504892ffa70"}, + {file = "pydantic-1.10.24-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f154a8a46a0d950c055254f8f010ba07e742ac4404a3b6e281a31913ac45ccd0"}, + {file = "pydantic-1.10.24-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f25d2f792afcd874cc8339c1da1cc52739f4f3d52993ed1f6c263ef2afadc47"}, + {file = "pydantic-1.10.24-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:49a6f0178063f15eaea6cbcb2dba04db0b73db9834bc7b1e1c4dbea28c7cd22f"}, + {file = "pydantic-1.10.24-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:bb3df10be3c7d264947180615819aeec0916f19650f2ba7309ed1fe546ead0d2"}, + {file = "pydantic-1.10.24-cp37-cp37m-win_amd64.whl", hash = "sha256:fa0ebefc169439267e4b4147c7d458908788367640509ed32c90a91a63ebb579"}, + {file = "pydantic-1.10.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1a5ef77efeb54def2695f2b8f4301aae8c7aa2b334bd15f61c18ef54317621"}, + {file = "pydantic-1.10.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02f7a25e8949d8ca568e4bcef2ffed7881d7843286e7c3488bdd3b67f092059c"}, + {file = "pydantic-1.10.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da2775712dda8b89e701ed2a72d5d81d23dbc6af84089da8a0f61a0be439c8c"}, + {file = "pydantic-1.10.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75259be0558ca3af09192ad7b18557f2e9033ad4cbd48c252131f5292f6374fd"}, + {file = "pydantic-1.10.24-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:1a1ae996daa3d43c530b8d0bacc7e2d9cb55e3991f0e6b7cc2cb61a0fb9f6667"}, + {file = "pydantic-1.10.24-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:34109b0afa63b36eec2f2b115694e48ae5ee52f7d3c1baa0be36f80e586bda52"}, + {file = "pydantic-1.10.24-cp38-cp38-win_amd64.whl", hash = "sha256:4d7336bfcdb8cb58411e6b498772ba2cff84a2ce92f389bae3a8f1bb2c840c49"}, + {file = "pydantic-1.10.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25fb9a69a21d711deb5acefdab9ff8fb49e6cc77fdd46d38217d433bff2e3de2"}, + {file = "pydantic-1.10.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6af36a8fb3072526b5b38d3f341b12d8f423188e7d185f130c0079fe02cdec7f"}, + {file = "pydantic-1.10.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fc35569dfd15d3b3fc06a22abee0a45fdde0784be644e650a8769cd0b2abd94"}, + {file = "pydantic-1.10.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fac7fbcb65171959973f3136d0792c3d1668bc01fd414738f0898b01f692f1b4"}, + {file = "pydantic-1.10.24-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fc3f4a6544517380658b63b144c7d43d5276a343012913b7e5d18d9fba2f12bb"}, + {file = "pydantic-1.10.24-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:415c638ca5fd57b915a62dd38c18c8e0afe5adf5527be6f8ce16b4636b616816"}, + {file = "pydantic-1.10.24-cp39-cp39-win_amd64.whl", hash = "sha256:a5bf94042efbc6ab56b18a5921f426ebbeefc04f554a911d76029e7be9057d01"}, + {file = "pydantic-1.10.24-py3-none-any.whl", hash = "sha256:093768eba26db55a88b12f3073017e3fdee319ef60d3aef5c6c04a4e484db193"}, + {file = "pydantic-1.10.24.tar.gz", hash = "sha256:7e6d1af1bd3d2312079f28c9baf2aafb4a452a06b50717526e5ac562e37baa53"}, +] + +[package.dependencies] +email-validator = {version = ">=1.0.3", optional = true, markers = "extra == \"email\""} +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + [[package]] name = "pyjwt" version = "2.10.1" @@ -985,4 +1141,4 @@ test = ["pytest", "pytest-cov"] [metadata] lock-version = "2.1" python-versions = "~3.11" -content-hash = "05ddcbb69688c90cda60cb89cf75c48a2f67e5ed6e66cb57065af10bbb019666" +content-hash = "5c5f164366caa76416eb16bdd439210870a950870a4cdb82fa3a433caa4c8f1b" diff --git a/lambdas/shared/pyproject.toml b/lambdas/shared/pyproject.toml index 86d9a1c7c..e68a01382 100644 --- a/lambdas/shared/pyproject.toml +++ b/lambdas/shared/pyproject.toml @@ -18,17 +18,20 @@ packages = [ [tool.poetry.dependencies] python = "~3.11" -boto3 = "~1.40.68" +"fhir.resources" = "~7.0.2" +boto3 = "~1.40.72" mypy-boto3-dynamodb = "^1.40.44" moto = "~5.1.16" python-stdnum = "^2.1" -coverage = "^7.10.7" +coverage = "^7.11.3" redis = "^4.6.0" cache = "^1.0.3" +pydantic = "~1.10.13" pyjwt = "^2.10.1" +jsonpath-ng = "^1.6.0" [tool.poetry.group.dev.dependencies] -coverage = "^7.10.7" +coverage = "^7.11.3" [build-system] requires = ["poetry-core"] diff --git a/lambdas/shared/src/common/aws_s3_utils.py b/lambdas/shared/src/common/aws_s3_utils.py new file mode 100644 index 000000000..623187295 --- /dev/null +++ b/lambdas/shared/src/common/aws_s3_utils.py @@ -0,0 +1,15 @@ +"""Non-imms Utility Functions""" + +from common.clients import get_s3_client, logger + + +def move_file(bucket_name: str, source_file_key: str, destination_file_key: str) -> None: + """Moves a file from one location to another within a single S3 bucket by copying and then deleting the file.""" + s3_client = get_s3_client() + s3_client.copy_object( + Bucket=bucket_name, + CopySource={"Bucket": bucket_name, "Key": source_file_key}, + Key=destination_file_key, + ) + s3_client.delete_object(Bucket=bucket_name, Key=source_file_key) + logger.info("File moved from %s to %s", source_file_key, destination_file_key) diff --git a/lambdas/shared/src/common/cache.py b/lambdas/shared/src/common/cache.py index 1d34bedef..94fd9abbd 100644 --- a/lambdas/shared/src/common/cache.py +++ b/lambdas/shared/src/common/cache.py @@ -10,24 +10,24 @@ def __init__(self, directory): self.cache_file.seek(0) content = self.cache_file.read() if len(content) == 0: - self.cache = {} + self.cache_dict = {} else: - self.cache = json.loads(content) + self.cache_dict = json.loads(content) def put(self, key: str, value: dict): - self.cache[key] = value + self.cache_dict[key] = value self._overwrite() def get(self, key: str) -> dict | None: - return self.cache.get(key, None) + return self.cache_dict.get(key, None) def delete(self, key: str): - if key not in self.cache: + if key not in self.cache_dict: return - del self.cache[key] + del self.cache_dict[key] def _overwrite(self): with open(self.cache_file.name, "w") as self.cache_file: self.cache_file.seek(0) - self.cache_file.write(json.dumps(self.cache)) + self.cache_file.write(json.dumps(self.cache_dict)) self.cache_file.truncate() diff --git a/lambdas/shared/src/common/clients.py b/lambdas/shared/src/common/clients.py index 2e3ff6004..534e3203e 100644 --- a/lambdas/shared/src/common/clients.py +++ b/lambdas/shared/src/common/clients.py @@ -14,9 +14,6 @@ REGION_NAME = os.getenv("AWS_REGION", "eu-west-2") -s3_client = boto3_client("s3", region_name=REGION_NAME) - -# for lambdas which require a global s3_client global_s3_client = None diff --git a/backend/src/models/constants.py b/lambdas/shared/src/common/models/constants.py similarity index 68% rename from backend/src/models/constants.py rename to lambdas/shared/src/common/models/constants.py index 027d20090..b985ce126 100644 --- a/backend/src/models/constants.py +++ b/lambdas/shared/src/common/models/constants.py @@ -58,3 +58,22 @@ class Constants: DISEASES_TO_VACCINE_TYPE_HASH_KEY = "diseases_to_vacc" REINSTATED_RECORD_STATUS = "reinstated" + + +class Urls: + """Urls which are expected to be used within the FHIR Immunization Resource json data""" + + nhs_number = "https://fhir.nhs.uk/Id/nhs-number" + vaccination_procedure = "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure" + snomed = "http://snomed.info/sct" # NOSONAR(S5332) + nhs_number_verification_status_structure_definition = ( + "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-NHSNumberVerificationStatus" + ) + nhs_number_verification_status_code_system = ( + "https://fhir.hl7.org.uk/CodeSystem/UKCore-NHSNumberVerificationStatusEngland" + ) + ods_organization_code = "https://fhir.nhs.uk/Id/ods-organization-code" + urn_school_number = "https://fhir.hl7.org.uk/Id/urn-school-number" + + +SUPPLIER_PERMISSIONS_HASH_KEY = "supplier_permissions" diff --git a/lambdas/shared/src/common/models/errors.py b/lambdas/shared/src/common/models/errors.py index 0d09c7494..48c19f78f 100644 --- a/lambdas/shared/src/common/models/errors.py +++ b/lambdas/shared/src/common/models/errors.py @@ -3,11 +3,6 @@ from enum import Enum -class Severity(str, Enum): - error = "error" - warning = "warning" - - class Code(str, Enum): forbidden = "forbidden" not_found = "not-found" @@ -22,99 +17,14 @@ class Code(str, Enum): unauthorized = "unauthorized" -@dataclass -class UnauthorizedError(RuntimeError): - response: dict | str - message: str - - def __str__(self): - return f"{self.message}\n{self.response}" - - @staticmethod - def to_operation_outcome() -> dict: - msg = "Unauthorized request" - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.forbidden, - diagnostics=msg, - ) - - -@dataclass -class UnauthorizedVaxError(RuntimeError): - response: dict | str - message: str - - def __str__(self): - return f"{self.message}\n{self.response}" - - @staticmethod - def to_operation_outcome() -> dict: - msg = "Unauthorized request for vaccine type" - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.forbidden, - diagnostics=msg, - ) - - -@dataclass -class UnauthorizedVaxOnRecordError(RuntimeError): - response: dict | str - message: str - - def __str__(self): - return f"{self.message}\n{self.response}" - - @staticmethod - def to_operation_outcome() -> dict: - msg = "Unauthorized request for vaccine type present in the stored immunization resource" - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.forbidden, - diagnostics=msg, - ) - - -@dataclass -class TokenValidationError(RuntimeError): - response: dict | str - message: str - - def __str__(self): - return f"{self.message}\n{self.response}" - - @staticmethod - def to_operation_outcome() -> dict: - msg = "Missing/Invalid Token" - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.invalid, - diagnostics=msg, - ) +class Severity(str, Enum): + error = "error" + warning = "warning" -@dataclass -class ConflictError(RuntimeError): - response: dict | str - message: str - - def __str__(self): - return f"{self.message}\n{self.response}" - - @staticmethod - def to_operation_outcome() -> dict: - msg = "Conflict" - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.duplicate, - diagnostics=msg, - ) +class MandatoryError(Exception): + def __init__(self, message=None): + self.message = message @dataclass @@ -155,97 +65,47 @@ def to_operation_outcome(self) -> dict: ) -@dataclass -class UnhandledResponseError(RuntimeError): - """Use this error when the response from an external service (ex: dynamodb) can't be handled""" - - response: dict | str - message: str - - def __str__(self): - return f"{self.message}\n{self.response}" - +class ApiValidationError(RuntimeError): def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.exception, - diagnostics=self.__str__(), - ) + pass @dataclass -class BadRequestError(RuntimeError): - """Use when payload is missing required parameters""" - - response: dict | str - message: str +class InconsistentIdentifierError(ApiValidationError): + """Use this when the local identifier in the payload does not match the existing identifier for the update.""" - def __str__(self): - return f"{self.message}\n{self.response}" + msg: str def to_operation_outcome(self) -> dict: return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.incomplete, - diagnostics=self.__str__(), + resource_id=str(uuid.uuid4()), severity=Severity.error, code=Code.invariant, diagnostics=self.msg ) -class MandatoryError(Exception): - def __init__(self, message=None): - self.message = message - - -class ValidationError(RuntimeError): - def to_operation_outcome(self) -> dict: - pass - - -class UnhandledAuditTableError(Exception): - """A custom exception for when an unexpected error occurs whilst adding the file to the audit table.""" - - -class VaccineTypePermissionsError(Exception): - """A custom exception for when the supplier does not have the necessary vaccine type permissions.""" - - -class InvalidFileKeyError(Exception): - """A custom exception for when the file key is invalid.""" - - -class UnhandledSqsError(Exception): - """A custom exception for when an unexpected error occurs whilst sending a message to SQS.""" - - @dataclass -class InvalidPatientId(ValidationError): - """Use this when NHS Number is invalid or doesn't exist""" +class InconsistentResourceVersionError(ApiValidationError): + """Use this when the resource version in the request and actual resource version do not match""" - patient_identifier: str - - def __str__(self): - return f"NHS Number: {self.patient_identifier} is invalid or it doesn't exist." + message: str def to_operation_outcome(self) -> dict: return create_operation_outcome( resource_id=str(uuid.uuid4()), severity=Severity.error, - code=Code.exception, - diagnostics=self.__str__(), + code=Code.invariant, + diagnostics=self.message, ) @dataclass -class InconsistentIdError(ValidationError): - """Use this when the specified id in the message is inconsistent with the path - see: http://hl7.org/fhir/R4/http.html#update""" +class UnhandledResponseError(RuntimeError): + """Use this error when the response from an external service (ex: dynamodb) can't be handled""" - imms_id: str + response: dict | str + message: str def __str__(self): - return f"The provided id:{self.imms_id} doesn't match with the content of the message" + return f"{self.message}\n{self.response}" def to_operation_outcome(self) -> dict: return create_operation_outcome( @@ -256,8 +116,12 @@ def to_operation_outcome(self) -> dict: ) +class UnhandledAuditTableError(Exception): + """A custom exception for when an unexpected error occurs whilst adding the file to the audit table.""" + + @dataclass -class CustomValidationError(ValidationError): +class CustomValidationError(ApiValidationError): """Custom validation error""" message: str @@ -293,68 +157,6 @@ def to_operation_outcome(self) -> dict: ) -@dataclass -class ServerError(RuntimeError): - """Use when there is a server error""" - - response: dict | str - message: str - - def __str__(self): - return f"{self.message}\n{self.response}" - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.server_error, - diagnostics=self.__str__(), - ) - - -@dataclass -class ParameterException(RuntimeError): - message: str - - def __str__(self): - return self.message - - -class UnauthorizedSystemError(RuntimeError): - def __init__(self, message="Unauthorized system"): - super().__init__(message) - self.message = message - - def to_operation_outcome(self) -> dict: - return create_operation_outcome( - resource_id=str(uuid.uuid4()), - severity=Severity.error, - code=Code.forbidden, - diagnostics=self.message, - ) - - -class MessageNotSuccessfulError(Exception): - """ - Generic error message for any scenario which either prevents sending to the Imms API, or which results in a - non-successful response from the Imms API - """ - - def __init__(self, message=None): - self.message = message - - -class RecordProcessorError(Exception): - """ - Exception for re-raising exceptions which have already occurred in the Record Processor. - The diagnostics dictionary received from the Record Processor is passed to the exception as an argument - and is stored as an attribute. - """ - - def __init__(self, diagnostics_dictionary: dict): - self.diagnostics_dictionary = diagnostics_dictionary - - def create_operation_outcome(resource_id: str, severity: Severity, code: Code, diagnostics: str) -> dict: """Create an OperationOutcome object. Do not use `fhir.resource` library since it adds unnecessary validations""" return { diff --git a/backend/src/models/fhir_immunization.py b/lambdas/shared/src/common/models/fhir_immunization.py similarity index 89% rename from backend/src/models/fhir_immunization.py rename to lambdas/shared/src/common/models/fhir_immunization.py index 1f6e61867..2d6aaf0ff 100644 --- a/backend/src/models/fhir_immunization.py +++ b/lambdas/shared/src/common/models/fhir_immunization.py @@ -2,9 +2,9 @@ from fhir.resources.R4B.immunization import Immunization -from models.fhir_immunization_post_validators import PostValidators -from models.fhir_immunization_pre_validators import PreValidators -from models.utils.validation_utils import get_vaccine_type +from common.models.fhir_immunization_post_validators import PostValidators +from common.models.fhir_immunization_pre_validators import PreValidators +from common.models.utils.validation_utils import get_vaccine_type class ImmunizationValidator: @@ -60,9 +60,7 @@ def validate(self, immunization_json_data: dict) -> Immunization: if self.add_post_validators and not reduce_validation: self.run_post_validators(immunization_json_data, vaccine_type) - def run_postalCode_validator(self, values: dict) -> None: + def run_postal_code_validator(self, values: dict) -> None: """Run pre validation on the FHIR Immunization Resource JSON data""" if error := PreValidators.pre_validate_patient_address_postal_code(self, values): raise ValueError(error) - else: - pass diff --git a/backend/src/models/fhir_immunization_post_validators.py b/lambdas/shared/src/common/models/fhir_immunization_post_validators.py similarity index 94% rename from backend/src/models/fhir_immunization_post_validators.py rename to lambdas/shared/src/common/models/fhir_immunization_post_validators.py index f38aea1e2..5bc2aef15 100644 --- a/backend/src/models/fhir_immunization_post_validators.py +++ b/lambdas/shared/src/common/models/fhir_immunization_post_validators.py @@ -1,11 +1,11 @@ "FHIR Immunization Post Validators" -from models.errors import MandatoryError -from models.field_locations import FieldLocations -from models.field_names import FieldNames -from models.mandation_functions import MandationFunctions -from models.utils.base_utils import obtain_field_location, obtain_field_value -from models.validation_sets import ValidationSets +from common.models.errors import MandatoryError +from common.models.field_locations import FieldLocations +from common.models.field_names import FieldNames +from common.models.mandation_functions import MandationFunctions +from common.models.utils.base_utils import obtain_field_location, obtain_field_value +from common.models.validation_sets import ValidationSets class PostValidators: diff --git a/backend/src/models/fhir_immunization_pre_validators.py b/lambdas/shared/src/common/models/fhir_immunization_pre_validators.py similarity index 94% rename from backend/src/models/fhir_immunization_pre_validators.py rename to lambdas/shared/src/common/models/fhir_immunization_pre_validators.py index 8f45585c9..2903f4433 100644 --- a/backend/src/models/fhir_immunization_pre_validators.py +++ b/lambdas/shared/src/common/models/fhir_immunization_pre_validators.py @@ -1,9 +1,8 @@ "FHIR Immunization Pre Validators" -from constants import Urls -from models.constants import Constants -from models.errors import MandatoryError -from models.utils.generic_utils import ( +from common.models.constants import Constants, Urls +from common.models.errors import MandatoryError +from common.models.utils.generic_utils import ( check_for_unknown_elements, generate_field_location_for_extension, get_generic_extension_value, @@ -13,7 +12,7 @@ practitioner_name_family_field_location, practitioner_name_given_field_location, ) -from models.utils.pre_validator_utils import PreValidation +from common.models.utils.pre_validator_utils import PreValidation class PreValidators: @@ -111,14 +110,14 @@ def validate(self): all_errors = "; ".join(self.errors) raise ValueError(f"Validation errors: {all_errors}") - def pre_validate_resource_type(self, values: dict) -> dict: + def pre_validate_resource_type(self, values: dict) -> None: """Pre-validate that resourceType is 'Immunization'""" if values.get("resourceType") != "Immunization": raise ValueError( "This service only accepts FHIR Immunization Resources (i.e. resourceType must equal 'Immunization')" ) - def pre_validate_contained_contents(self, values: dict) -> dict: + def pre_validate_contained_contents(self, values: dict) -> None: """ Pre-validate that contained exists and there is exactly one patient resource in contained, a maximum of one practitioner resource, and no other resources @@ -168,7 +167,7 @@ def pre_validate_contained_contents(self, values: dict) -> dict: if errors: raise ValueError("; ".join(errors)) - def pre_validate_top_level_elements(self, values: dict) -> dict: + def pre_validate_top_level_elements(self, values: dict) -> None: """Pre-validate that disallowed top level elements are not present""" errors = [] @@ -184,7 +183,7 @@ def pre_validate_top_level_elements(self, values: dict) -> dict: if errors: raise ValueError("; ".join(errors)) - def pre_validate_patient_reference(self, values: dict) -> dict: + def pre_validate_patient_reference(self, values: dict) -> None: """ Pre-validate that: - patient.reference exists and it is a reference @@ -208,7 +207,7 @@ def pre_validate_patient_reference(self, values: dict) -> dict: f"The reference '{patient_reference}' does not match the id of the contained Patient resource" ) - def pre_validate_practitioner_reference(self, values: dict) -> dict: + def pre_validate_practitioner_reference(self, values: dict) -> None: """ Pre-validate that, if there is a contained Practitioner resource, there is exactly one reference to it from the performer, and that the performer does not reference any other internal resources @@ -261,7 +260,7 @@ def pre_validate_patient_identifier_extension(self, values: dict) -> None: except (KeyError, IndexError): pass - def pre_validate_patient_identifier(self, values: dict) -> dict: + def pre_validate_patient_identifier(self, values: dict) -> None: """ Pre-validate that, if contained[?(@.resourceType=='Patient')].identifier exists, then it is a list of length 1 """ @@ -272,7 +271,7 @@ def pre_validate_patient_identifier(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_patient_identifier_value(self, values: dict) -> dict: + def pre_validate_patient_identifier_value(self, values: dict) -> None: """ Pre-validate that, if contained[?(@.resourceType=='Patient')].identifier[0].value ( legacy CSV field name: NHS_NUMBER) exists, then it is a string of 10 characters @@ -288,7 +287,7 @@ def pre_validate_patient_identifier_value(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_patient_name(self, values: dict) -> dict: + def pre_validate_patient_name(self, values: dict) -> None: """ Pre-validate that, if contained[?(@.resourceType=='Patient')].name exists, then it is an array of length 1 """ @@ -331,7 +330,7 @@ def pre_validate_patient_name_family(self, values: dict) -> None: except (KeyError, IndexError, AttributeError): pass - def pre_validate_patient_birth_date(self, values: dict) -> dict: + def pre_validate_patient_birth_date(self, values: dict) -> None: """ Pre-validate that, if contained[?(@.resourceType=='Patient')].birthDate (legacy CSV field name: PERSON_DOB) exists, then it is a string in the format YYYY-MM-DD, representing a valid date @@ -343,7 +342,7 @@ def pre_validate_patient_birth_date(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_patient_gender(self, values: dict) -> dict: + def pre_validate_patient_gender(self, values: dict) -> None: """ Pre-validate that, if contained[?(@.resourceType=='Patient')].gender (legacy CSV field name: PERSON_GENDER_CODE) exists, then it is a string, which is one of the following: male, female, other, unknown @@ -355,7 +354,7 @@ def pre_validate_patient_gender(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_patient_address(self, values: dict) -> dict: + def pre_validate_patient_address(self, values: dict) -> None: """ Pre-validate that, if contained[?(@.resourceType=='Patient')].address exists, then it is an array of length 1 """ @@ -366,7 +365,7 @@ def pre_validate_patient_address(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_patient_address_postal_code(self, values: dict) -> dict: + def pre_validate_patient_address_postal_code(self, values: dict) -> None: """ Pre-validate that, if contained[?(@.resourceType=='Patient')].address[0].postalCode (legacy CSV field name: PERSON_POSTCODE) exists, then it is a non-empty string @@ -386,7 +385,7 @@ def pre_validate_patient_address_postal_code(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_occurrence_date_time(self, values: dict) -> dict: + def pre_validate_occurrence_date_time(self, values: dict) -> None: """ Pre-validate that, if occurrenceDateTime exists (legacy CSV field name: DATE_AND_TIME), then it is a string in the format "YYYY-MM-DDThh:mm:ss+zz:zz" or "YYYY-MM-DDThh:mm:ss-zz:zz" @@ -403,7 +402,7 @@ def pre_validate_occurrence_date_time(self, values: dict) -> dict: except KeyError: pass - def pre_validate_performer(self, values: dict) -> dict: + def pre_validate_performer(self, values: dict) -> None: """ Pre-validate that there is exactly one performer instance where actor.type is 'Organization'. """ @@ -422,7 +421,7 @@ def pre_validate_performer(self, values: dict) -> dict: except (KeyError, AttributeError): pass - def pre_validate_organization_identifier_value(self, values: dict) -> dict: + def pre_validate_organization_identifier_value(self, values: dict) -> None: """ Pre-validate that, if performer[?(@.actor.type=='Organization').identifier.value] (legacy CSV field name: SITE_CODE) exists, then it is a non-empty string. @@ -436,7 +435,7 @@ def pre_validate_organization_identifier_value(self, values: dict) -> dict: except (KeyError, IndexError, AttributeError): pass - def pre_validate_identifier(self, values: dict) -> dict: + def pre_validate_identifier(self, values: dict) -> None: """Pre-validate that identifier exists and is a list of length 1 and are an array of objects""" try: field_value = values["identifier"] @@ -445,7 +444,7 @@ def pre_validate_identifier(self, values: dict) -> dict: except KeyError as error: raise MandatoryError("Validation errors: identifier is a mandatory field") from error - def pre_validate_identifier_value(self, values: dict) -> dict: + def pre_validate_identifier_value(self, values: dict) -> None: """ Pre-validate that, if identifier[0].value (legacy CSV field name: UNIQUE_ID) exists, then it is a non-empty string @@ -456,7 +455,7 @@ def pre_validate_identifier_value(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_identifier_system(self, values: dict) -> dict: + def pre_validate_identifier_system(self, values: dict) -> None: """ Pre-validate that, if identifier[0].system (legacy CSV field name: UNIQUE_ID_URI) exists, then it is a non-empty string @@ -467,7 +466,7 @@ def pre_validate_identifier_system(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_status(self, values: dict) -> dict: + def pre_validate_status(self, values: dict) -> None: """ Pre-validate that, if status exists, then its value is "completed" @@ -480,7 +479,7 @@ def pre_validate_status(self, values: dict) -> dict: except KeyError: pass - def pre_validate_practitioner_name(self, values: dict) -> dict: + def pre_validate_practitioner_name(self, values: dict) -> None: """ Pre-validate that, if contained[?(@.resourceType=='Practitioner')].name exists, then it is an array of length 1 @@ -492,7 +491,7 @@ def pre_validate_practitioner_name(self, values: dict) -> dict: except (KeyError, IndexError, AttributeError): pass - def pre_validate_practitioner_name_given(self, values: dict) -> dict: + def pre_validate_practitioner_name_given(self, values: dict) -> None: """ Pre-validate that, if contained[?(@.resourceType=='Practitioner')].name[{index}].given index dynamically determined (legacy CSV field name:PERSON_FORENAME) exists, then it is an array containing a single non-empty @@ -505,7 +504,7 @@ def pre_validate_practitioner_name_given(self, values: dict) -> dict: except (KeyError, IndexError, AttributeError): pass - def pre_validate_practitioner_name_family(self, values: dict) -> dict: + def pre_validate_practitioner_name_family(self, values: dict) -> None: """ Pre-validate that, if contained[?(@.resourceType=='Practitioner')].name[{index}].family index dynamically determined (legacy CSV field name:PERSON_SURNAME) exists, @@ -518,7 +517,7 @@ def pre_validate_practitioner_name_family(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_recorded(self, values: dict) -> dict: + def pre_validate_recorded(self, values: dict) -> None: """ Pre-validate that, if occurrenceDateTime exists (legacy CSV field name: RECORDED_DATE), then it is a string in the format "YYYY-MM-DDThh:mm:ss+zz:zz" or "YYYY-MM-DDThh:mm:ss-zz:zz" @@ -531,7 +530,7 @@ def pre_validate_recorded(self, values: dict) -> dict: except KeyError: pass - def pre_validate_primary_source(self, values: dict) -> dict: + def pre_validate_primary_source(self, values: dict) -> None: """ Pre-validate that, if primarySource (legacy CSV field name: PRIMARY_SOURCE) exists, then it is a boolean """ @@ -541,7 +540,7 @@ def pre_validate_primary_source(self, values: dict) -> dict: except KeyError: pass - def pre_validate_value_codeable_concept(self, values: dict) -> dict: + def pre_validate_value_codeable_concept(self, values: dict) -> None: """Pre-validate that valueCodeableConcept with coding exists within extension""" if "extension" not in values: raise MandatoryError("Validation errors: extension is a mandatory field") @@ -563,7 +562,7 @@ def pre_validate_value_codeable_concept(self, values: dict) -> dict: "')].valueCodeableConcept.coding is a mandatory field" ) - def pre_validate_extension_length(self, values: dict) -> dict: + def pre_validate_extension_length(self, values: dict) -> None: """Pre-validate that, if extension exists, then the length of the list should be 1""" try: field_value = values["extension"] @@ -573,7 +572,7 @@ def pre_validate_extension_length(self, values: dict) -> dict: except KeyError: pass - def pre_validate_extension_url(self, values: dict) -> dict: + def pre_validate_extension_url(self, values: dict) -> None: """Pre-validate that, if extension exists, then its url should be a valid one""" try: field_value = values["extension"][0]["url"] @@ -585,7 +584,7 @@ def pre_validate_extension_url(self, values: dict) -> dict: except KeyError: pass - def pre_validate_vaccination_procedure_code(self, values: dict) -> dict: + def pre_validate_vaccination_procedure_code(self, values: dict) -> None: """ Pre-validate that, if extension[?(@.url=='https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore- VaccinationProcedure')].valueCodeableConcept.coding[?(@.system=='http://snomed.info/sct')].code @@ -602,7 +601,7 @@ def pre_validate_vaccination_procedure_code(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_vaccination_procedure_display(self, values: dict) -> dict: + def pre_validate_vaccination_procedure_display(self, values: dict) -> None: """ Pre-validate that, if extension[?(@.url=='https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore- VaccinationProcedure')].valueCodeableConcept.coding[?(@.system=='http://snomed.info/sct')].display @@ -618,7 +617,7 @@ def pre_validate_vaccination_procedure_display(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_vaccination_situation_code(self, values: dict) -> dict: + def pre_validate_vaccination_situation_code(self, values: dict) -> None: """ Pre-validate that, if extension[?(@.url=='https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore- VaccinationSituation')].valueCodeableConcept.coding[?(@.system=='http://snomed.info/sct')].code @@ -634,7 +633,7 @@ def pre_validate_vaccination_situation_code(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_vaccination_situation_display(self, values: dict) -> dict: + def pre_validate_vaccination_situation_display(self, values: dict) -> None: """ Pre-validate that, if extension[?(@.url=='https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore- VaccinationSituation')].valueCodeableConcept.coding[?(@.system=='http://snomed.info/sct')].display @@ -650,7 +649,7 @@ def pre_validate_vaccination_situation_display(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_protocol_applied(self, values: dict) -> dict: + def pre_validate_protocol_applied(self, values: dict) -> None: """Pre-validate that, if protocolApplied exists, then it is a list of length 1""" try: field_value = values["protocolApplied"] @@ -660,7 +659,7 @@ def pre_validate_protocol_applied(self, values: dict) -> dict: DOSE_NUMBER_MAX_VALUE = 9 - def pre_validate_dose_number_positive_int(self, values: dict) -> dict: + def pre_validate_dose_number_positive_int(self, values: dict) -> None: """ Pre-validate that, if protocolApplied[0].doseNumberPositiveInt (legacy CSV field : dose_sequence) exists, then it is an integer from 1 to 9 (DOSE_NUMBER_MAX_VALUE) @@ -672,7 +671,7 @@ def pre_validate_dose_number_positive_int(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_dose_number_string(self, values: dict) -> dict: + def pre_validate_dose_number_string(self, values: dict) -> None: """ Pre-validate that, if protocolApplied[0].doseNumberString exists, then it is a non-empty string @@ -684,7 +683,7 @@ def pre_validate_dose_number_string(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_target_disease(self, values: dict) -> dict: + def pre_validate_target_disease(self, values: dict) -> None: """ Pre-validate that protocolApplied[0].targetDisease exists, and each of its elements contains a coding field """ @@ -696,7 +695,7 @@ def pre_validate_target_disease(self, values: dict) -> dict: except (KeyError, IndexError) as error: raise ValueError("protocolApplied[0].targetDisease is a mandatory field") from error - def pre_validate_target_disease_codings(self, values: dict) -> dict: + def pre_validate_target_disease_codings(self, values: dict) -> None: """ Pre-validate that, if they exist, each protocolApplied[0].targetDisease[{index}].valueCodeableConcept.coding has exactly one element where the system is the snomed url @@ -715,7 +714,7 @@ def pre_validate_target_disease_codings(self, values: dict) -> dict: except KeyError: pass - def pre_validate_disease_type_coding_codes(self, values: dict) -> dict: + def pre_validate_disease_type_coding_codes(self, values: dict) -> None: """ Pre-validate that, if protocolApplied[0].targetDisease[{i}].coding[?(@.system=='http://snomed.info/sct')].code exists, then it is a non-empty string @@ -733,7 +732,7 @@ def pre_validate_disease_type_coding_codes(self, values: dict) -> dict: except KeyError: pass - def pre_validate_manufacturer_display(self, values: dict) -> dict: + def pre_validate_manufacturer_display(self, values: dict) -> None: """ Pre-validate that, if manufacturer.display (legacy CSV field name: VACCINE_MANUFACTURER) exists, then it is a non-empty string @@ -744,7 +743,7 @@ def pre_validate_manufacturer_display(self, values: dict) -> dict: except KeyError: pass - def pre_validate_lot_number(self, values: dict) -> dict: + def pre_validate_lot_number(self, values: dict) -> None: """ Pre-validate that, if lotNumber (legacy CSV field name: BATCH_NUMBER) exists, then it is a non-empty string @@ -755,7 +754,7 @@ def pre_validate_lot_number(self, values: dict) -> dict: except KeyError: pass - def pre_validate_expiration_date(self, values: dict) -> dict: + def pre_validate_expiration_date(self, values: dict) -> None: """ Pre-validate that, if expirationDate (legacy CSV field name: EXPIRY_DATE) exists, then it is a string in the format YYYY-MM-DD, representing a valid date @@ -766,7 +765,7 @@ def pre_validate_expiration_date(self, values: dict) -> dict: except KeyError: pass - def pre_validate_site_coding(self, values: dict) -> dict: + def pre_validate_site_coding(self, values: dict) -> None: """Pre-validate that, if site.coding exists, then each code system is unique""" try: field_value = values["site"]["coding"] @@ -774,7 +773,7 @@ def pre_validate_site_coding(self, values: dict) -> dict: except KeyError: pass - def pre_validate_site_coding_code(self, values: dict) -> dict: + def pre_validate_site_coding_code(self, values: dict) -> None: """ Pre-validate that, if site.coding[?(@.system=='http://snomed.info/sct')].code (legacy CSV field name: SITE_OF_VACCINATION_CODE) exists, then it is a non-empty string @@ -787,7 +786,7 @@ def pre_validate_site_coding_code(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_site_coding_display(self, values: dict) -> dict: + def pre_validate_site_coding_display(self, values: dict) -> None: """ Pre-validate that, if site.coding[?(@.system=='http://snomed.info/sct')].display (legacy CSV field name: SITE_OF_VACCINATION_TERM) exists, then it is a non-empty string @@ -800,7 +799,7 @@ def pre_validate_site_coding_display(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_route_coding(self, values: dict) -> dict: + def pre_validate_route_coding(self, values: dict) -> None: """Pre-validate that, if route.coding exists, then each code system is unique""" try: field_value = values["route"]["coding"] @@ -808,7 +807,7 @@ def pre_validate_route_coding(self, values: dict) -> dict: except KeyError: pass - def pre_validate_route_coding_code(self, values: dict) -> dict: + def pre_validate_route_coding_code(self, values: dict) -> None: """ Pre-validate that, if route.coding[?(@.system=='http://snomed.info/sct')].code (legacy CSV field name: ROUTE_OF_VACCINATION_CODE) exists, then it is a non-empty string @@ -821,7 +820,7 @@ def pre_validate_route_coding_code(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_route_coding_display(self, values: dict) -> dict: + def pre_validate_route_coding_display(self, values: dict) -> None: """ Pre-validate that, if route.coding[?(@.system=='http://snomed.info/sct')].display (legacy CSV field name: ROUTE_OF_VACCINATION_TERM) exists, then it is a non-empty string @@ -834,7 +833,7 @@ def pre_validate_route_coding_display(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_dose_quantity_value(self, values: dict) -> dict: + def pre_validate_dose_quantity_value(self, values: dict) -> None: """ Pre-validate that, if doseQuantity.value (legacy CSV field name: DOSE_AMOUNT) exists, then it is a number representing an integer or decimal @@ -850,7 +849,7 @@ def pre_validate_dose_quantity_value(self, values: dict) -> dict: except KeyError: pass - def pre_validate_dose_quantity_system(self, values: dict) -> dict: + def pre_validate_dose_quantity_system(self, values: dict) -> None: """ Pre-validate that if doseQuantity.system exists then it is a non-empty string: If system exists, it must be a non-empty string. @@ -861,7 +860,7 @@ def pre_validate_dose_quantity_system(self, values: dict) -> dict: except KeyError: pass - def pre_validate_dose_quantity_code(self, values: dict) -> dict: + def pre_validate_dose_quantity_code(self, values: dict) -> None: """ Pre-validate that, if doseQuantity.code (legacy CSV field name: DOSE_UNIT_CODE) exists, then it is a non-empty string @@ -872,7 +871,7 @@ def pre_validate_dose_quantity_code(self, values: dict) -> dict: except KeyError: pass - def pre_validate_dose_quantity_system_and_code(self, values: dict) -> dict: + def pre_validate_dose_quantity_system_and_code(self, values: dict) -> None: """ Pre-validate doseQuantity.code and doseQuantity.system: 1. If code exists, system MUST also exist (FHIR SimpleQuantity rule). @@ -885,7 +884,7 @@ def pre_validate_dose_quantity_system_and_code(self, values: dict) -> dict: return values - def pre_validate_dose_quantity_unit(self, values: dict) -> dict: + def pre_validate_dose_quantity_unit(self, values: dict) -> None: """ Pre-validate that, if doseQuantity.unit (legacy CSV field name: DOSE_UNIT_TERM) exists, then it is a non-empty string @@ -896,7 +895,7 @@ def pre_validate_dose_quantity_unit(self, values: dict) -> dict: except KeyError: pass - def pre_validate_reason_code_codings(self, values: dict) -> dict: + def pre_validate_reason_code_codings(self, values: dict) -> None: """ Pre-validate that, if they exist, each reasonCode[{index}].coding is a list of length 1 """ @@ -910,7 +909,7 @@ def pre_validate_reason_code_codings(self, values: dict) -> dict: except KeyError: pass - def pre_validate_reason_code_coding_codes(self, values: dict) -> dict: + def pre_validate_reason_code_coding_codes(self, values: dict) -> None: """ Pre-validate that, if they exist, each reasonCode[{index}].coding[0].code (legacy CSV field name: INDICATION_CODE) is a non-empty string @@ -925,7 +924,7 @@ def pre_validate_reason_code_coding_codes(self, values: dict) -> dict: except KeyError: pass - def pre_validate_organization_identifier_system(self, values: dict) -> dict: + def pre_validate_organization_identifier_system(self, values: dict) -> None: """ Pre-validate that, if performer[?(@.actor.type=='Organization').identifier.system] (legacy CSV field name: SITE_CODE_TYPE_URI) exists, then it is a non-empty string @@ -939,7 +938,7 @@ def pre_validate_organization_identifier_system(self, values: dict) -> dict: except (KeyError, IndexError, AttributeError): pass - def pre_validate_location_identifier_value(self, values: dict) -> dict: + def pre_validate_location_identifier_value(self, values: dict) -> None: """ Pre-validate that, if location.identifier.value (legacy CSV field name: LOCATION_CODE) exists, then it is a non-empty string @@ -950,7 +949,7 @@ def pre_validate_location_identifier_value(self, values: dict) -> dict: except KeyError: pass - def pre_validate_location_identifier_system(self, values: dict) -> dict: + def pre_validate_location_identifier_system(self, values: dict) -> None: """ Pre-validate that, if location.identifier.system (legacy CSV field name: LOCATION_CODE_TYPE_URI) exists, then it is a non-empty string @@ -961,7 +960,7 @@ def pre_validate_location_identifier_system(self, values: dict) -> dict: except KeyError: pass - def pre_validate_vaccine_code(self, values: dict) -> dict: + def pre_validate_vaccine_code(self, values: dict) -> None: """ Pre-validate that, if vaccineCode.coding[?(@.system=='http://snomed.info/sct')].code (legacy CSV field : VACCINE_PRODUCT_CODE) exists, then it is a valid snomed code @@ -978,7 +977,7 @@ def pre_validate_vaccine_code(self, values: dict) -> dict: except (KeyError, IndexError): pass - def pre_validate_vaccine_display(self, values: dict) -> dict: + def pre_validate_vaccine_display(self, values: dict) -> None: """ Pre-validate that, if vaccineCode.coding[?(@.system=='http://snomed.info/sct')].display (legacy CSV field : VACCINE_PRODUCT_TERM) exists, then it is a non-empty string diff --git a/backend/src/models/field_locations.py b/lambdas/shared/src/common/models/field_locations.py similarity index 97% rename from backend/src/models/field_locations.py rename to lambdas/shared/src/common/models/field_locations.py index 031fabd37..71d42a6ab 100644 --- a/backend/src/models/field_locations.py +++ b/lambdas/shared/src/common/models/field_locations.py @@ -5,8 +5,8 @@ from dataclasses import dataclass, field -from constants import Urls -from models.utils.generic_utils import ( +from common.models.constants import Urls +from common.models.utils.generic_utils import ( generate_field_location_for_extension, patient_name_family_field_location, patient_name_given_field_location, diff --git a/backend/src/models/field_names.py b/lambdas/shared/src/common/models/field_names.py similarity index 100% rename from backend/src/models/field_names.py rename to lambdas/shared/src/common/models/field_names.py diff --git a/backend/src/models/immunization_record_metadata.py b/lambdas/shared/src/common/models/immunization_record_metadata.py similarity index 75% rename from backend/src/models/immunization_record_metadata.py rename to lambdas/shared/src/common/models/immunization_record_metadata.py index 31a6b0721..1753e9065 100644 --- a/backend/src/models/immunization_record_metadata.py +++ b/lambdas/shared/src/common/models/immunization_record_metadata.py @@ -2,11 +2,14 @@ from dataclasses import dataclass +from fhir.resources.R4B.identifier import Identifier + @dataclass class ImmunizationRecordMetadata: """Simple data class for the Immunization Record Metadata""" + identifier: Identifier resource_version: int is_deleted: bool is_reinstated: bool diff --git a/backend/src/models/mandation_functions.py b/lambdas/shared/src/common/models/mandation_functions.py similarity index 96% rename from backend/src/models/mandation_functions.py rename to lambdas/shared/src/common/models/mandation_functions.py index 99d2ef526..7f594097a 100644 --- a/backend/src/models/mandation_functions.py +++ b/lambdas/shared/src/common/models/mandation_functions.py @@ -2,7 +2,7 @@ from dataclasses import dataclass -from models.errors import MandatoryError +from common.models.errors import MandatoryError @dataclass diff --git a/backend/src/models/obtain_field_value.py b/lambdas/shared/src/common/models/obtain_field_value.py similarity index 98% rename from backend/src/models/obtain_field_value.py rename to lambdas/shared/src/common/models/obtain_field_value.py index 80e33c7d3..3d3ff7e43 100644 --- a/backend/src/models/obtain_field_value.py +++ b/lambdas/shared/src/common/models/obtain_field_value.py @@ -1,7 +1,7 @@ """Functions for obtaining a field value from the FHIR immunization resource json data""" -from constants import Urls -from models.utils.generic_utils import ( +from common.models.constants import Urls +from common.models.utils.generic_utils import ( get_contained_patient, get_contained_practitioner, get_generic_extension_value, @@ -13,9 +13,6 @@ class ObtainFieldValue: """Functions for obtaining a field value from the FHIR immunization resource json data""" - def __init__(self) -> None: - pass - @staticmethod def target_disease(imms: dict): return imms["protocolApplied"][0]["targetDisease"] diff --git a/lambdas/shared/src/common/models/utils/__init__.py b/lambdas/shared/src/common/models/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/src/models/utils/base_utils.py b/lambdas/shared/src/common/models/utils/base_utils.py similarity index 90% rename from backend/src/models/utils/base_utils.py rename to lambdas/shared/src/common/models/utils/base_utils.py index dc523c155..b39219f29 100644 --- a/backend/src/models/utils/base_utils.py +++ b/lambdas/shared/src/common/models/utils/base_utils.py @@ -1,7 +1,7 @@ """Utils for backend src code""" -from models.field_locations import FieldLocations -from models.obtain_field_value import ObtainFieldValue +from common.models.field_locations import FieldLocations +from common.models.obtain_field_value import ObtainFieldValue FIELD_LOCATIONS = FieldLocations() diff --git a/backend/src/models/utils/generic_utils.py b/lambdas/shared/src/common/models/utils/generic_utils.py similarity index 95% rename from backend/src/models/utils/generic_utils.py rename to lambdas/shared/src/common/models/utils/generic_utils.py index 8871e529a..8bd6fd951 100644 --- a/backend/src/models/utils/generic_utils.py +++ b/lambdas/shared/src/common/models/utils/generic_utils.py @@ -4,7 +4,7 @@ import datetime import json import urllib.parse -from typing import Any, Dict, Literal, Optional, Union +from typing import Any, Dict, Literal, Optional from fhir.resources.R4B.bundle import ( Bundle as FhirBundle, @@ -17,7 +17,15 @@ from fhir.resources.R4B.immunization import Immunization from stdnum.verhoeff import validate -from models.constants import Constants +from common.models.constants import Constants + + +def get_nhs_number(imms: dict): + try: + nhs_number = [x for x in imms["contained"] if x["resourceType"] == "Patient"][0]["identifier"][0]["value"] + except (KeyError, IndexError): + nhs_number = "TBC" + return nhs_number def get_contained_resource(imms: dict, resource: Literal["Patient", "Practitioner", "QuestionnaireResponse"]): @@ -37,7 +45,7 @@ def get_contained_practitioner(imms: dict): def get_generic_extension_value( json_data: dict, url: str, system: str, field_type: Literal["code", "display"] -) -> Union[str, None]: +) -> str | None: """Get the value of an extension field, given its url, field_type, and system""" value_codeable_concept = [x for x in json_data["extension"] if x.get("url") == url][0]["valueCodeableConcept"] value_codeable_concept_coding = value_codeable_concept["coding"] @@ -67,7 +75,7 @@ def is_actor_referencing_contained_resource(element, contained_resource_id): return False -def check_for_unknown_elements(resource, resource_type) -> Union[None, list]: +def check_for_unknown_elements(resource, resource_type) -> list | None: """ Checks each key in the resource to see if it is allowed. If any disallowed keys are found, returns a list containing an error message for each disallowed element @@ -256,7 +264,7 @@ def obtain_current_name_period(period: dict, occurrence_date: datetime) -> bool: return True -def get_current_name_instance(names: list, occurrence_date: datetime) -> dict: +def get_current_name_instance(names: list, occurrence_date: datetime) -> dict | tuple: """Selects the correct "current" name instance based on the 'period' and 'use' criteria.""" # DUE TO RUNNING PRE_VALIDATE_PATIENT_NAME AND PRE_VALIDATE_PRACTITIONER NAME BEFORE THE RESPECTIVE CHECKS @@ -278,9 +286,10 @@ def get_current_name_instance(names: list, occurrence_date: datetime) -> dict: for index, name in valid_name_instances: try: # Check for 'period' and occurrence date - if isinstance(name, dict): - if "period" not in name or obtain_current_name_period(name.get("period", {}), occurrence_date): - current_names.append((index, name)) + if isinstance(name, dict) and ( + "period" not in name or obtain_current_name_period(name.get("period", {}), occurrence_date) + ): + current_names.append((index, name)) except (KeyError, ValueError): continue diff --git a/backend/src/models/utils/pre_validator_utils.py b/lambdas/shared/src/common/models/utils/pre_validator_utils.py similarity index 89% rename from backend/src/models/utils/pre_validator_utils.py rename to lambdas/shared/src/common/models/utils/pre_validator_utils.py index d68d45149..bf626f601 100644 --- a/backend/src/models/utils/pre_validator_utils.py +++ b/lambdas/shared/src/common/models/utils/pre_validator_utils.py @@ -1,6 +1,6 @@ from datetime import date, datetime from decimal import Decimal -from typing import Optional, Union +from typing import Optional from .generic_utils import is_valid_simple_snomed, nhs_number_mod11_check @@ -33,17 +33,14 @@ def for_string( if len(field_value) == 0: raise ValueError(f"{field_location} must be a non-empty string") - if max_length: - if len(field_value) > max_length: - raise ValueError(f"{field_location} must be {max_length} or fewer characters") + if max_length and len(field_value) > max_length: + raise ValueError(f"{field_location} must be {max_length} or fewer characters") - if predefined_values: - if field_value not in predefined_values: - raise ValueError(f"{field_location} must be one of the following: " + str(", ".join(predefined_values))) + if predefined_values and field_value not in predefined_values: + raise ValueError(f"{field_location} must be one of the following: " + str(", ".join(predefined_values))) - if not spaces_allowed: - if " " in field_value: - raise ValueError(f"{field_location} must not contain spaces") + if not spaces_allowed and " " in field_value: + raise ValueError(f"{field_location} must not contain spaces") @staticmethod def for_list( @@ -72,6 +69,18 @@ def for_list( if max_length is not None and len(field_value) > max_length: raise ValueError(f"{field_location} must be an array of maximum length {max_length}") + PreValidation.for_list_element_type( + field_value, field_location, elements_are_strings, string_element_max_length, elements_are_dicts + ) + + @staticmethod + def for_list_element_type( + field_value: str, + field_location: str, + elements_are_strings: bool = False, + string_element_max_length: Optional[int] = None, + elements_are_dicts: bool = False, + ): if elements_are_strings: for idx, element in enumerate(field_value): PreValidation.for_string(element, f"{field_location}[{idx}]", max_length=string_element_max_length) @@ -192,12 +201,11 @@ def for_positive_integer(field_value: int, field_location: str, max_value: int = if field_value <= 0: raise ValueError(f"{field_location} must be a positive integer") - if max_value: - if field_value > max_value: - raise ValueError(f"{field_location} must be an integer in the range 1 to {max_value}") + if max_value and field_value > max_value: + raise ValueError(f"{field_location} must be an integer in the range 1 to {max_value}") @staticmethod - def for_integer_or_decimal(field_value: Union[int, Decimal], field_location: str): + def for_integer_or_decimal(field_value: int | Decimal, field_location: str): """ Apply pre-validation to a decimal field to ensure that it is an integer or decimal, which does not exceed the maximum allowed number of decimal places (if applicable) diff --git a/backend/src/models/utils/validation_utils.py b/lambdas/shared/src/common/models/utils/validation_utils.py similarity index 73% rename from backend/src/models/utils/validation_utils.py rename to lambdas/shared/src/common/models/utils/validation_utils.py index 5b7d0a0e2..3ae5fd32f 100644 --- a/backend/src/models/utils/validation_utils.py +++ b/lambdas/shared/src/common/models/utils/validation_utils.py @@ -1,12 +1,13 @@ """Utils for backend folder""" -from clients import redis_client -from constants import Urls -from models.constants import Constants -from models.errors import InconsistentIdentifierError, InconsistentResourceVersion, MandatoryError -from models.field_names import FieldNames -from models.obtain_field_value import ObtainFieldValue -from models.utils.base_utils import obtain_field_location +from fhir.resources.R4B.identifier import Identifier + +from common.models.constants import Constants, Urls +from common.models.errors import InconsistentIdentifierError, InconsistentResourceVersionError, MandatoryError +from common.models.field_names import FieldNames +from common.models.obtain_field_value import ObtainFieldValue +from common.models.utils.base_utils import obtain_field_location +from common.redis_client import get_redis_client def get_target_disease_codes(immunization: dict): @@ -50,13 +51,12 @@ def convert_disease_codes_to_vaccine_type( otherwise raises a value error """ key = ":".join(sorted(disease_codes_input)) - vaccine_type = redis_client.hget(Constants.DISEASES_TO_VACCINE_TYPE_HASH_KEY, key) + vaccine_type = get_redis_client().hget(Constants.DISEASES_TO_VACCINE_TYPE_HASH_KEY, key) if not vaccine_type: raise ValueError( - "Validation errors: protocolApplied[0].targetDisease[*].coding[?(@.system=='" - "http://snomed.info/sct" - f"')].code - {disease_codes_input} is not a valid combination of disease codes for this service" + f"Validation errors: protocolApplied[0].targetDisease[*].coding[?(@.system=='{Urls.snomed}')].code" + f" - {disease_codes_input} is not a valid combination of disease codes for this service" ) return vaccine_type @@ -80,26 +80,21 @@ def get_vaccine_type(immunization: dict): return convert_disease_codes_to_vaccine_type(target_diseases) -def validate_identifiers_match(new_immunization: dict, existing_immunization: dict) -> None: - """Checks if the local identifier system and values match for an existing and new immunization record. Raises an +def validate_identifiers_match(new_identifier: Identifier, existing_identifier: Identifier) -> None: + """Checks if the identifier system and values match for a new and existing Identifier. Raises an InconsistentIdentifierError if there is a mismatch. Use this function in the Update Imms journey""" - new_system = new_immunization["identifier"][0]["system"] - new_value = new_immunization["identifier"][0]["value"] - existing_system = existing_immunization["identifier"][0]["system"] - existing_value = existing_immunization["identifier"][0]["value"] - - if new_system != existing_system and new_value != existing_value: + if new_identifier.system != existing_identifier.system and new_identifier.value != existing_identifier.value: raise InconsistentIdentifierError( "Validation errors: identifier[0].system and identifier[0].value doesn't match with the stored content" ) - if new_system != existing_system: + if new_identifier.system != existing_identifier.system: raise InconsistentIdentifierError( "Validation errors: identifier[0].system doesn't match with the stored content" ) - if new_value != existing_value: + if new_identifier.value != existing_identifier.value: raise InconsistentIdentifierError("Validation errors: identifier[0].value doesn't match with the stored content") return None @@ -111,12 +106,12 @@ def validate_resource_versions_match( """Checks if the resource version in the request and the resource version of the actual Immunization record matches. Raises a InconsistentResourceVersion if they do not match.""" if actual_resource_version > resource_version_in_request: - raise InconsistentResourceVersion( + raise InconsistentResourceVersionError( f"Validation errors: The requested immunization resource {imms_id} has changed since the last retrieve." ) if actual_resource_version < resource_version_in_request: - raise InconsistentResourceVersion( + raise InconsistentResourceVersionError( f"Validation errors: The requested immunization resource {imms_id} version is inconsistent with the " f"existing version." ) diff --git a/backend/src/models/validation_sets.py b/lambdas/shared/src/common/models/validation_sets.py similarity index 96% rename from backend/src/models/validation_sets.py rename to lambdas/shared/src/common/models/validation_sets.py index 592d8e14b..f515e678a 100644 --- a/backend/src/models/validation_sets.py +++ b/lambdas/shared/src/common/models/validation_sets.py @@ -1,6 +1,6 @@ """Validation sets for each vaccine type""" -from models.mandation_functions import MandationRules +from common.models.mandation_functions import MandationRules class ValidationSets: @@ -17,9 +17,6 @@ class ValidationSets: The validator will then automatically pick up the correct validation set. """ - def __init__(self) -> None: - pass - vaccine_type_agnostic = { "patient_identifier_value": MandationRules.required, "patient_name_given": MandationRules.mandatory, diff --git a/lambdas/shared/src/common/s3_reader.py b/lambdas/shared/src/common/s3_reader.py index 6b0505b91..5fac2ce72 100644 --- a/lambdas/shared/src/common/s3_reader.py +++ b/lambdas/shared/src/common/s3_reader.py @@ -1,4 +1,4 @@ -from common.clients import logger, s3_client +from common.clients import get_s3_client, logger class S3Reader: @@ -12,7 +12,7 @@ class S3Reader: @staticmethod def read(bucket_name, file_key): try: - s3_file = s3_client.get_object(Bucket=bucket_name, Key=file_key) + s3_file = get_s3_client().get_object(Bucket=bucket_name, Key=file_key) return s3_file["Body"].read().decode("utf-8") except Exception as error: # pylint: disable=broad-except diff --git a/lambdas/shared/tests/test_common/models/__init__.py b/lambdas/shared/tests/test_common/models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lambdas/shared/tests/test_common/models/utils/__init__.py b/lambdas/shared/tests/test_common/models/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/tests/models/utils/test_generic_utils.py b/lambdas/shared/tests/test_common/models/utils/test_generic_utils.py similarity index 96% rename from backend/tests/models/utils/test_generic_utils.py rename to lambdas/shared/tests/test_common/models/utils/test_generic_utils.py index bfde88936..fcd61c8d9 100644 --- a/backend/tests/models/utils/test_generic_utils.py +++ b/lambdas/shared/tests/test_common/models/utils/test_generic_utils.py @@ -3,9 +3,8 @@ import unittest from datetime import date, datetime -from src.models.utils.generic_utils import form_json - -from testing_utils.generic_utils import format_date_types, load_json_data +from common.models.utils.generic_utils import form_json +from test_common.testing_utils.generic_utils import format_date_types, load_json_data class TestFormJson(unittest.TestCase): diff --git a/backend/tests/models/utils/test_validation_utils.py b/lambdas/shared/tests/test_common/models/utils/test_validation_utils.py similarity index 93% rename from backend/tests/models/utils/test_validation_utils.py rename to lambdas/shared/tests/test_common/models/utils/test_validation_utils.py index b6ede9212..6abbfed78 100644 --- a/backend/tests/models/utils/test_validation_utils.py +++ b/lambdas/shared/tests/test_common/models/utils/test_validation_utils.py @@ -1,28 +1,29 @@ import unittest from copy import deepcopy +from fhir.resources.R4B.identifier import Identifier from jsonpath_ng.ext import parse -from models.errors import InconsistentIdentifierError, InconsistentResourceVersion -from models.fhir_immunization import ImmunizationValidator -from models.obtain_field_value import ObtainFieldValue -from models.utils.generic_utils import ( +from common.models.errors import InconsistentIdentifierError, InconsistentResourceVersionError +from common.models.fhir_immunization import ImmunizationValidator +from common.models.obtain_field_value import ObtainFieldValue +from common.models.utils.generic_utils import ( get_current_name_instance, obtain_current_name_period, obtain_name_field_location, patient_and_practitioner_value_and_index, ) -from models.utils.validation_utils import validate_identifiers_match, validate_resource_versions_match -from testing_utils.generic_utils import ( +from common.models.utils.validation_utils import validate_identifiers_match, validate_resource_versions_match +from test_common.testing_utils.generic_utils import ( load_json_data, ) -from testing_utils.values_for_tests import InvalidValues, NameInstances, ValidValues +from test_common.testing_utils.values_for_tests import InvalidValues, NameInstances, ValidValues class TestValidatorUtils(unittest.TestCase): """Test immunization validation utils on the FHIR model""" - MOCK_LOCAL_IDENTIFIER = {"identifier": [{"system": "https://mock-identifier.co.uk/vaccs/", "value": "123"}]} + MOCK_LOCAL_IDENTIFIER = Identifier(system="https://mock-identifier.co.uk/vaccs/", value="123") def setUp(self): """Set up for each test. This runs before every test""" @@ -298,7 +299,7 @@ def test_validate_resource_versions_match_raises_error_when_versions_do_not_matc for actual_version, expected_error in test_cases: with self.subTest(actual_version=actual_version, expected_error=expected_error): - with self.assertRaises(InconsistentResourceVersion) as error: + with self.assertRaises(InconsistentResourceVersionError) as error: validate_resource_versions_match(3, actual_version, "12345-id") self.assertEqual(str(error.exception), expected_error) @@ -312,15 +313,15 @@ def test_validate_identifiers_match_raises_error_when_versions_do_not_match(self match""" test_cases = [ ( - {"identifier": [{"system": "https://mock-identifier.co.uk/vaccs/", "value": "different_val"}]}, + Identifier(system="https://mock-identifier.co.uk/vaccs/", value="different_val"), "Validation errors: identifier[0].value doesn't match with the stored content", ), ( - {"identifier": [{"system": "https://different-identifier.co.uk/vaccs/", "value": "123"}]}, + Identifier(system="https://different-identifier.co.uk/vaccs/", value="123"), "Validation errors: identifier[0].system doesn't match with the stored content", ), ( - {"identifier": [{"system": "https://different-identifier.co.uk/vaccs/", "value": "different_val"}]}, + Identifier(system="https://different-identifier.co.uk/vaccs/", value="different_val"), "Validation errors: identifier[0].system and identifier[0].value doesn't match with the stored content", ), ] diff --git a/backend/tests/sample_data/bundle_patient_resource.json b/lambdas/shared/tests/test_common/sample_data/bundle_patient_resource.json similarity index 100% rename from backend/tests/sample_data/bundle_patient_resource.json rename to lambdas/shared/tests/test_common/sample_data/bundle_patient_resource.json diff --git a/backend/tests/sample_data/completed_covid_immunization_event.json b/lambdas/shared/tests/test_common/sample_data/completed_covid_immunization_event.json similarity index 100% rename from backend/tests/sample_data/completed_covid_immunization_event.json rename to lambdas/shared/tests/test_common/sample_data/completed_covid_immunization_event.json diff --git a/backend/tests/sample_data/completed_covid_immunization_event_filtered_for_search_using_bundle_patient_resource.json b/lambdas/shared/tests/test_common/sample_data/completed_covid_immunization_event_filtered_for_search_using_bundle_patient_resource.json similarity index 100% rename from backend/tests/sample_data/completed_covid_immunization_event_filtered_for_search_using_bundle_patient_resource.json rename to lambdas/shared/tests/test_common/sample_data/completed_covid_immunization_event_filtered_for_search_using_bundle_patient_resource.json diff --git a/backend/tests/sample_data/completed_covid_immunization_event_for_read.json b/lambdas/shared/tests/test_common/sample_data/completed_covid_immunization_event_for_read.json similarity index 100% rename from backend/tests/sample_data/completed_covid_immunization_event_for_read.json rename to lambdas/shared/tests/test_common/sample_data/completed_covid_immunization_event_for_read.json diff --git a/backend/tests/sample_data/completed_flu_immunization_event.json b/lambdas/shared/tests/test_common/sample_data/completed_flu_immunization_event.json similarity index 100% rename from backend/tests/sample_data/completed_flu_immunization_event.json rename to lambdas/shared/tests/test_common/sample_data/completed_flu_immunization_event.json diff --git a/backend/tests/sample_data/completed_hpv_immunization_event.json b/lambdas/shared/tests/test_common/sample_data/completed_hpv_immunization_event.json similarity index 100% rename from backend/tests/sample_data/completed_hpv_immunization_event.json rename to lambdas/shared/tests/test_common/sample_data/completed_hpv_immunization_event.json diff --git a/backend/tests/sample_data/completed_mmr_immunization_event.json b/lambdas/shared/tests/test_common/sample_data/completed_mmr_immunization_event.json similarity index 100% rename from backend/tests/sample_data/completed_mmr_immunization_event.json rename to lambdas/shared/tests/test_common/sample_data/completed_mmr_immunization_event.json diff --git a/backend/tests/sample_data/completed_rsv_immunization_event.json b/lambdas/shared/tests/test_common/sample_data/completed_rsv_immunization_event.json similarity index 100% rename from backend/tests/sample_data/completed_rsv_immunization_event.json rename to lambdas/shared/tests/test_common/sample_data/completed_rsv_immunization_event.json diff --git a/backend/tests/sample_data/reduce_validation_hpv_immunization_event.json b/lambdas/shared/tests/test_common/sample_data/reduce_validation_hpv_immunization_event.json similarity index 100% rename from backend/tests/sample_data/reduce_validation_hpv_immunization_event.json rename to lambdas/shared/tests/test_common/sample_data/reduce_validation_hpv_immunization_event.json diff --git a/backend/tests/sample_data/sample_immunization_response _for _not_done_event.json b/lambdas/shared/tests/test_common/sample_data/sample_immunization_response _for _not_done_event.json similarity index 100% rename from backend/tests/sample_data/sample_immunization_response _for _not_done_event.json rename to lambdas/shared/tests/test_common/sample_data/sample_immunization_response _for _not_done_event.json diff --git a/lambdas/shared/tests/test_common/test_clients.py b/lambdas/shared/tests/test_common/test_clients.py index 2396a6e68..4497d8dae 100644 --- a/lambdas/shared/tests/test_common/test_clients.py +++ b/lambdas/shared/tests/test_common/test_clients.py @@ -1,11 +1,12 @@ -import importlib -import logging import unittest from unittest.mock import MagicMock, patch -import common.clients as clients +from moto import mock_aws +import common.clients + +@mock_aws class TestClients(unittest.TestCase): BUCKET_NAME = "default-bucket" AWS_REGION = "eu-west-2" @@ -14,17 +15,14 @@ def setUp(self): # Patch boto3.client self.boto3_client_patch = patch("boto3.client", autospec=True) self.mock_boto3_client = self.boto3_client_patch.start() - self.addCleanup(self.boto3_client_patch.stop) # Patch logging.getLogger self.logging_patch = patch("logging.getLogger", autospec=True) self.mock_getLogger = self.logging_patch.start() - self.addCleanup(self.logging_patch.stop) # Patch os.getenv self.getenv_patch = patch("os.getenv", autospec=True) self.mock_getenv = self.getenv_patch.start() - self.addCleanup(self.getenv_patch.stop) # Set environment variable mock return values self.mock_getenv.side_effect = lambda key, default=None: { @@ -36,55 +34,31 @@ def setUp(self): self.mock_logger_instance = MagicMock() self.mock_getLogger.return_value = self.mock_logger_instance - # Reload the module under test to apply patches - importlib.reload(clients) - - def test_env_variables_loaded(self): - """Test that environment variables are loaded correctly""" - self.assertEqual(clients.CONFIG_BUCKET_NAME, self.BUCKET_NAME) - self.assertEqual(clients.REGION_NAME, self.AWS_REGION) - - def test_boto3_client_created_for_s3(self): - """Test that S3 boto3 client is created with correct region""" - self.mock_boto3_client.assert_any_call("s3", region_name=self.AWS_REGION) - - def test_boto3_client_created_for_firehose(self): - """Test that Firehose boto3 client is created with correct region""" - self.mock_boto3_client.assert_any_call("firehose", region_name=self.AWS_REGION) - - def test_logger_is_initialized(self): - """Test that a logger instance is initialized""" - self.mock_getLogger.assert_called_once_with() - self.assertTrue(hasattr(clients, "logger")) - - def test_logger_set_level(self): - """Test that logger level is set to INFO""" - self.mock_logger_instance.setLevel.assert_called_once_with(logging.INFO) + def tearDown(self): + self.getenv_patch.stop() + self.logging_patch.stop() + self.boto3_client_patch.stop() def test_global_s3_client(self): """Test global_s3_client is not initialized on import""" - importlib.reload(clients) - self.assertEqual(clients.global_s3_client, None) + self.assertEqual(common.clients.global_s3_client, None) def test_global_s3_client_initialization(self): """Test global_s3_client is initialized exactly once even with multiple invocations""" - importlib.reload(clients) - clients.get_s3_client() - self.assertNotEqual(clients.global_s3_client, None) + common.clients.get_s3_client() + self.assertNotEqual(common.clients.global_s3_client, None) call_count = self.mock_boto3_client.call_count - clients.get_s3_client() + common.clients.get_s3_client() self.assertEqual(self.mock_boto3_client.call_count, call_count) def test_global_sqs_client(self): """Test global_sqs_client is not initialized on import""" - importlib.reload(clients) - self.assertEqual(clients.global_sqs_client, None) + self.assertEqual(common.clients.global_sqs_client, None) def test_global_sqs_client_initialization(self): """Test global_sqs_client is initialized exactly once even with multiple invocations""" - importlib.reload(clients) - clients.get_sqs_client() - self.assertNotEqual(clients.global_sqs_client, None) + common.clients.get_sqs_client() + self.assertNotEqual(common.clients.global_sqs_client, None) call_count = self.mock_boto3_client.call_count - clients.get_sqs_client() + common.clients.get_sqs_client() self.assertEqual(self.mock_boto3_client.call_count, call_count) diff --git a/lambdas/shared/tests/test_common/test_errors.py b/lambdas/shared/tests/test_common/test_errors.py index 2c1bda8e3..b2f51c84b 100644 --- a/lambdas/shared/tests/test_common/test_errors.py +++ b/lambdas/shared/tests/test_common/test_errors.py @@ -23,88 +23,20 @@ def assert_resource_type_and_id(self, context, resource_type, resource_id): def assert_operation_outcome(self, outcome): self.assertEqual(outcome.get("resourceType"), "OperationOutcome") - def test_errors_unauthorized_error(self): - """Test correct operation of UnauthorizedError""" - test_response = "test_response" - test_message = "test_message" - - with self.assertRaises(errors.UnauthorizedError) as context: - raise errors.UnauthorizedError(test_response, test_message) - self.assert_response_message(context, test_response, test_message) - self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") - outcome = context.exception.to_operation_outcome() - self.assert_operation_outcome(outcome) - issue = outcome.get("issue")[0] - self.assertEqual(issue.get("severity"), errors.Severity.error) - self.assertEqual(issue.get("code"), errors.Code.forbidden) - self.assertEqual(issue.get("diagnostics"), "Unauthorized request") - - def test_errors_unauthorized_vax_error(self): - """Test correct operation of UnauthorizedVaxError""" - test_response = "test_response" - test_message = "test_message" - - with self.assertRaises(errors.UnauthorizedVaxError) as context: - raise errors.UnauthorizedVaxError(test_response, test_message) - self.assert_response_message(context, test_response, test_message) - self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") - outcome = context.exception.to_operation_outcome() - self.assert_operation_outcome(outcome) - issue = outcome.get("issue")[0] - self.assertEqual(issue.get("severity"), errors.Severity.error) - self.assertEqual(issue.get("code"), errors.Code.forbidden) - self.assertEqual(issue.get("diagnostics"), "Unauthorized request for vaccine type") - - def test_errors_unauthorized_vax_on_record_error(self): - """Test correct operation of UnauthorizedVaxOnRecordError""" - test_response = "test_response" - test_message = "test_message" - - with self.assertRaises(errors.UnauthorizedVaxOnRecordError) as context: - raise errors.UnauthorizedVaxOnRecordError(test_response, test_message) - self.assert_response_message(context, test_response, test_message) - self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") - outcome = context.exception.to_operation_outcome() - self.assert_operation_outcome(outcome) - issue = outcome.get("issue")[0] - self.assertEqual(issue.get("severity"), errors.Severity.error) - self.assertEqual(issue.get("code"), errors.Code.forbidden) - self.assertEqual( - issue.get("diagnostics"), - "Unauthorized request for vaccine type present in the stored immunization resource", - ) - - def test_errors_token_validation_error(self): - """Test correct operation of TokenValidationError""" - test_response = "test_response" + def test_errors_mandatory_error(self): + """Test correct operation of MandatoryError""" test_message = "test_message" - with self.assertRaises(errors.TokenValidationError) as context: - raise errors.TokenValidationError(test_response, test_message) - self.assert_response_message(context, test_response, test_message) - self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") - outcome = context.exception.to_operation_outcome() - self.assert_operation_outcome(outcome) - issue = outcome.get("issue")[0] - self.assertEqual(issue.get("severity"), errors.Severity.error) - self.assertEqual(issue.get("code"), errors.Code.invalid) - self.assertEqual(issue.get("diagnostics"), "Missing/Invalid Token") + with self.assertRaises(errors.MandatoryError) as context: + raise errors.MandatoryError(test_message) + self.assertEqual(str(context.exception.message), test_message) - def test_errors_conflict_error(self): - """Test correct operation of ConflictError""" - test_response = "test_response" - test_message = "test_message" + def test_errors_mandatory_error_no_message(self): + """Test correct operation of MandatoryError with no message""" - with self.assertRaises(errors.ConflictError) as context: - raise errors.ConflictError(test_response, test_message) - self.assert_response_message(context, test_response, test_message) - self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") - outcome = context.exception.to_operation_outcome() - self.assert_operation_outcome(outcome) - issue = outcome.get("issue")[0] - self.assertEqual(issue.get("severity"), errors.Severity.error) - self.assertEqual(issue.get("code"), errors.Code.duplicate) - self.assertEqual(issue.get("diagnostics"), "Conflict") + with self.assertRaises(errors.MandatoryError) as context: + raise errors.MandatoryError() + self.assertIsNone(context.exception.message) def test_errors_resource_not_found_error(self): """Test correct operation of ResourceNotFoundError""" @@ -150,101 +82,58 @@ def test_errors_resource_found_error(self): f"{test_resource_type} resource does exist. ID: {test_resource_id}", ) - def test_errors_unhandled_response_error(self): - """Test correct operation of UnhandledResponseError""" - test_response = "test_response" - test_message = "test_message" + def test_errors_inconsistent_identifier_error(self): + """Test correct operation of InconsistentIdentifierError""" + test_imms_id = "test_imms_id" + + with self.assertRaises(errors.InconsistentIdentifierError) as context: + raise errors.InconsistentIdentifierError(test_imms_id) + self.assertEqual(context.exception.msg, test_imms_id) - with self.assertRaises(errors.UnhandledResponseError) as context: - raise errors.UnhandledResponseError(test_response, test_message) - self.assert_response_message(context, test_response, test_message) - self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") outcome = context.exception.to_operation_outcome() self.assert_operation_outcome(outcome) issue = outcome.get("issue")[0] self.assertEqual(issue.get("severity"), errors.Severity.error) - self.assertEqual(issue.get("code"), errors.Code.exception) - self.assertEqual(issue.get("diagnostics"), f"{test_message}\n{test_response}") + self.assertEqual(issue.get("code"), errors.Code.invariant) + self.assertEqual(issue.get("diagnostics"), test_imms_id) - def test_errors_bad_request_error(self): - """Test correct operation of BadRequestError""" - test_response = "test_response" + def test_errors_inconsistent_resource_version(self): + """Test correct operation of InconsistentResourceVersion""" test_message = "test_message" - with self.assertRaises(errors.BadRequestError) as context: - raise errors.BadRequestError(test_response, test_message) - self.assert_response_message(context, test_response, test_message) - self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") + with self.assertRaises(errors.InconsistentResourceVersionError) as context: + raise errors.InconsistentResourceVersionError(test_message) + self.assertEqual(context.exception.message, test_message) + outcome = context.exception.to_operation_outcome() self.assert_operation_outcome(outcome) issue = outcome.get("issue")[0] self.assertEqual(issue.get("severity"), errors.Severity.error) - self.assertEqual(issue.get("code"), errors.Code.incomplete) - self.assertEqual(issue.get("diagnostics"), f"{test_message}\n{test_response}") + self.assertEqual(issue.get("code"), errors.Code.invariant) + self.assertEqual(issue.get("diagnostics"), test_message) - def test_errors_mandatory_error(self): - """Test correct operation of MandatoryError""" + def test_errors_unhandled_response_error(self): + """Test correct operation of UnhandledResponseError""" + test_response = "test_response" test_message = "test_message" - with self.assertRaises(errors.MandatoryError) as context: - raise errors.MandatoryError(test_message) - self.assertEqual(str(context.exception.message), test_message) - - def test_errors_mandatory_error_no_message(self): - """Test correct operation of MandatoryError with no message""" - - with self.assertRaises(errors.MandatoryError) as context: - raise errors.MandatoryError() - self.assertIsNone(context.exception.message) - - def test_errors_validation_error(self): - """Test correct operation of ValidationError""" - with self.assertRaises(errors.ValidationError) as context: - raise errors.ValidationError() - outcome = context.exception.to_operation_outcome() - self.assertIsNone(outcome) - - def test_errors_invalid_patient_id(self): - """Test correct operation of InvalidPatientId""" - test_patient_identifier = "test_patient_identifier" - - with self.assertRaises(errors.InvalidPatientId) as context: - raise errors.InvalidPatientId(test_patient_identifier) - self.assertEqual(context.exception.patient_identifier, test_patient_identifier) - self.assertEqual( - str(context.exception), - f"NHS Number: {test_patient_identifier} is invalid or it doesn't exist.", - ) + with self.assertRaises(errors.UnhandledResponseError) as context: + raise errors.UnhandledResponseError(test_response, test_message) + self.assert_response_message(context, test_response, test_message) + self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") outcome = context.exception.to_operation_outcome() self.assert_operation_outcome(outcome) issue = outcome.get("issue")[0] self.assertEqual(issue.get("severity"), errors.Severity.error) self.assertEqual(issue.get("code"), errors.Code.exception) - self.assertEqual( - issue.get("diagnostics"), - f"NHS Number: {test_patient_identifier} is invalid or it doesn't exist.", - ) - - def test_errors_inconsistent_id_error(self): - """Test correct operation of InconsistentIdError""" - test_imms_id = "test_imms_id" + self.assertEqual(issue.get("diagnostics"), f"{test_message}\n{test_response}") - with self.assertRaises(errors.InconsistentIdError) as context: - raise errors.InconsistentIdError(test_imms_id) - self.assertEqual(context.exception.imms_id, test_imms_id) - self.assertEqual( - str(context.exception), - f"The provided id:{test_imms_id} doesn't match with the content of the message", - ) + def test_errors_api_validation_error(self): + """Test correct operation of ApiValidationError""" + with self.assertRaises(errors.ApiValidationError) as context: + raise errors.ApiValidationError() outcome = context.exception.to_operation_outcome() - self.assert_operation_outcome(outcome) - issue = outcome.get("issue")[0] - self.assertEqual(issue.get("severity"), errors.Severity.error) - self.assertEqual(issue.get("code"), errors.Code.exception) - self.assertEqual( - issue.get("diagnostics"), - f"The provided id:{test_imms_id} doesn't match with the content of the message", - ) + self.assertIsNone(outcome) def test_errors_custom_validation_error(self): """Test correct operation of CustomValidationError""" @@ -281,80 +170,3 @@ def test_errors_identifier_duplication_error(self): issue.get("diagnostics"), f"The provided identifier: {test_identifier} is duplicated", ) - - def test_errors_server_error(self): - """Test correct operation of ServerError""" - test_response = "test_response" - test_message = "test_message" - - with self.assertRaises(errors.ServerError) as context: - raise errors.ServerError(test_response, test_message) - self.assert_response_message(context, test_response, test_message) - self.assertEqual(str(context.exception), f"{test_message}\n{test_response}") - outcome = context.exception.to_operation_outcome() - self.assert_operation_outcome(outcome) - issue = outcome.get("issue")[0] - self.assertEqual(issue.get("severity"), errors.Severity.error) - self.assertEqual(issue.get("code"), errors.Code.server_error) - self.assertEqual(issue.get("diagnostics"), f"{test_message}\n{test_response}") - - def test_errors_parameter_exception(self): - """Test correct operation of ParameterException""" - test_message = "test_message" - - with self.assertRaises(errors.ParameterException) as context: - raise errors.ParameterException(test_message) - self.assertEqual(context.exception.message, test_message) - self.assertEqual(str(context.exception), test_message) - - def test_errors_unauthorized_system_error(self): - """Test correct operation of UnauthorizedSystemError""" - test_message = "test_message" - - with self.assertRaises(errors.UnauthorizedSystemError) as context: - raise errors.UnauthorizedSystemError(test_message) - self.assertEqual(context.exception.message, test_message) - self.assertEqual(str(context.exception), test_message) - outcome = context.exception.to_operation_outcome() - self.assert_operation_outcome(outcome) - issue = outcome.get("issue")[0] - self.assertEqual(issue.get("severity"), errors.Severity.error) - self.assertEqual(issue.get("code"), errors.Code.forbidden) - self.assertEqual(issue.get("diagnostics"), test_message) - - def test_errors_unauthorized_system_error_no_message(self): - """Test correct operation of UnauthorizedSystemError with no message""" - - with self.assertRaises(errors.UnauthorizedSystemError) as context: - raise errors.UnauthorizedSystemError() - self.assertEqual(context.exception.message, "Unauthorized system") - self.assertEqual(str(context.exception), "Unauthorized system") - outcome = context.exception.to_operation_outcome() - self.assert_operation_outcome(outcome) - issue = outcome.get("issue")[0] - self.assertEqual(issue.get("severity"), errors.Severity.error) - self.assertEqual(issue.get("code"), errors.Code.forbidden) - self.assertEqual(issue.get("diagnostics"), "Unauthorized system") - - def test_errors_message_not_successful_error(self): - """Test correct operation of MessageNotSuccessfulError""" - test_message = "test_message" - - with self.assertRaises(errors.MessageNotSuccessfulError) as context: - raise errors.MessageNotSuccessfulError(test_message) - self.assertEqual(str(context.exception.message), test_message) - - def test_errors_message_not_successful_error_no_message(self): - """Test correct operation of MessageNotSuccessfulError with no message""" - - with self.assertRaises(errors.MessageNotSuccessfulError) as context: - raise errors.MessageNotSuccessfulError() - self.assertIsNone(context.exception.message) - - def test_errors_record_processor_error(self): - """Test correct operation of RecordProcessorError""" - test_diagnostics = {"test_diagnostic": "test_value"} - - with self.assertRaises(errors.RecordProcessorError) as context: - raise errors.RecordProcessorError(test_diagnostics) - self.assertEqual(context.exception.diagnostics_dictionary, test_diagnostics) diff --git a/backend/tests/test_utils.py b/lambdas/shared/tests/test_common/test_generic_utils.py similarity index 87% rename from backend/tests/test_utils.py rename to lambdas/shared/tests/test_common/test_generic_utils.py index 7acac6b84..d150c5dfb 100644 --- a/backend/tests/test_utils.py +++ b/lambdas/shared/tests/test_common/test_generic_utils.py @@ -2,13 +2,13 @@ import unittest from copy import deepcopy -from unittest.mock import patch +from unittest.mock import Mock, patch -from models.utils.validation_utils import ( +from common.models.utils.validation_utils import ( convert_disease_codes_to_vaccine_type, get_vaccine_type, ) -from testing_utils.generic_utils import load_json_data, update_target_disease_code +from test_common.testing_utils.generic_utils import load_json_data, update_target_disease_code class TestGenericUtils(unittest.TestCase): @@ -17,12 +17,13 @@ class TestGenericUtils(unittest.TestCase): def setUp(self): """Set up for each test. This runs before every test""" self.json_data = load_json_data(filename="completed_mmr_immunization_event.json") - self.redis_patcher = patch("models.utils.validation_utils.redis_client") - self.mock_redis_client = self.redis_patcher.start() + self.mock_redis = Mock() + self.redis_getter_patcher = patch("common.models.utils.validation_utils.get_redis_client") + self.mock_redis_getter = self.redis_getter_patcher.start() def tearDown(self): """Tear down after each test. This runs after every test""" - self.redis_patcher.stop() + self.redis_getter_patcher.stop() def test_convert_disease_codes_to_vaccine_type_returns_vaccine_type(self): """ @@ -37,7 +38,7 @@ def test_convert_disease_codes_to_vaccine_type_returns_vaccine_type(self): (["36653000", "14189004", "36989005"], "MMR"), (["55735004"], "RSV"), ] - self.mock_redis_client.hget.side_effect = [ + self.mock_redis.hget.side_effect = [ "COVID", "FLU", "HPV", @@ -46,6 +47,7 @@ def test_convert_disease_codes_to_vaccine_type_returns_vaccine_type(self): "MMR", "RSV", ] + self.mock_redis_getter.return_value = self.mock_redis for combination, vaccine_type in valid_combinations: self.assertEqual(convert_disease_codes_to_vaccine_type(combination), vaccine_type) @@ -60,8 +62,9 @@ def test_convert_disease_codes_to_vaccine_type_raises_error_on_none(self): ["14189004", "36989005"], ["14189004", "36989005", "36653000", "840539006"], ] - self.mock_redis_client.hget.side_effect = None - self.mock_redis_client.hget.return_value = None # Simulate no match in Redis for invalid combinations + self.mock_redis.hget.side_effect = None + self.mock_redis.hget.return_value = None # Simulate no match in Redis for invalid combinations + self.mock_redis_getter.return_value = self.mock_redis for invalid_combination in invalid_combinations: with self.assertRaises(ValueError): convert_disease_codes_to_vaccine_type(invalid_combination) @@ -71,14 +74,16 @@ def test_get_vaccine_type(self): Test that get_vaccine_type returns the correct vaccine type when given valid json data with a valid combination of target disease code, or raises an appropriate error otherwise """ - self.mock_redis_client.hget.return_value = "RSV" + self.mock_redis.hget.return_value = "RSV" + self.mock_redis_getter.return_value = self.mock_redis # TEST VALID DATA valid_json_data = load_json_data(filename="completed_rsv_immunization_event.json") vac_type = get_vaccine_type(valid_json_data) self.assertEqual(vac_type, "RSV") - self.mock_redis_client.hget.return_value = "FLU" + self.mock_redis.hget.return_value = "FLU" + self.mock_redis_getter.return_value = self.mock_redis # VALID DATA: coding field with multiple coding systems including SNOMED flu_json_data = load_json_data(filename="completed_flu_immunization_event.json") valid_target_disease_element = { @@ -99,7 +104,8 @@ def test_get_vaccine_type(self): self.assertEqual(get_vaccine_type(flu_json_data), "FLU") # TEST INVALID DATA FOR SINGLE TARGET DISEASE - self.mock_redis_client.hget.return_value = None # Reset mock for invalid cases + self.mock_redis.hget.return_value = None # Reset mock for invalid cases + self.mock_redis_getter.return_value = self.mock_redis covid_json_data = load_json_data(filename="completed_covid_immunization_event.json") # INVALID DATA, SINGLE TARGET DISEASE: No targetDisease field diff --git a/backend/tests/test_immunization_post_validator.py b/lambdas/shared/tests/test_common/test_immunization_post_validator.py similarity index 87% rename from backend/tests/test_immunization_post_validator.py rename to lambdas/shared/tests/test_common/test_immunization_post_validator.py index 66925d151..994c5f9b9 100644 --- a/backend/tests/test_immunization_post_validator.py +++ b/lambdas/shared/tests/test_common/test_immunization_post_validator.py @@ -2,22 +2,22 @@ import unittest from copy import deepcopy -from unittest.mock import patch +from unittest.mock import Mock, patch from jsonpath_ng.ext import parse from pydantic import ValidationError -from models.fhir_immunization import ImmunizationValidator -from testing_utils.generic_utils import ( +from common.models.fhir_immunization import ImmunizationValidator +from test_common.testing_utils.generic_utils import ( load_json_data, update_contained_resource_field, ) -from testing_utils.generic_utils import ( +from test_common.testing_utils.generic_utils import ( # these have an underscore to avoid pytest collecting them as tests test_invalid_values_rejected as _test_invalid_values_rejected, ) -from testing_utils.mandation_test_utils import MandationTests -from testing_utils.values_for_tests import NameInstances +from test_common.testing_utils.mandation_test_utils import MandationTests +from test_common.testing_utils.values_for_tests import NameInstances class TestImmunizationModelPostValidationRules(unittest.TestCase): @@ -40,18 +40,20 @@ def setUp(self): "MMR", "RSV", ] - self.redis_patcher = patch("models.utils.validation_utils.redis_client") - self.mock_redis_client = self.redis_patcher.start() + self.mock_redis = Mock() + self.redis_getter_patcher = patch("common.models.utils.validation_utils.get_redis_client") + self.mock_redis_getter = self.redis_getter_patcher.start() def tearDown(self): """Tear down after each test. This runs after every test""" - self.redis_patcher.stop() + self.redis_getter_patcher.stop() def test_collected_errors(self): """Test that when passed multiple validation errors, it returns a list of all expected errors""" covid_json_data = deepcopy(self.completed_json_data["COVID"]) - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis for patient in covid_json_data["contained"]: if patient["resourceType"] == "Patient": for name in patient["name"]: @@ -83,7 +85,8 @@ def test_collected_errors(self): def test_sample_data(self): """Test that each piece of valid sample data passes post validation""" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis for json_data in list(self.completed_json_data.values()): self.assertIsNone(self.validator.validate(json_data)) @@ -99,7 +102,7 @@ def test_post_validate_and_set_vaccine_type(self): "protocolApplied[0].targetDisease[0].coding[?(@.system=='http://snomed.info/sct')].code" ) - self.mock_redis_client.hget.side_effect = [ + self.mock_redis.hget.side_effect = [ "COVID", "FLU", "HPV", @@ -107,6 +110,7 @@ def test_post_validate_and_set_vaccine_type(self): "RSV", None, ] + self.mock_redis_getter.return_value = self.mock_redis # Test that a valid combination of disease codes is accepted for vaccine_type in [ "COVID", @@ -127,8 +131,9 @@ def test_post_validate_and_set_vaccine_type(self): + " - ['INVALID_VALUE'] is not a valid combination of disease codes for this service", ) - self.mock_redis_client.hget.side_effect = None - self.mock_redis_client.hget.return_value = None + self.mock_redis.hget.side_effect = None + self.mock_redis.hget.return_value = None + self.mock_redis_getter.return_value = self.mock_redis # Test that an invalid combination of disease codes is rejected invalid_target_disease = [ { @@ -178,7 +183,8 @@ def test_post_validate_and_set_vaccine_type(self): def test_post_vaccination_procedure_code(self): """Test that the JSON data is rejected if it does not contain vaccination_procedure_code""" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis field_location = ( "extension[?(@.url=='https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure')]" + ".valueCodeableConcept.coding[?(@.system=='http://snomed.info/sct')].code" @@ -201,12 +207,14 @@ def test_post_patient_identifier_value(self): Test that the JSON data is accepted when it does not contain patient_identifier_value """ field_location = "contained[?(@.resourceType=='Patient')].identifier[0].value" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis MandationTests.test_missing_field_accepted(self, field_location) def test_post_patient_name_given(self): """Test that the JSON data is rejected if it does not contain patient_name_given""" - self.mock_redis_client.hget.return_value = "RSV" + self.mock_redis.hget.return_value = "RSV" + self.mock_redis_getter.return_value = self.mock_redis valid_json_data = deepcopy(self.completed_json_data["RSV"]) patient_name_given_field_location = "contained[?(@.resourceType=='Patient')].name[0].given" expected_error_message = f"Validation errors: {patient_name_given_field_location} is a mandatory field" @@ -249,7 +257,8 @@ def test_post_patient_name_given(self): def test_post_patient_name_family(self): """Test that the JSON data is rejected if it does not contain patient_name_family""" valid_json_data = deepcopy(self.completed_json_data["RSV"]) - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis patient_name_family_field_location = "contained[?(@.resourceType=='Patient')].name[0].family" expected_error_message = f"{patient_name_family_field_location} is a mandatory field" @@ -280,17 +289,20 @@ def test_post_patient_name_family(self): def test_post_patient_birth_date(self): """Test that the JSON data is rejected if it does not contain patient_birth_date""" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis MandationTests.test_missing_mandatory_field_rejected(self, "contained[?(@.resourceType=='Patient')].birthDate") def test_post_patient_gender(self): """Test that the JSON data is rejected if it does not contain patient_gender""" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis MandationTests.test_missing_mandatory_field_rejected(self, "contained[?(@.resourceType=='Patient')].gender") def test_post_patient_address_postal_code(self): """Test that the JSON data is rejected if it does not contain patient_address_postal_code""" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis field_location = "contained[?(@.resourceType=='Patient')].address[0].postalCode" MandationTests.test_missing_mandatory_field_rejected(self, field_location) @@ -307,24 +319,28 @@ def test_post_occurrence_date_time(self): def test_post_organization_identifier_value(self): """Test that the JSON data is rejected if it does not contain organization_identifier_value""" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis MandationTests.test_missing_mandatory_field_rejected( self, "performer[?(@.actor.type=='Organization')].actor.identifier.value" ) def test_post_identifer_value(self): """Test that the JSON data is rejected if it does not contain identifier_value""" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis MandationTests.test_missing_mandatory_field_rejected(self, "identifier[0].value") def test_post_identifer_system(self): """Test that the JSON data is rejected if it does not contain identifier_system""" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis MandationTests.test_missing_mandatory_field_rejected(self, "identifier[0].system") def test_post_practitioner_name_given(self): """Test that the JSON data is rejected if it does not contain practitioner_name_given""" - self.mock_redis_client.hget.return_value = "RSV" + self.mock_redis.hget.return_value = "RSV" + self.mock_redis_getter.return_value = self.mock_redis valid_json_data = deepcopy(self.completed_json_data["RSV"]) practitioner_name_given_field_location = "contained[?(@.resourceType=='Practitioner')].name[0].given" @@ -348,7 +364,8 @@ def test_post_practitioner_name_given(self): def test_post_practitioner_name_family(self): """Test that the JSON data is rejected if it does not contain practitioner_name_family""" - self.mock_redis_client.hget.return_value = "RSV" + self.mock_redis.hget.return_value = "RSV" + self.mock_redis_getter.return_value = self.mock_redis valid_json_data = deepcopy(self.completed_json_data["RSV"]) practitioner_name_family_field_location = "contained[?(@.resourceType=='Practitioner')].name[0].family" @@ -372,12 +389,14 @@ def test_post_practitioner_name_family(self): def test_post_recorded(self): """Test that the JSON data is rejected if it does not contain recorded""" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis MandationTests.test_missing_mandatory_field_rejected(self, "recorded") def test_post_primary_source(self): """Test that the JSON data is rejected if it does not contain primary_source""" - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis MandationTests.test_missing_mandatory_field_rejected(self, "primarySource") # TODO: To confirm with imms if dose number string validation is correct (current working assumption is yes) @@ -419,8 +438,9 @@ def test_post_dose_number_positive_int(self): # dose_number_positive_int exists, dose_number_string does not exist valid_json_data = deepcopy(self.completed_json_data[vaccine_type]) - self.mock_redis_client.hget.side_effect = None - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.side_effect = None + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis MandationTests.test_present_field_accepted(self, valid_json_data) # dose_number_positive_int does not exist, dose_number_string exists @@ -446,21 +466,23 @@ def test_post_manufacturer_display(self): Test that present or absent manufacturer_display is accepted or rejected as appropriate dependent on other fields """ - self.mock_redis_client.hget.side_effect = None - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.side_effect = None + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis field_location = "manufacturer.display" for vaccine_type in self.all_vaccine_types: MandationTests.test_missing_field_accepted(self, field_location, self.completed_json_data[vaccine_type]) def test_post_lot_number(self): """Test that present or absent lot_number is accepted or rejected as appropriate dependent on other fields""" - self.mock_redis_client.hget.side_effect = [ + self.mock_redis.hget.side_effect = [ "COVID", "FLU", "HPV", "MMR", "RSV", ] + self.mock_redis_getter.return_value = self.mock_redis field_location = "lotNumber" for vaccine_type in self.all_vaccine_types: MandationTests.test_missing_field_accepted(self, field_location, self.completed_json_data[vaccine_type]) @@ -470,13 +492,14 @@ def test_post_expiration_date(self): Test that present or absent expiration_date is accepted or rejected as appropriate dependent on other fields """ - self.mock_redis_client.hget.side_effect = [ + self.mock_redis.hget.side_effect = [ "COVID", "FLU", "HPV", "MMR", "RSV", ] + self.mock_redis_getter.return_value = self.mock_redis field_location = "expirationDate" for vaccine_type in self.all_vaccine_types: MandationTests.test_missing_field_accepted(self, field_location, self.completed_json_data[vaccine_type]) @@ -514,13 +537,14 @@ def test_post_dose_quantity_value(self): """ Test that present or absent dose_quantity_value is accepted or rejected as appropriate dependent on other fields """ - self.mock_redis_client.hget.side_effect = [ + self.mock_redis.hget.side_effect = [ "COVID", "FLU", "HPV", "MMR", "RSV", ] + self.mock_redis_getter.return_value = self.mock_redis field_location = "doseQuantity.value" for vaccine_type in self.all_vaccine_types: MandationTests.test_missing_field_accepted(self, field_location, self.completed_json_data[vaccine_type]) @@ -529,21 +553,23 @@ def test_post_dose_quantity_code(self): """ Test that present or absent dose_quantity_code is accepted or rejected as appropriate dependent on other fields """ - self.mock_redis_client.hget.side_effect = [ + self.mock_redis.hget.side_effect = [ "COVID", "FLU", "HPV", "MMR", "RSV", ] + self.mock_redis_getter.return_value = self.mock_redis field_location = "doseQuantity.code" for vaccine_type in self.all_vaccine_types: MandationTests.test_missing_field_accepted(self, field_location, self.completed_json_data[vaccine_type]) def test_post_dose_quantity_unit(self): """Test that the JSON data is accepted when dose_quantity_unit is absent""" - self.mock_redis_client.hget.side_effect = None - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.side_effect = None + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis MandationTests.test_missing_field_accepted(self, "doseQuantity.unit") # NOTE: THIS TEST IS COMMENTED OUT AS IT IS TESTING A REQUIRED ELEMENT (VALIDATION SHOULD ALWAYS PASS), @@ -555,8 +581,9 @@ def test_post_dose_quantity_unit(self): def test_post_organization_identifier_system(self): """Test that the JSON data is rejected if it does not contain organization_identifier_system""" - self.mock_redis_client.hget.side_effect = None - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.side_effect = None + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis MandationTests.test_missing_mandatory_field_rejected( self, "performer[?(@.actor.type=='Organization')].actor.identifier.system" ) @@ -569,8 +596,9 @@ def test_post_pre_validate_extension_url(self): and get passed only with the snomed url. """ # Test case: missing "extension" - self.mock_redis_client.hget.side_effect = None - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.side_effect = None + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis invalid_json_data = deepcopy(self.completed_json_data["COVID"]) invalid_json_data["extension"][0]["valueCodeableConcept"]["coding"][0]["system"] = ( "https://xyz/Extension-UKCore-VaccinationProcedure" @@ -591,13 +619,14 @@ def test_post_location_identifier_value(self): Test that the JSON data is rejected if it does and does not contain location_identifier_value as appropriate """ - self.mock_redis_client.hget.side_effect = [ + self.mock_redis.hget.side_effect = [ "COVID", "FLU", "HPV", "MMR", "RSV", ] + self.mock_redis_getter.return_value = self.mock_redis field_location = "location.identifier.value" # Test cases for COVID, FLU, HPV and MMR where it is mandatory for vaccine_type in ( @@ -614,13 +643,14 @@ def test_post_location_identifier_system(self): """ Test that the JSON data is rejected if it does and does not contain location_identifier_system as appropriate """ - self.mock_redis_client.hget.side_effect = [ + self.mock_redis.hget.side_effect = [ "COVID", "FLU", "HPV", "MMR", "RSV", ] + self.mock_redis_getter.return_value = self.mock_redis field_location = "location.identifier.system" # Test cases for COVID, FLU, HPV and MMR where it is mandatory for vaccine_type in ( @@ -635,8 +665,9 @@ def test_post_location_identifier_system(self): def test_post_no_snomed_code(self): """test that only snomed system is accepted""" - self.mock_redis_client.hget.side_effect = None - self.mock_redis_client.return_value = "COVID" + self.mock_redis.hget.side_effect = None + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis covid_json_data = deepcopy(self.completed_json_data["COVID"]) invalid_target_disease_value = [ diff --git a/backend/tests/test_immunization_pre_validator.py b/lambdas/shared/tests/test_common/test_immunization_pre_validator.py similarity index 98% rename from backend/tests/test_immunization_pre_validator.py rename to lambdas/shared/tests/test_common/test_immunization_pre_validator.py index b1c53ae65..e4842e964 100644 --- a/backend/tests/test_immunization_pre_validator.py +++ b/lambdas/shared/tests/test_common/test_immunization_pre_validator.py @@ -3,32 +3,32 @@ import unittest from copy import deepcopy from decimal import Decimal -from unittest.mock import patch +from unittest.mock import Mock, patch from jsonpath_ng.ext import parse -from models.constants import Constants -from models.fhir_immunization import ImmunizationValidator -from models.fhir_immunization_pre_validators import PreValidators -from models.utils.generic_utils import ( +from common.models.constants import Constants +from common.models.fhir_immunization import ImmunizationValidator +from common.models.fhir_immunization_pre_validators import PreValidators +from common.models.utils.generic_utils import ( get_generic_extension_value, patient_name_family_field_location, patient_name_given_field_location, practitioner_name_family_field_location, practitioner_name_given_field_location, ) -from testing_utils.generic_utils import ( +from test_common.testing_utils.generic_utils import ( load_json_data, ) -from testing_utils.generic_utils import ( +from test_common.testing_utils.generic_utils import ( test_invalid_values_rejected as _test_invalid_values_rejected, ) -from testing_utils.generic_utils import ( +from test_common.testing_utils.generic_utils import ( # these have an underscore to avoid pytest collecting them as tests test_valid_values_accepted as _test_valid_values_accepted, ) -from testing_utils.pre_validation_test_utils import ValidatorModelTests -from testing_utils.values_for_tests import InvalidValues, ValidValues +from test_common.testing_utils.pre_validation_test_utils import ValidatorModelTests +from test_common.testing_utils.values_for_tests import InvalidValues, ValidValues class TestImmunizationModelPreValidationRules(unittest.TestCase): @@ -38,8 +38,9 @@ def setUp(self): """Set up for each test. This runs before every test""" self.json_data = load_json_data(filename="completed_covid_immunization_event.json") self.validator = ImmunizationValidator(add_post_validators=False) - self.redis_patcher = patch("models.utils.validation_utils.redis_client") - self.mock_redis_client = self.redis_patcher.start() + self.mock_redis = Mock() + self.redis_getter_patcher = patch("common.models.utils.validation_utils.get_redis_client") + self.mock_redis_getter = self.redis_getter_patcher.start() def tearDown(self): patch.stopall() @@ -592,7 +593,7 @@ def test_pre_validate_patient_address_postal_code(self): } ] } - result = self.validator.run_postalCode_validator(values) + result = self.validator.run_postal_code_validator(values) self.assertIsNone(result) def test_pre_validate_occurrence_date_time(self): @@ -809,7 +810,8 @@ def test_pre_validate_missing_valueCodeableConcept2(self): def test_pre_validate_missing_valueCodeableConcept3(self): # Test case: valid data (should not raise an exception) - self.mock_redis_client.hget.return_value = "COVID" + self.mock_redis.hget.return_value = "COVID" + self.mock_redis_getter.return_value = self.mock_redis valid_json_data = deepcopy(self.json_data) try: self.validator.validate(valid_json_data) diff --git a/backend/tests/test_model_utils.py b/lambdas/shared/tests/test_common/test_model_utils.py similarity index 93% rename from backend/tests/test_model_utils.py rename to lambdas/shared/tests/test_common/test_model_utils.py index a64fac812..3141b9eb3 100644 --- a/backend/tests/test_model_utils.py +++ b/lambdas/shared/tests/test_common/test_model_utils.py @@ -2,7 +2,7 @@ import unittest -from models.utils.generic_utils import nhs_number_mod11_check +from common.models.utils.generic_utils import nhs_number_mod11_check "test" diff --git a/lambdas/shared/tests/test_common/test_s3_reader.py b/lambdas/shared/tests/test_common/test_s3_reader.py index f56cae4bc..33fe70b24 100644 --- a/lambdas/shared/tests/test_common/test_s3_reader.py +++ b/lambdas/shared/tests/test_common/test_s3_reader.py @@ -1,44 +1,40 @@ import unittest -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, Mock, patch from common.s3_reader import S3Reader +@patch("common.s3_reader.get_s3_client") class TestS3Reader(unittest.TestCase): def setUp(self): self.s3_reader = S3Reader() self.bucket = "test-bucket" self.key = "test.json" - # Patch s3_client - self.s3_client_patcher = patch("common.s3_reader.s3_client") - self.mock_s3_client = self.s3_client_patcher.start() - self.logger_info_patcher = patch("logging.Logger.info") self.mock_logger_info = self.logger_info_patcher.start() self.logger_exception_patcher = patch("logging.Logger.exception") self.mock_logger_exception = self.logger_exception_patcher.start() def tearDown(self): - self.s3_client_patcher.stop() self.logger_info_patcher.stop() self.logger_exception_patcher.stop() - def test_read_success(self): + def test_read_success(self, mock_get_s3_client): + mock_s3 = Mock() mock_body = MagicMock() mock_body.read.return_value = b'{"foo": "bar"}' - self.mock_s3_client.get_object.return_value = {"Body": mock_body} - + mock_s3.get_object.return_value = {"Body": mock_body} + mock_get_s3_client.return_value = mock_s3 result = self.s3_reader.read(self.bucket, self.key) - self.assertEqual(result, '{"foo": "bar"}') - self.mock_s3_client.get_object.assert_called_once_with(Bucket=self.bucket, Key=self.key) - - def test_read_raises_exception(self): - self.mock_s3_client.get_object.side_effect = Exception("S3 error") + mock_s3.get_object.assert_called_once_with(Bucket=self.bucket, Key=self.key) + def test_read_raises_exception(self, mock_get_s3_client): + mock_s3 = Mock() + mock_s3.get_object.side_effect = Exception("S3 error") + mock_get_s3_client.return_value = mock_s3 with self.assertRaises(Exception) as context: self.s3_reader.read(self.bucket, self.key) - self.assertIn("S3 error", str(context.exception)) self.mock_logger_exception.assert_called_once() diff --git a/lambdas/shared/tests/test_common/test_utils.py b/lambdas/shared/tests/test_common/test_utils.py new file mode 100644 index 000000000..6aa6af778 --- /dev/null +++ b/lambdas/shared/tests/test_common/test_utils.py @@ -0,0 +1,46 @@ +import unittest +from unittest.mock import patch + +import boto3 +from moto import mock_aws + +from common.aws_s3_utils import move_file + + +@mock_aws +class TestUtils(unittest.TestCase): + def setUp(self): + self.bucket_name = "move-bucket" + self.s3_client = boto3.client("s3", region_name="eu-west-2") + self.s3_client.create_bucket( + Bucket=self.bucket_name, + CreateBucketConfiguration={"LocationConstraint": "eu-west-2"}, + ) + + self.logger_info_patcher = patch("logging.Logger.info") + self.mock_logger_info = self.logger_info_patcher.start() + + def tearDown(self): + for obj in self.s3_client.list_objects_v2(Bucket=self.bucket_name).get("Contents", []): + self.s3_client.delete_object(Bucket=self.bucket_name, Key=obj["Key"]) + self.s3_client.delete_bucket(Bucket=self.bucket_name) + self.logger_info_patcher.stop() + + def test_move_file(self): + """VED-167 test that the file has been moved to the appropriate location""" + bucket_name = self.bucket_name + file_key = "src/move_file_test.csv" + dest_key = "dest/move_file_test.csv" + self.s3_client.put_object(Bucket=bucket_name, Key=file_key, Body="dummy content") + move_file(bucket_name, file_key, dest_key) + # Assert the destination object exists + response = self.s3_client.get_object(Bucket=bucket_name, Key=dest_key) + content = response["Body"].read().decode() + self.assertEqual(content, "dummy content") + + # Assert the source object no longer exists + with self.assertRaises(self.s3_client.exceptions.NoSuchKey): + self.s3_client.get_object(Bucket=bucket_name, Key=file_key) + + # Logger assertion (if logger is mocked) + self.mock_logger_info.assert_called_with("File moved from %s to %s", file_key, dest_key) diff --git a/lambdas/shared/tests/test_common/testing_utils/__init__.py b/lambdas/shared/tests/test_common/testing_utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/tests/testing_utils/generic_utils.py b/lambdas/shared/tests/test_common/testing_utils/generic_utils.py similarity index 100% rename from backend/tests/testing_utils/generic_utils.py rename to lambdas/shared/tests/test_common/testing_utils/generic_utils.py diff --git a/backend/tests/testing_utils/immunization_utils.py b/lambdas/shared/tests/test_common/testing_utils/immunization_utils.py similarity index 90% rename from backend/tests/testing_utils/immunization_utils.py rename to lambdas/shared/tests/test_common/testing_utils/immunization_utils.py index 2db9b36e3..d220d46b7 100644 --- a/backend/tests/testing_utils/immunization_utils.py +++ b/lambdas/shared/tests/test_common/testing_utils/immunization_utils.py @@ -2,8 +2,8 @@ from fhir.resources.R4B.immunization import Immunization -from testing_utils.generic_utils import load_json_data -from testing_utils.values_for_tests import ValidValues +from test_common.testing_utils.generic_utils import load_json_data +from test_common.testing_utils.values_for_tests import ValidValues VALID_NHS_NUMBER = ValidValues.nhs_number diff --git a/backend/tests/testing_utils/mandation_test_utils.py b/lambdas/shared/tests/test_common/testing_utils/mandation_test_utils.py similarity index 100% rename from backend/tests/testing_utils/mandation_test_utils.py rename to lambdas/shared/tests/test_common/testing_utils/mandation_test_utils.py diff --git a/backend/tests/testing_utils/pre_validation_test_utils.py b/lambdas/shared/tests/test_common/testing_utils/pre_validation_test_utils.py similarity index 100% rename from backend/tests/testing_utils/pre_validation_test_utils.py rename to lambdas/shared/tests/test_common/testing_utils/pre_validation_test_utils.py diff --git a/backend/tests/testing_utils/values_for_tests.py b/lambdas/shared/tests/test_common/testing_utils/values_for_tests.py similarity index 100% rename from backend/tests/testing_utils/values_for_tests.py rename to lambdas/shared/tests/test_common/testing_utils/values_for_tests.py diff --git a/package-lock.json b/package-lock.json index e7b15d52a..61f844490 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "license": "MIT", "devDependencies": { - "@redocly/cli": "^2.11.0", + "@redocly/cli": "^2.11.1", "husky": "^9.1.7", "license-checker": "^25.0.1", "lint-staged": "^16.2.6", @@ -526,9 +526,9 @@ "license": "BSD-3-Clause" }, "node_modules/@redocly/ajv": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.3.tgz", - "integrity": "sha512-4P3iZse91TkBiY+Dx5DUgxQ9GXkVJf++cmI0MOyLDxV9b5MUBI4II6ES8zA5JCbO72nKAJxWrw4PUPW+YP3ZDQ==", + "version": "8.11.4", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.4.tgz", + "integrity": "sha512-77MhyFgZ1zGMwtCpqsk532SJEc3IJmSOXKTCeWoMTAvPnQOkuOgxEip1n5pG5YX1IzCTJ4kCvPKr8xYyzWFdhg==", "dev": true, "license": "MIT", "dependencies": { @@ -543,9 +543,9 @@ } }, "node_modules/@redocly/cli": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-2.11.0.tgz", - "integrity": "sha512-Wr8me9M5tQ4pZT7Z0Llxojlo8L0GBBt45zceQ8iKyBmJUHWDbKYYdKubZBCH0XktQLEA8HitYBGN1unsxwx20g==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-2.11.1.tgz", + "integrity": "sha512-doNs+sdrFzzXmyf1yIeJbPh8OChacHWkvTE9N0QbuCmnYQ4k0v1IMP20qsitkwR+fK8O1hXSnFnSTVvIunMVVw==", "dev": true, "license": "MIT", "dependencies": { @@ -553,8 +553,8 @@ "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-trace-node": "2.0.1", "@opentelemetry/semantic-conventions": "1.34.0", - "@redocly/openapi-core": "2.11.0", - "@redocly/respect-core": "2.11.0", + "@redocly/openapi-core": "2.11.1", + "@redocly/respect-core": "2.11.1", "abort-controller": "^3.0.0", "chokidar": "^3.5.1", "colorette": "^1.2.0", @@ -610,9 +610,9 @@ } }, "node_modules/@redocly/config": { - "version": "0.37.0", - "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.37.0.tgz", - "integrity": "sha512-cYN+rTTCQIp5mVt1xumJsNqpZcaPVUf1x0ryD0QKXpVKsxKc+lHaMF2P1CqMgdQNY9B7i84z/kvxD0EhxzlxbQ==", + "version": "0.38.0", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.38.0.tgz", + "integrity": "sha512-kSgMG3rRzgXIP/6gWMRuWbu9/ms0Cyuphcx19dPR9qlgc1tt9IKYPsFQ+KhJuEtqd3bcY/+Uflysf33dQkZWVQ==", "dev": true, "license": "MIT", "dependencies": { @@ -620,14 +620,14 @@ } }, "node_modules/@redocly/openapi-core": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-2.11.0.tgz", - "integrity": "sha512-CF4QpCoxxHIB7Dib1XnhdL0WuW4dO4zvNfaEWpN7TASlitOX2mhrc6sD3dYG9knW1iG16e3Oauv2O+tVJx1E9Q==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-2.11.1.tgz", + "integrity": "sha512-FVCDnZxaoUJwLQxfW4inCojxUO56J3ntu7dDAE2qyWd6tJBK45CnXMQQUxpqeRTeXROr3jYQoApAw+GCEnyBeg==", "dev": true, "license": "MIT", "dependencies": { - "@redocly/ajv": "^8.11.2", - "@redocly/config": "^0.37.0", + "@redocly/ajv": "^8.11.4", + "@redocly/config": "^0.38.0", "ajv-formats": "^2.1.1", "colorette": "^1.2.0", "js-levenshtein": "^1.1.6", @@ -655,16 +655,16 @@ } }, "node_modules/@redocly/respect-core": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@redocly/respect-core/-/respect-core-2.11.0.tgz", - "integrity": "sha512-lAvDILvq82IIei2gVyapGyfuWEamJgCiGO++yQriVk4Wr0hE3lF7ZWusUM3aGZrxEWCVGeeLwbMBpv1BQOnmEg==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@redocly/respect-core/-/respect-core-2.11.1.tgz", + "integrity": "sha512-jSMJvCJeo5gmhQfg82AhuwCG0h8gbW5vqHyRITBu8KHVsBiQTgvfhXepu8SKHeJu0OexYtEc0nUnGLJlefevYw==", "dev": true, "license": "MIT", "dependencies": { "@faker-js/faker": "^7.6.0", "@noble/hashes": "^1.8.0", - "@redocly/ajv": "8.11.2", - "@redocly/openapi-core": "2.11.0", + "@redocly/ajv": "8.11.4", + "@redocly/openapi-core": "2.11.1", "better-ajv-errors": "^1.2.0", "colorette": "^2.0.20", "json-pointer": "^0.6.2", @@ -677,23 +677,6 @@ "npm": ">=10" } }, - "node_modules/@redocly/respect-core/node_modules/@redocly/ajv": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js-replace": "^1.0.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/@redocly/respect-core/node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", diff --git a/package.json b/package.json index 389c4a9e8..9c56c877e 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "license": "MIT", "homepage": "https://github.com/NHSDigital/immunisation-fhir-api", "devDependencies": { - "@redocly/cli": "^2.11.0", + "@redocly/cli": "^2.11.1", "husky": "^9.1.7", "license-checker": "^25.0.1", "lint-staged": "^16.2.6", diff --git a/sonar-project.properties b/sonar-project.properties index 5abedcf21..bba260dd4 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,7 +5,7 @@ sonar.host.url=https://sonarcloud.io sonar.python.version=3.11 sonar.exclusions=**/e2e/**,**/e2e_batch/**,**/devtools/**,**/proxies/**,**/utilities/scripts/**,**/infrastructure/account/**,**/infrastructure/instance/**,**/infrastructure/grafana/**,**/terraform_aws_backup/**,**/tests/** sonar.python.coverage.reportPaths=backend-coverage.xml,delta-coverage.xml,ack-lambda-coverage.xml,filenameprocessor-coverage.xml,recordforwarder-coverage.xml,recordprocessor-coverage.xml,mesh_processor-coverage.xml,redis_sync-coverage.xml,mns_subscription-coverage.xml,id_sync-coverage.xml,shared-coverage.xml,batchprocessorfilter-coverage.xml -sonar.cpd.exclusions=**/cache.py,**/authentication.py,**/test_cache.py,**/test_authentication.py,**/mns_service.py,**/errors.py,**/Dockerfile,lambdas/shared/src/common/**,filenameprocessor/src/logging_decorator.py +sonar.cpd.exclusions=**/Dockerfile sonar.issue.ignore.multicriteria=exclude_snomed_urls,exclude_hl7_urls sonar.issue.ignore.multicriteria.exclude_snomed_urls.ruleKey=python:S5332 sonar.issue.ignore.multicriteria.exclude_snomed_urls.resourceKey=**http://snomed\.info/sct** diff --git a/specification/immunisation-fhir-api.json b/specification/immunisation-fhir-api.json index 455bc77d8..e034f1941 100644 --- a/specification/immunisation-fhir-api.json +++ b/specification/immunisation-fhir-api.json @@ -1688,6 +1688,839 @@ } } }, + "/Immunization/$search-by-identifier": { + "get": { + "summary": "Search immunisations by record identifier", + "operationId": "searchImmunizationByIdentifier", + "description": "Use this interaction to find Immunization records by their unique identifier assigned by the submitting system. Provide the composite FHIR search parameter `identifier` as `|`, for example: `identifier=https://supplierABC/identifiers/vacc|e2154d29-1ead-4830-a513-0d59705078fa`. This returns a Bundle of matching Immunization resources.", + "parameters": [ + { + "$ref": "#/components/parameters/CorrelationID" + }, + { + "$ref": "#/components/parameters/RequestID" + }, + { + "$ref": "#/components/parameters/Identifier" + } + ], + "responses": { + "200": { + "description": "Search immunisation operation successful", + "content": { + "application/fhir+json": { + "schema": { + "description": "FHIR Bundle containing the query results - search immunisation by identifier operation successful.", + "type": "object", + "required": ["resourceType", "type", "total", "entry"], + "properties": { + "resourceType": { + "description": "FHIR resource type. Always `Bundle`.", + "type": "string", + "example": "Bundle" + }, + "type": { + "description": "Indicates how the bundle is intended to be used. Always `searchset`.", + "type": "string", + "example": "searchset" + }, + "link": { + "type": "array", + "items": { + "type": "object", + "properties": { + "relation": { + "description": "A name which details the functional use for this link - see [http://www.iana.org/assignments/link-relations/link-relations.xhtml#link-relations-1](http://www.iana.org/assignments/link-relations/link-relations.xhtml#link-relations-1). Always `Self`.", + "type": "string" + }, + "url": { + "description": "A url representing the search applied by the API to generate the result which may differ from the request if unrecognised or unsupported parameters have been ignored.", + "type": "string" + } + }, + "required": ["relation", "url"] + } + }, + "entry": { + "description": "List of matching immunisations and associated patient. If there were no matching immunisations, this is an empty list.", + "type": "array", + "items": { + "type": "object", + "required": ["fullUrl", "resource", "search"], + "properties": { + "fullUrl": { + "description": "URI for the Immunization or Patient resource.", + "type": "string", + "example": "https://sandbox.api.service.nhs.uk/immunisation-fhir-api/Immunization/191f288a-17f3-4cd5-a33c-a52aade6473c" + }, + "resource": { + "description": "The Immunization resource.", + "oneOf": [ + { + "description": "A matching immunisation, formatted as a FHIR Immunization resource.", + "type": "object", + "required": [ + "resourceType", + "extension", + "identifier", + "status", + "vaccineCode", + "occurrence[X]", + "recorded", + "primarySource", + "location", + "performer", + "protocolApplied" + ], + "properties": { + "resourceType": { + "description": "FHIR resource type. Always `Immunization`.", + "type": "string", + "example": "Immunization" + }, + "id": { + "description": "Immunization record Id.", + "type": "string", + "example": "191f288a-17f3-4cd5-a33c-a52aade6473c" + }, + "meta": { + "type": "object", + "properties": { + "versionId": { + "type": "string", + "pattern": "[A-Za-z0-9\\-\\.]{1,64}", + "description": "The version specific identifier, as it appears in the version portion of the URL. This value changes when the resource is created, updated, or deleted." + }, + "lastUpdated": { + "type": "string", + "pattern": "([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))", + "description": "When the resource last changed - e.g. when the version changed.", + "example": "2017-01-01T00:00:00Z" + }, + "source": { + "type": "string", + "description": "Identifies where the resource comes from." + }, + "profile": { + "type": "array", + "items": { + "type": "string", + "pattern": "\\S*", + "description": "A list of profiles (references to [StructureDefinition](structuredefinition.html#) resources) that this resource claims to conform to. The URL is a reference to [StructureDefinition.url](structuredefinition-definitions.html#StructureDefinition.url)." + } + }, + "security": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Coding", + "description": "Security labels applied to this resource. These tags connect specific resources to the overall security policy and infrastructure." + } + }, + "tag": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Coding", + "description": "Tags applied to this resource. Tags are intended to be used to identify and relate resources to process and workflow, and applications are not required to consider the tags when interpreting the meaning of a resource." + } + } + }, + "required": ["versionId"] + }, + "extension": { + "description": "FHIR extension wrapper for the vaccination procedure performed. Always contains exactly one object.", + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": [ + "url", + "valueCodeableConcept" + ], + "properties": { + "url": { + "description": "URI for the type of extension - https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure", + "type": "string", + "example": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure" + }, + "valueCodeableConcept": { + "description": "Wrapper for the vaccination procedure coding.", + "type": "object", + "required": ["coding"], + "properties": { + "coding": { + "description": "Wrapper for the vaccination procedure coding.", + "type": "array", + "items": { + "type": "object", + "required": [ + "system", + "code", + "display" + ], + "properties": { + "system": { + "description": "The identification of the code system that defines the meaning of the symbol in the code.", + "type": "string", + "example": "http://snomed.info/sct" + }, + "code": { + "description": "A particular code in the system.", + "type": "string", + "example": "1303503001" + }, + "display": { + "description": "Representation defined by the system.", + "type": "string", + "example": "Administration of RSV (respiratory syncytial virus) vaccine" + } + } + } + } + } + } + } + } + }, + "identifier": { + "description": "Unique identifier for this immunisation record, as generated by the source system.", + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": ["system", "value"], + "properties": { + "use": { + "description": "Identifier use as defined by https://www.hl7.org/fhir/valueset-identifier-use.html.", + "type": "string", + "enum": [ + "usual", + "official", + "temp", + "secondary", + "old" + ], + "example": "official" + }, + "system": { + "description": "URI of the namespace of this identifier.", + "type": "string", + "example": "https://supplierABC/identifiers/vacc" + }, + "value": { + "description": "Identifier value within `system`.", + "type": "string", + "example": "e2154d29-1ead-4830-a513-0d59705078fa" + } + } + } + }, + "status": { + "description": "Status of the immunisation event. This is *not* an indication of patient immunity, only whether the immunisation was completed or not. Currently we only return details of completed immunisations.", + "type": "string", + "enum": ["completed"], + "example": "completed" + }, + "vaccineCode": { + "description": "Vaccine product administered.", + "type": "object", + "required": ["coding"], + "properties": { + "coding": { + "description": "Wrapper for the vaccine product details.", + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "system", + "code", + "display" + ], + "properties": { + "system": { + "description": "The identification of the code system that defines the meaning of the symbol in the code.", + "type": "string", + "example": "http://snomed.info/sct" + }, + "code": { + "description": "SNOMED code for the vaccine product.", + "type": "string", + "example": "42605811000001109" + }, + "display": { + "description": "Description of the vaccine product.", + "type": "string", + "example": "Abrysvo vaccine powder and solvent for solution for injection 0.5ml vials (Pfizer Ltd)" + } + } + } + } + } + }, + "patient": { + "description": "The patient who was immunised.", + "type": "object", + "required": [ + "reference", + "type", + "identifier" + ], + "properties": { + "reference": { + "description": "URI for the associated Patient resource in the bundle.", + "type": "string", + "example": "urn:uuid:a7a5bc28-5831-4158-8a73-0d3e6e43c1ac" + }, + "type": { + "description": "Type of resource this reference refers to. Always `Patient`.", + "type": "string", + "example": "Patient" + }, + "identifier": { + "description": "Business identifier for linked Patient. Always an NHS number.", + "type": "object", + "required": ["system", "value"], + "properties": { + "system": { + "description": "URI of coding system used to identify linked patient. Always https://fhir.nhs.uk/Id/nhs-number", + "type": "string", + "example": "https://fhir.nhs.uk/Id/nhs-number" + }, + "value": { + "description": "Value in coding system representing linked patient.", + "type": "string", + "example": "9000000009" + } + } + } + } + }, + "occurrence[X]": { + "type": "object", + "description": "When immunizations are given a specific date and time should always be known. The string data type for this element is not supported. Only occurrenceDateTime SHALL be used.", + "properties": { + "occurrenceDateTime": { + "description": "Date and time of immunisation.", + "type": "string", + "example": "2021-02-07T13:28:17.271000+00:00" + } + }, + "required": ["occurrenceDateTime"] + }, + "recorded": { + "description": "The date the occurrence of the immunization was first captured in the record - potentially significantly after the occurrence of the event.", + "type": "string", + "example": "2021-02-07T13:28:17.271000+00:00" + }, + "primarySource": { + "description": "An indication that the content of the record is based on information from the person who administered the vaccine. This reflects the context under which the data was originally recorded.", + "type": "boolean", + "example": true + }, + "location": { + "type": "object", + "description": "The service delivery location where the vaccine administration occurred.", + "properties": { + "identifier": { + "type": "object", + "description": "An identifier for the service delivery location.", + "properties": { + "system": { + "description": "The system which defines the location. Typically this will be https://fhir.nhs.uk/Id/ods-organization-code for a health setting or https://fhir.hl7.org.uk/Id/urn-school-number for an education setting.", + "type": "string", + "example": "https://fhir.nhs.uk/Id/ods-organization-code" + }, + "value": { + "description": "A code from the system to represent the location. An ODS code of X99999 represents a location where a code is not available.", + "type": "string", + "example": "X99999" + } + }, + "required": ["system", "value"] + } + }, + "required": ["identifier"] + }, + "manufacturer": { + "description": "Vaccine manufacturer details.", + "type": "object", + "properties": { + "display": { + "description": "Decsription of the vaccine manufacturer.", + "type": "string", + "example": "AstraZeneca Ltd" + } + } + }, + "lotNumber": { + "description": "Lot number of the vaccine product.", + "type": "string", + "example": "4120Z001" + }, + "expirationDate": { + "description": "Date vaccine batch expires.", + "type": "string", + "example": "2021-04-29" + }, + "site": { + "description": "Body site where vaccine was administered.", + "type": "object", + "properties": { + "coding": { + "description": "Wrapper for the vaccination body site details.", + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "system": { + "description": "Coding system used to describe vaccination body site.", + "type": "string", + "example": "http://snomed.info/sct" + }, + "code": { + "description": "Code for the vaccination body site.", + "type": "string", + "example": "368208006" + }, + "display": { + "description": "Description of the vaccination body site.", + "type": "string", + "example": "Left upper arm structure (body structure)" + } + } + } + } + }, + "required": ["coding"] + }, + "route": { + "description": "The path by which the vaccine product is taken into the body.", + "type": "object", + "required": ["coding"], + "properties": { + "coding": { + "description": "Wrapper for the vaccination route details.", + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "system": { + "description": "Coding system used to describe vaccination route.", + "type": "string", + "example": "http://snomed.info/sct" + }, + "code": { + "description": "Code for the vaccination route.", + "type": "string", + "example": "78421000" + }, + "display": { + "description": "Description of the vaccination route.", + "type": "string", + "example": "Intramuscular route (qualifier value)" + } + } + } + } + } + }, + "doseQuantity": { + "description": "The quantity of vaccine product that was administered.", + "type": "object", + "properties": { + "value": { + "description": "Number of units administered.", + "type": "number", + "example": 1 + }, + "unit": { + "description": "Description of unit.", + "type": "string", + "example": "milliliter" + }, + "system": { + "description": "System that defines coded unit form.", + "type": "string", + "example": "http://unitsofmeasure.org" + }, + "code": { + "description": "Code describing the unit.", + "type": "string", + "example": "ml" + } + } + }, + "performer": { + "description": "Details of the organisation that performed the immunisation.", + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": ["actor"], + "properties": { + "actor": { + "description": "Organisation that performed the immunisation.", + "type": "object", + "required": ["type", "identifier"], + "properties": { + "type": { + "description": "Type of actor. Always `Organisation`.", + "type": "string", + "example": "Organisation" + }, + "identifier": { + "description": "Organisation identifier.", + "type": "object", + "required": ["system", "value"], + "properties": { + "system": { + "description": "Coding system used for the organisation identifier. Always `https://fhir.nhs.uk/Id/ods-organization-code`.", + "type": "string", + "example": "https://fhir.nhs.uk/Id/ods-organization-code" + }, + "value": { + "description": "Organisation's ODS code.", + "type": "string", + "example": "B0C4P" + } + } + }, + "display": { + "description": "Organisation that performed the immunisation.", + "type": "string", + "example": "UNIVERSITY HOSPITAL OF WALES" + } + } + } + } + } + }, + "reasonCode": { + "description": "Reasons why the vaccine was administered.", + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["coding"], + "properties": { + "coding": { + "description": "Wrapper for the reason code details.", + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "system", + "code", + "display" + ], + "properties": { + "system": { + "description": "Coding system used to describe the reason for administration of vaccine.", + "type": "string", + "example": "http://snomed.info/sct" + }, + "code": { + "description": "SNOMED code for the vaccination reason.", + "type": "string", + "example": "443684005" + }, + "display": { + "description": "Description of the vaccination reason.", + "type": "string", + "example": "Disease outbreak (event)" + } + } + } + } + } + } + }, + "protocolApplied": { + "description": "The protocol (set of recommendations) being followed by the provider who administered the dose.", + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "targetDisease", + "doseNumber[X]" + ], + "properties": { + "targetDisease": { + "type": "array", + "description": "The vaccine preventable disease the dose is being administered against.", + "items": { + "type": "object", + "properties": { + "coding": { + "type": "array", + "description": "A reference to a code defined by a terminology system.", + "items": { + "type": "object", + "properties": { + "system": { + "description": "The identification of the code system that defines the meaning of the symbol in the code.", + "type": "string" + }, + "code": { + "description": "A symbol in syntax defined by the system. The symbol may be a predefined code or an expression in a syntax defined by the coding system", + "type": "string" + }, + "display": { + "description": "A representation of the meaning of the code in the system, following the rules of the system.", + "type": "string" + } + }, + "required": [ + "system", + "code", + "display" + ] + } + } + }, + "required": ["coding"] + } + }, + "doseNumber[X]": { + "type": "object", + "description": "Nominal position in a series. This SHALL be provided but may be populated using either of the dataTypes available: PositiveInt or String. The use of an integer is preferred. Maximum value is 9.", + "properties": { + "doseNumberPositiveInt": { + "description": "Nominal position in a course of vaccines. This `SHOULD be populated` where the data is available. Maximum value is 9.", + "type": "integer", + "maximum": 9, + "example": 1 + }, + "doseNumberString": { + "description": "Description of the dose sequence where it is not a numeric or a reason a dose number cannot be provided. \nA string should only be used in cases where an integer is not available.", + "type": "string" + } + } + } + } + } + } + } + }, + { + "description": "Demographic information about the patient receiving an immunisation.", + "type": "object", + "required": ["resourceType", "id"], + "properties": { + "resourceType": { + "description": "FHIR resource type. Always `Patient`.", + "type": "string", + "example": "Patient" + }, + "id": { + "description": "Patient ID (NHS Number)", + "type": "string", + "example": "9000000009" + }, + "identifier": { + "description": "Unique identifier for this patient. Always an NHS number.", + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["system", "value"], + "properties": { + "system": { + "description": "Coding system used to identify patients.", + "type": "string", + "example": "https://fhir.nhs.uk/Id/nhs-number" + }, + "value": { + "description": "Code identifying the patient.", + "type": "string", + "example": "9000000009" + } + } + } + } + } + } + ] + }, + "search": { + "description": "Search-related information for the Immunization.", + "type": "object", + "required": ["mode"], + "properties": { + "mode": { + "description": "Indicates why this resource is in the result set. For Immunization resources this is always `match`.", + "enum": ["match"] + } + } + } + } + } + }, + "total": { + "description": "Number of matching immunisations found.", + "type": "integer", + "example": 1 + } + } + }, + "example": { + "resourceType": "Bundle", + "type": "searchset", + "link": [ + { + "relation": "self", + "url": "https://sandbox.api.service.nhs.uk/immunisation-fhir-api/Immunization?immunization.target=RSV&_include=Immunization%3Apatient&patient.identifier=https%3A%2F%2Ffhir.nhs.uk%2FId%2Fnhs-number%7C9000000009" + } + ], + "entry": [ + { + "fullUrl": "https://sandbox.api.service.nhs.uk/immunisation-fhir-api/Immunization/191f288a-17f3-4cd5-a33c-a52aade6473c", + "resource": { + "resourceType": "Immunization", + "id": "191f288a-17f3-4cd5-a33c-a52aade6473c", + "meta": { + "versionId": "1" + }, + "extension": [ + { + "url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "1303503001", + "display": "Administration of RSV (respiratory syncytial virus) vaccine" + } + ] + } + } + ], + "identifier": [ + { + "use": "official", + "system": "https://supplierABC/identifiers/vacc", + "value": "e2154d29-1ead-4830-a513-0d59705078fa" + } + ], + "status": "completed", + "vaccineCode": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "42605811000001109", + "display": "Abrysvo vaccine powder and solvent for solution for injection 0.5ml vials (Pfizer Ltd)" + } + ] + }, + "patient": { + "reference": "urn:uuid:a7a5bc28-5831-4158-8a73-0d3e6e43c1ac", + "type": "Patient", + "identifier": { + "system": "https://fhir.nhs.uk/Id/nhs-number", + "value": "9000000009" + } + }, + "occurrenceDateTime": "2021-02-07T13:28:17.271000+00:00", + "recorded": "2021-02-07T13:28:17.271000+00:00", + "primarySource": true, + "location": { + "identifier": { + "system": "https://fhir.nhs.uk/Id/ods-organization-code", + "value": "X99999" + } + }, + "manufacturer": { + "display": "AstraZeneca Ltd" + }, + "lotNumber": "4120Z001", + "expirationDate": "2021-07-02", + "site": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "368208006", + "display": "Left upper arm structure (body structure)" + } + ] + }, + "route": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "78421000", + "display": "Intramuscular route (qualifier value)" + } + ] + }, + "doseQuantity": { + "value": 0.5, + "unit": "milliliter", + "system": "http://unitsofmeasure.org", + "code": "ml" + }, + "performer": [ + { + "actor": { + "type": "Organization", + "identifier": { + "system": "https://fhir.nhs.uk/Id/ods-organization-code", + "value": "B0C4P" + }, + "display": "UNIVERSITY HOSPITAL OF WALES" + } + } + ], + "reasonCode": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "443684005", + "display": "Disease outbreak (event)" + } + ] + } + ], + "protocolApplied": [ + { + "targetDisease": [ + { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "55735004", + "display": "Respiratory syncytial virus infection (disorder)" + } + ] + } + ], + "doseNumberPositiveInt": 1 + } + ] + }, + "search": { + "mode": "match" + } + } + ], + "total": 1 + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/4XX-imms-search-by-identifier" + } + } + } + }, "/Immunization/{id}": { "get": { "summary": "Retrieve a record of an immunisation by its unique identifier", @@ -3428,6 +4261,40 @@ } } }, + "4XX-imms-search-by-identifier": { + "description": "Below are examples of potential HTTP status codes and their associated error codes, which could be returned in the event of a fault.\n\n| HTTP status | Error code | Description | Example |\n| ----------- | -------------------------- | --------------------------------------------- |--------------------------------------------------------------------------------------|\n| 400 | Bad Request | Missing identifier parameter | {\"resourceType\":\"OperationOutcome\",\"id\":\"2d4b2d7c-6c5e-4e3a-8f0d-4e6d9bdc3e5a\",\"meta\":{\"profile\":[\"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome\"]},\"issue\":[{\"severity\":\"error\",\"code\":\"invalid\",\"details\":{\"coding\":[{\"system\":\"https://fhir.nhs.uk/Codesystem/http-error-codes\",\"code\":\"INVALID\"}]},\"diagnostics\":\"Search parameter identifier must have one value.\"}]} |\n| 400 | Bad Request | Invalid value for identifier | {\"resourceType\":\"OperationOutcome\",\"id\":\"3c5e8a1f-2f61-4a9c-8f5e-615e8ea5d9a0\",\"meta\":{\"profile\":[\"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome\"]},\"issue\":[{\"severity\":\"error\",\"code\":\"invalid\",\"details\":{\"coding\":[{\"system\":\"https://fhir.nhs.uk/Codesystem/http-error-codes\",\"code\":\"INVALID\"}]},\"diagnostics\":\"identifier must be in the format of \\\"|\\\" e.g. \\\"https://supplierABC/identifiers/vacc|e2154d29-1ead-4830-a513-0d59705078fa\\\"\"}]} |\n| 400 | Bad Request | Conflicting search parameters | {\"resourceType\":\"OperationOutcome\",\"id\":\"1a7f4b9c-54b0-4a4d-8b2a-9f3f6a2b7cde\",\"meta\":{\"profile\":[\"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome\"]},\"issue\":[{\"severity\":\"error\",\"code\":\"invalid\",\"details\":{\"coding\":[{\"system\":\"https://fhir.nhs.uk/Codesystem/http-error-codes\",\"code\":\"INVALID\"}]},\"diagnostics\":\"When using 'identifier', do not supply patient.identifier, -immunization.target, -date.from or -date.to.\"}]} |\n| 401 | Unauthorized | Authorization is required for the interaction that was attempted | {\"resourceType\":\"OperationOutcome\",\"id\":\"a5abca2a-4eda-41da-b2cc-95d48c6b791d\",\"meta\":{\"profile\":[\"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome\"]},\"issue\":[{\"severity\":\"error\",\"code\":\"expired\",\"details\":{\"coding\":[{\"system\":\"https://fhir.nhs.uk/Codesystem/http-error-codes\",\"code\":\"SEND_UNAUTHORIZED\"}]},\"diagnostics\":\"The sender has not provided a token or it has expired or is otherwise invalid.\"}]} |\n| 403 | Forbidden | The sender does not have permissions to access this resource | {\"resourceType\":\"OperationOutcome\",\"id\":\"a5abca2a-4eda-41da-b2cc-95d48c6b791d\",\"meta\":{\"profile\":[\"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome\"]},\"issue\":[{\"severity\":\"error\",\"code\":\"forbidden\",\"details\":{\"coding\":[{\"system\":\"https://fhir.nhs.uk/Codesystem/http-error-codes\",\"code\":\"SEND_UNAUTHORIZED\"}]},\"diagnostics\":\"The sender does not have permissions to access this resource. Please check your credentials and permissions.\"}]} |\n| 403 | Forbidden | The sender does not have permission for the specific operation or vaccine type | {\"resourceType\": \"OperationOutcome\", \"id\": \"1b7eec0a-316f-4f6e-a342-4b37f6705050\", \"meta\": {\"profile\": [\"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome\"]}, \"issue\": [{\"severity\": \"error\", \"code\": \"forbidden\", \"details\": {\"coding\": [{\"system\": \"https://fhir.nhs.uk/Codesystem/http-error-codes\", \"code\": \"FORBIDDEN\"}]}, \"diagnostics\": \"Unauthorized request for vaccine type\"}]} |\n", + "content": { + "application/fhir+json": { + "schema": { + "$ref": "#/components/schemas/OperationOutcome" + }, + "example": { + "resourceType": "OperationOutcome", + "id": "3c5e8a1f-2f61-4a9c-8f5e-615e8ea5d9a0", + "meta": { + "profile": [ + "https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome" + ] + }, + "issue": [ + { + "severity": "error", + "code": "expired", + "details": { + "coding": [ + { + "system": "https://fhir.nhs.uk/Codesystem/http-error-codes", + "code": "SEND_UNAUTHORIZED" + } + ] + }, + "diagnostics": "The sender has not provided a token or it has expired or is otherwise invalid." + } + ] + } + } + } + }, "4XX-imms-read": { "description": "Below are examples of potential HTTP status codes and their associated error codes, which could be returned in the event of a fault.\n\n| HTTP status | Error code | Description | Example |\n| ----------- | -------------------------- | --------------------------------------------- |--------------------------------------------------------------------------------------|\n| 400 | Bad Request | Missing immunization event identifier (id) | {\"resourceType\": \"OperationOutcome\", \"id\": \"438f5c0d-f89d-46ec-b4ba-c08dc5a44ff3\", \"meta\": {\"profile\": [\"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome\"]}, \"issue\": [{\"severity\": \"error\", \"code\": \"invalid\", \"details\": {\"coding\": [{\"system\": \"https://fhir.nhs.uk/Codesystem/http-error-codes\", \"code\": \"INVALID\"}]}, \"diagnostics\": \"the provided event ID is either missing or not in the expected format.\"}]} |\n| 401 | Unauthorized | Authorization is required for the interaction that was attempted | {\"resourceType\":\"OperationOutcome\",\"id\":\"a5abca2a-4eda-41da-b2cc-95d48c6b791d\",\"meta\":{\"profile\":[\"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome\"]},\"issue\":[{\"severity\":\"error\",\"code\":\"expired\",\"details\":{\"coding\":[{\"system\":\"https://fhir.nhs.uk/Codesystem/http-error-codes\",\"code\":\"SEND_UNAUTHORIZED\"}]},\"diagnostics\":\"The sender has not provided a token or it has expired or is otherwise invalid.\"}]} |\n| 403 | Forbidden | The sender does not have permissions to access this resource | {\"resourceType\":\"OperationOutcome\",\"id\":\"a5abca2a-4eda-41da-b2cc-95d48c6b791d\",\"meta\":{\"profile\":[\"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome\"]},\"issue\":[{\"severity\":\"error\",\"code\":\"forbidden\",\"details\":{\"coding\":[{\"system\":\"https://fhir.nhs.uk/Codesystem/http-error-codes\",\"code\":\"SEND_UNAUTHORIZED\"}]},\"diagnostics\":\"The sender does not have permissions to access this resource. Please check your credentials and permissions.\"}]} |\n| 403 | Forbidden | The sender does not have permission for the specific operation or vaccine type | {\"resourceType\": \"OperationOutcome\", \"id\": \"1b7eec0a-316f-4f6e-a342-4b37f6705050\", \"meta\": {\"profile\": [\"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome\"]}, \"issue\": [{\"severity\": \"error\", \"code\": \"forbidden\", \"details\": {\"coding\": [{\"system\": \"https://fhir.nhs.uk/Codesystem/http-error-codes\", \"code\": \"FORBIDDEN\"}]}, \"diagnostics\": \"Unauthorized request for vaccine type\"}]} |\n| 404 | Not Found | Unrecognized immunization event identifier (id) | {\"resourceType\": \"OperationOutcome\", \"id\": \"5b51e331-5f9c-442f-a4c3-b5f52a1ba83e\", \"meta\": {\"profile\": [\"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome\"]}, \"issue\": [{\"severity\": \"error\", \"code\": \"not-found\", \"details\": {\"coding\": [{\"system\": \"https://fhir.nhs.uk/Codesystem/http-error-codes\", \"code\": \"NOT-FOUND\"}]}, \"diagnostics\": \"The requested resource was not found.\"}]} |\n", "content": { @@ -3725,6 +4592,16 @@ "default": "Immunization:patient" } }, + "Identifier": { + "in": "query", + "name": "identifier", + "description": "Search by Immunization resource identifier. Expressed as `|` per FHIR search, for example `https://supplierABC/identifiers/vacc|e2154d29-1ead-4830-a513-0d59705078fa`. When using this parameter, other search parameters such as `patient.identifier` and `-immunization.target` are not required.", + "required": true, + "schema": { + "type": "string", + "example": "https://supplierABC/identifiers/vacc|e2154d29-1ead-4830-a513-0d59705078fa" + } + }, "E-Tag": { "in": "header", "name": "E-Tag", diff --git a/specification/immunisation-fhir-api.yaml b/specification/immunisation-fhir-api.yaml index f04926ad0..9171dee37 100644 --- a/specification/immunisation-fhir-api.yaml +++ b/specification/immunisation-fhir-api.yaml @@ -2,6 +2,9 @@ openapi: 3.0.0 info: title: Immunization-fhir-api version: Computed and injected at build time by `utilities/scripts/set_version.py` + license: + name: MIT + url: https://opensource.org/licenses/MIT description: | ## Overview @@ -766,201 +769,6 @@ components: description: The base language in which the resource is written. required: - resourceType - DomainResource: - type: object - properties: - text: - $ref: "#/components/schemas/Narrative" - description: A human-readable narrative that contains a summary of the resource and can be used to represent the content of the resource to a human. The narrative need not encode all the structured data, but is required to contain sufficient detail to make it "clinically safe" for a human to just read the narrative. Resource definitions may define what content should be represented in the narrative to ensure clinical safety. - contained: - type: array - items: - description: These resources do not have an independent existence apart from the resource that contains them - they cannot be identified independently, and nor can they have their own independent transaction scope. - type: object - discriminator: - propertyName: resourceType - properties: - resourceType: - type: string - enum: - - Resource - - DomainResource - - Account - - ActivityDefinition - - AdverseEvent - - AllergyIntolerance - - Appointment - - AppointmentResponse - - AuditEvent - - Basic - - Binary - - BiologicallyDerivedProduct - - BodyStructure - - Bundle - - CapabilityStatement - - CarePlan - - CareTeam - - CatalogEntry - - ChargeItem - - ChargeItemDefinition - - Claim - - ClaimResponse - - ClinicalImpression - - CodeSystem - - Communication - - CommunicationRequest - - CompartmentDefinition - - Composition - - ConceptMap - - Condition - - Consent - - Contract - - Coverage - - CoverageEligibilityRequest - - CoverageEligibilityResponse - - DetectedIssue - - Device - - DeviceDefinition - - DeviceMetric - - DeviceRequest - - DeviceUseStatement - - DiagnosticReport - - DocumentManifest - - DocumentReference - - EffectEvidenceSynthesis - - Encounter - - Endpoint - - EnrollmentRequest - - EnrollmentResponse - - EpisodeOfCare - - EventDefinition - - Evidence - - EvidenceVariable - - ExampleScenario - - ExplanationOfBenefit - - FamilyMemberHistory - - Flag - - Goal - - GraphDefinition - - Group - - GuidanceResponse - - HealthcareService - - ImagingStudy - - Immunization - - ImmunizationEvaluation - - ImmunizationRecommendation - - ImplementationGuide - - InsurancePlan - - Invoice - - Library - - Linkage - - List - - Location - - Measure - - MeasureReport - - Media - - Medication - - MedicationAdministration - - MedicationDispense - - MedicationKnowledge - - MedicationRequest - - MedicationStatement - - MedicinalProduct - - MedicinalProductAuthorization - - MedicinalProductContraindication - - MedicinalProductIndication - - MedicinalProductIngredient - - MedicinalProductInteraction - - MedicinalProductManufactured - - MedicinalProductPackaged - - MedicinalProductPharmaceutical - - MedicinalProductUndesirableEffect - - MessageDefinition - - MessageHeader - - MolecularSequence - - NamingSystem - - NutritionOrder - - Observation - - ObservationDefinition - - OperationDefinition - - OperationOutcome - - Organization - - OrganizationAffiliation - - Parameters - - Patient - - PaymentNotice - - PaymentReconciliation - - Person - - PlanDefinition - - Practitioner - - PractitionerRole - - Procedure - - Provenance - - Questionnaire - - QuestionnaireResponse - - RelatedPerson - - RequestGroup - - ResearchDefinition - - ResearchElementDefinition - - ResearchStudy - - ResearchSubject - - RiskAssessment - - RiskEvidenceSynthesis - - Schedule - - SearchParameter - - ServiceRequest - - Slot - - Specimen - - SpecimenDefinition - - StructureDefinition - - StructureMap - - Subscription - - Substance - - SubstanceNucleicAcid - - SubstancePolymer - - SubstanceProtein - - SubstanceReferenceInformation - - SubstanceSourceMaterial - - SubstanceSpecification - - SupplyDelivery - - SupplyRequest - - Task - - TerminologyCapabilities - - TestReport - - TestScript - - ValueSet - - VerificationResult - - VisionPrescription - id: - type: string - pattern: '[A-Za-z0-9\-\.]{1,64}' - description: The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes. - meta: - $ref: "#/components/schemas/Meta" - description: The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource. - implicitRules: - type: string - pattern: \S* - description: A reference to a set of rules that were followed when the resource was constructed, and which must be understood when processing the content. Often, this is a reference to an implementation guide that defines the special rules along with other profiles etc. - language: - type: string - pattern: '[^\s]+(\s[^\s]+)*' - description: The base language in which the resource is written. - required: - - resourceType - extension: - type: array - items: - $ref: "#/components/schemas/Extension" - description: May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. - modifierExtension: - type: array - items: - $ref: "#/components/schemas/Extension" - description: |- - May be used to represent additional information that is not part of the basic definition of the resource and that modifies the understanding of the element that contains it and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer is allowed to define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions. - - Modifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself). Immunization: type: object required: diff --git a/tests/e2e/poetry.lock b/tests/e2e/poetry.lock index 93282ea48..30fc1bb25 100644 --- a/tests/e2e/poetry.lock +++ b/tests/e2e/poetry.lock @@ -2,18 +2,18 @@ [[package]] name = "boto3" -version = "1.40.68" +version = "1.40.72" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.40.68-py3-none-any.whl", hash = "sha256:4f08115e3a4d1e1056003e433d393e78c20da6af7753409992bb33fb69f04186"}, - {file = "boto3-1.40.68.tar.gz", hash = "sha256:c7994989e5bbba071b7c742adfba35773cf03e87f5d3f9f2b0a18c1664417b61"}, + {file = "boto3-1.40.72-py3-none-any.whl", hash = "sha256:1063a295712f2605d3e463e4dc1fe32fce17cf77a0f4d3bb14249d68533ee856"}, + {file = "boto3-1.40.72.tar.gz", hash = "sha256:58d30dd5e046789a760db7a49f817650b8ff08d8d169e127976a61f44b7c59ad"}, ] [package.dependencies] -botocore = ">=1.40.68,<1.41.0" +botocore = ">=1.40.72,<1.41.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.14.0,<0.15.0" @@ -22,14 +22,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.40.68" +version = "1.40.72" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.40.68-py3-none-any.whl", hash = "sha256:9d514f9c9054e1af055f2cbe9e0d6771d407a600206d45a01b54d5f09538fecb"}, - {file = "botocore-1.40.68.tar.gz", hash = "sha256:28f41b463d9f012a711ee8b61d4e26cd14ee3b450b816d5dee849aa79155e856"}, + {file = "botocore-1.40.72-py3-none-any.whl", hash = "sha256:4f859e5aaf871fe59aac431d6bba59cc0c8ed8a38da2a6a5345700bdc5c74b32"}, + {file = "botocore-1.40.72.tar.gz", hash = "sha256:f69199ff6570695556e733fa052f2739e01e0c592c9b60f843f84c77ba3bcdf3"}, ] [package.dependencies] @@ -764,4 +764,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.1" python-versions = "~3.11" -content-hash = "c278ca9c39b0c3d9a88cabec2788ea0cdd2d281b5fbf2762dccbda12f52e7c95" +content-hash = "c98a1c816223bb9757590e5deaae225606f6d47163b7dc6a2f2f98b863e61ca7" diff --git a/tests/e2e/pyproject.toml b/tests/e2e/pyproject.toml index 64bbba0fe..1347ed2bb 100644 --- a/tests/e2e/pyproject.toml +++ b/tests/e2e/pyproject.toml @@ -8,7 +8,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "~3.11" -boto3 = "^1.40.68" +boto3 = "^1.40.72" mypy-boto3-dynamodb = "^1.40.44" simplejson = "^3.20.2" diff --git a/tests/e2e_batch/poetry.lock b/tests/e2e_batch/poetry.lock index 64eec22de..394088d5c 100644 --- a/tests/e2e_batch/poetry.lock +++ b/tests/e2e_batch/poetry.lock @@ -2,18 +2,18 @@ [[package]] name = "boto3" -version = "1.40.68" +version = "1.40.72" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.40.68-py3-none-any.whl", hash = "sha256:4f08115e3a4d1e1056003e433d393e78c20da6af7753409992bb33fb69f04186"}, - {file = "boto3-1.40.68.tar.gz", hash = "sha256:c7994989e5bbba071b7c742adfba35773cf03e87f5d3f9f2b0a18c1664417b61"}, + {file = "boto3-1.40.72-py3-none-any.whl", hash = "sha256:1063a295712f2605d3e463e4dc1fe32fce17cf77a0f4d3bb14249d68533ee856"}, + {file = "boto3-1.40.72.tar.gz", hash = "sha256:58d30dd5e046789a760db7a49f817650b8ff08d8d169e127976a61f44b7c59ad"}, ] [package.dependencies] -botocore = ">=1.40.68,<1.41.0" +botocore = ">=1.40.72,<1.41.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.14.0,<0.15.0" @@ -22,14 +22,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.40.68" +version = "1.40.72" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.40.68-py3-none-any.whl", hash = "sha256:9d514f9c9054e1af055f2cbe9e0d6771d407a600206d45a01b54d5f09538fecb"}, - {file = "botocore-1.40.68.tar.gz", hash = "sha256:28f41b463d9f012a711ee8b61d4e26cd14ee3b450b816d5dee849aa79155e856"}, + {file = "botocore-1.40.72-py3-none-any.whl", hash = "sha256:4f859e5aaf871fe59aac431d6bba59cc0c8ed8a38da2a6a5345700bdc5c74b32"}, + {file = "botocore-1.40.72.tar.gz", hash = "sha256:f69199ff6570695556e733fa052f2739e01e0c592c9b60f843f84c77ba3bcdf3"}, ] [package.dependencies] @@ -322,4 +322,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.1" python-versions = "~3.11" -content-hash = "0ff787b16d3a7aff957c2339935da3e2b2f47a676240bc4ee72777cc2f9d8a6e" +content-hash = "a6d6463b88d34c47e3eff634ff0a298c69ca0efdb7a1a647c3a2e4ac6b8385b6" diff --git a/tests/e2e_batch/pyproject.toml b/tests/e2e_batch/pyproject.toml index a5c0b086f..b1066799d 100644 --- a/tests/e2e_batch/pyproject.toml +++ b/tests/e2e_batch/pyproject.toml @@ -8,5 +8,5 @@ readme = "README.md" [tool.poetry.dependencies] python = "~3.11" -boto3 = "~1.40.68" +boto3 = "~1.40.72" pandas = "^2.3.3" diff --git a/tests/e2e_batch/scenarios.py b/tests/e2e_batch/scenarios.py index 394652aac..052146458 100644 --- a/tests/e2e_batch/scenarios.py +++ b/tests/e2e_batch/scenarios.py @@ -3,9 +3,10 @@ from datetime import datetime, timezone import pandas as pd +from clients import logger +from errors import DynamoDBMismatchError from vax_suppliers import OdsVax, TestPair -from clients import logger from constants import ( ACK_BUCKET, RAVS_URI, @@ -15,7 +16,6 @@ Operation, OperationOutcome, ) -from errors import DynamoDBMismatchError from utils import ( aws_cleanup, create_row, diff --git a/tests/e2e_batch/test_e2e_batch.py b/tests/e2e_batch/test_e2e_batch.py index b4ca5394d..63a45a077 100644 --- a/tests/e2e_batch/test_e2e_batch.py +++ b/tests/e2e_batch/test_e2e_batch.py @@ -1,6 +1,7 @@ import time import unittest +from clients import logger from scenarios import ( TestCase, create_test_cases, @@ -9,7 +10,6 @@ scenarios, ) -from clients import logger from constants import ( ACK_BUCKET, INPUT_PREFIX, diff --git a/tests/e2e_batch/utils.py b/tests/e2e_batch/utils.py index bb95bf69a..e240741f5 100644 --- a/tests/e2e_batch/utils.py +++ b/tests/e2e_batch/utils.py @@ -11,7 +11,6 @@ import pandas as pd from boto3.dynamodb.conditions import Key from botocore.exceptions import ClientError - from clients import ( ack_metadata_queue_url, audit_table, @@ -21,6 +20,8 @@ s3_client, sqs_client, ) +from errors import AckFileNotFoundError, DynamoDBMismatchError + from constants import ( ACK_BUCKET, ACK_PREFIX, @@ -33,7 +34,6 @@ ActionFlag, environment, ) -from errors import AckFileNotFoundError, DynamoDBMismatchError def upload_file_to_s3(file_name, bucket, prefix):