From 497feba3199cc68db77a4da8dbb53ea4d63a8e78 Mon Sep 17 00:00:00 2001 From: Sarah Johnson Date: Wed, 7 Feb 2024 14:55:50 +0000 Subject: [PATCH 1/3] validate_json_schema() function --- .vscode/settings.json | 9 +- Makefile | 6 +- dpytools/config/config.py | 4 +- dpytools/config/properties/__init__.py | 2 +- dpytools/config/properties/base.py | 7 +- dpytools/config/properties/string.py | 10 +- dpytools/http_clients/base.py | 40 +-- dpytools/logger/logger.py | 7 +- dpytools/slack/slack.py | 9 +- dpytools/sns/sns.py | 7 +- dpytools/validation/json/validation.py | 68 ++++ poetry.lock | 300 +++++++++--------- .../logging_schema.json} | 0 .../logging_schema_with_error.json} | 0 tests/test_cases/pipeline_config.json | 20 ++ .../pipeline_config_invalid_data_type.json | 20 ++ ...ipeline_config_missing_required_field.json | 19 ++ tests/test_cases/pipeline_config_schema.json | 72 +++++ tests/test_http.py | 37 +-- tests/test_json_validation.py | 132 ++++++++ tests/test_logging.py | 4 +- tests/test_nothing.py | 7 - 22 files changed, 557 insertions(+), 223 deletions(-) create mode 100644 dpytools/validation/json/validation.py rename tests/{schema.json => test_cases/logging_schema.json} (100%) rename tests/{schema_with_error.json => test_cases/logging_schema_with_error.json} (100%) create mode 100644 tests/test_cases/pipeline_config.json create mode 100644 tests/test_cases/pipeline_config_invalid_data_type.json create mode 100644 tests/test_cases/pipeline_config_missing_required_field.json create mode 100644 tests/test_cases/pipeline_config_schema.json create mode 100644 tests/test_json_validation.py delete mode 100644 tests/test_nothing.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 9b38853..2748e2d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,12 @@ "tests" ], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "python.testing.unittestArgs": [ + "-v", + "-s", + "./tests", + "-p", + "test_*.py" + ], } \ No newline at end of file diff --git a/Makefile b/Makefile index c64d35b..c01abb7 100644 --- a/Makefile +++ b/Makefile @@ -5,11 +5,11 @@ help: @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' fmt: ## (Format) - runs black and isort against the codebase - poetry run black ./src/* - poetry run isort ./src/* + poetry run black ./dpytools/* + poetry run isort ./dpytools/* lint: ## Run the ruff python linter - poetry run ruff ./src/* + poetry run ruff ./dpytools/* test: ## Run pytest and check test coverage poetry run pytest --cov-report term-missing --cov=dpytools diff --git a/dpytools/config/config.py b/dpytools/config/config.py index efc38e3..07a00e0 100644 --- a/dpytools/config/config.py +++ b/dpytools/config/config.py @@ -2,8 +2,8 @@ from .properties.base import BaseProperty -class Config: +class Config: @staticmethod def from_env(config_dict: Dict[str, BaseProperty]): # TODO = read in and populate property classes as @@ -34,5 +34,3 @@ def assert_valid_config(self): # For each of the properties you imbided above, run # self.type_is_valid() # self.secondary_validation() - - diff --git a/dpytools/config/properties/__init__.py b/dpytools/config/properties/__init__.py index d6eca2d..f8c6d89 100644 --- a/dpytools/config/properties/__init__.py +++ b/dpytools/config/properties/__init__.py @@ -1 +1 @@ -from .string import StringProperty \ No newline at end of file +from .string import StringProperty diff --git a/dpytools/config/properties/base.py b/dpytools/config/properties/base.py index 02dd34c..b1376de 100644 --- a/dpytools/config/properties/base.py +++ b/dpytools/config/properties/base.py @@ -1,6 +1,7 @@ from abc import ABCMeta, abstractmethod from dataclasses import dataclass -from typing import Any, Union, Tuple, Optional +from typing import Any + @dataclass class BaseProperty(metaclass=ABCMeta): @@ -20,7 +21,7 @@ class BaseProperty(metaclass=ABCMeta): def type_is_valid(self): """ Validate that the property looks like - its of the correct type + its of the correct type """ ... @@ -32,4 +33,4 @@ def secondary_validation(self): Non type based validation you might want to run against a configuration value. """ - ... \ No newline at end of file + ... diff --git a/dpytools/config/properties/string.py b/dpytools/config/properties/string.py index 74aa102..99bfa56 100644 --- a/dpytools/config/properties/string.py +++ b/dpytools/config/properties/string.py @@ -11,12 +11,14 @@ class StringProperty(BaseProperty): def type_is_valid(self): """ Validate that the property looks like - its of the correct type + its of the correct type """ try: str(self.value) except Exception as err: - raise Exception(f"Cannot cast {self.name} value {self.value} to string.") from err + raise Exception( + f"Cannot cast {self.name} value {self.value} to string." + ) from err def secondary_validation_passed(self): """ @@ -25,7 +27,7 @@ def secondary_validation_passed(self): """ if len(self.value) == 0: raise ValueError(f"Str value for {self.name} is an empty string") - + if self.regex: # TODO - confirm the value matches the regex ... @@ -36,4 +38,4 @@ def secondary_validation_passed(self): if self.max_len: # TODO - confirm the value matches or is less than the max length - ... \ No newline at end of file + ... diff --git a/dpytools/http_clients/base.py b/dpytools/http_clients/base.py index 8ae89ea..ab6458d 100644 --- a/dpytools/http_clients/base.py +++ b/dpytools/http_clients/base.py @@ -1,7 +1,8 @@ +import logging + import backoff import requests from requests.exceptions import HTTPError -import logging # Function to log retry attempts @@ -15,19 +16,14 @@ def __init__(self, backoff_max=30): self.backoff_max = backoff_max # GET request method with exponential backoff - @backoff.on_exception( - backoff.expo, - HTTPError, - max_time=30, - on_backoff=log_retry - ) + @backoff.on_exception(backoff.expo, HTTPError, max_time=30, on_backoff=log_retry) def get(self, url, *args, **kwargs): """ Sends a GET request to the specified URL with optional extra arguments. - This method is a thin wrapper around `requests.get()`. Any additional arguments - are passed directly to `requests.get()`. For more information on the available - arguments, refer to the `requests.get()` documentation: + This method is a thin wrapper around `requests.get()`. Any additional arguments + are passed directly to `requests.get()`. For more information on the available + arguments, refer to the `requests.get()` documentation: https://docs.python-requests.org/en/latest/api/#requests.get Args: @@ -40,22 +36,22 @@ def get(self, url, *args, **kwargs): Raises: HTTPError: If the request fails for a network-related reason. """ - return self._handle_request('GET', url, *args, **kwargs) + return self._handle_request("GET", url, *args, **kwargs) # POST request method with exponential backoff @backoff.on_exception( - backoff.expo, + backoff.expo, HTTPError, - max_time=30, + max_time=30, on_backoff=log_retry, ) def post(self, url, *args, **kwargs): """ Sends a POST request to the specified URL with optional extra arguments. - This method is a thin wrapper around `requests.post()`. Any additional arguments - are passed directly to `requests.post()`. For more information on the available - arguments, refer to the `requests.post()` documentation: + This method is a thin wrapper around `requests.post()`. Any additional arguments + are passed directly to `requests.post()`. For more information on the available + arguments, refer to the `requests.post()` documentation: https://docs.python-requests.org/en/latest/api/#requests.post Args: @@ -69,8 +65,8 @@ def post(self, url, *args, **kwargs): Raises: HTTPError: If the request fails for a network-related reason. """ - return self._handle_request('POST', url, *args, **kwargs) - + return self._handle_request("POST", url, *args, **kwargs) + # Method to handle requests for GET and POST def _handle_request(self, method, url, *args, **kwargs): logging.info(f"Sending {method} request to {url}") @@ -80,8 +76,12 @@ def _handle_request(self, method, url, *args, **kwargs): return response except HTTPError as http_err: - logging.error(f"HTTP error occurred: {http_err} when sending a {method} to {url} with headers {kwargs.get('headers')}") + logging.error( + f"HTTP error occurred: {http_err} when sending a {method} to {url} with headers {kwargs.get('headers')}" + ) raise http_err except Exception as err: - logging.error(f"Other error occurred: {err} when sending a {method} to {url} with headers {kwargs.get('headers')}") + logging.error( + f"Other error occurred: {err} when sending a {method} to {url} with headers {kwargs.get('headers')}" + ) raise err diff --git a/dpytools/logger/logger.py b/dpytools/logger/logger.py index 3c4a10a..fcb5a1b 100644 --- a/dpytools/logger/logger.py +++ b/dpytools/logger/logger.py @@ -1,8 +1,9 @@ -from typing import Dict, List, Optional, Union -import structlog -import traceback import json +import traceback from datetime import datetime, timezone +from typing import Dict, List, Optional + +import structlog def level_to_severity(level: int) -> int: diff --git a/dpytools/slack/slack.py b/dpytools/slack/slack.py index 13eb617..efaaf3a 100644 --- a/dpytools/slack/slack.py +++ b/dpytools/slack/slack.py @@ -1,11 +1,12 @@ import logging + from dpytools.http_clients.base import BaseHttpClient -class SlackNotifier: +class SlackNotifier: def __init__(self, webhook_url): if not webhook_url: - raise ValueError('webhook_url is not set') + raise ValueError("webhook_url is not set") self.webhook_url = webhook_url self.http_client = BaseHttpClient() @@ -20,7 +21,7 @@ def notify(self, msg_dict: dict): response = self.http_client.post(self.webhook_url, json=msg_dict) response.raise_for_status() except Exception as e: - logging.error(f'Failed to send notification: {e}') + logging.error(f"Failed to send notification: {e}") def msg_str(self, msg: str): """ @@ -28,4 +29,4 @@ def msg_str(self, msg: str): The msg parameter is wrapped into a dictionary before being sent. """ - self.notify({'text': msg}) \ No newline at end of file + self.notify({"text": msg}) diff --git a/dpytools/sns/sns.py b/dpytools/sns/sns.py index ab20c43..0c50a2b 100644 --- a/dpytools/sns/sns.py +++ b/dpytools/sns/sns.py @@ -1,4 +1,3 @@ - # See here: https://docs.aws.amazon.com/code-library/latest/ug/python_3_sns_code_examples.html # Ideally we want to publish a message by passing in a dictionary. # If SNS doesn not support that stringify any dict that is a message @@ -12,12 +11,11 @@ from typing import Union + # Note: return True if it works and False if we hit errors # (so we can control the flow in calling programs) def publish(topic: str, msg: Union[str, dict]) -> bool: - """ - - """ + """ """ # For this you'll want boto3 again, create a subscription @@ -25,7 +23,6 @@ def publish(topic: str, msg: Union[str, dict]) -> bool: # The get_message() needs to pull from the queue that's # been subscribed to. class Subscription: - def __init__(self, topic): """ subscrube to a topic (i.e setup to read messages) diff --git a/dpytools/validation/json/validation.py b/dpytools/validation/json/validation.py new file mode 100644 index 0000000..97a12a3 --- /dev/null +++ b/dpytools/validation/json/validation.py @@ -0,0 +1,68 @@ +import json +from pathlib import Path +from typing import Dict, Optional, Union +from urllib.parse import urlparse + +import jsonschema +from jsonschema import ValidationError + +""" +# data_dict is for passing in a dictionary to be validated +validate_json_schema("/path/to/schema.json", data_dict=some_dictionary) + +# msg is to include a helpful context when debugging (i.e "what we're validating") +validate_json_schema("/path/to/schema.json", data_dict=some_dictionary, msg="Some helpful message should this validation fail") + +validate_json_schema("/path/to/schema.json", data_path="/path/to/some/json", msg="Some helpful message should this validation fail") + +# indent should pretty print the json contents of the error to make it +# more easily parsed by humans +validate_json_schema("/path/to/schema.json", data_dict=some_dictionary, indent=2) +""" + + +def validate_json_schema( + schema_path: str, + data_dict_or_path: Union[Dict, str], + msg: str = "", + indent: int = 2, +) -> Optional[ValidationError]: + """ + Validate metadata.json files against schema provided. + + `schema_path`: file path of schema to validate against + `data_dict_or_path`: file path or dictionary of data to be validated + `msg`: optional string to provide additional information about validation + `indent`: optional integer to be used when indenting json output + """ + # Load schema as dict + parsed_schema_path = urlparse(schema_path) + if parsed_schema_path.scheme == "http": + # TODO Load schema from URL + raise NotImplementedError("Validation from remote schema not yet supported") + else: + with open(Path(schema_path), "r") as f: + schema_from_path = json.load(f) + + # Load data as dict + if isinstance(data_dict_or_path, Dict): + data_to_validate = data_dict_or_path + elif isinstance(data_dict_or_path, str): + with open(Path(data_dict_or_path), "r") as f: + data_to_validate = json.load(f) + else: + raise ValueError("Invalid data format") + + # Validate data against schema + print(msg) + try: + jsonschema.validate(data_to_validate, schema_from_path) + except jsonschema.ValidationError as err: + # TODO Handle jsonschema.SchemaError? + print(f"Error when validating data: {err.message}") + # If the error relates to a specific field, print the error's location + if err.json_path != "$": + print(f"Error in data field: {err.json_path}") + # Print the data that failed validation + print(f"Contents of data:\n{json.dumps(data_to_validate, indent=indent)}") + return err diff --git a/poetry.lock b/poetry.lock index 5ed96e0..fc596ef 100644 --- a/poetry.lock +++ b/poetry.lock @@ -32,29 +32,33 @@ files = [ [[package]] name = "black" -version = "23.11.0" +version = "23.12.1" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, - {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, - {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, - {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, - {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, - {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, - {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, - {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, - {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, - {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, - {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, - {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, - {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, - {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, - {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, - {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, - {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, - {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, + {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, + {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, + {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, + {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, + {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, + {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, + {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, + {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, + {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, + {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, + {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, + {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, + {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, + {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, + {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, + {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, + {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, + {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, + {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, + {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, + {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, + {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, ] [package.dependencies] @@ -68,19 +72,19 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -209,63 +213,63 @@ files = [ [[package]] name = "coverage" -version = "7.3.2" +version = "7.4.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, - {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, - {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, - {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, - {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, - {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, - {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, - {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, - {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, - {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, - {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, - {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, - {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, - {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, + {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, + {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, + {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, + {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, + {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, + {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, + {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, + {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, + {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, + {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, + {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, + {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, ] [package.dependencies] @@ -312,20 +316,17 @@ files = [ [[package]] name = "isort" -version = "5.12.0" +version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" files = [ - {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, - {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, ] [package.extras] -colors = ["colorama (>=0.4.3)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] +colors = ["colorama (>=0.4.6)"] [[package]] name = "jsonschema" @@ -386,39 +387,39 @@ files = [ [[package]] name = "pathspec" -version = "0.11.2" +version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] name = "platformdirs" -version = "4.0.0" +version = "4.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, - {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -467,19 +468,40 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "referencing" -version = "0.32.1" +version = "0.33.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" files = [ - {file = "referencing-0.32.1-py3-none-any.whl", hash = "sha256:7e4dc12271d8e15612bfe35792f5ea1c40970dadf8624602e33db2758f7ee554"}, - {file = "referencing-0.32.1.tar.gz", hash = "sha256:3c57da0513e9563eb7e203ebe9bb3a1b509b042016433bd1e45a2853466c3dd3"}, + {file = "referencing-0.33.0-py3-none-any.whl", hash = "sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5"}, + {file = "referencing-0.33.0.tar.gz", hash = "sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7"}, ] [package.dependencies] attrs = ">=22.2.0" rpds-py = ">=0.7.0" +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[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 = "rpds-py" version = "0.17.1" @@ -588,67 +610,46 @@ files = [ {file = "rpds_py-0.17.1.tar.gz", hash = "sha256:0210b2668f24c078307260bf88bdac9d6f1093635df5123789bfee4d8d7fc8e7"}, ] -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.7" -files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, -] - -[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 = "ruff" -version = "0.1.6" +version = "0.1.15" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.6-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:88b8cdf6abf98130991cbc9f6438f35f6e8d41a02622cc5ee130a02a0ed28703"}, - {file = "ruff-0.1.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c549ed437680b6105a1299d2cd30e4964211606eeb48a0ff7a93ef70b902248"}, - {file = "ruff-0.1.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cf5f701062e294f2167e66d11b092bba7af6a057668ed618a9253e1e90cfd76"}, - {file = "ruff-0.1.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:05991ee20d4ac4bb78385360c684e4b417edd971030ab12a4fbd075ff535050e"}, - {file = "ruff-0.1.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87455a0c1f739b3c069e2f4c43b66479a54dea0276dd5d4d67b091265f6fd1dc"}, - {file = "ruff-0.1.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:683aa5bdda5a48cb8266fcde8eea2a6af4e5700a392c56ea5fb5f0d4bfdc0240"}, - {file = "ruff-0.1.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:137852105586dcbf80c1717facb6781555c4e99f520c9c827bd414fac67ddfb6"}, - {file = "ruff-0.1.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd98138a98d48a1c36c394fd6b84cd943ac92a08278aa8ac8c0fdefcf7138f35"}, - {file = "ruff-0.1.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0cd909d25f227ac5c36d4e7e681577275fb74ba3b11d288aff7ec47e3ae745"}, - {file = "ruff-0.1.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8fd1c62a47aa88a02707b5dd20c5ff20d035d634aa74826b42a1da77861b5ff"}, - {file = "ruff-0.1.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fd89b45d374935829134a082617954120d7a1470a9f0ec0e7f3ead983edc48cc"}, - {file = "ruff-0.1.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:491262006e92f825b145cd1e52948073c56560243b55fb3b4ecb142f6f0e9543"}, - {file = "ruff-0.1.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ea284789861b8b5ca9d5443591a92a397ac183d4351882ab52f6296b4fdd5462"}, - {file = "ruff-0.1.6-py3-none-win32.whl", hash = "sha256:1610e14750826dfc207ccbcdd7331b6bd285607d4181df9c1c6ae26646d6848a"}, - {file = "ruff-0.1.6-py3-none-win_amd64.whl", hash = "sha256:4558b3e178145491e9bc3b2ee3c4b42f19d19384eaa5c59d10acf6e8f8b57e33"}, - {file = "ruff-0.1.6-py3-none-win_arm64.whl", hash = "sha256:03910e81df0d8db0e30050725a5802441c2022ea3ae4fe0609b76081731accbc"}, - {file = "ruff-0.1.6.tar.gz", hash = "sha256:1b09f29b16c6ead5ea6b097ef2764b42372aebe363722f1605ecbcd2b9207184"}, + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df"}, + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d432aec35bfc0d800d4f70eba26e23a352386be3a6cf157083d18f6f5881c8"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9405fa9ac0e97f35aaddf185a1be194a589424b8713e3b97b762336ec79ff807"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66ec24fe36841636e814b8f90f572a8c0cb0e54d8b5c2d0e300d28a0d7bffec"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6f8ad828f01e8dd32cc58bc28375150171d198491fc901f6f98d2a39ba8e3ff5"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86811954eec63e9ea162af0ffa9f8d09088bab51b7438e8b6488b9401863c25e"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4025ac5e87d9b80e1f300207eb2fd099ff8200fa2320d7dc066a3f4622dc6b"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b17b93c02cdb6aeb696effecea1095ac93f3884a49a554a9afa76bb125c114c1"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ddb87643be40f034e97e97f5bc2ef7ce39de20e34608f3f829db727a93fb82c5"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:abf4822129ed3a5ce54383d5f0e964e7fef74a41e48eb1dfad404151efc130a2"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6c629cf64bacfd136c07c78ac10a54578ec9d1bd2a9d395efbee0935868bf852"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1bab866aafb53da39c2cadfb8e1c4550ac5340bb40300083eb8967ba25481447"}, + {file = "ruff-0.1.15-py3-none-win32.whl", hash = "sha256:2417e1cb6e2068389b07e6fa74c306b2810fe3ee3476d5b8a96616633f40d14f"}, + {file = "ruff-0.1.15-py3-none-win_amd64.whl", hash = "sha256:3837ac73d869efc4182d9036b1405ef4c73d9b1f88da2413875e34e0d6919587"}, + {file = "ruff-0.1.15-py3-none-win_arm64.whl", hash = "sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360"}, + {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, ] [[package]] name = "structlog" -version = "23.2.0" +version = "23.3.0" description = "Structured Logging for Python" optional = false python-versions = ">=3.8" files = [ - {file = "structlog-23.2.0-py3-none-any.whl", hash = "sha256:16a167e87b9fa7fae9a972d5d12805ef90e04857a93eba479d4be3801a6a1482"}, - {file = "structlog-23.2.0.tar.gz", hash = "sha256:334666b94707f89dbc4c81a22a8ccd34449f0201d5b1ee097a030b577fa8c858"}, + {file = "structlog-23.3.0-py3-none-any.whl", hash = "sha256:d6922a88ceabef5b13b9eda9c4043624924f60edbb00397f4d193bd754cde60a"}, + {file = "structlog-23.3.0.tar.gz", hash = "sha256:24b42b914ac6bc4a4e6f716e82ac70d7fb1e8c3b1035a765591953bfc37101a5"}, ] [package.extras] dev = ["structlog[tests,typing]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "twisted"] +docs = ["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"] @@ -665,32 +666,33 @@ files = [ [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] name = "urllib3" -version = "2.1.0" +version = "2.2.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, - {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, + {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, + {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = ">=3.9, <3.12" -content-hash = "89971abc642ac8eee0ac24fb29e4b692fe789360aba1aafd071a68a036efa43f" +content-hash = "3787a66b3361f30ff714f018623a0e219c492cff7de767b0ac48b2fa386285e7" diff --git a/tests/schema.json b/tests/test_cases/logging_schema.json similarity index 100% rename from tests/schema.json rename to tests/test_cases/logging_schema.json diff --git a/tests/schema_with_error.json b/tests/test_cases/logging_schema_with_error.json similarity index 100% rename from tests/schema_with_error.json rename to tests/test_cases/logging_schema_with_error.json diff --git a/tests/test_cases/pipeline_config.json b/tests/test_cases/pipeline_config.json new file mode 100644 index 0000000..46e38c9 --- /dev/null +++ b/tests/test_cases/pipeline_config.json @@ -0,0 +1,20 @@ +{ + "schema": "airflow.schemas.ingress.sdmx.v1.schema.json", + "required_files": [ + { + "matches": "*.sdmx", + "count": 1 + } + ], + "supplementary_distributions": [ + { + "matches": "*.sdmx", + "count": 1 + } + ], + "priority": 1, + "contact": [ + "jobloggs@ons.gov.uk" + ], + "pipeline": "default" +} \ No newline at end of file diff --git a/tests/test_cases/pipeline_config_invalid_data_type.json b/tests/test_cases/pipeline_config_invalid_data_type.json new file mode 100644 index 0000000..7c4e7b4 --- /dev/null +++ b/tests/test_cases/pipeline_config_invalid_data_type.json @@ -0,0 +1,20 @@ +{ + "schema": "airflow.schemas.ingress.sdmx.v1.schema.json", + "required_files": [ + { + "matches": "*.sdmx", + "count": 1 + } + ], + "supplementary_distributions": [ + { + "matches": "*.sdmx", + "count": "1" + } + ], + "priority": 1, + "contact": [ + "jobloggs@ons.gov.uk" + ], + "pipeline": "default" +} \ No newline at end of file diff --git a/tests/test_cases/pipeline_config_missing_required_field.json b/tests/test_cases/pipeline_config_missing_required_field.json new file mode 100644 index 0000000..e320844 --- /dev/null +++ b/tests/test_cases/pipeline_config_missing_required_field.json @@ -0,0 +1,19 @@ +{ + "schema": "airflow.schemas.ingress.sdmx.v1.schema.json", + "required_files": [ + { + "matches": "*.sdmx", + "count": 1 + } + ], + "supplementary_distributions": [ + { + "matches": "*.sdmx", + "count": 1 + } + ], + "contact": [ + "jobloggs@ons.gov.uk" + ], + "pipeline": "default" +} \ No newline at end of file diff --git a/tests/test_cases/pipeline_config_schema.json b/tests/test_cases/pipeline_config_schema.json new file mode 100644 index 0000000..e6cae62 --- /dev/null +++ b/tests/test_cases/pipeline_config_schema.json @@ -0,0 +1,72 @@ +{ + "$id": "airflow.schemas.ingress.sdmx.v1.schema.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "schema": { + "type": "string" + }, + "required_files": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "matches": { + "type": "string" + }, + "count": { + "type": "integer" + } + }, + "required": [ + "matches", + "count" + ] + } + ] + }, + "supplementary_distributions": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "matches": { + "type": "string" + }, + "count": { + "type": "integer" + } + }, + "required": [ + "matches", + "count" + ] + } + ] + }, + "priority": { + "type": "integer" + }, + "contact": { + "type": "array", + "items": [ + { + "type": "string" + } + ] + }, + "pipeline": { + "type": "string" + } + }, + "required": [ + "schema", + "required_files", + "supplementary_distributions", + "priority", + "contact", + "pipeline" + ] +} \ No newline at end of file diff --git a/tests/test_http.py b/tests/test_http.py index d44b969..a57532c 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -1,10 +1,11 @@ import pytest from unittest.mock import patch, MagicMock from requests import HTTPError, Response -from dpytools.http_clients.base import BaseHttpClient +from dpytools.http_clients.base import BaseHttpClient + # Mock the requests.request method -@patch('requests.request') +@patch("requests.request") def test_get(mock_request): """ Test that the get method returns a response object @@ -13,20 +14,20 @@ def test_get(mock_request): # Create a mock response object mock_response = MagicMock(Response) mock_response.status_code = 200 - mock_response.content = b'Test response content' + mock_response.content = b"Test response content" mock_request.return_value = mock_response - + # Create an instance of BaseHttpClient and make a GET request client = BaseHttpClient() - response = client.get('http://example.com') + response = client.get("http://example.com") # Assertions to check the response status, content and the request call assert response.status_code == 200 - assert response.content.decode() == 'Test response content' - mock_request.assert_called_once_with('GET', 'http://example.com') + assert response.content.decode() == "Test response content" + mock_request.assert_called_once_with("GET", "http://example.com") -@patch('requests.request') +@patch("requests.request") def test_post(mock_request): """ Test that the post method returns a response object @@ -35,20 +36,20 @@ def test_post(mock_request): # Create a mock response object mock_response = MagicMock(Response) mock_response.status_code = 200 - mock_response.content = b'Test response content' + mock_response.content = b"Test response content" mock_request.return_value = mock_response - + # Create an instance of BaseHttpClient and make a POST request client = BaseHttpClient() - response = client.post('http://example.com') + response = client.post("http://example.com") # Assertions to check the response status, content and the request call assert response.status_code == 200 - assert response.content.decode() == 'Test response content' - mock_request.assert_called_once_with('POST', 'http://example.com') + assert response.content.decode() == "Test response content" + mock_request.assert_called_once_with("POST", "http://example.com") -@patch('requests.request') +@patch("requests.request") def test_backoff_on_exception(mock_request): """ Test that the get method retries on HTTPError @@ -59,12 +60,12 @@ def test_backoff_on_exception(mock_request): mock_response.status_code = 200 # Raise HTTPError on the first call, then return the mock_response - mock_request.side_effect = [HTTPError('HTTP Error'), mock_response] - + mock_request.side_effect = [HTTPError("HTTP Error"), mock_response] + # Create an instance of BaseHttpClient and make a GET request client = BaseHttpClient() - response = client.get('http://example.com') + response = client.get("http://example.com") # Assertions to check the response status and the number of request calls assert response.status_code == 200 - assert mock_request.call_count == 2 \ No newline at end of file + assert mock_request.call_count == 2 diff --git a/tests/test_json_validation.py b/tests/test_json_validation.py new file mode 100644 index 0000000..d948402 --- /dev/null +++ b/tests/test_json_validation.py @@ -0,0 +1,132 @@ +from pathlib import Path + +import pytest +from dpytools.validation.json.validation import validate_json_schema + + +def test_validate_json_schema_data_path(): + """ + Validate data (as file path) against schema + """ + pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" + pipeline_config = "tests/test_cases/pipeline_config.json" + assert ( + validate_json_schema( + pipeline_config_schema, + pipeline_config, + "Validating pipeline_config.json", + ) + is None + ) + + +def test_validate_json_schema_data_dict(): + """ + Validate data (as dictionary) against schema + """ + pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" + pipeline_config = { + "schema": "airflow.schemas.ingress.sdmx.v1.schema.json", + "required_files": [{"matches": "*.sdmx", "count": 1}], + "supplementary_distributions": [{"matches": "*.sdmx", "count": 1}], + "priority": 1, + "contact": ["jobloggs@ons.gov.uk"], + "pipeline": "default", + } + assert ( + validate_json_schema( + pipeline_config_schema, + pipeline_config, + "Validating pipeline_config dict", + ) + is None + ) + + +def test_validate_json_schema_invalid_data_format(): + """ + Raise ValueError if data is not a file path or dictionary + """ + pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" + pipeline_config = ["Invalid", "data", "format"] + with pytest.raises(ValueError): + validate_json_schema(pipeline_config_schema, pipeline_config) + + +def test_validate_json_schema_url(): + """ + Raise NotImplementedError if schema path is a URL (i.e. not a local file) + """ + pipeline_config_schema = "http://example.org" + pipeline_config = "tests/test_cases/pipeline_config.json" + with pytest.raises(NotImplementedError): + validate_json_schema(pipeline_config_schema, pipeline_config) + + +def test_validate_json_schema_data_path_required_field_missing(): + """ + Raises ValidationError due to missing field in data (as file path) being validated + """ + pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" + pipeline_config = "tests/test_cases/pipeline_config_missing_required_field.json" + err = validate_json_schema( + pipeline_config_schema, + pipeline_config, + "Validating pipeline_config_missing_required_field.json", + ) + assert err.message == "'priority' is a required property" + + +def test_validate_json_schema_data_path_invalid_data_type(): + """ + Raises ValidationError due to invalid data type in data (as file path) being validated + """ + pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" + pipeline_config = "tests/test_cases/pipeline_config_invalid_data_type.json" + err = validate_json_schema( + pipeline_config_schema, + pipeline_config, + "Validating pipeline_config_invalid_data_type.json", + ) + assert err.message == "'1' is not of type 'integer'" + + +def test_validate_json_schema_data_dict_required_field_missing(): + """ + Raises ValidationError due to missing field in data (as dictionary) being validated + """ + pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" + pipeline_config = { + "schema": "airflow.schemas.ingress.sdmx.v1.schema.json", + "required_files": [{"matches": "*.sdmx", "count": 1}], + "supplementary_distributions": [{"matches": "*.sdmx", "count": 1}], + "contact": ["jobloggs@ons.gov.uk"], + "pipeline": "default", + } + err = validate_json_schema( + pipeline_config_schema, + pipeline_config, + "Validating pipeline_config with required field missing", + ) + assert err.message == "'priority' is a required property" + + +def test_validate_json_schema_data_dict_invalid_data_type(): + """ + Raises ValidationError due to invalid data type in data (as dictionary) being validated + """ + pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" + pipeline_config = { + "schema": "airflow.schemas.ingress.sdmx.v1.schema.json", + "required_files": [{"matches": "*.sdmx", "count": 1}], + "supplementary_distributions": [{"matches": "*.sdmx", "count": "1"}], + "priority": 1, + "contact": ["jobloggs@ons.gov.uk"], + "pipeline": "default", + } + err = validate_json_schema( + pipeline_config_schema, + pipeline_config, + f"Validating pipeline_config dict with invalid data type", + ) + assert err.message == "'1' is not of type 'integer'" diff --git a/tests/test_logging.py b/tests/test_logging.py index bab547b..138237c 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -43,10 +43,10 @@ def do_something(level: str): # Schemas to validate log entries against # Created with https://www.liquid-technologies.com/online-json-to-schema-converter -with open("tests/schema.json", "r") as fp: +with open("tests/test_cases/logging_schema.json", "r") as fp: schema = json.load(fp) -with open("tests/schema_with_error.json", "r") as fp: +with open("tests/test_cases/logging_schema_with_error.json", "r") as fp: schema_with_error = json.load(fp) diff --git a/tests/test_nothing.py b/tests/test_nothing.py deleted file mode 100644 index e483b19..0000000 --- a/tests/test_nothing.py +++ /dev/null @@ -1,7 +0,0 @@ - -# TODO - remove as soon as we add our first test. -def test_nothing(): - """ - Empty test to stop failures for 0 coverage - """ - ... \ No newline at end of file From e64697a6e186a03809c3dae1f932e05b72ccb360 Mon Sep 17 00:00:00 2001 From: Sarah Johnson Date: Fri, 9 Feb 2024 09:43:36 +0000 Subject: [PATCH 2/3] PR comments addressed --- dpytools/validation/json/validation.py | 104 +++++++++-------- tests/test_json_validation.py | 154 +++++++++++++++++++------ 2 files changed, 177 insertions(+), 81 deletions(-) diff --git a/dpytools/validation/json/validation.py b/dpytools/validation/json/validation.py index 97a12a3..44ca910 100644 --- a/dpytools/validation/json/validation.py +++ b/dpytools/validation/json/validation.py @@ -6,63 +6,77 @@ import jsonschema from jsonschema import ValidationError -""" -# data_dict is for passing in a dictionary to be validated -validate_json_schema("/path/to/schema.json", data_dict=some_dictionary) - -# msg is to include a helpful context when debugging (i.e "what we're validating") -validate_json_schema("/path/to/schema.json", data_dict=some_dictionary, msg="Some helpful message should this validation fail") - -validate_json_schema("/path/to/schema.json", data_path="/path/to/some/json", msg="Some helpful message should this validation fail") - -# indent should pretty print the json contents of the error to make it -# more easily parsed by humans -validate_json_schema("/path/to/schema.json", data_dict=some_dictionary, indent=2) -""" - def validate_json_schema( - schema_path: str, - data_dict_or_path: Union[Dict, str], - msg: str = "", - indent: int = 2, -) -> Optional[ValidationError]: + schema_path: Union[Path, str], + data_dict: Optional[Dict] = None, + data_path: Union[Path, str, None] = None, + error_msg: Optional[str] = "", + indent: Optional[int] = 2, +): """ - Validate metadata.json files against schema provided. + Validate metadata.json files against schema. + + Either `data_dict` or `data_path` must be provided. - `schema_path`: file path of schema to validate against - `data_dict_or_path`: file path or dictionary of data to be validated - `msg`: optional string to provide additional information about validation - `indent`: optional integer to be used when indenting json output + `msg` and `indent` are used to format the error message if validation fails. """ + # Confirm that *either* `data_dict` *or* `data_path` has been provided, otherwise raise ValueError + if data_dict and data_path: + raise ValueError( + "Both a dictionary and file path of data have been provided - please specify either one or the other, not both." + ) + if data_dict is None and data_path is None: + raise ValueError( + "Please provide either a dictionary or a file path of the data to be validated against the schema." + ) + # Load schema as dict - parsed_schema_path = urlparse(schema_path) - if parsed_schema_path.scheme == "http": - # TODO Load schema from URL - raise NotImplementedError("Validation from remote schema not yet supported") - else: - with open(Path(schema_path), "r") as f: - schema_from_path = json.load(f) + if isinstance(schema_path, str): + parsed_schema_path = urlparse(schema_path) + if parsed_schema_path.scheme == "http": + # TODO Load schema from URL + raise NotImplementedError("Validation from remote schema not yet supported") + schema_path = Path(schema_path).absolute() + if not schema_path.exists(): + raise ValueError(f"Schema path '{schema_path}' does not exist") + with open(schema_path, "r") as f: + schema_from_path = json.load(f) - # Load data as dict - if isinstance(data_dict_or_path, Dict): - data_to_validate = data_dict_or_path - elif isinstance(data_dict_or_path, str): - with open(Path(data_dict_or_path), "r") as f: + # Load data to be validated as dict + if data_dict: + if not isinstance(data_dict, Dict): + raise ValueError("Invalid data format") + data_to_validate = data_dict + + if data_path: + if isinstance(data_path, str): + data_path = Path(data_path).absolute() + if not isinstance(data_path, Path): + raise ValueError("Invalid data format") + if not data_path.exists(): + raise ValueError(f"Data path '{data_path}' does not exist") + with open(data_path, "r") as f: data_to_validate = json.load(f) - else: - raise ValueError("Invalid data format") # Validate data against schema - print(msg) try: jsonschema.validate(data_to_validate, schema_from_path) except jsonschema.ValidationError as err: # TODO Handle jsonschema.SchemaError? - print(f"Error when validating data: {err.message}") - # If the error relates to a specific field, print the error's location + # If error is in a specific field, get the JSON path of the error location if err.json_path != "$": - print(f"Error in data field: {err.json_path}") - # Print the data that failed validation - print(f"Contents of data:\n{json.dumps(data_to_validate, indent=indent)}") - return err + error_location = err.json_path + else: + error_location = "JSON data" + # Create formatted message to be output on ValidationError + if error_msg or indent: + formatted_msg = f""" +Exception: {error_msg} +Error details: {err.message} +Error location: {error_location} +JSON data: +{json.dumps(data_to_validate, indent=indent)} +""" + raise ValidationError(formatted_msg) from err + raise err diff --git a/tests/test_json_validation.py b/tests/test_json_validation.py index d948402..e4b8bc5 100644 --- a/tests/test_json_validation.py +++ b/tests/test_json_validation.py @@ -1,4 +1,5 @@ from pathlib import Path +from jsonschema import ValidationError import pytest from dpytools.validation.json.validation import validate_json_schema @@ -12,9 +13,9 @@ def test_validate_json_schema_data_path(): pipeline_config = "tests/test_cases/pipeline_config.json" assert ( validate_json_schema( - pipeline_config_schema, - pipeline_config, - "Validating pipeline_config.json", + schema_path=pipeline_config_schema, + data_path=pipeline_config, + error_msg="Validating pipeline_config.json", ) is None ) @@ -35,22 +36,74 @@ def test_validate_json_schema_data_dict(): } assert ( validate_json_schema( - pipeline_config_schema, - pipeline_config, - "Validating pipeline_config dict", + schema_path=pipeline_config_schema, + data_dict=pipeline_config, + error_msg="Validating pipeline_config dict", ) is None ) -def test_validate_json_schema_invalid_data_format(): +def test_validate_json_schema_data_dict_and_data_path(): + pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" + pipeline_config_path = "tests/test_cases/pipeline_config.json" + pipeline_config_dict = { + "schema": "airflow.schemas.ingress.sdmx.v1.schema.json", + "required_files": [{"matches": "*.sdmx", "count": 1}], + "supplementary_distributions": [{"matches": "*.sdmx", "count": 1}], + "priority": 1, + "contact": ["jobloggs@ons.gov.uk"], + "pipeline": "default", + } + with pytest.raises(ValueError) as err: + validate_json_schema( + schema_path=pipeline_config_schema, + data_dict=pipeline_config_dict, + data_path=pipeline_config_path, + ) + assert ( + "Both a dictionary and file path of data have been provided - please specify either one or the other, not both." + in str(err.value) + ) + + +def test_validate_json_schema_no_data_dict_or_data_path(): + pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" + + with pytest.raises(ValueError) as err: + validate_json_schema( + schema_path=pipeline_config_schema, + ) + assert ( + "Please provide either a dictionary or a file path of the data to be validated against the schema." + in str(err.value) + ) + + +def test_validate_json_schema_invalid_data_path_format(): + """ + Raise ValueError if data_path is not a file path + """ + pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" + pipeline_config = ["Invalid", "data", "format"] + with pytest.raises(ValueError) as err: + validate_json_schema( + schema_path=pipeline_config_schema, data_path=pipeline_config + ) + assert "Invalid data format" in str(err.value) + + +def test_validate_json_schema_invalid_data_dict_format(): """ - Raise ValueError if data is not a file path or dictionary + Raise ValueError if data_dict is not a dictionary """ pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" pipeline_config = ["Invalid", "data", "format"] - with pytest.raises(ValueError): - validate_json_schema(pipeline_config_schema, pipeline_config) + with pytest.raises(ValueError) as err: + validate_json_schema( + schema_path=pipeline_config_schema, data_dict=pipeline_config + ) + assert "Invalid data format" in str(err.value) def test_validate_json_schema_url(): @@ -59,8 +112,33 @@ def test_validate_json_schema_url(): """ pipeline_config_schema = "http://example.org" pipeline_config = "tests/test_cases/pipeline_config.json" - with pytest.raises(NotImplementedError): - validate_json_schema(pipeline_config_schema, pipeline_config) + with pytest.raises(NotImplementedError) as err: + validate_json_schema( + schema_path=pipeline_config_schema, data_path=pipeline_config + ) + assert "Validation from remote schema not yet supported" in str(err.value) + + +def test_validate_json_schema_invalid_schema_path(): + pipeline_config_schema = "tests/test_cases/does_not_exist.json" + pipeline_config = "tests/test_cases/pipeline_config.json" + schema_path = Path(pipeline_config_schema).absolute() + with pytest.raises(ValueError) as err: + validate_json_schema( + schema_path=pipeline_config_schema, data_path=pipeline_config + ) + assert f"Schema path '{schema_path}' does not exist" in str(err.value) + + +def test_validate_json_schema_invalid_data_path(): + pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" + pipeline_config = "tests/test_cases/does_not_exist.json" + data_path = Path(pipeline_config).absolute() + with pytest.raises(ValueError) as err: + validate_json_schema( + schema_path=pipeline_config_schema, data_path=pipeline_config + ) + assert f"Data path '{data_path}' does not exist" in str(err.value) def test_validate_json_schema_data_path_required_field_missing(): @@ -69,12 +147,13 @@ def test_validate_json_schema_data_path_required_field_missing(): """ pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" pipeline_config = "tests/test_cases/pipeline_config_missing_required_field.json" - err = validate_json_schema( - pipeline_config_schema, - pipeline_config, - "Validating pipeline_config_missing_required_field.json", - ) - assert err.message == "'priority' is a required property" + with pytest.raises(ValidationError) as err: + validate_json_schema( + schema_path=pipeline_config_schema, + data_path=pipeline_config, + error_msg="Error validating pipeline_config_missing_required_field.json", + ) + assert "'priority' is a required property" in str(err.value) def test_validate_json_schema_data_path_invalid_data_type(): @@ -83,12 +162,13 @@ def test_validate_json_schema_data_path_invalid_data_type(): """ pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" pipeline_config = "tests/test_cases/pipeline_config_invalid_data_type.json" - err = validate_json_schema( - pipeline_config_schema, - pipeline_config, - "Validating pipeline_config_invalid_data_type.json", - ) - assert err.message == "'1' is not of type 'integer'" + with pytest.raises(ValidationError) as err: + validate_json_schema( + schema_path=pipeline_config_schema, + data_path=pipeline_config, + error_msg="Error validating pipeline_config_invalid_data_type.json", + ) + assert "'1' is not of type 'integer'" in str(err.value) def test_validate_json_schema_data_dict_required_field_missing(): @@ -103,12 +183,13 @@ def test_validate_json_schema_data_dict_required_field_missing(): "contact": ["jobloggs@ons.gov.uk"], "pipeline": "default", } - err = validate_json_schema( - pipeline_config_schema, - pipeline_config, - "Validating pipeline_config with required field missing", - ) - assert err.message == "'priority' is a required property" + with pytest.raises(ValidationError) as err: + validate_json_schema( + schema_path=pipeline_config_schema, + data_dict=pipeline_config, + error_msg="Error validating pipeline_config with required field missing", + ) + assert "'priority' is a required property" in str(err.value) def test_validate_json_schema_data_dict_invalid_data_type(): @@ -124,9 +205,10 @@ def test_validate_json_schema_data_dict_invalid_data_type(): "contact": ["jobloggs@ons.gov.uk"], "pipeline": "default", } - err = validate_json_schema( - pipeline_config_schema, - pipeline_config, - f"Validating pipeline_config dict with invalid data type", - ) - assert err.message == "'1' is not of type 'integer'" + with pytest.raises(ValidationError) as err: + validate_json_schema( + schema_path=pipeline_config_schema, + data_dict=pipeline_config, + error_msg="Error validating pipeline_config dict with invalid data type", + ) + assert "'1' is not of type 'integer'" in str(err.value) From 34b32021c3099229ad9c7e48948d4ed1addf685f Mon Sep 17 00:00:00 2001 From: Sarah Johnson Date: Fri, 9 Feb 2024 12:39:09 +0000 Subject: [PATCH 3/3] Changes made --- dpytools/config/config.py | 3 --- dpytools/config/properties/__init__.py | 2 +- dpytools/config/properties/base.py | 8 ++++++-- dpytools/config/properties/intproperty.py | 18 +++++++++++++----- dpytools/config/properties/string.py | 6 +++--- dpytools/validation/json/validation.py | 19 ++++++++++++------- tests/test_json_validation.py | 15 +++++++++------ 7 files changed, 44 insertions(+), 27 deletions(-) diff --git a/dpytools/config/config.py b/dpytools/config/config.py index 1128e19..4dda9a7 100644 --- a/dpytools/config/config.py +++ b/dpytools/config/config.py @@ -9,17 +9,14 @@ class Config: - def __init__(self): self._properties_to_validate: List[BaseProperty] = [] @staticmethod def from_env(config_dict: Dict[str, Dict[str, Any]]) -> Config: - config = Config() for env_var_name, value in config_dict.items(): - value_for_property = os.environ.get(env_var_name, None) assert ( value_for_property is not None diff --git a/dpytools/config/properties/__init__.py b/dpytools/config/properties/__init__.py index 0a16ae7..26d2776 100644 --- a/dpytools/config/properties/__init__.py +++ b/dpytools/config/properties/__init__.py @@ -1,2 +1,2 @@ -from .string import StringProperty from .intproperty import IntegerProperty +from .string import StringProperty diff --git a/dpytools/config/properties/base.py b/dpytools/config/properties/base.py index 2cb9449..11a6195 100644 --- a/dpytools/config/properties/base.py +++ b/dpytools/config/properties/base.py @@ -14,7 +14,9 @@ def name(self): @name.setter def name(self, value): - raise ValueError(f"Trying to change name property to value {value} but you cannot change a property name after instantiation.") + raise ValueError( + f"Trying to change name property to value {value} but you cannot change a property name after instantiation." + ) @property def value(self): @@ -22,7 +24,9 @@ def value(self): @value.setter def value(self, value): - raise ValueError(f"Trying to change value to {value} but you cannot change a property value after instantiation.") + raise ValueError( + f"Trying to change value to {value} but you cannot change a property value after instantiation." + ) @abstractmethod def type_is_valid(self): diff --git a/dpytools/config/properties/intproperty.py b/dpytools/config/properties/intproperty.py index f776738..4a5ff81 100644 --- a/dpytools/config/properties/intproperty.py +++ b/dpytools/config/properties/intproperty.py @@ -1,7 +1,9 @@ -from typing import Optional from dataclasses import dataclass +from typing import Optional + from .base import BaseProperty + @dataclass class IntegerProperty(BaseProperty): min_val: Optional[int] @@ -10,12 +12,14 @@ class IntegerProperty(BaseProperty): def type_is_valid(self): """ Validate that the property looks like - its of the correct type + its of the correct type """ try: int(self._value) except Exception as err: - raise Exception(f"Cannot cast {self._name} value {self._value} to integer.") from err + raise Exception( + f"Cannot cast {self._name} value {self._value} to integer." + ) from err def secondary_validation(self): """ @@ -26,7 +30,11 @@ def secondary_validation(self): raise ValueError(f"Integer value for {self._name} does not exist.") if self.min_val and self._value < self.min_val: - raise ValueError(f"Integer value for {self._name} is lower than allowed minimum.") + raise ValueError( + f"Integer value for {self._name} is lower than allowed minimum." + ) if self.max_val and self._value > self.max_val: - raise ValueError(f"Integer value for {self._name} is higher than allowed maximum.") \ No newline at end of file + raise ValueError( + f"Integer value for {self._name} is higher than allowed maximum." + ) diff --git a/dpytools/config/properties/string.py b/dpytools/config/properties/string.py index ae1921a..cfb31c1 100644 --- a/dpytools/config/properties/string.py +++ b/dpytools/config/properties/string.py @@ -1,8 +1,8 @@ -from typing import Optional +import re from dataclasses import dataclass -from .base import BaseProperty +from typing import Optional -import re +from .base import BaseProperty @dataclass diff --git a/dpytools/validation/json/validation.py b/dpytools/validation/json/validation.py index 207a3b9..3f63864 100644 --- a/dpytools/validation/json/validation.py +++ b/dpytools/validation/json/validation.py @@ -10,16 +10,16 @@ def validate_json_schema( schema_path: Union[Path, str], data_dict: Optional[Dict] = None, - data_path: Union[Path, str, None] = None, - error_msg: Optional[str] = "", - indent: Optional[int] = 2, + data_path: Optional[Union[Path, str]] = None, + error_msg: Optional[str] = None, + indent: Optional[int] = None, ): """ - Validate metadata.json files against schema. + Validate a JSON file against a schema. Either `data_dict` or `data_path` must be provided. - `error_msg` and `indent` are used to format the error message if validation fails. + `error_msg` and `indent` can be used to format the error message if validation fails. """ # Confirm that *either* `data_dict` *or* `data_path` has been provided, otherwise raise ValueError if data_dict and data_path: @@ -48,7 +48,9 @@ def validate_json_schema( # Load data to be validated if data_dict: if not isinstance(data_dict, Dict): - raise ValueError("Invalid data format") + raise ValueError( + "Invalid data format, `data_dict` should be a Python dictionary" + ) data_to_validate = data_dict if data_path: @@ -56,7 +58,9 @@ def validate_json_schema( if isinstance(data_path, str): data_path = Path(data_path).absolute() if not isinstance(data_path, Path): - raise ValueError("Invalid data format") + raise ValueError( + "Invalid data format, `data_path` should be a pathlib.Path or string of file location" + ) # Check `data_path` exists if not data_path.exists(): raise ValueError(f"Data path '{data_path}' does not exist") @@ -82,5 +86,6 @@ def validate_json_schema( JSON data: {json.dumps(data_to_validate, indent=indent)} """ + print(formatted_msg) raise ValidationError(formatted_msg) from err raise err diff --git a/tests/test_json_validation.py b/tests/test_json_validation.py index efedbc9..4060854 100644 --- a/tests/test_json_validation.py +++ b/tests/test_json_validation.py @@ -15,7 +15,6 @@ def test_validate_json_schema_data_path(): validate_json_schema( schema_path=pipeline_config_schema, data_path=pipeline_config, - error_msg="Validating pipeline_config.json", ) is None ) @@ -38,7 +37,6 @@ def test_validate_json_schema_data_dict(): validate_json_schema( schema_path=pipeline_config_schema, data_dict=pipeline_config, - error_msg="Validating pipeline_config dict", ) is None ) @@ -88,7 +86,7 @@ def test_validate_json_schema_no_data_dict_or_data_path(): def test_validate_json_schema_invalid_data_path_format(): """ - Raise ValueError if data_path is not a file path + Raise ValueError if data_path is not a string or file path """ pipeline_config_schema = "tests/test_cases/pipeline_config_schema.json" pipeline_config = ["Invalid", "data", "format"] @@ -96,7 +94,10 @@ def test_validate_json_schema_invalid_data_path_format(): validate_json_schema( schema_path=pipeline_config_schema, data_path=pipeline_config ) - assert "Invalid data format" in str(err.value) + assert ( + "Invalid data format, `data_path` should be a pathlib.Path or string of file location" + in str(err.value) + ) def test_validate_json_schema_invalid_data_dict_format(): @@ -109,12 +110,14 @@ def test_validate_json_schema_invalid_data_dict_format(): validate_json_schema( schema_path=pipeline_config_schema, data_dict=pipeline_config ) - assert "Invalid data format" in str(err.value) + assert "Invalid data format, `data_dict` should be a Python dictionary" in str( + err.value + ) def test_validate_json_schema_url(): """ - Raise NotImplementedError if schema path is a URL (i.e. not a local file) + Raise NotImplementedError if `schema_path` is a URL (i.e. not a local file) """ pipeline_config_schema = "http://example.org" pipeline_config = "tests/test_cases/pipeline_config.json"