From 64adfcc108319fd1d55058db0b4989ed227aceda Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 28 Nov 2023 09:21:16 +0000 Subject: [PATCH 01/48] tidy up --- .github/workflows/tests.yaml | 4 ++-- README.md | 7 ++++--- pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 5178e8f..842c3e2 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -15,10 +15,10 @@ jobs: python-version: '3.9' - name: Install pipenv - run: pip install pipenv + run: pip install poetry - name: Install test dependencies - run: pipenv sync --dev + run: poetry install --dev - name: Run Pytest run: pipenv run pytest --cov-report term-missing --cov=src --cov-config=./tests/coverage.rc ./tests/ \ No newline at end of file diff --git a/README.md b/README.md index 8090ac5..2dd5949 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,10 @@ The usage instructions please see the readme in the appropriate sub directory, t ## Development -All commits that make it to PR should have black and ruff already ran against them, you can do this via `make fmt`. -` +All commits that make it to PR should have black and ruff already ran against them, you can do this via `make fmt` and you can lint via `make lint`. + +For a full list of functionality provided by the makefile just run a naked `make`. ### Repository Organization -Each client, helper etc should be a sub directory of `./dpytools`. Separation between these sub codebaes should be maintained as much as possible aginst the day where we want to break some or all of these tools out into separate repositories. \ No newline at end of file +Each client, helper etc should be a sub directory of `./dpytools`. Separation between these sub codebases should be maintained as much as possible aginst the day where we want to break some or all of these tools out into separate repositories. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 53e9b28..b45da0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "dp-python-tools" +name = "dpytools" version = "0.1.0" description = "Simple reusable python resources for digital publishing" authors = ["Your Name "] From 2df182c1493ec9a6aff09ab3e9333ac793463400 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 28 Nov 2023 09:22:28 +0000 Subject: [PATCH 02/48] poetry not pipenv --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 842c3e2..9208703 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -18,7 +18,7 @@ jobs: run: pip install poetry - name: Install test dependencies - run: poetry install --dev + run: poetry install - name: Run Pytest run: pipenv run pytest --cov-report term-missing --cov=src --cov-config=./tests/coverage.rc ./tests/ \ No newline at end of file From fd390c8037c61e97fa2814c05974c79c17b65d10 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 28 Nov 2023 09:23:38 +0000 Subject: [PATCH 03/48] simplify table --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2dd5949..b8eece1 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ Simple reusable python resources for digital publishing. The following clients, helpers etc are availible upon installation of this package. -| Name | Import | Description | +| Name | Description | | ----- | ---------------- | ------ | -| [config](./dpytools/config/README.md) | `from dpytools import Config` | A simple validating configuration class | +| [Config](./dpytools/config/README.md) | A simple validating configuration class | **TODO** - all helpers and clients to appear in this list. The usage instructions please see the readme in the appropriate sub directory, this can also be access by clicking the link in the "name" column above. From dde0974eefc373bc4489bad8c29de91fb3337ed4 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 28 Nov 2023 09:30:00 +0000 Subject: [PATCH 04/48] fix table --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b8eece1..76e3fb4 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Simple reusable python resources for digital publishing. The following clients, helpers etc are availible upon installation of this package. | Name | Description | -| ----- | ---------------- | ------ | +| ----- | ---------------- | | [Config](./dpytools/config/README.md) | A simple validating configuration class | **TODO** - all helpers and clients to appear in this list. From 560a4ffb08add69a5594601f119d4bd649254217 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 28 Nov 2023 09:42:38 +0000 Subject: [PATCH 05/48] poetry not pipenv --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 9208703..d92b124 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -21,4 +21,4 @@ jobs: run: poetry install - name: Run Pytest - run: pipenv run pytest --cov-report term-missing --cov=src --cov-config=./tests/coverage.rc ./tests/ \ No newline at end of file + run: poetry run pytest --cov-report term-missing --cov=src --cov-config=./tests/coverage.rc ./tests/ \ No newline at end of file From c6ab98ff9a813b09d7fd861d1bab391024eedc2a Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 28 Nov 2023 09:52:04 +0000 Subject: [PATCH 06/48] sort out test runner --- .gitignore | 1 + Makefile | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5cb1af --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.coverage \ No newline at end of file diff --git a/Makefile b/Makefile index 94fc78a..fd898a7 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 (auto triggered on pre-commit) - pipenv run black ./src/* - pipenv run isort ./src/* + poetry run black ./src/* + poetry run isort ./src/* lint: ## Run the ruff python linter (auto triggered on pre-commit) - pipenv run ruff ./src/* + poetry run ruff ./src/* test: ## Run pytest and check test coverage (auto triggered on pre-push) - pipenv run pytest --cov-report term-missing --cov=src --cov-config=./tests/coverage.rc + poetry run pytest --cov-report term-missing --cov=dpytools From 889ac565a4d1786e44868c211fa1907d8e2ba125 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 28 Nov 2023 09:54:35 +0000 Subject: [PATCH 07/48] add missing coverage.rc --- tests/coverage.rc | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/coverage.rc diff --git a/tests/coverage.rc b/tests/coverage.rc new file mode 100644 index 0000000..e69de29 From 5e99a23644d6f05011e13c3ac44b276a1ff8ce4d Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 28 Nov 2023 10:04:38 +0000 Subject: [PATCH 08/48] tidy up comments --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index fd898a7..c64d35b 100644 --- a/Makefile +++ b/Makefile @@ -4,12 +4,12 @@ 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 (auto triggered on pre-commit) +fmt: ## (Format) - runs black and isort against the codebase poetry run black ./src/* poetry run isort ./src/* -lint: ## Run the ruff python linter (auto triggered on pre-commit) +lint: ## Run the ruff python linter poetry run ruff ./src/* -test: ## Run pytest and check test coverage (auto triggered on pre-push) +test: ## Run pytest and check test coverage poetry run pytest --cov-report term-missing --cov=dpytools From 90ac71a6dd9e20846541aeb796c20ea649f46cb8 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 28 Nov 2023 14:31:21 +0000 Subject: [PATCH 09/48] remove bad type hint --- dpytools/config/properties/string.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dpytools/config/properties/string.py b/dpytools/config/properties/string.py index a072552..adf5bee 100644 --- a/dpytools/config/properties/string.py +++ b/dpytools/config/properties/string.py @@ -8,7 +8,7 @@ class StringProperty(BaseProperty): min_len: Optional[int] max_len: Optional[int] - def type_is_valid(self) -> Optional[Exception]: + def type_is_valid(self): """ Validate that the property looks like its of the correct type @@ -18,7 +18,7 @@ def type_is_valid(self) -> Optional[Exception]: except Exception as err: raise Exception(f"Cannot cast {self.name} value {self.value} to string.") from err - def secondary_validation_passed(self) -> Optional[Exception]: + def secondary_validation_passed(self): """ Non type based validation you might want to run against a configuration value of this kind. From 1538b81b44b71e73d30c29c5cc0c6179b1694a7e Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Wed, 20 Dec 2023 14:24:13 +0000 Subject: [PATCH 10/48] All imports working --- README.md | 17 ++++++- dpytools/Pipfile | 15 ++++++ dpytools/Pipfile.lock | 59 ++++++++++++++++++++++++ dpytools/__init__.py | 2 +- dpytools/config/config.py | 3 +- dpytools/config/properties/__init__.py | 2 +- dpytools/config/properties/base.py | 2 +- dpytools/config/properties/string.py | 2 +- dpytools/{http => http_client}/README.md | 0 dpytools/{http => http_client}/http.py | 0 10 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 dpytools/Pipfile create mode 100644 dpytools/Pipfile.lock rename dpytools/{http => http_client}/README.md (100%) rename dpytools/{http => http_client}/http.py (100%) diff --git a/README.md b/README.md index 76e3fb4..e35135c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,22 @@ Simple reusable python resources for digital publishing. ## Installation -**TODO** +Follow these steps to install the package on your local machine: + +1. **Clone the repository** + + Open your terminal and run the following command: + + ```bash + git clone https://github.com/GSS-Cogs/dp-python-tools.git + +2. **Navigate to the cloned repositry** +| cd dp-python-tools + +3. **Install all the packages** + +| pip install + ## Usage diff --git a/dpytools/Pipfile b/dpytools/Pipfile new file mode 100644 index 0000000..2bd4271 --- /dev/null +++ b/dpytools/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +config = "*" +logger = "*" +slack = "*" +sns = "*" + +[dev-packages] + +[requires] +python_version = "2.7" diff --git a/dpytools/Pipfile.lock b/dpytools/Pipfile.lock new file mode 100644 index 0000000..a13d574 --- /dev/null +++ b/dpytools/Pipfile.lock @@ -0,0 +1,59 @@ +{ + "_meta": { + "hash": { + "sha256": "93db113234d7ef12258fcefd66bbe8f88abada307b11ad353ef1d6005a636901" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "2.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "config": { + "hashes": [ + "sha256:2dd4a03aa383d30711d5a3325a1858de225328d61950a85be5b74c100f63016d", + "sha256:79ffa009ff2663cc8ca29e56bcec031c044609d4bafaa4f884132a413101ce84" + ], + "index": "pypi", + "version": "==0.5.1" + }, + "logger": { + "hashes": [ + "sha256:4ecac57133c6376fa215f0fe6b4dc4d60e4d1ad8be005cab4e8a702df682f8b3" + ], + "index": "pypi", + "version": "==1.4" + }, + "python-http-client": { + "hashes": [ + "sha256:ad371d2bbedc6ea15c26179c6222a78bc9308d272435ddf1d5c84f068f249a36", + "sha256:bf841ee45262747e00dec7ee9971dfb8c7d83083f5713596488d67739170cea0" + ], + "index": "pypi", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==3.3.7" + }, + "slack": { + "hashes": [ + "sha256:e0b0cd3ba730e81aa82eef0973a7970652b99f6445cb71a6dde1d29e6d13b051" + ], + "index": "pypi", + "version": "==0.0.2" + }, + "sns": { + "hashes": [ + "sha256:2d847d23eb8e0ea20054dbadabdecfafc529e4c7a16a6ce70205c4bf8ebc6cf4" + ], + "index": "pypi", + "version": "==0.1" + } + }, + "develop": {} +} diff --git a/dpytools/__init__.py b/dpytools/__init__.py index bff3af3..cdb0a7c 100644 --- a/dpytools/__init__.py +++ b/dpytools/__init__.py @@ -1,4 +1,4 @@ -from http.http import HttpClient +from http_client.http import HttpClient from config.config import Config from logger.logger import logger from slack.slack import SlackNotifier diff --git a/dpytools/config/config.py b/dpytools/config/config.py index c8b8daf..efc38e3 100644 --- a/dpytools/config/config.py +++ b/dpytools/config/config.py @@ -1,7 +1,6 @@ from typing import Dict -from properties.base import BaseProperty - +from .properties.base import BaseProperty class Config: diff --git a/dpytools/config/properties/__init__.py b/dpytools/config/properties/__init__.py index c7e4bfb..d6eca2d 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 \ No newline at end of file diff --git a/dpytools/config/properties/base.py b/dpytools/config/properties/base.py index 7a1dd4d..02dd34c 100644 --- a/dpytools/config/properties/base.py +++ b/dpytools/config/properties/base.py @@ -3,7 +3,7 @@ from typing import Any, Union, Tuple, Optional @dataclass -class BaseProperty(meta=ABCMeta): +class BaseProperty(metaclass=ABCMeta): name: str value: Any diff --git a/dpytools/config/properties/string.py b/dpytools/config/properties/string.py index adf5bee..74aa102 100644 --- a/dpytools/config/properties/string.py +++ b/dpytools/config/properties/string.py @@ -1,6 +1,6 @@ from typing import Optional -from base import BaseProperty +from .base import BaseProperty class StringProperty(BaseProperty): diff --git a/dpytools/http/README.md b/dpytools/http_client/README.md similarity index 100% rename from dpytools/http/README.md rename to dpytools/http_client/README.md diff --git a/dpytools/http/http.py b/dpytools/http_client/http.py similarity index 100% rename from dpytools/http/http.py rename to dpytools/http_client/http.py From ac96694e81127d19bbc784742e2302d180d72e41 Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Wed, 20 Dec 2023 14:25:30 +0000 Subject: [PATCH 11/48] Minor changes --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e35135c..cd61df7 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,12 @@ Follow these steps to install the package on your local machine: git clone https://github.com/GSS-Cogs/dp-python-tools.git 2. **Navigate to the cloned repositry** -| cd dp-python-tools + ```bash + cd dp-python-tools 3. **Install all the packages** - -| pip install + ```bash + pip install ## Usage From 06d8300f31fa7d1801538c6e99cfc0cc068c8d27 Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Wed, 20 Dec 2023 14:44:25 +0000 Subject: [PATCH 12/48] movedd pipfiles --- dpytools/Pipfile | 15 ----------- dpytools/Pipfile.lock | 59 ------------------------------------------- 2 files changed, 74 deletions(-) delete mode 100644 dpytools/Pipfile delete mode 100644 dpytools/Pipfile.lock diff --git a/dpytools/Pipfile b/dpytools/Pipfile deleted file mode 100644 index 2bd4271..0000000 --- a/dpytools/Pipfile +++ /dev/null @@ -1,15 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -config = "*" -logger = "*" -slack = "*" -sns = "*" - -[dev-packages] - -[requires] -python_version = "2.7" diff --git a/dpytools/Pipfile.lock b/dpytools/Pipfile.lock deleted file mode 100644 index a13d574..0000000 --- a/dpytools/Pipfile.lock +++ /dev/null @@ -1,59 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "93db113234d7ef12258fcefd66bbe8f88abada307b11ad353ef1d6005a636901" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "2.7" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "config": { - "hashes": [ - "sha256:2dd4a03aa383d30711d5a3325a1858de225328d61950a85be5b74c100f63016d", - "sha256:79ffa009ff2663cc8ca29e56bcec031c044609d4bafaa4f884132a413101ce84" - ], - "index": "pypi", - "version": "==0.5.1" - }, - "logger": { - "hashes": [ - "sha256:4ecac57133c6376fa215f0fe6b4dc4d60e4d1ad8be005cab4e8a702df682f8b3" - ], - "index": "pypi", - "version": "==1.4" - }, - "python-http-client": { - "hashes": [ - "sha256:ad371d2bbedc6ea15c26179c6222a78bc9308d272435ddf1d5c84f068f249a36", - "sha256:bf841ee45262747e00dec7ee9971dfb8c7d83083f5713596488d67739170cea0" - ], - "index": "pypi", - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==3.3.7" - }, - "slack": { - "hashes": [ - "sha256:e0b0cd3ba730e81aa82eef0973a7970652b99f6445cb71a6dde1d29e6d13b051" - ], - "index": "pypi", - "version": "==0.0.2" - }, - "sns": { - "hashes": [ - "sha256:2d847d23eb8e0ea20054dbadabdecfafc529e4c7a16a6ce70205c4bf8ebc6cf4" - ], - "index": "pypi", - "version": "==0.1" - } - }, - "develop": {} -} From 01f94232c7153de179f2e22f95d6f33d4816500f Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Wed, 20 Dec 2023 14:45:21 +0000 Subject: [PATCH 13/48] moved pipfiles --- Pipfile | 15 +++++++++++++ Pipfile.lock | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 Pipfile create mode 100644 Pipfile.lock diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..2bd4271 --- /dev/null +++ b/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +config = "*" +logger = "*" +slack = "*" +sns = "*" + +[dev-packages] + +[requires] +python_version = "2.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..a13d574 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,59 @@ +{ + "_meta": { + "hash": { + "sha256": "93db113234d7ef12258fcefd66bbe8f88abada307b11ad353ef1d6005a636901" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "2.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "config": { + "hashes": [ + "sha256:2dd4a03aa383d30711d5a3325a1858de225328d61950a85be5b74c100f63016d", + "sha256:79ffa009ff2663cc8ca29e56bcec031c044609d4bafaa4f884132a413101ce84" + ], + "index": "pypi", + "version": "==0.5.1" + }, + "logger": { + "hashes": [ + "sha256:4ecac57133c6376fa215f0fe6b4dc4d60e4d1ad8be005cab4e8a702df682f8b3" + ], + "index": "pypi", + "version": "==1.4" + }, + "python-http-client": { + "hashes": [ + "sha256:ad371d2bbedc6ea15c26179c6222a78bc9308d272435ddf1d5c84f068f249a36", + "sha256:bf841ee45262747e00dec7ee9971dfb8c7d83083f5713596488d67739170cea0" + ], + "index": "pypi", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==3.3.7" + }, + "slack": { + "hashes": [ + "sha256:e0b0cd3ba730e81aa82eef0973a7970652b99f6445cb71a6dde1d29e6d13b051" + ], + "index": "pypi", + "version": "==0.0.2" + }, + "sns": { + "hashes": [ + "sha256:2d847d23eb8e0ea20054dbadabdecfafc529e4c7a16a6ce70205c4bf8ebc6cf4" + ], + "index": "pypi", + "version": "==0.1" + } + }, + "develop": {} +} From c6e435e3453ff7925641dab5eb62f3a6550767e9 Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Wed, 3 Jan 2024 13:04:44 +0000 Subject: [PATCH 14/48] Minor changes Signed-off-by: Moasib-Arif --- Pipfile | 15 ----- Pipfile.lock | 59 ------------------- README.md | 13 +--- dpytools/__init__.py | 6 +- .../{http_client => http_clients}/README.md | 0 .../{http_client => http_clients}/http.py | 0 tests/test_nothing.py | 7 +++ 7 files changed, 13 insertions(+), 87 deletions(-) delete mode 100644 Pipfile delete mode 100644 Pipfile.lock rename dpytools/{http_client => http_clients}/README.md (100%) rename dpytools/{http_client => http_clients}/http.py (100%) create mode 100644 tests/test_nothing.py diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 2bd4271..0000000 --- a/Pipfile +++ /dev/null @@ -1,15 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -config = "*" -logger = "*" -slack = "*" -sns = "*" - -[dev-packages] - -[requires] -python_version = "2.7" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index a13d574..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,59 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "93db113234d7ef12258fcefd66bbe8f88abada307b11ad353ef1d6005a636901" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "2.7" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "config": { - "hashes": [ - "sha256:2dd4a03aa383d30711d5a3325a1858de225328d61950a85be5b74c100f63016d", - "sha256:79ffa009ff2663cc8ca29e56bcec031c044609d4bafaa4f884132a413101ce84" - ], - "index": "pypi", - "version": "==0.5.1" - }, - "logger": { - "hashes": [ - "sha256:4ecac57133c6376fa215f0fe6b4dc4d60e4d1ad8be005cab4e8a702df682f8b3" - ], - "index": "pypi", - "version": "==1.4" - }, - "python-http-client": { - "hashes": [ - "sha256:ad371d2bbedc6ea15c26179c6222a78bc9308d272435ddf1d5c84f068f249a36", - "sha256:bf841ee45262747e00dec7ee9971dfb8c7d83083f5713596488d67739170cea0" - ], - "index": "pypi", - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==3.3.7" - }, - "slack": { - "hashes": [ - "sha256:e0b0cd3ba730e81aa82eef0973a7970652b99f6445cb71a6dde1d29e6d13b051" - ], - "index": "pypi", - "version": "==0.0.2" - }, - "sns": { - "hashes": [ - "sha256:2d847d23eb8e0ea20054dbadabdecfafc529e4c7a16a6ce70205c4bf8ebc6cf4" - ], - "index": "pypi", - "version": "==0.1" - } - }, - "develop": {} -} diff --git a/README.md b/README.md index cd61df7..902bc87 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,13 @@ Simple reusable python resources for digital publishing. Follow these steps to install the package on your local machine: -1. **Clone the repository** +1. **Install the package** Open your terminal and run the following command: ```bash - git clone https://github.com/GSS-Cogs/dp-python-tools.git - -2. **Navigate to the cloned repositry** - ```bash - cd dp-python-tools - -3. **Install all the packages** - ```bash - pip install + pip install git+https://github.com/GSS-Cogs/dp-python-tools.git + ``` ## Usage diff --git a/dpytools/__init__.py b/dpytools/__init__.py index cdb0a7c..048de67 100644 --- a/dpytools/__init__.py +++ b/dpytools/__init__.py @@ -1,5 +1,5 @@ -from http_client.http import HttpClient -from config.config import Config +from http_clients.http import HttpClient +from config.config import Config from logger.logger import logger from slack.slack import SlackNotifier -from sns.sns import Subscription, publish \ No newline at end of file +from sns.sns import Subscription, publish diff --git a/dpytools/http_client/README.md b/dpytools/http_clients/README.md similarity index 100% rename from dpytools/http_client/README.md rename to dpytools/http_clients/README.md diff --git a/dpytools/http_client/http.py b/dpytools/http_clients/http.py similarity index 100% rename from dpytools/http_client/http.py rename to dpytools/http_clients/http.py diff --git a/tests/test_nothing.py b/tests/test_nothing.py new file mode 100644 index 0000000..e483b19 --- /dev/null +++ b/tests/test_nothing.py @@ -0,0 +1,7 @@ + +# 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 ff0fee8f283fd8e783614bbe991958d485c1a8ae Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Thu, 4 Jan 2024 13:58:32 +0000 Subject: [PATCH 15/48] Error test not working --- dpytools/__init__.py | 12 +- dpytools/http/http.py | 18 --- dpytools/{http => http_clients}/README.md | 0 dpytools/http_clients/http.py | 51 +++++++ poetry.lock | 160 +++++++++++++++++++++- pyproject.toml | 1 + tests/test_http.py | 56 ++++++++ 7 files changed, 274 insertions(+), 24 deletions(-) delete mode 100644 dpytools/http/http.py rename dpytools/{http => http_clients}/README.md (100%) create mode 100644 dpytools/http_clients/http.py create mode 100644 tests/test_http.py diff --git a/dpytools/__init__.py b/dpytools/__init__.py index bff3af3..e71df9a 100644 --- a/dpytools/__init__.py +++ b/dpytools/__init__.py @@ -1,5 +1,7 @@ -from http.http import HttpClient -from config.config import Config -from logger.logger import logger -from slack.slack import SlackNotifier -from sns.sns import Subscription, publish \ No newline at end of file +from http_clients.http import HttpClient +# from config.config import Config +# from logger.logger import logger +# from slack.slack import SlackNotifier +# from sns.sns import Subscription, publish + +print("All imports successful!") \ No newline at end of file diff --git a/dpytools/http/http.py b/dpytools/http/http.py deleted file mode 100644 index 0abe2c7..0000000 --- a/dpytools/http/http.py +++ /dev/null @@ -1,18 +0,0 @@ - -# Import and use backoff -# https://pypi.org/project/backoff/ - -class HttpClient: - - # Methods should use backoff - # Methods should lof what they are trying to do before they do it. - # When you backoff and retry it should log each failure and why, - # as well as which attempt number it is. - - def get(): - ... - - def post(): - ... - - \ No newline at end of file diff --git a/dpytools/http/README.md b/dpytools/http_clients/README.md similarity index 100% rename from dpytools/http/README.md rename to dpytools/http_clients/README.md diff --git a/dpytools/http_clients/http.py b/dpytools/http_clients/http.py new file mode 100644 index 0000000..cfedd87 --- /dev/null +++ b/dpytools/http_clients/http.py @@ -0,0 +1,51 @@ +import backoff +import requests +import logging + + +def log_retry(details): + logging.error(f"Request failed, retrying... Attempt #{details['tries']}") + + +class HttpClient: + + def __init__(self, backoff_max=30): + self.backoff_max = backoff_max + + + @backoff.on_exception( + backoff.expo, + requests.exceptions.RequestException, + max_time=lambda self: self.backoff_max, + on_backoff=log_retry + ) + def get(self, url, *args, **kwargs): + # Extract the 'timeout' argument if it exists + timeout = kwargs.pop('timeout', None) + logging.info(f"Sending GET request to {url}") + return self._request(requests.get, url,timeout=timeout, *args, **kwargs) + + + @backoff.on_exception( + backoff.expo, + requests.exceptions.RequestException, + max_time=lambda self: self.backoff_max, + on_backoff=log_retry, + ) + def post(self, url, *args, **kwargs): + # Extract the 'timeout' argument if it exists + timeout = kwargs.pop('timeout', None) + logging.info(f"Sending POST request to {url}") + return self._request(requests.post, url,timeout=timeout, *args, **kwargs) + + + def _request(self, method, url, *args, **kwargs): + try: + response = method(url, *args, **kwargs) + response.raise_for_status() + return response + except requests.exceptions.RequestException as e: + logging.error(f"Request failed due to {str(e)}, retrying...") + raise + + \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 707fccd..03b2144 100644 --- a/poetry.lock +++ b/poetry.lock @@ -42,6 +42,116 @@ d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "certifi" +version = "2023.11.17" +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"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + [[package]] name = "click" version = "8.1.7" @@ -148,6 +258,17 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + [[package]] name = "iniconfig" version = "2.0.0" @@ -279,6 +400,27 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[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" @@ -327,7 +469,23 @@ files = [ {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] +[[package]] +name = "urllib3" +version = "2.1.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"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "3f9b13c7807ad36f5361e3460bf1ebefb3015de5d909df5cfa63e118fc445442" +content-hash = "e25cdab637bc4051e3321f1500e78f8777294027aade05f9e372ebbab4d7be48" diff --git a/pyproject.toml b/pyproject.toml index b45da0b..afb71cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.9" +requests = "^2.31.0" [tool.poetry.group.dev.dependencies] pytest = "^7.4.3" diff --git a/tests/test_http.py b/tests/test_http.py new file mode 100644 index 0000000..e58e48f --- /dev/null +++ b/tests/test_http.py @@ -0,0 +1,56 @@ +import pytest +from unittest import mock +from requests.exceptions import RequestException +from ..dpytools.http_clients.http import HttpClient + + +@mock.patch('http.requests.get') +def test_get_method(mock_get): + """ + Test that the get method calls requests.get and + returns a response with status code 200 + """ + mock_get.return_value.status_code = 200 + client = HttpClient() + response = client.get('http://testurl.com') + assert mock_get.called + assert response.status_code == 200 + + +@mock.patch('http.requests.post') +def test_post_method(mock_post): + """ + Test that the post method calls requests.post and + returns a response with status code 200 + """ + mock_post.return_value.status_code = 200 + client = HttpClient() + response = client.post('http://testurl.com') + assert mock_post.called + assert response.status_code == 200 + + +@mock.patch('http.requests.get') +def test_backoff_on_exception(mock_get): + """ + Test that the get method retries on RequestException + """ + mock_get.side_effect = RequestException + client = HttpClient() + with pytest.raises(RequestException): + client.get('http://testurl.com') + assert mock_get.call_count == client.backoff_max + + +@mock.patch('http.requests.get') +def test_propagate_kwargs(mock_get): + """ + Test that the get method propagates keyword arguments + """ + mock_get.return_value.status_code = 200 + client = HttpClient() + headers = {'test-header': 'test-value'} + client.get('http://testurl.com', headers=headers) + args, kwargs = mock_get.call_args + assert 'headers' in kwargs + assert kwargs['headers'] == headers \ No newline at end of file From fe17166e9c11c5c68bf60545e92645c51ad9a225 Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Thu, 4 Jan 2024 14:01:13 +0000 Subject: [PATCH 16/48] minor chnages --- dpytools/__init__.py | 9 --------- dpytools/http_clients/http.py | 19 ------------------- 2 files changed, 28 deletions(-) diff --git a/dpytools/__init__.py b/dpytools/__init__.py index 2ed70e1..048de67 100644 --- a/dpytools/__init__.py +++ b/dpytools/__init__.py @@ -1,14 +1,5 @@ from http_clients.http import HttpClient -<<<<<<< HEAD -# from config.config import Config -# from logger.logger import logger -# from slack.slack import SlackNotifier -# from sns.sns import Subscription, publish - -print("All imports successful!") -======= from config.config import Config from logger.logger import logger from slack.slack import SlackNotifier from sns.sns import Subscription, publish ->>>>>>> main diff --git a/dpytools/http_clients/http.py b/dpytools/http_clients/http.py index 1fecd85..cfedd87 100644 --- a/dpytools/http_clients/http.py +++ b/dpytools/http_clients/http.py @@ -1,4 +1,3 @@ -<<<<<<< HEAD import backoff import requests import logging @@ -48,23 +47,5 @@ def _request(self, method, url, *args, **kwargs): except requests.exceptions.RequestException as e: logging.error(f"Request failed due to {str(e)}, retrying...") raise -======= - -# Import and use backoff -# https://pypi.org/project/backoff/ - -class HttpClient: - - # Methods should use backoff - # Methods should lof what they are trying to do before they do it. - # When you backoff and retry it should log each failure and why, - # as well as which attempt number it is. - - def get(): - ... - - def post(): - ... ->>>>>>> main \ No newline at end of file From 9cc33fdcc4a25a0ee38722f9bc24c19453d9bb16 Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Fri, 5 Jan 2024 15:36:21 +0000 Subject: [PATCH 17/48] Working HTTP Client --- .vscode/settings.json | 7 ++ dpytools/__init__.py | 6 +- dpytools/http_clients/http.py | 68 +++++++++++------- poetry.lock | 8 +-- pyproject.toml | 1 + tests/test_http.py | 128 ++++++++++++++++++++++++---------- 6 files changed, 149 insertions(+), 69 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9b38853 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/dpytools/__init__.py b/dpytools/__init__.py index 048de67..8b13789 100644 --- a/dpytools/__init__.py +++ b/dpytools/__init__.py @@ -1,5 +1 @@ -from http_clients.http import HttpClient -from config.config import Config -from logger.logger import logger -from slack.slack import SlackNotifier -from sns.sns import Subscription, publish + diff --git a/dpytools/http_clients/http.py b/dpytools/http_clients/http.py index cfedd87..dbb32b8 100644 --- a/dpytools/http_clients/http.py +++ b/dpytools/http_clients/http.py @@ -1,51 +1,69 @@ import backoff -import requests +import http.client +from http.client import HTTPException +from urllib.parse import urlparse import logging +# Function to log retry attempts def log_retry(details): logging.error(f"Request failed, retrying... Attempt #{details['tries']}") class HttpClient: - + # Initialize HttpClient with a backoff_max value def __init__(self, backoff_max=30): self.backoff_max = backoff_max - - + + # GET request method with exponential backoff @backoff.on_exception( - backoff.expo, - requests.exceptions.RequestException, - max_time=lambda self: self.backoff_max, - on_backoff=log_retry + backoff.expo, + HTTPException, + max_time=30, + on_backoff=log_retry ) def get(self, url, *args, **kwargs): - # Extract the 'timeout' argument if it exists - timeout = kwargs.pop('timeout', None) + timeout = kwargs.pop('timeout', None) logging.info(f"Sending GET request to {url}") - return self._request(requests.get, url,timeout=timeout, *args, **kwargs) - + return self._request("GET", url, timeout=timeout, *args, **kwargs) + # POST request method with exponential backoff @backoff.on_exception( - backoff.expo, - requests.exceptions.RequestException, - max_time=lambda self: self.backoff_max, - on_backoff=log_retry, + backoff.expo, + HTTPException, + max_time=30, + on_backoff=log_retry, ) def post(self, url, *args, **kwargs): - # Extract the 'timeout' argument if it exists - timeout = kwargs.pop('timeout', None) + timeout = kwargs.pop('timeout', None) logging.info(f"Sending POST request to {url}") - return self._request(requests.post, url,timeout=timeout, *args, **kwargs) + return self._request("POST", url, timeout=timeout, *args, **kwargs) - + # Private method to handle the request def _request(self, method, url, *args, **kwargs): try: - response = method(url, *args, **kwargs) - response.raise_for_status() + headers = kwargs.pop('headers', {}) + + url_parts = urlparse(url) + https_connection = http.client.HTTPSConnection(url_parts.netloc) + path = url_parts.path or '/' + https_connection.request(method, path, headers=headers, *args, **kwargs) + + response = https_connection.getresponse() + response_content = response.read() + https_connection.close() + + # Raise an exception if the HTTP status is 400 or above + if response.status >= 400: + raise HTTPException(f"HTTP request failed with status {response.status}, response: {response_content.decode()}") + return response - except requests.exceptions.RequestException as e: + + # Handle HTTPException separately to log and retry + except HTTPException as e: logging.error(f"Request failed due to {str(e)}, retrying...") raise - - \ No newline at end of file + # Handle other exceptions + except Exception as e: + logging.error(f"An unexpected error occurred: {str(e)}") + raise \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 03b2144..acffcc0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -362,13 +362,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pytest" -version = "7.4.3" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -488,4 +488,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "e25cdab637bc4051e3321f1500e78f8777294027aade05f9e372ebbab4d7be48" +content-hash = "d99ed10e259668238742b8b52d9a39fa9f005c85fa9318b26a7cf37516a47d34" diff --git a/pyproject.toml b/pyproject.toml index afb71cc..39c3667 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.9" requests = "^2.31.0" +pytest = "^7.4.4" [tool.poetry.group.dev.dependencies] pytest = "^7.4.3" diff --git a/tests/test_http.py b/tests/test_http.py index e58e48f..e84874b 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -1,56 +1,114 @@ import pytest -from unittest import mock -from requests.exceptions import RequestException -from ..dpytools.http_clients.http import HttpClient +from unittest.mock import patch, MagicMock +from http.client import HTTPResponse, HTTPException +from dpytools.http_clients.http import HttpClient +# Mock the HTTPSConnection class +@patch('http.client.HTTPSConnection') +def test_get(mock_connection): + """ + Test that the get method returns a response object + """ + + # Create a mock response object + mock_response = MagicMock(HTTPResponse) + mock_response.status = 200 + mock_response.read.return_value = b'Test response content' + mock_connection.return_value.getresponse.return_value = mock_response + + # Create an instance of HttpClient and make a GET request + client = HttpClient() + response = client.get('http://example.com') -@mock.patch('http.requests.get') -def test_get_method(mock_get): + # Assertions to check the response status, content and the connection call + assert response.status == 200 + assert response.read().decode() == 'Test response content' + mock_connection.assert_called_once_with('example.com') + + +@patch('http.client.HTTPSConnection') +def test_post(mock_connection): """ - Test that the get method calls requests.get and - returns a response with status code 200 + Test that the post method returns a response object """ - mock_get.return_value.status_code = 200 + + # Create a mock response object + mock_response = MagicMock(HTTPResponse) + mock_response.status = 200 + mock_response.read.return_value = b'Test response content' + mock_connection.return_value.getresponse.return_value = mock_response + + # Create an instance of HttpClient and make a POST request client = HttpClient() - response = client.get('http://testurl.com') - assert mock_get.called - assert response.status_code == 200 + response = client.post('http://example.com') + + # Assertions to check the response status, content and the connection call + assert response.status == 200 + assert response.read().decode() == 'Test response content' + mock_connection.assert_called_once_with('example.com') -@mock.patch('http.requests.post') -def test_post_method(mock_post): +@patch('http.client.HTTPSConnection') +def test_backoff_on_exception(mock_connection): """ - Test that the post method calls requests.post and - returns a response with status code 200 + Test that the get method retries on HTTPException """ - mock_post.return_value.status_code = 200 + + # Create a mock response object + mock_response = MagicMock(HTTPResponse) + mock_response.status = 200 + + # Raise HTTPException on the first call, then return the mock_response + mock_connection.return_value.getresponse.side_effect = [HTTPException('HTTP Error'), mock_response] + + # Create an instance of HttpClient and make a GET request client = HttpClient() - response = client.post('http://testurl.com') - assert mock_post.called - assert response.status_code == 200 + response = client.get('http://example.com') + # Assertions to check the response status and the number of getresponse calls + assert response.status == 200 + assert mock_connection.return_value.getresponse.call_count == 2 -@mock.patch('http.requests.get') -def test_backoff_on_exception(mock_get): + +@patch('http.client.HTTPSConnection') +def test_request(mock_connection): """ - Test that the get method retries on RequestException + Test that the _request method returns a response object """ - mock_get.side_effect = RequestException + + # Create a mock response object + mock_response = MagicMock(HTTPResponse) + mock_response.status = 200 + mock_response.read.return_value = b'Test response content' + mock_connection.return_value.getresponse.return_value = mock_response + + # Create an instance of HttpClient and make a request client = HttpClient() - with pytest.raises(RequestException): - client.get('http://testurl.com') - assert mock_get.call_count == client.backoff_max + response = client._request('GET', 'http://example.com') + # Assertions to check the response status, content and the connection call + assert response.status == 200 + assert response.read().decode() == 'Test response content' + mock_connection.assert_called_once_with('example.com') -@mock.patch('http.requests.get') -def test_propagate_kwargs(mock_get): + +@patch('http.client.HTTPSConnection') +def test_request_with_timeout(mock_connection): """ - Test that the get method propagates keyword arguments + Test _request method with timeout passed as kwargs """ - mock_get.return_value.status_code = 200 + + # Create a mock response object + mock_response = MagicMock(HTTPResponse) + mock_response.status = 200 + mock_response.read.return_value = b'Test response content' + mock_connection.return_value.getresponse.return_value = mock_response + + # Create an instance of HttpClient and make a request with a timeout client = HttpClient() - headers = {'test-header': 'test-value'} - client.get('http://testurl.com', headers=headers) - args, kwargs = mock_get.call_args - assert 'headers' in kwargs - assert kwargs['headers'] == headers \ No newline at end of file + response = client._request('GET', 'http://example.com', timeout=5) + + # Assertions to check the response status, content and the connection call + assert response.status == 200 + assert response.read().decode() == 'Test response content' + mock_connection.assert_called_once_with('example.com') \ No newline at end of file From 628194bbe2f57249babd9474923a4aa341050e72 Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Fri, 5 Jan 2024 15:54:03 +0000 Subject: [PATCH 18/48] Minor changes --- .../http_clients/{http.py => http_custom.py} | 0 poetry.lock | 48 ++++++++++++++++++- pyproject.toml | 2 + tests/test_http.py | 2 +- 4 files changed, 50 insertions(+), 2 deletions(-) rename dpytools/http_clients/{http.py => http_custom.py} (100%) diff --git a/dpytools/http_clients/http.py b/dpytools/http_clients/http_custom.py similarity index 100% rename from dpytools/http_clients/http.py rename to dpytools/http_clients/http_custom.py diff --git a/poetry.lock b/poetry.lock index acffcc0..b659ab9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,16 @@ # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +[[package]] +name = "backoff" +version = "2.2.1" +description = "Function decoration for backoff and retry" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, + {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, +] + [[package]] name = "black" version = "23.11.0" @@ -258,6 +269,20 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "http-client" +version = "0.1.22" +description = "Fast and robust HTTP client based on PyCurl" +optional = false +python-versions = "*" +files = [ + {file = "http_client-0.1.22.tar.gz", hash = "sha256:f33d77e4e08b70659e1497eeb3d2121a3c6e8a4c252b3c6a56e87a16bb442f91"}, +] + +[package.dependencies] +pycurl = "*" +six = "*" + [[package]] name = "idna" version = "3.6" @@ -360,6 +385,16 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pycurl" +version = "7.45.2" +description = "PycURL -- A Python Interface To The cURL library" +optional = false +python-versions = ">=3.5" +files = [ + {file = "pycurl-7.45.2.tar.gz", hash = "sha256:5730590be0271364a5bddd9e245c9cc0fb710c4cbacbdd95264a3122d23224ca"}, +] + [[package]] name = "pytest" version = "7.4.4" @@ -447,6 +482,17 @@ files = [ {file = "ruff-0.1.6.tar.gz", hash = "sha256:1b09f29b16c6ead5ea6b097ef2764b42372aebe363722f1605ecbcd2b9207184"}, ] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + [[package]] name = "tomli" version = "2.0.1" @@ -488,4 +534,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "d99ed10e259668238742b8b52d9a39fa9f005c85fa9318b26a7cf37516a47d34" +content-hash = "eb6fcf9517e94cc494a812a0f83d8907331f360ab237c66c9251a081aaf8289d" diff --git a/pyproject.toml b/pyproject.toml index 39c3667..556bee5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,8 @@ readme = "README.md" python = "^3.9" requests = "^2.31.0" pytest = "^7.4.4" +backoff = "^2.2.1" +http-client = "^0.1.22" [tool.poetry.group.dev.dependencies] pytest = "^7.4.3" diff --git a/tests/test_http.py b/tests/test_http.py index e84874b..ca44079 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -1,7 +1,7 @@ import pytest from unittest.mock import patch, MagicMock from http.client import HTTPResponse, HTTPException -from dpytools.http_clients.http import HttpClient +from dpytools.http_clients.http_custom import HttpClient # Mock the HTTPSConnection class @patch('http.client.HTTPSConnection') From 2705d5203310a30cc578f62fac28bd46f2f6fddf Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Mon, 8 Jan 2024 10:18:30 +0000 Subject: [PATCH 19/48] Added all the imports --- dpytools/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dpytools/__init__.py b/dpytools/__init__.py index 8b13789..1adaef2 100644 --- a/dpytools/__init__.py +++ b/dpytools/__init__.py @@ -1 +1,5 @@ - +from http_clients.http_custom import HttpClient +from config.config import Config +from logger.logger import logger +from slack.slack import SlackNotifier +from sns.sns import Subscription, publish \ No newline at end of file From 6aaad33389282d1b9bfaa99fb3021e22651d47da Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Mon, 8 Jan 2024 10:28:02 +0000 Subject: [PATCH 20/48] updated pycurl --- ...7.45.2-cp39-cp39-macosx_10_9_universal2.whl | Bin 0 -> 199395 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pycurl-7.45.2-cp39-cp39-macosx_10_9_universal2.whl diff --git a/pycurl-7.45.2-cp39-cp39-macosx_10_9_universal2.whl b/pycurl-7.45.2-cp39-cp39-macosx_10_9_universal2.whl new file mode 100644 index 0000000000000000000000000000000000000000..4cf35119ad44408ceafb38eb344a195444b469af GIT binary patch literal 199395 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

ELO*tEm%6I3j~C^f76 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

9uu&H*tMOg`EZD+VcZ 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

ELO*tEm%6I3j~C^f76 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

9uu&H*tMOg`EZD+VcZ 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

>^8C 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 diff --git a/pyproject.toml b/pyproject.toml index 556bee5..54b271b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,6 @@ python = "^3.9" requests = "^2.31.0" pytest = "^7.4.4" backoff = "^2.2.1" -http-client = "^0.1.22" [tool.poetry.group.dev.dependencies] pytest = "^7.4.3" From 977bd4f681d7409ca71965aaa6f755eacafd8fe5 Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Mon, 8 Jan 2024 11:13:18 +0000 Subject: [PATCH 22/48] removed import --- dpytools/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dpytools/__init__.py b/dpytools/__init__.py index 1adaef2..e69de29 100644 --- a/dpytools/__init__.py +++ b/dpytools/__init__.py @@ -1,5 +0,0 @@ -from http_clients.http_custom import HttpClient -from config.config import Config -from logger.logger import logger -from slack.slack import SlackNotifier -from sns.sns import Subscription, publish \ No newline at end of file From bae9186ac4f84c6122d1228acbbfc4b5fb681123 Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Mon, 8 Jan 2024 15:27:55 +0000 Subject: [PATCH 23/48] Comments addressed --- .../http_clients/{http_custom.py => base.py} | 8 +++---- tests/test_http.py | 22 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) rename dpytools/http_clients/{http_custom.py => base.py} (85%) diff --git a/dpytools/http_clients/http_custom.py b/dpytools/http_clients/base.py similarity index 85% rename from dpytools/http_clients/http_custom.py rename to dpytools/http_clients/base.py index dbb32b8..c9b92a7 100644 --- a/dpytools/http_clients/http_custom.py +++ b/dpytools/http_clients/base.py @@ -10,7 +10,7 @@ def log_retry(details): logging.error(f"Request failed, retrying... Attempt #{details['tries']}") -class HttpClient: +class BaseHttpClient: # Initialize HttpClient with a backoff_max value def __init__(self, backoff_max=30): self.backoff_max = backoff_max @@ -55,15 +55,15 @@ def _request(self, method, url, *args, **kwargs): # Raise an exception if the HTTP status is 400 or above if response.status >= 400: - raise HTTPException(f"HTTP request failed with status {response.status}, response: {response_content.decode()}") + raise HTTPException(f"{method} request to {url} failed with status {response.status}, response: {response_content.decode()}") return response # Handle HTTPException separately to log and retry except HTTPException as e: - logging.error(f"Request failed due to {str(e)}, retrying...") + logging.error(f"Request failed due to {str(e)} when sending a {method} to {url} with headers {headers}, retrying...") raise # Handle other exceptions except Exception as e: - logging.error(f"An unexpected error occurred: {str(e)}") + logging.error(f"An unexpected error occurred: {str(e)} when sending a {method} to {url} with headers {headers}") raise \ No newline at end of file diff --git a/tests/test_http.py b/tests/test_http.py index ca44079..85c65f0 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -1,7 +1,7 @@ import pytest from unittest.mock import patch, MagicMock from http.client import HTTPResponse, HTTPException -from dpytools.http_clients.http_custom import HttpClient +from dpytools.http_clients.base import BaseHttpClient # Mock the HTTPSConnection class @patch('http.client.HTTPSConnection') @@ -16,8 +16,8 @@ def test_get(mock_connection): mock_response.read.return_value = b'Test response content' mock_connection.return_value.getresponse.return_value = mock_response - # Create an instance of HttpClient and make a GET request - client = HttpClient() + # Create an instance of BaseHttpClient and make a GET request + client = BaseHttpClient() response = client.get('http://example.com') # Assertions to check the response status, content and the connection call @@ -38,8 +38,8 @@ def test_post(mock_connection): mock_response.read.return_value = b'Test response content' mock_connection.return_value.getresponse.return_value = mock_response - # Create an instance of HttpClient and make a POST request - client = HttpClient() + # Create an instance of BaseHttpClient and make a POST request + client = BaseHttpClient() response = client.post('http://example.com') # Assertions to check the response status, content and the connection call @@ -61,8 +61,8 @@ def test_backoff_on_exception(mock_connection): # Raise HTTPException on the first call, then return the mock_response mock_connection.return_value.getresponse.side_effect = [HTTPException('HTTP Error'), mock_response] - # Create an instance of HttpClient and make a GET request - client = HttpClient() + # Create an instance of BaseHttpClient and make a GET request + client = BaseHttpClient() response = client.get('http://example.com') # Assertions to check the response status and the number of getresponse calls @@ -82,8 +82,8 @@ def test_request(mock_connection): mock_response.read.return_value = b'Test response content' mock_connection.return_value.getresponse.return_value = mock_response - # Create an instance of HttpClient and make a request - client = HttpClient() + # Create an instance of BaseHttpClient and make a request + client = BaseHttpClient() response = client._request('GET', 'http://example.com') # Assertions to check the response status, content and the connection call @@ -104,8 +104,8 @@ def test_request_with_timeout(mock_connection): mock_response.read.return_value = b'Test response content' mock_connection.return_value.getresponse.return_value = mock_response - # Create an instance of HttpClient and make a request with a timeout - client = HttpClient() + # Create an instance of BaseHttpClient and make a request with a timeout + client = BaseHttpClient() response = client._request('GET', 'http://example.com', timeout=5) # Assertions to check the response status, content and the connection call From ecbd25e279deb856638a2fb8221c80f82503712f Mon Sep 17 00:00:00 2001 From: Sarah Johnson Date: Tue, 9 Jan 2024 10:29:20 +0000 Subject: [PATCH 24/48] Draft PR --- .gitignore | 5 +- dpytools/logger/logger.py | 183 +++++++++++++++++++- dpytools/logger/middleware.py | 51 ++++++ dpytools/logger/utils.py | 15 ++ poetry.lock | 311 +++++++++++++++++++++++++++++++++- pyproject.toml | 5 +- 6 files changed, 564 insertions(+), 6 deletions(-) create mode 100644 dpytools/logger/middleware.py create mode 100644 dpytools/logger/utils.py diff --git a/.gitignore b/.gitignore index c5cb1af..71e58d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -.coverage \ No newline at end of file +.coverage +.venv +.DS_Store +*.pyc \ No newline at end of file diff --git a/dpytools/logger/logger.py b/dpytools/logger/logger.py index ba3db7c..19010fa 100644 --- a/dpytools/logger/logger.py +++ b/dpytools/logger/logger.py @@ -1,2 +1,183 @@ +import logging +import os +import sys -logger = "I will be the logger" \ No newline at end of file +import structlog + +from utils import str_to_bool + +logger = structlog.stdlib.get_logger() +HUMAN_FRIENDLY_LOGS = str_to_bool(os.environ.get("HUMAN_FRIENDLY_LOGS", "false")) + +# #4 TODO Add some testing - see https://www.structlog.org/en/stable/testing.html for guidance + + +# #4 QUESTION What does this class actually do? If I comment it out, everything still works... (I think) +class DpLogger: + def __init__(self, namespace: str): + self.namespace = namespace + + def log(self, event, level, data=None): + trace_id = generate_trace_id() + span_id = generate_span_id() + + severity = { + "debug": 3, # INFO + "warning": 2, # WARNING + "error": 1, # ERROR + }.get( + level, 3 + ) # Default to INFO + + log_entry = { + "namespace": self.namespace, + "event": event, + "trace_id": trace_id, + "span_id": span_id, + "severity": severity, + "data": data if data is not None else {}, + } + + logger.log(level, **{"event_dict": log_entry}) + + def debug(self, event, data=None): + self.log(event, "debug", data) + + def warning(self, event, data=None): + self.log(event, "warning", data) + + def error(self, event, data=None): + self.log(event, "error", data) + + +def generate_trace_id(): + # TODO Implement this (https://opencensus.io/tracing/span/traceid/) + return "your_trace_id" + + +def generate_span_id(): + # TODO Implement this (https://opencensus.io/tracing/span/spanid/) + return "your_span_id" + + +# #4 TODO Output formatting (https://github.com/ONSdigital/dp-standards/blob/main/LOGGING_STANDARDS.md#output-formatting) +# #4 UTF-8 encoding is done (structlog.processors.UnicodeEncoder()) +# #4 Still need to work out how to format JSON logs as JSON Lines (may need to create a callable to pass to structlog.processors.JSONRenderer()) +# #4 TODO Add namespace to output - using an env var? +def configure_logger(enable_console_logs: bool = HUMAN_FRIENDLY_LOGS): + shared_processors = [ + structlog.processors.TimeStamper(fmt="iso", key="created_at"), + structlog.processors.UnicodeEncoder(), + structlog.stdlib.add_log_level, + structlog.stdlib.add_logger_name, + structlog.contextvars.merge_contextvars, + structlog.processors.CallsiteParameterAdder( + { + structlog.processors.CallsiteParameter.PATHNAME, + structlog.processors.CallsiteParameter.FILENAME, + structlog.processors.CallsiteParameter.MODULE, + structlog.processors.CallsiteParameter.FUNC_NAME, + structlog.processors.CallsiteParameter.THREAD, + structlog.processors.CallsiteParameter.THREAD_NAME, + structlog.processors.CallsiteParameter.PROCESS, + structlog.processors.CallsiteParameter.PROCESS_NAME, + } + ), + structlog.stdlib.ExtraAdder(), + ] + + structlog.configure( + processors=shared_processors + + [structlog.stdlib.ProcessorFormatter.wrap_for_formatter], + logger_factory=structlog.stdlib.LoggerFactory(), + wrapper_class=structlog.stdlib.BoundLogger, + cache_logger_on_first_use=True, + ) + # TODO Pass a callable to JSONRenderer to format logs as JSON Lines + logs_render = ( + structlog.dev.ConsoleRenderer(colors=True) + if enable_console_logs + else structlog.processors.JSONRenderer() + ) + + _configure_default_logging_by_custom(shared_processors, logs_render) + + +def _configure_default_logging_by_custom(shared_processors, logs_render): + handler = logging.StreamHandler() + formatter = structlog.stdlib.ProcessorFormatter( + foreign_pre_chain=shared_processors, + processors=[ + _extract_from_record, + structlog.stdlib.ProcessorFormatter.remove_processors_meta, + logs_render, + ], + ) + handler.setFormatter(formatter) + root_uvicorn_logger = logging.getLogger() + root_uvicorn_logger.addHandler(handler) + root_uvicorn_logger.setLevel(logging.INFO) + + +def _extract_from_record(_, __, event_dict): + record = event_dict["_record"] + event_dict["thread_name"] = record.threadName + event_dict["process_name"] = record.processName + return event_dict + + +# #4 TODO Add namespace to create_log_event function arguments? +# #4 Do we need to specify a format for, e.g., http_data? The DP logging standards include a list of fields for HTTP events (https://github.com/ONSdigital/dp-standards/blob/main/LOGGING_STANDARDS.md#http-event-data), as well as auth events and errors +# Define a function to create a log event with the specified structure +def create_log_event( + event: str, + severity: str, + http_data: dict = None, + auth_data: dict = None, + errors: list = None, + raw: str = None, + data: dict = None, +): + # Create a log event following the specified structure + log_event = { + "namespace": "your-service-name", + "event": event, + "severity": severity, + } + + if http_data: + log_event["http"] = http_data + if auth_data: + log_event["auth"] = auth_data + if errors: + log_event["errors"] = errors + if raw: + log_event["raw"] = raw + if data: + log_event["data"] = data + + logger.info(**log_event) + + +# Handle uncaught exceptions +def handle_exception(exc_type, exc_value, exc_traceback): + """ + Log any uncaught exception instead of letting it be printed by Python + """ + if issubclass(exc_type, KeyboardInterrupt): + sys.__excepthook__(exc_type, exc_value, exc_traceback) + return + + logging.RootLogger.error( + "Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback) + ) + + create_log_event( + event="Uncaught exception", + severity="ERROR", + errors=[{"message": str(exc_value)}], + ) + sys.__excepthook__(exc_type, exc_value, exc_traceback) + + +sys.excepthook = handle_exception diff --git a/dpytools/logger/middleware.py b/dpytools/logger/middleware.py new file mode 100644 index 0000000..9a0d0f3 --- /dev/null +++ b/dpytools/logger/middleware.py @@ -0,0 +1,51 @@ +import time + +import structlog +from fastapi import Request, Response +from structlog.contextvars import bind_contextvars, clear_contextvars + +from logger import configure_logger, logger + +configure_logger() + + +async def logging_middleware(request: Request, call_next) -> Response: + clear_contextvars() + # These context vars will be added to all log entries emitted during the request + request_id = request.headers.get("request-id") + bind_contextvars(request_id=request_id) + + start_time = time.perf_counter_ns() + + try: + response = await call_next(request) + except Exception as exc: + response = Response(status_code=500) + structlog.get_logger("api.error").exception("Uncaught exception", exc_info=exc) + raise + finally: + process_time = time.perf_counter_ns() - start_time + status_code = response.status_code + + log_message = "Request completed" + log_context = { + "data": { + "path": request.url.path, + "method": request.method, + "status_code": status_code, + "process_time": process_time / 10**9, # Convert to seconds + } + } + + if status_code == 200: + logger.info(log_message, **log_context) + elif status_code == 404: + log_message = "Resource not found" + logger.info(log_message, **log_context) + elif status_code == 406: + log_message = "Request not acceptable" + logger.info(log_message, **log_context) + + response.headers["X-Process-Time"] = str(process_time / 10**9) + + return response diff --git a/dpytools/logger/utils.py b/dpytools/logger/utils.py new file mode 100644 index 0000000..a0f5ffb --- /dev/null +++ b/dpytools/logger/utils.py @@ -0,0 +1,15 @@ +from typing import Dict + + +# Mapping of strings to bools for str_to_bool function to work +valid_bool_values: Dict[str, bool] = { + "true": True, + "1": True, + "false": False, + "0": False, +} + + +def str_to_bool(str_val: str, default: bool = False) -> bool: + """Convert input strings to the correct boolean format in order to set environment variable value""" + return valid_bool_values.get(str_val.lower(), default) diff --git a/poetry.lock b/poetry.lock index 707fccd..1989733 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,37 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + +[[package]] +name = "anyio" +version = "4.2.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, + {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] [[package]] name = "black" @@ -148,6 +181,36 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "fastapi" +version = "0.108.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi-0.108.0-py3-none-any.whl", hash = "sha256:8c7bc6d315da963ee4cdb605557827071a9a7f95aeb8fcdd3bde48cdc8764dd7"}, + {file = "fastapi-0.108.0.tar.gz", hash = "sha256:5056e504ac6395bf68493d71fcfc5352fdbd5fda6f88c21f6420d80d81163296"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.29.0,<0.33.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + [[package]] name = "iniconfig" version = "2.0.0" @@ -159,6 +222,20 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "isodate" +version = "0.6.1" +description = "An ISO 8601 date/time/duration parser and formatter" +optional = false +python-versions = "*" +files = [ + {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, + {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, +] + +[package.dependencies] +six = "*" + [[package]] name = "isort" version = "5.12.0" @@ -239,6 +316,156 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pydantic" +version = "2.5.3" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"}, + {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.14.6" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.14.6" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"}, + {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"}, + {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"}, + {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"}, + {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"}, + {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"}, + {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"}, + {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"}, + {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"}, + {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"}, + {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"}, + {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"}, + {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"}, + {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"}, + {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"}, + {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"}, + {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"}, + {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"}, + {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"}, + {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"}, + {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"}, + {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"}, + {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"}, + {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"}, + {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"}, + {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"}, + {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"}, + {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"}, + {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"}, + {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"}, + {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"}, + {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"}, + {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"}, + {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"}, + {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pyparsing" +version = "3.1.1" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + [[package]] name = "pytest" version = "7.4.3" @@ -279,6 +506,27 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "rdflib" +version = "7.0.0" +description = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information." +optional = false +python-versions = ">=3.8.1,<4.0.0" +files = [ + {file = "rdflib-7.0.0-py3-none-any.whl", hash = "sha256:0438920912a642c866a513de6fe8a0001bd86ef975057d6962c79ce4771687cd"}, + {file = "rdflib-7.0.0.tar.gz", hash = "sha256:9995eb8569428059b8c1affd26b25eac510d64f5043d9ce8c84e0d0036e995ae"}, +] + +[package.dependencies] +isodate = ">=0.6.0,<0.7.0" +pyparsing = ">=2.1.0,<4" + +[package.extras] +berkeleydb = ["berkeleydb (>=18.1.0,<19.0.0)"] +html = ["html5lib (>=1.0,<2.0)"] +lxml = ["lxml (>=4.3.0,<5.0.0)"] +networkx = ["networkx (>=2.0.0,<3.0.0)"] + [[package]] name = "ruff" version = "0.1.6" @@ -305,6 +553,63 @@ files = [ {file = "ruff-0.1.6.tar.gz", hash = "sha256:1b09f29b16c6ead5ea6b097ef2764b42372aebe363722f1605ecbcd2b9207184"}, ] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "starlette" +version = "0.32.0.post1" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.8" +files = [ + {file = "starlette-0.32.0.post1-py3-none-any.whl", hash = "sha256:cd0cb10ddb49313f609cedfac62c8c12e56c7314b66d89bb077ba228bada1b09"}, + {file = "starlette-0.32.0.post1.tar.gz", hash = "sha256:e54e2b7e2fb06dff9eac40133583f10dfa05913f5a85bf26f427c7a40a9a3d02"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] + +[[package]] +name = "structlog" +version = "23.2.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"}, +] + +[package.extras] +dev = ["structlog[tests,typing]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "twisted"] +tests = ["freezegun (>=0.2.8)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "simplejson"] +typing = ["mypy (>=1.4)", "rich", "twisted"] + [[package]] name = "tomli" version = "2.0.1" @@ -329,5 +634,5 @@ files = [ [metadata] lock-version = "2.0" -python-versions = "^3.9" -content-hash = "3f9b13c7807ad36f5361e3460bf1ebefb3015de5d909df5cfa63e118fc445442" +python-versions = ">=3.9, <3.12" +content-hash = "d2c30664d53ec742f544a207a43720fbc2b8987990e2685fef03e5cf9dc7b201" diff --git a/pyproject.toml b/pyproject.toml index b45da0b..89d44a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,10 @@ license = "MIT" readme = "README.md" [tool.poetry.dependencies] -python = "^3.9" +python = ">=3.9, <3.12" +structlog = "^23.2.0" +fastapi = "^0.108.0" +rdflib = "^7.0.0" [tool.poetry.group.dev.dependencies] pytest = "^7.4.3" From fd4e86fdae650498ef4bb2e7c03fb3521ec3cb17 Mon Sep 17 00:00:00 2001 From: NickPapONS Date: Thu, 11 Jan 2024 16:42:14 +0000 Subject: [PATCH 25/48] string and integer classes --- dpytools/config/properties/base.py | 11 ++++++++ dpytools/config/properties/integer.py | 38 +++++++++++++++++++++++++++ dpytools/config/properties/string.py | 5 +++- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 dpytools/config/properties/integer.py diff --git a/dpytools/config/properties/base.py b/dpytools/config/properties/base.py index 02dd34c..c058ce8 100644 --- a/dpytools/config/properties/base.py +++ b/dpytools/config/properties/base.py @@ -9,6 +9,11 @@ class BaseProperty(metaclass=ABCMeta): # TODO: getter # allow someone to get the property + def _get_name(self): + return self.name + + def _get_value(self): + return self.value # TODO: setter # categorically disallow anyone from @@ -16,6 +21,12 @@ class BaseProperty(metaclass=ABCMeta): # has been instantiated. # Refuse to do it, and log an error. + def _set_name(self, value): + self.name = value + + def _set_value(self, new_value): + self.value = new_value + @abstractmethod def type_is_valid(self): """ diff --git a/dpytools/config/properties/integer.py b/dpytools/config/properties/integer.py new file mode 100644 index 0000000..ee7fd1c --- /dev/null +++ b/dpytools/config/properties/integer.py @@ -0,0 +1,38 @@ +from typing import Optional + +from .base import BaseProperty + + +class IntegerProperty(BaseProperty): + # TODO: Is there any scenario where IntegerProperty should have regex? + regex: Optional[str] + min_val: Optional[int] + max_val: Optional[int] + + def type_is_valid(self): + """ + Validate that the property looks like + 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 + + def secondary_validation_passed(self): + """ + Non type based validation you might want to + run against a configuration value of this kind. + """ + if not self.value: + raise ValueError(f"Integer value for {self.name} does not exist") + + if self.regex: + # TODO - confirm the value matches the regex + ... + + if self.min_val: + assert self.value >= self.min_val + + if self.max_val: + assert self.value <= self.max_val \ No newline at end of file diff --git a/dpytools/config/properties/string.py b/dpytools/config/properties/string.py index 74aa102..5ea0931 100644 --- a/dpytools/config/properties/string.py +++ b/dpytools/config/properties/string.py @@ -29,11 +29,14 @@ def secondary_validation_passed(self): if self.regex: # TODO - confirm the value matches the regex ... + assert self.value == self.regex if self.min_len: # TODO - confirm the string matches of exceeds the minimum length ... + assert len(self.value) >= self.min_len if self.max_len: # TODO - confirm the value matches or is less than the max length - ... \ No newline at end of file + ... + assert len(self.value) <= self.max_len \ No newline at end of file From bbcee5ffbe980064a21b7b2dc8c247bb5ca25fa3 Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Mon, 15 Jan 2024 09:25:11 +0000 Subject: [PATCH 26/48] Http client uses requests library --- dpytools/http_clients/base.py | 52 ++++++---------- tests/test_http.py | 108 ++++++++++------------------------ 2 files changed, 49 insertions(+), 111 deletions(-) diff --git a/dpytools/http_clients/base.py b/dpytools/http_clients/base.py index c9b92a7..4eb725f 100644 --- a/dpytools/http_clients/base.py +++ b/dpytools/http_clients/base.py @@ -1,7 +1,6 @@ import backoff -import http.client -from http.client import HTTPException -from urllib.parse import urlparse +import requests +from requests.exceptions import HTTPError import logging @@ -18,52 +17,35 @@ def __init__(self, backoff_max=30): # GET request method with exponential backoff @backoff.on_exception( backoff.expo, - HTTPException, + HTTPError, max_time=30, on_backoff=log_retry ) def get(self, url, *args, **kwargs): - timeout = kwargs.pop('timeout', None) - logging.info(f"Sending GET request to {url}") - return self._request("GET", url, timeout=timeout, *args, **kwargs) + return self._handle_request('GET', url, *args, **kwargs) # POST request method with exponential backoff @backoff.on_exception( backoff.expo, - HTTPException, + HTTPError, max_time=30, on_backoff=log_retry, ) def post(self, 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): timeout = kwargs.pop('timeout', None) - logging.info(f"Sending POST request to {url}") - return self._request("POST", url, timeout=timeout, *args, **kwargs) - - # Private method to handle the request - def _request(self, method, url, *args, **kwargs): + logging.info(f"Sending {method} request to {url}") try: - headers = kwargs.pop('headers', {}) - - url_parts = urlparse(url) - https_connection = http.client.HTTPSConnection(url_parts.netloc) - path = url_parts.path or '/' - https_connection.request(method, path, headers=headers, *args, **kwargs) - - response = https_connection.getresponse() - response_content = response.read() - https_connection.close() - - # Raise an exception if the HTTP status is 400 or above - if response.status >= 400: - raise HTTPException(f"{method} request to {url} failed with status {response.status}, response: {response_content.decode()}") - + response = requests.request(method, url, timeout=timeout, *args, **kwargs) + response.raise_for_status() return response - # Handle HTTPException separately to log and retry - except HTTPException as e: - logging.error(f"Request failed due to {str(e)} when sending a {method} to {url} with headers {headers}, retrying...") + except HTTPError as http_err: + logging.error(f"HTTP error occurred: {http_err} when sending a {method} to {url} with headers {kwargs.get('headers')}") + raise + except Exception as err: + logging.error(f"Other error occurred: {err} when sending a {method} to {url} with headers {kwargs.get('headers')}") raise - # Handle other exceptions - except Exception as e: - logging.error(f"An unexpected error occurred: {str(e)} when sending a {method} to {url} with headers {headers}") - raise \ No newline at end of file diff --git a/tests/test_http.py b/tests/test_http.py index 85c65f0..1142b00 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -1,114 +1,70 @@ import pytest from unittest.mock import patch, MagicMock -from http.client import HTTPResponse, HTTPException +from requests import HTTPError, Response from dpytools.http_clients.base import BaseHttpClient -# Mock the HTTPSConnection class -@patch('http.client.HTTPSConnection') -def test_get(mock_connection): +# Mock the requests.request method +@patch('requests.request') +def test_get(mock_request): """ Test that the get method returns a response object """ # Create a mock response object - mock_response = MagicMock(HTTPResponse) - mock_response.status = 200 - mock_response.read.return_value = b'Test response content' - mock_connection.return_value.getresponse.return_value = mock_response + mock_response = MagicMock(Response) + mock_response.status_code = 200 + 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') - # Assertions to check the response status, content and the connection call - assert response.status == 200 - assert response.read().decode() == 'Test response content' - mock_connection.assert_called_once_with('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', timeout=None) -@patch('http.client.HTTPSConnection') -def test_post(mock_connection): +@patch('requests.request') +def test_post(mock_request): """ Test that the post method returns a response object """ # Create a mock response object - mock_response = MagicMock(HTTPResponse) - mock_response.status = 200 - mock_response.read.return_value = b'Test response content' - mock_connection.return_value.getresponse.return_value = mock_response + mock_response = MagicMock(Response) + mock_response.status_code = 200 + 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') - # Assertions to check the response status, content and the connection call - assert response.status == 200 - assert response.read().decode() == 'Test response content' - mock_connection.assert_called_once_with('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', timeout=None) -@patch('http.client.HTTPSConnection') -def test_backoff_on_exception(mock_connection): +@patch('requests.request') +def test_backoff_on_exception(mock_request): """ - Test that the get method retries on HTTPException + Test that the get method retries on HTTPError """ # Create a mock response object - mock_response = MagicMock(HTTPResponse) - mock_response.status = 200 + mock_response = MagicMock(Response) + mock_response.status_code = 200 - # Raise HTTPException on the first call, then return the mock_response - mock_connection.return_value.getresponse.side_effect = [HTTPException('HTTP Error'), mock_response] + # Raise HTTPError on the first call, then return the 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') - # Assertions to check the response status and the number of getresponse calls - assert response.status == 200 - assert mock_connection.return_value.getresponse.call_count == 2 - - -@patch('http.client.HTTPSConnection') -def test_request(mock_connection): - """ - Test that the _request method returns a response object - """ - - # Create a mock response object - mock_response = MagicMock(HTTPResponse) - mock_response.status = 200 - mock_response.read.return_value = b'Test response content' - mock_connection.return_value.getresponse.return_value = mock_response - - # Create an instance of BaseHttpClient and make a request - client = BaseHttpClient() - response = client._request('GET', 'http://example.com') - - # Assertions to check the response status, content and the connection call - assert response.status == 200 - assert response.read().decode() == 'Test response content' - mock_connection.assert_called_once_with('example.com') - - -@patch('http.client.HTTPSConnection') -def test_request_with_timeout(mock_connection): - """ - Test _request method with timeout passed as kwargs - """ - - # Create a mock response object - mock_response = MagicMock(HTTPResponse) - mock_response.status = 200 - mock_response.read.return_value = b'Test response content' - mock_connection.return_value.getresponse.return_value = mock_response - - # Create an instance of BaseHttpClient and make a request with a timeout - client = BaseHttpClient() - response = client._request('GET', 'http://example.com', timeout=5) - - # Assertions to check the response status, content and the connection call - assert response.status == 200 - assert response.read().decode() == 'Test response content' - mock_connection.assert_called_once_with('example.com') \ No newline at end of file + # 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 From 4fe4e8314926f507ff528508b07076db5a5ff2f6 Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Mon, 15 Jan 2024 13:56:35 +0000 Subject: [PATCH 27/48] Made the Requested Changes --- dpytools/http_clients/base.py | 44 +++++++++++++++++++++++++++++++---- tests/test_http.py | 4 ++-- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/dpytools/http_clients/base.py b/dpytools/http_clients/base.py index 4eb725f..8ae89ea 100644 --- a/dpytools/http_clients/base.py +++ b/dpytools/http_clients/base.py @@ -22,6 +22,24 @@ def __init__(self, backoff_max=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: + https://docs.python-requests.org/en/latest/api/#requests.get + + Args: + url (str): The URL to send the GET request to. + *args: Optional positional arguments passed to `requests.get()`. + **kwargs: Optional keyword arguments passed to `requests.get()`. + + Returns: + Response: The Response object from `requests.get()`. + Raises: + HTTPError: If the request fails for a network-related reason. + """ return self._handle_request('GET', url, *args, **kwargs) # POST request method with exponential backoff @@ -32,20 +50,38 @@ def get(self, url, *args, **kwargs): 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: + https://docs.python-requests.org/en/latest/api/#requests.post + + Args: + url (str): The URL to send the POST request to. + *args: Optional positional arguments passed to `requests.post()`. + **kwargs: Optional keyword arguments passed to `requests.post()`. + + Returns: + Response: The Response object from `requests.post()`. + + Raises: + HTTPError: If the request fails for a network-related reason. + """ return self._handle_request('POST', url, *args, **kwargs) # Method to handle requests for GET and POST def _handle_request(self, method, url, *args, **kwargs): - timeout = kwargs.pop('timeout', None) logging.info(f"Sending {method} request to {url}") try: - response = requests.request(method, url, timeout=timeout, *args, **kwargs) + response = requests.request(method, url, *args, **kwargs) response.raise_for_status() 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')}") - raise + 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')}") - raise + raise err diff --git a/tests/test_http.py b/tests/test_http.py index 1142b00..d44b969 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -23,7 +23,7 @@ def test_get(mock_request): # 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', timeout=None) + mock_request.assert_called_once_with('GET', 'http://example.com') @patch('requests.request') @@ -45,7 +45,7 @@ def test_post(mock_request): # 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', timeout=None) + mock_request.assert_called_once_with('POST', 'http://example.com') @patch('requests.request') From b1fcc3f7b6223370c76a0f33576ae2cbe35e908f Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 15 Jan 2024 20:30:33 +0000 Subject: [PATCH 28/48] simple logging implementation --- dpytools/__init__.py | 5 - dpytools/logger/logger.py | 298 +++++++++++++++------------------- dpytools/logger/middleware.py | 51 ------ dpytools/logger/utils.py | 15 -- tests/test_logging.py | 49 ++++++ 5 files changed, 179 insertions(+), 239 deletions(-) delete mode 100644 dpytools/logger/middleware.py delete mode 100644 dpytools/logger/utils.py create mode 100644 tests/test_logging.py diff --git a/dpytools/__init__.py b/dpytools/__init__.py index bff3af3..e69de29 100644 --- a/dpytools/__init__.py +++ b/dpytools/__init__.py @@ -1,5 +0,0 @@ -from http.http import HttpClient -from config.config import Config -from logger.logger import logger -from slack.slack import SlackNotifier -from sns.sns import Subscription, publish \ No newline at end of file diff --git a/dpytools/logger/logger.py b/dpytools/logger/logger.py index 19010fa..d7f531f 100644 --- a/dpytools/logger/logger.py +++ b/dpytools/logger/logger.py @@ -1,183 +1,145 @@ -import logging -import os -import sys - +from typing import Dict, List, Optional, Union import structlog +import traceback +import json +from datetime import datetime -from utils import str_to_bool +def level_to_severity(level: int) -> int: + """ + Helper to convert logging level to severity, please + see: https://github.com/ONSdigital/dp-standards/blob/main/LOGGING_STANDARDS.md#severity-levels + """ + if level > 40: + return 0 + elif level > 30: + return 1 + elif level > 20: + return 2 + else: + return 3 + +def create_error_dict(error: Exception) -> List[Dict]: + """ + Take a python Exception and create a sub dict/document + matching DP logging standards. + """ -logger = structlog.stdlib.get_logger() -HUMAN_FRIENDLY_LOGS = str_to_bool(os.environ.get("HUMAN_FRIENDLY_LOGS", "false")) + # Note: "stack trace" guidance is very go orientated, + # this will be fine for now. + error_dict = { + "message": str(error), + "stack_trace": traceback.format_exc().split("\n") + } -# #4 TODO Add some testing - see https://www.structlog.org/en/stable/testing.html for guidance + # Listify in keeping with expected DP logging structures + return [error_dict] +def dp_serializer(event_log, **kw) -> Dict: + """ + Simple serialiser to align structlog defaults + with output expected by: + https://github.com/ONSdigital/dp-standards/blob/main/LOGGING_STANDARDS.md + """ + + # Note: literally just avoiding also logging the superfluous top level + # "event" key - we just want its contents + return json.dumps(event_log["event"], **kw) -# #4 QUESTION What does this class actually do? If I comment it out, everything still works... (I think) class DpLogger: - def __init__(self, namespace: str): + + def __init__(self, namespace: str, test_mode: bool = False): + """ + Simple python logger to create structured logs in keeping + with https://github.com/ONSdigital/dp-standards/blob/main/LOGGING_STANDARDS.md + + namespace: (required) the namespace for the app in question + test_mode: FOR USAGE DURING TESTING ONLY, makes logging statments return + their structured logs. + """ + structlog.configure( + processors=[ + structlog.processors.JSONRenderer(dp_serializer) + ]) + self._logger = structlog.stdlib.get_logger() self.namespace = namespace + self.test_mode = test_mode - def log(self, event, level, data=None): - trace_id = generate_trace_id() - span_id = generate_span_id() + def _log(self, event, level, error: Optional[List] = None, data: Optional[Dict] = None): + log_entry = self._create_log_entry(event, level, data, error) + self._logger.log(level, log_entry) - severity = { - "debug": 3, # INFO - "warning": 2, # WARNING - "error": 1, # ERROR - }.get( - level, 3 - ) # Default to INFO + if self.test_mode: + return log_entry + def _create_log_entry(self, event, level, data, error) -> Dict: log_entry = { - "namespace": self.namespace, - "event": event, - "trace_id": trace_id, - "span_id": span_id, - "severity": severity, - "data": data if data is not None else {}, - } - - logger.log(level, **{"event_dict": log_entry}) - - def debug(self, event, data=None): - self.log(event, "debug", data) - - def warning(self, event, data=None): - self.log(event, "warning", data) - - def error(self, event, data=None): - self.log(event, "error", data) - - -def generate_trace_id(): - # TODO Implement this (https://opencensus.io/tracing/span/traceid/) - return "your_trace_id" - - -def generate_span_id(): - # TODO Implement this (https://opencensus.io/tracing/span/spanid/) - return "your_span_id" - - -# #4 TODO Output formatting (https://github.com/ONSdigital/dp-standards/blob/main/LOGGING_STANDARDS.md#output-formatting) -# #4 UTF-8 encoding is done (structlog.processors.UnicodeEncoder()) -# #4 Still need to work out how to format JSON logs as JSON Lines (may need to create a callable to pass to structlog.processors.JSONRenderer()) -# #4 TODO Add namespace to output - using an env var? -def configure_logger(enable_console_logs: bool = HUMAN_FRIENDLY_LOGS): - shared_processors = [ - structlog.processors.TimeStamper(fmt="iso", key="created_at"), - structlog.processors.UnicodeEncoder(), - structlog.stdlib.add_log_level, - structlog.stdlib.add_logger_name, - structlog.contextvars.merge_contextvars, - structlog.processors.CallsiteParameterAdder( - { - structlog.processors.CallsiteParameter.PATHNAME, - structlog.processors.CallsiteParameter.FILENAME, - structlog.processors.CallsiteParameter.MODULE, - structlog.processors.CallsiteParameter.FUNC_NAME, - structlog.processors.CallsiteParameter.THREAD, - structlog.processors.CallsiteParameter.THREAD_NAME, - structlog.processors.CallsiteParameter.PROCESS, - structlog.processors.CallsiteParameter.PROCESS_NAME, + "created_at": datetime.now().isoformat(), # TODO - might not be quite the right ISOtime, investigate + "namespace": self.namespace, + "event": event, + "trace_id": "not-implemented", + "span_id": "not-implemented", + "severity": level_to_severity(level), + "data": data if data is not None else {}, } - ), - structlog.stdlib.ExtraAdder(), - ] - - structlog.configure( - processors=shared_processors - + [structlog.stdlib.ProcessorFormatter.wrap_for_formatter], - logger_factory=structlog.stdlib.LoggerFactory(), - wrapper_class=structlog.stdlib.BoundLogger, - cache_logger_on_first_use=True, - ) - # TODO Pass a callable to JSONRenderer to format logs as JSON Lines - logs_render = ( - structlog.dev.ConsoleRenderer(colors=True) - if enable_console_logs - else structlog.processors.JSONRenderer() - ) - - _configure_default_logging_by_custom(shared_processors, logs_render) - - -def _configure_default_logging_by_custom(shared_processors, logs_render): - handler = logging.StreamHandler() - formatter = structlog.stdlib.ProcessorFormatter( - foreign_pre_chain=shared_processors, - processors=[ - _extract_from_record, - structlog.stdlib.ProcessorFormatter.remove_processors_meta, - logs_render, - ], - ) - handler.setFormatter(formatter) - root_uvicorn_logger = logging.getLogger() - root_uvicorn_logger.addHandler(handler) - root_uvicorn_logger.setLevel(logging.INFO) - - -def _extract_from_record(_, __, event_dict): - record = event_dict["_record"] - event_dict["thread_name"] = record.threadName - event_dict["process_name"] = record.processName - return event_dict - - -# #4 TODO Add namespace to create_log_event function arguments? -# #4 Do we need to specify a format for, e.g., http_data? The DP logging standards include a list of fields for HTTP events (https://github.com/ONSdigital/dp-standards/blob/main/LOGGING_STANDARDS.md#http-event-data), as well as auth events and errors -# Define a function to create a log event with the specified structure -def create_log_event( - event: str, - severity: str, - http_data: dict = None, - auth_data: dict = None, - errors: list = None, - raw: str = None, - data: dict = None, -): - # Create a log event following the specified structure - log_event = { - "namespace": "your-service-name", - "event": event, - "severity": severity, - } - - if http_data: - log_event["http"] = http_data - if auth_data: - log_event["auth"] = auth_data - if errors: - log_event["errors"] = errors - if raw: - log_event["raw"] = raw - if data: - log_event["data"] = data - - logger.info(**log_event) - - -# Handle uncaught exceptions -def handle_exception(exc_type, exc_value, exc_traceback): - """ - Log any uncaught exception instead of letting it be printed by Python - """ - if issubclass(exc_type, KeyboardInterrupt): - sys.__excepthook__(exc_type, exc_value, exc_traceback) - return - - logging.RootLogger.error( - "Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback) - ) - - create_log_event( - event="Uncaught exception", - severity="ERROR", - errors=[{"message": str(exc_value)}], - ) - sys.__excepthook__(exc_type, exc_value, exc_traceback) + if error: + log_entry["errors"] = create_error_dict(error) + + return log_entry + + def debug(self, event: str, raw: str = None, data: Dict = None): + """ + Log at the debug level. + + event: the thing that's happened, a simple short english statement + raw : a raw string of any log messages captured for a third paty library + data : arbitrary key values pairs that may be of use in providing context + """ + self._log(event, 10, raw=raw, data=data) + + def info(self, event: str, raw: str= None, data: Dict =None): + """ + Log at the info level. + + event: the thing that's happened, a simple short english statement + raw : a raw string of any log messages captured for a third paty library + data : arbitrary key values pairs that may be of use in providing context + """ + self._log(event, 20, raw=raw, data=data) + + def warning(self, event: str, raw: str = None, data: Dict = None): + """ + Log at the warning level. + + event: the thing that's happened, a simple short english statement + raw : a raw string of any log messages captured for a third paty library + data : arbitrary key values pairs that may be of use in providing context + """ + self._log(event, 30, raw=raw, data=data) + + def error(self, event: str, error: Exception, raw: str = None, data: Dict = None): + """ + Log at the error level. + + event: the thing that's happened, a simple short english statement + error: a caught python Exceotion + raw : a raw string of any log messages captured for a third paty library + data : arbitrary key values pairs that may be of use in providing context + """ + self._log(event, 40, error=error, raw=raw, data=data) + + def critical(self, event: str, error: Exception, raw: str = None, data: Dict = None): + """ + IMPORTANT: You should only be logging at the critical level during + application failure, i.e if you're app is not in this process of falling + over you should not be logging a critical. + + Log at the critical level. + + event: the thing that's happened, a simple short english statement + error: a caught python Exceotion + raw : a raw string of any log messages captured for a third paty library + data : arbitrary key values pairs that may be of use in providing context + """ + self._log(event, 50, error=error, raw=raw, data=data) -sys.excepthook = handle_exception diff --git a/dpytools/logger/middleware.py b/dpytools/logger/middleware.py deleted file mode 100644 index 9a0d0f3..0000000 --- a/dpytools/logger/middleware.py +++ /dev/null @@ -1,51 +0,0 @@ -import time - -import structlog -from fastapi import Request, Response -from structlog.contextvars import bind_contextvars, clear_contextvars - -from logger import configure_logger, logger - -configure_logger() - - -async def logging_middleware(request: Request, call_next) -> Response: - clear_contextvars() - # These context vars will be added to all log entries emitted during the request - request_id = request.headers.get("request-id") - bind_contextvars(request_id=request_id) - - start_time = time.perf_counter_ns() - - try: - response = await call_next(request) - except Exception as exc: - response = Response(status_code=500) - structlog.get_logger("api.error").exception("Uncaught exception", exc_info=exc) - raise - finally: - process_time = time.perf_counter_ns() - start_time - status_code = response.status_code - - log_message = "Request completed" - log_context = { - "data": { - "path": request.url.path, - "method": request.method, - "status_code": status_code, - "process_time": process_time / 10**9, # Convert to seconds - } - } - - if status_code == 200: - logger.info(log_message, **log_context) - elif status_code == 404: - log_message = "Resource not found" - logger.info(log_message, **log_context) - elif status_code == 406: - log_message = "Request not acceptable" - logger.info(log_message, **log_context) - - response.headers["X-Process-Time"] = str(process_time / 10**9) - - return response diff --git a/dpytools/logger/utils.py b/dpytools/logger/utils.py deleted file mode 100644 index a0f5ffb..0000000 --- a/dpytools/logger/utils.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Dict - - -# Mapping of strings to bools for str_to_bool function to work -valid_bool_values: Dict[str, bool] = { - "true": True, - "1": True, - "false": False, - "0": False, -} - - -def str_to_bool(str_val: str, default: bool = False) -> bool: - """Convert input strings to the correct boolean format in order to set environment variable value""" - return valid_bool_values.get(str_val.lower(), default) diff --git a/tests/test_logging.py b/tests/test_logging.py new file mode 100644 index 0000000..4cd56b9 --- /dev/null +++ b/tests/test_logging.py @@ -0,0 +1,49 @@ +from dpytools.logger.logger import DpLogger + +def test_debug_no_arbitrary_data(): + """ + Test that we get the expected structure when constructing + a debug log. + """ + + # test_mode=True returns the dictionary being logged + # from the logging statement. + logger = DpLogger("test-1", test_mode=True) + logged_dict = logger.debug("Something") + + # Now that "logged_dict" contains the json fields + # we need to compare them to a https://pypi.org/project/jsonschema/ + # of how a log of this kind should be populated. + + # Example log: + # {"created_at": "2024-01-15T19:35:29.470825", "namespace": "test-1", "event": "Something", "trace_id": "not-implemented", "span_id": "not-implemented", "severity": 0, "data": {}} + + # Note: + # Don't manually write json schemas + # 1.) Use the logger + # 2.) Careful check the logs match the spec + # 3.) Use a site like this to get you started and tweak as needed: + # https://www.liquid-technologies.com/online-json-to-schema-converter + +# Also write a test for: +# - logger.info +# - logger.warning + +# And also +# - one of the above with arbitrary data key values +# - one of the above with a "raw" field + +# Lastly for: +# - error +# - critical ("fatal" in go terms) +# but for these two remember you'd be passing in an error. + +# And that's it, as long as the logger is constructing logging structures +# that validate against the json schemas we're done. +# NOTE: ignore hpp and auth fields for now, we'll pick those up later. + + + + + + From 8a1a7eeb2d52b17914431116db126c98f307de40 Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 15 Jan 2024 20:43:51 +0000 Subject: [PATCH 29/48] add raw --- dpytools/logger/logger.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dpytools/logger/logger.py b/dpytools/logger/logger.py index d7f531f..1600fff 100644 --- a/dpytools/logger/logger.py +++ b/dpytools/logger/logger.py @@ -64,14 +64,14 @@ def __init__(self, namespace: str, test_mode: bool = False): self.namespace = namespace self.test_mode = test_mode - def _log(self, event, level, error: Optional[List] = None, data: Optional[Dict] = None): - log_entry = self._create_log_entry(event, level, data, error) + def _log(self, event, level, error: Optional[List] = None, data: Optional[Dict] = None, raw: str = None): + log_entry = self._create_log_entry(event, level, data, error, raw) self._logger.log(level, log_entry) if self.test_mode: return log_entry - def _create_log_entry(self, event, level, data, error) -> Dict: + def _create_log_entry(self, event, level, data, error, raw) -> Dict: log_entry = { "created_at": datetime.now().isoformat(), # TODO - might not be quite the right ISOtime, investigate "namespace": self.namespace, @@ -85,6 +85,9 @@ def _create_log_entry(self, event, level, data, error) -> Dict: if error: log_entry["errors"] = create_error_dict(error) + if raw: + log_entry["raw"] = raw + return log_entry def debug(self, event: str, raw: str = None, data: Dict = None): From 514e3b84becd8c014c7e2be35dabe84e1a148fd0 Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Mon, 22 Jan 2024 13:07:09 +0000 Subject: [PATCH 30/48] Salck Notification Working --- dpytools/slack/slack.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/dpytools/slack/slack.py b/dpytools/slack/slack.py index dc826c5..ab56432 100644 --- a/dpytools/slack/slack.py +++ b/dpytools/slack/slack.py @@ -1,14 +1,25 @@ +import os +import logging +from dpytools.http_clients.base import BaseHttpClient class SlackNotifier: def __init__(self): - # Set a webhok via an env var, ask Mike for a - #web hook url. - ... - - def notify(self, msg: str): - # Check formatting options for messages to slack. - # From memory you style it via sending a dictionary. - # It's a post request so do use the http client - # we've developing elsewhere in this library. - ... \ No newline at end of file + self.webhook_url = os.getenv("SLACK_WEBHOOK_URL") + if not self.webhook_url: + raise ValueError('SLACK_WEBHOOK_URL is not set') + self.http_client = BaseHttpClient() + self.validate_webhook_url() + + def validate_webhook_url(self): + response = self.http_client.get(self.webhook_url) + if response.status_code != 200: + logging.error(f'Invalid SLACK_WEBHOOK_URL: {response.status_code}') + raise ValueError('Invalid SLACK_WEBHOOK_URL') + + def notify(self, msg: dict): + try: + response = self.http_client.post(self.webhook_url, json=msg) + response.raise_for_status() + except Exception as e: + logging.error(f'Failed to send notification: {e}') From 2813ec6f1c0f3b2a04a5c75963e8efae9913b3d2 Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Mon, 22 Jan 2024 13:07:41 +0000 Subject: [PATCH 31/48] Added tests --- tests/test_slack.py | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/test_slack.py diff --git a/tests/test_slack.py b/tests/test_slack.py new file mode 100644 index 0000000..376e2bc --- /dev/null +++ b/tests/test_slack.py @@ -0,0 +1,51 @@ +import pytest +from unittest.mock import patch, MagicMock +from requests import HTTPError, Response +from dpytools.http_clients.base import BaseHttpClient +from dpytools.slack.slack import SlackNotifier + +@patch('os.getenv') +@patch.object(BaseHttpClient, 'get') +def test_validate_webhook_url(mock_get, mock_getenv): + """ + Test that the validate_webhook_url method raises an exception for invalid URLs + """ + mock_getenv.return_value = 'http://example.com' + mock_response = MagicMock(Response) + mock_response.status_code = 404 + mock_get.return_value = mock_response + + with pytest.raises(ValueError): + notifier = SlackNotifier() + +@patch('os.getenv') +@patch.object(BaseHttpClient, 'get') +def test_validate_webhook_url_success(mock_get, mock_getenv): + """ + Test that the validate_webhook_url method does not raise an exception for valid URLs + """ + mock_getenv.return_value = 'http://example.com' + mock_response = MagicMock(Response) + mock_response.status_code = 200 + mock_get.return_value = mock_response + + try: + notifier = SlackNotifier() + except ValueError: + pytest.fail("Unexpected ValueError ..") + +@patch('os.getenv') +@patch.object(BaseHttpClient, 'post') +def test_notify(mock_post, mock_getenv): + """ + Test that the notify method sends a POST request + """ + mock_getenv.return_value = 'http://example.com' + mock_response = MagicMock(Response) + mock_response.status_code = 200 + mock_post.return_value = mock_response + + notifier = SlackNotifier() + notifier.notify({'text': 'Test message'}) + + mock_post.assert_called_once_with('http://example.com', json={'text': 'Test message'}) \ No newline at end of file From b255396851a6b36bd707614cd7490e56b3e41f80 Mon Sep 17 00:00:00 2001 From: Sarah Johnson Date: Mon, 22 Jan 2024 14:43:16 +0000 Subject: [PATCH 32/48] Tests added --- .gitignore | 3 +- dpytools/logger/logger.py | 49 ++-- poetry.lock | 437 +++++++++++++---------------------- pyproject.toml | 3 +- tests/fixtures/log_output.py | 13 ++ tests/schema.json | 50 ++++ tests/schema_with_error.json | 79 +++++++ tests/test_logging.py | 142 +++++++++--- 8 files changed, 447 insertions(+), 329 deletions(-) create mode 100644 tests/fixtures/log_output.py create mode 100644 tests/schema.json create mode 100644 tests/schema_with_error.json diff --git a/.gitignore b/.gitignore index 71e58d5..ac145ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .coverage .venv .DS_Store -*.pyc \ No newline at end of file +*.pyc +.vscode \ No newline at end of file diff --git a/dpytools/logger/logger.py b/dpytools/logger/logger.py index 1600fff..e4e5c45 100644 --- a/dpytools/logger/logger.py +++ b/dpytools/logger/logger.py @@ -4,6 +4,7 @@ import json from datetime import datetime + def level_to_severity(level: int) -> int: """ Helper to convert logging level to severity, please @@ -18,6 +19,7 @@ def level_to_severity(level: int) -> int: else: return 3 + def create_error_dict(error: Exception) -> List[Dict]: """ Take a python Exception and create a sub dict/document @@ -28,12 +30,13 @@ def create_error_dict(error: Exception) -> List[Dict]: # this will be fine for now. error_dict = { "message": str(error), - "stack_trace": traceback.format_exc().split("\n") + "stack_trace": traceback.format_exc().split("\n"), } # Listify in keeping with expected DP logging structures return [error_dict] + def dp_serializer(event_log, **kw) -> Dict: """ Simple serialiser to align structlog defaults @@ -45,26 +48,31 @@ def dp_serializer(event_log, **kw) -> Dict: # "event" key - we just want its contents return json.dumps(event_log["event"], **kw) -class DpLogger: +class DpLogger: def __init__(self, namespace: str, test_mode: bool = False): """ Simple python logger to create structured logs in keeping with https://github.com/ONSdigital/dp-standards/blob/main/LOGGING_STANDARDS.md - + namespace: (required) the namespace for the app in question - test_mode: FOR USAGE DURING TESTING ONLY, makes logging statments return - their structured logs. + test_mode: FOR USAGE DURING TESTING ONLY, makes logging statements return their structured logs. """ structlog.configure( - processors=[ - structlog.processors.JSONRenderer(dp_serializer) - ]) + processors=[structlog.processors.JSONRenderer(dp_serializer)] + ) self._logger = structlog.stdlib.get_logger() self.namespace = namespace self.test_mode = test_mode - def _log(self, event, level, error: Optional[List] = None, data: Optional[Dict] = None, raw: str = None): + def _log( + self, + event, + level, + error: Optional[List] = None, + data: Optional[Dict] = None, + raw: str = None, + ): log_entry = self._create_log_entry(event, level, data, error, raw) self._logger.log(level, log_entry) @@ -73,14 +81,14 @@ def _log(self, event, level, error: Optional[List] = None, data: Optional[Dict] def _create_log_entry(self, event, level, data, error, raw) -> Dict: log_entry = { - "created_at": datetime.now().isoformat(), # TODO - might not be quite the right ISOtime, investigate - "namespace": self.namespace, - "event": event, - "trace_id": "not-implemented", - "span_id": "not-implemented", - "severity": level_to_severity(level), - "data": data if data is not None else {}, - } + "created_at": datetime.now().isoformat(), # TODO - might not be quite the right ISOtime, investigate + "namespace": self.namespace, + "event": event, + "trace_id": "not-implemented", + "span_id": "not-implemented", + "severity": level_to_severity(level), + "data": data if data is not None else {}, + } if error: log_entry["errors"] = create_error_dict(error) @@ -100,7 +108,7 @@ def debug(self, event: str, raw: str = None, data: Dict = None): """ self._log(event, 10, raw=raw, data=data) - def info(self, event: str, raw: str= None, data: Dict =None): + def info(self, event: str, raw: str = None, data: Dict = None): """ Log at the info level. @@ -131,7 +139,9 @@ def error(self, event: str, error: Exception, raw: str = None, data: Dict = None """ self._log(event, 40, error=error, raw=raw, data=data) - def critical(self, event: str, error: Exception, raw: str = None, data: Dict = None): + def critical( + self, event: str, error: Exception, raw: str = None, data: Dict = None + ): """ IMPORTANT: You should only be logging at the critical level during application failure, i.e if you're app is not in this process of falling @@ -145,4 +155,3 @@ def critical(self, event: str, error: Exception, raw: str = None, data: Dict = N data : arbitrary key values pairs that may be of use in providing context """ self._log(event, 50, error=error, raw=raw, data=data) - diff --git a/poetry.lock b/poetry.lock index 1989733..588d954 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,37 +1,23 @@ # This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. [[package]] -name = "annotated-types" -version = "0.6.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, -] - -[[package]] -name = "anyio" -version = "4.2.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, - {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} - [package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "black" @@ -181,36 +167,6 @@ files = [ [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "fastapi" -version = "0.108.0" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fastapi-0.108.0-py3-none-any.whl", hash = "sha256:8c7bc6d315da963ee4cdb605557827071a9a7f95aeb8fcdd3bde48cdc8764dd7"}, - {file = "fastapi-0.108.0.tar.gz", hash = "sha256:5056e504ac6395bf68493d71fcfc5352fdbd5fda6f88c21f6420d80d81163296"}, -] - -[package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.29.0,<0.33.0" -typing-extensions = ">=4.8.0" - -[package.extras] -all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] - -[[package]] -name = "idna" -version = "3.6" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, -] - [[package]] name = "iniconfig" version = "2.0.0" @@ -222,20 +178,6 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "isodate" -version = "0.6.1" -description = "An ISO 8601 date/time/duration parser and formatter" -optional = false -python-versions = "*" -files = [ - {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, - {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, -] - -[package.dependencies] -six = "*" - [[package]] name = "isort" version = "5.12.0" @@ -253,6 +195,41 @@ pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib" plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] +[[package]] +name = "jsonschema" +version = "4.21.1" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.21.1-py3-none-any.whl", hash = "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f"}, + {file = "jsonschema-4.21.1.tar.gz", hash = "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + [[package]] name = "mypy-extensions" version = "1.0.0" @@ -316,156 +293,6 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] -[[package]] -name = "pydantic" -version = "2.5.3" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"}, - {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"}, -] - -[package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.14.6" -typing-extensions = ">=4.6.1" - -[package.extras] -email = ["email-validator (>=2.0.0)"] - -[[package]] -name = "pydantic-core" -version = "2.14.6" -description = "" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"}, - {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"}, - {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"}, - {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"}, - {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"}, - {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"}, - {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"}, - {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"}, - {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"}, - {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"}, - {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"}, - {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"}, - {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"}, - {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"}, - {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"}, - {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"}, - {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"}, - {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"}, - {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"}, - {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"}, - {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"}, - {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"}, - {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"}, - {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"}, - {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"}, - {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"}, - {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"}, - {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"}, - {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"}, - {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"}, - {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"}, - {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"}, - {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"}, - {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"}, - {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pyparsing" -version = "3.1.1" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -optional = false -python-versions = ">=3.6.8" -files = [ - {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, - {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - [[package]] name = "pytest" version = "7.4.3" @@ -507,25 +334,127 @@ pytest = ">=4.6" testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] -name = "rdflib" -version = "7.0.0" -description = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information." +name = "referencing" +version = "0.32.1" +description = "JSON Referencing + Python" optional = false -python-versions = ">=3.8.1,<4.0.0" +python-versions = ">=3.8" files = [ - {file = "rdflib-7.0.0-py3-none-any.whl", hash = "sha256:0438920912a642c866a513de6fe8a0001bd86ef975057d6962c79ce4771687cd"}, - {file = "rdflib-7.0.0.tar.gz", hash = "sha256:9995eb8569428059b8c1affd26b25eac510d64f5043d9ce8c84e0d0036e995ae"}, + {file = "referencing-0.32.1-py3-none-any.whl", hash = "sha256:7e4dc12271d8e15612bfe35792f5ea1c40970dadf8624602e33db2758f7ee554"}, + {file = "referencing-0.32.1.tar.gz", hash = "sha256:3c57da0513e9563eb7e203ebe9bb3a1b509b042016433bd1e45a2853466c3dd3"}, ] [package.dependencies] -isodate = ">=0.6.0,<0.7.0" -pyparsing = ">=2.1.0,<4" +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" -[package.extras] -berkeleydb = ["berkeleydb (>=18.1.0,<19.0.0)"] -html = ["html5lib (>=1.0,<2.0)"] -lxml = ["lxml (>=4.3.0,<5.0.0)"] -networkx = ["networkx (>=2.0.0,<3.0.0)"] +[[package]] +name = "rpds-py" +version = "0.17.1" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.17.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4128980a14ed805e1b91a7ed551250282a8ddf8201a4e9f8f5b7e6225f54170d"}, + {file = "rpds_py-0.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ff1dcb8e8bc2261a088821b2595ef031c91d499a0c1b031c152d43fe0a6ecec8"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d65e6b4f1443048eb7e833c2accb4fa7ee67cc7d54f31b4f0555b474758bee55"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a71169d505af63bb4d20d23a8fbd4c6ce272e7bce6cc31f617152aa784436f29"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:436474f17733c7dca0fbf096d36ae65277e8645039df12a0fa52445ca494729d"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10162fe3f5f47c37ebf6d8ff5a2368508fe22007e3077bf25b9c7d803454d921"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:720215373a280f78a1814becb1312d4e4d1077b1202a56d2b0815e95ccb99ce9"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70fcc6c2906cfa5c6a552ba7ae2ce64b6c32f437d8f3f8eea49925b278a61453"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91e5a8200e65aaac342a791272c564dffcf1281abd635d304d6c4e6b495f29dc"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:99f567dae93e10be2daaa896e07513dd4bf9c2ecf0576e0533ac36ba3b1d5394"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24e4900a6643f87058a27320f81336d527ccfe503984528edde4bb660c8c8d59"}, + {file = "rpds_py-0.17.1-cp310-none-win32.whl", hash = "sha256:0bfb09bf41fe7c51413f563373e5f537eaa653d7adc4830399d4e9bdc199959d"}, + {file = "rpds_py-0.17.1-cp310-none-win_amd64.whl", hash = "sha256:20de7b7179e2031a04042e85dc463a93a82bc177eeba5ddd13ff746325558aa6"}, + {file = "rpds_py-0.17.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:65dcf105c1943cba45d19207ef51b8bc46d232a381e94dd38719d52d3980015b"}, + {file = "rpds_py-0.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:01f58a7306b64e0a4fe042047dd2b7d411ee82e54240284bab63e325762c1147"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:071bc28c589b86bc6351a339114fb7a029f5cddbaca34103aa573eba7b482382"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae35e8e6801c5ab071b992cb2da958eee76340e6926ec693b5ff7d6381441745"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149c5cd24f729e3567b56e1795f74577aa3126c14c11e457bec1b1c90d212e38"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e796051f2070f47230c745d0a77a91088fbee2cc0502e9b796b9c6471983718c"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e820ee1004327609b28db8307acc27f5f2e9a0b185b2064c5f23e815f248f8"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1957a2ab607f9added64478a6982742eb29f109d89d065fa44e01691a20fc20a"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8587fd64c2a91c33cdc39d0cebdaf30e79491cc029a37fcd458ba863f8815383"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4dc889a9d8a34758d0fcc9ac86adb97bab3fb7f0c4d29794357eb147536483fd"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2953937f83820376b5979318840f3ee47477d94c17b940fe31d9458d79ae7eea"}, + {file = "rpds_py-0.17.1-cp311-none-win32.whl", hash = "sha256:1bfcad3109c1e5ba3cbe2f421614e70439f72897515a96c462ea657261b96518"}, + {file = "rpds_py-0.17.1-cp311-none-win_amd64.whl", hash = "sha256:99da0a4686ada4ed0f778120a0ea8d066de1a0a92ab0d13ae68492a437db78bf"}, + {file = "rpds_py-0.17.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1dc29db3900cb1bb40353772417800f29c3d078dbc8024fd64655a04ee3c4bdf"}, + {file = "rpds_py-0.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82ada4a8ed9e82e443fcef87e22a3eed3654dd3adf6e3b3a0deb70f03e86142a"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d36b2b59e8cc6e576f8f7b671e32f2ff43153f0ad6d0201250a7c07f25d570e"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3677fcca7fb728c86a78660c7fb1b07b69b281964673f486ae72860e13f512ad"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:516fb8c77805159e97a689e2f1c80655c7658f5af601c34ffdb916605598cda2"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df3b6f45ba4515632c5064e35ca7f31d51d13d1479673185ba8f9fefbbed58b9"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a967dd6afda7715d911c25a6ba1517975acd8d1092b2f326718725461a3d33f9"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dbbb95e6fc91ea3102505d111b327004d1c4ce98d56a4a02e82cd451f9f57140"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02866e060219514940342a1f84303a1ef7a1dad0ac311792fbbe19b521b489d2"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2528ff96d09f12e638695f3a2e0c609c7b84c6df7c5ae9bfeb9252b6fa686253"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd345a13ce06e94c753dab52f8e71e5252aec1e4f8022d24d56decd31e1b9b23"}, + {file = "rpds_py-0.17.1-cp312-none-win32.whl", hash = "sha256:2a792b2e1d3038daa83fa474d559acfd6dc1e3650ee93b2662ddc17dbff20ad1"}, + {file = "rpds_py-0.17.1-cp312-none-win_amd64.whl", hash = "sha256:292f7344a3301802e7c25c53792fae7d1593cb0e50964e7bcdcc5cf533d634e3"}, + {file = "rpds_py-0.17.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:8ffe53e1d8ef2520ebcf0c9fec15bb721da59e8ef283b6ff3079613b1e30513d"}, + {file = "rpds_py-0.17.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4341bd7579611cf50e7b20bb8c2e23512a3dc79de987a1f411cb458ab670eb90"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4eb548daf4836e3b2c662033bfbfc551db58d30fd8fe660314f86bf8510b93"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b686f25377f9c006acbac63f61614416a6317133ab7fafe5de5f7dc8a06d42eb"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e21b76075c01d65d0f0f34302b5a7457d95721d5e0667aea65e5bb3ab415c25"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b86b21b348f7e5485fae740d845c65a880f5d1eda1e063bc59bef92d1f7d0c55"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f175e95a197f6a4059b50757a3dca33b32b61691bdbd22c29e8a8d21d3914cae"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1701fc54460ae2e5efc1dd6350eafd7a760f516df8dbe51d4a1c79d69472fbd4"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9051e3d2af8f55b42061603e29e744724cb5f65b128a491446cc029b3e2ea896"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7450dbd659fed6dd41d1a7d47ed767e893ba402af8ae664c157c255ec6067fde"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5a024fa96d541fd7edaa0e9d904601c6445e95a729a2900c5aec6555fe921ed6"}, + {file = "rpds_py-0.17.1-cp38-none-win32.whl", hash = "sha256:da1ead63368c04a9bded7904757dfcae01eba0e0f9bc41d3d7f57ebf1c04015a"}, + {file = "rpds_py-0.17.1-cp38-none-win_amd64.whl", hash = "sha256:841320e1841bb53fada91c9725e766bb25009cfd4144e92298db296fb6c894fb"}, + {file = "rpds_py-0.17.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:f6c43b6f97209e370124baf2bf40bb1e8edc25311a158867eb1c3a5d449ebc7a"}, + {file = "rpds_py-0.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7d63ec01fe7c76c2dbb7e972fece45acbb8836e72682bde138e7e039906e2c"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81038ff87a4e04c22e1d81f947c6ac46f122e0c80460b9006e6517c4d842a6ec"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810685321f4a304b2b55577c915bece4c4a06dfe38f6e62d9cc1d6ca8ee86b99"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25f071737dae674ca8937a73d0f43f5a52e92c2d178330b4c0bb6ab05586ffa6"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa5bfb13f1e89151ade0eb812f7b0d7a4d643406caaad65ce1cbabe0a66d695f"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfe07308b311a8293a0d5ef4e61411c5c20f682db6b5e73de6c7c8824272c256"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a000133a90eea274a6f28adc3084643263b1e7c1a5a66eb0a0a7a36aa757ed74"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d0e8a6434a3fbf77d11448c9c25b2f25244226cfbec1a5159947cac5b8c5fa4"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efa767c220d94aa4ac3a6dd3aeb986e9f229eaf5bce92d8b1b3018d06bed3772"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:dbc56680ecf585a384fbd93cd42bc82668b77cb525343170a2d86dafaed2a84b"}, + {file = "rpds_py-0.17.1-cp39-none-win32.whl", hash = "sha256:270987bc22e7e5a962b1094953ae901395e8c1e1e83ad016c5cfcfff75a15a3f"}, + {file = "rpds_py-0.17.1-cp39-none-win_amd64.whl", hash = "sha256:2a7b2f2f56a16a6d62e55354dd329d929560442bd92e87397b7a9586a32e3e76"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a3264e3e858de4fc601741498215835ff324ff2482fd4e4af61b46512dd7fc83"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f2f3b28b40fddcb6c1f1f6c88c6f3769cd933fa493ceb79da45968a21dccc920"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9584f8f52010295a4a417221861df9bea4c72d9632562b6e59b3c7b87a1522b7"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c64602e8be701c6cfe42064b71c84ce62ce66ddc6422c15463fd8127db3d8066"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:060f412230d5f19fc8c8b75f315931b408d8ebf56aec33ef4168d1b9e54200b1"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9412abdf0ba70faa6e2ee6c0cc62a8defb772e78860cef419865917d86c7342"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9737bdaa0ad33d34c0efc718741abaafce62fadae72c8b251df9b0c823c63b22"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f0e4dc0f17dcea4ab9d13ac5c666b6b5337042b4d8f27e01b70fae41dd65c57"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1db228102ab9d1ff4c64148c96320d0be7044fa28bd865a9ce628ce98da5973d"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8bbd8e56f3ba25a7d0cf980fc42b34028848a53a0e36c9918550e0280b9d0b6"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:be22ae34d68544df293152b7e50895ba70d2a833ad9566932d750d3625918b82"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bf046179d011e6114daf12a534d874958b039342b347348a78b7cdf0dd9d6041"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:1a746a6d49665058a5896000e8d9d2f1a6acba8a03b389c1e4c06e11e0b7f40d"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0b8bf5b8db49d8fd40f54772a1dcf262e8be0ad2ab0206b5a2ec109c176c0a4"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f7f4cb1f173385e8a39c29510dd11a78bf44e360fb75610594973f5ea141028b"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7fbd70cb8b54fe745301921b0816c08b6d917593429dfc437fd024b5ba713c58"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bdf1303df671179eaf2cb41e8515a07fc78d9d00f111eadbe3e14262f59c3d0"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad059a4bd14c45776600d223ec194e77db6c20255578bb5bcdd7c18fd169361"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3664d126d3388a887db44c2e293f87d500c4184ec43d5d14d2d2babdb4c64cad"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:698ea95a60c8b16b58be9d854c9f993c639f5c214cf9ba782eca53a8789d6b19"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:c3d2010656999b63e628a3c694f23020322b4178c450dc478558a2b6ef3cb9bb"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:938eab7323a736533f015e6069a7d53ef2dcc841e4e533b782c2bfb9fb12d84b"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e626b365293a2142a62b9a614e1f8e331b28f3ca57b9f05ebbf4cf2a0f0bdc5"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:380e0df2e9d5d5d339803cfc6d183a5442ad7ab3c63c2a0982e8c824566c5ccc"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b760a56e080a826c2e5af09002c1a037382ed21d03134eb6294812dda268c811"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5576ee2f3a309d2bb403ec292d5958ce03953b0e57a11d224c1f134feaf8c40f"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3c3461ebb4c4f1bbc70b15d20b565759f97a5aaf13af811fcefc892e9197ba"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:637b802f3f069a64436d432117a7e58fab414b4e27a7e81049817ae94de45d8d"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffee088ea9b593cc6160518ba9bd319b5475e5f3e578e4552d63818773c6f56a"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ac732390d529d8469b831949c78085b034bff67f584559340008d0f6041a049"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:93432e747fb07fa567ad9cc7aaadd6e29710e515aabf939dfbed8046041346c6"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7b7d9ca34542099b4e185b3c2a2b2eda2e318a7dbde0b0d83357a6d4421b5296"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:0387ce69ba06e43df54e43968090f3626e231e4bc9150e4c3246947567695f68"}, + {file = "rpds_py-0.17.1.tar.gz", hash = "sha256:0210b2668f24c078307260bf88bdac9d6f1093635df5123789bfee4d8d7fc8e7"}, +] [[package]] name = "ruff" @@ -553,46 +482,6 @@ files = [ {file = "ruff-0.1.6.tar.gz", hash = "sha256:1b09f29b16c6ead5ea6b097ef2764b42372aebe363722f1605ecbcd2b9207184"}, ] -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "sniffio" -version = "1.3.0" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, -] - -[[package]] -name = "starlette" -version = "0.32.0.post1" -description = "The little ASGI library that shines." -optional = false -python-versions = ">=3.8" -files = [ - {file = "starlette-0.32.0.post1-py3-none-any.whl", hash = "sha256:cd0cb10ddb49313f609cedfac62c8c12e56c7314b66d89bb077ba228bada1b09"}, - {file = "starlette-0.32.0.post1.tar.gz", hash = "sha256:e54e2b7e2fb06dff9eac40133583f10dfa05913f5a85bf26f427c7a40a9a3d02"}, -] - -[package.dependencies] -anyio = ">=3.4.0,<5" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} - -[package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] - [[package]] name = "structlog" version = "23.2.0" @@ -635,4 +524,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.9, <3.12" -content-hash = "d2c30664d53ec742f544a207a43720fbc2b8987990e2685fef03e5cf9dc7b201" +content-hash = "89971abc642ac8eee0ac24fb29e4b692fe789360aba1aafd071a68a036efa43f" diff --git a/pyproject.toml b/pyproject.toml index 89d44a9..61ff25f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,8 +9,7 @@ readme = "README.md" [tool.poetry.dependencies] python = ">=3.9, <3.12" structlog = "^23.2.0" -fastapi = "^0.108.0" -rdflib = "^7.0.0" +jsonschema = "^4.21.1" [tool.poetry.group.dev.dependencies] pytest = "^7.4.3" diff --git a/tests/fixtures/log_output.py b/tests/fixtures/log_output.py new file mode 100644 index 0000000..03e2e4b --- /dev/null +++ b/tests/fixtures/log_output.py @@ -0,0 +1,13 @@ +import pytest +import structlog +from structlog.testing import LogCapture + + +@pytest.fixture(name="log_output") +def fixture_log_output(): + return LogCapture() + + +@pytest.fixture(autouse=True) +def fixture_configure_structlog(log_output): + structlog.configure(processors=[log_output]) diff --git a/tests/schema.json b/tests/schema.json new file mode 100644 index 0000000..9f2d475 --- /dev/null +++ b/tests/schema.json @@ -0,0 +1,50 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "event": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "event": { + "type": "string" + }, + "trace_id": { + "type": "string" + }, + "span_id": { + "type": "string" + }, + "severity": { + "type": "integer" + }, + "data": { + "type": "object" + }, + "raw": { + "type": "string" + } + }, + "required": [ + "created_at", + "namespace", + "event", + "trace_id", + "span_id", + "severity" + ] + }, + "log_level": { + "type": "string" + } + }, + "required": [ + "event", + "log_level" + ] +} \ No newline at end of file diff --git a/tests/schema_with_error.json b/tests/schema_with_error.json new file mode 100644 index 0000000..42bdaeb --- /dev/null +++ b/tests/schema_with_error.json @@ -0,0 +1,79 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "event": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "event": { + "type": "string" + }, + "trace_id": { + "type": "string" + }, + "span_id": { + "type": "string" + }, + "severity": { + "type": "integer" + }, + "data": { + "type": "object" + }, + "raw": { + "type": "string" + }, + "errors": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "stack_trace": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ] + } + }, + "required": [ + "message", + "stack_trace" + ] + } + ] + } + }, + "required": [ + "created_at", + "namespace", + "event", + "trace_id", + "span_id", + "severity", + "errors" + ] + }, + "log_level": { + "type": "string" + } + }, + "required": [ + "event", + "log_level" + ] +} \ No newline at end of file diff --git a/tests/test_logging.py b/tests/test_logging.py index 4cd56b9..bab547b 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -1,49 +1,127 @@ +import json from dpytools.logger.logger import DpLogger +import pytest +import structlog +from structlog.testing import LogCapture +import jsonschema -def test_debug_no_arbitrary_data(): +# TODO Add tests for http and auth fields + + +# TODO Figure out how to move fixtures to tests.fixtures.log_output.py +@pytest.fixture(name="log_output") +def fixture_log_output(): + return LogCapture() + + +@pytest.fixture(autouse=True) +def fixture_configure_structlog(log_output): + structlog.configure(processors=[log_output]) + + +# Create logger +# test_mode=True returns the dictionary being logged from the logging statement. +logger = DpLogger("test-1", test_mode=True) + + +def do_something(level: str): + if level == "debug": + logger.debug("Debug") + elif level == "arbitrary": + logger.debug("Debug", data={"arbitrary_key": "arbitrary_value"}) + elif level == "raw": + logger.debug("Debug", raw="raw") + elif level == "info": + logger.info("Info") + elif level == "warning": + logger.warning("Warning") + elif level == "error": + logger.error("Error", ValueError) + elif level == "critical": + logger.critical("Critical", ValueError) + + +# 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: + schema = json.load(fp) + +with open("tests/schema_with_error.json", "r") as fp: + schema_with_error = json.load(fp) + + +def test_debug_no_arbitrary_data(log_output): """ Test that we get the expected structure when constructing a debug log. """ + do_something("debug") + output_dict = log_output.entries[0] + # Validate output_dict against schema + assert jsonschema.validate(instance=output_dict, schema=schema) is None + - # test_mode=True returns the dictionary being logged - # from the logging statement. - logger = DpLogger("test-1", test_mode=True) - logged_dict = logger.debug("Something") +def test_info_no_arbitrary_data(log_output): + """ + Test that we get the expected structure when constructing + an info log. + """ + do_something("info") + output_dict = log_output.entries[0] + # Validate output_dict against schema + assert jsonschema.validate(instance=output_dict, schema=schema) is None - # Now that "logged_dict" contains the json fields - # we need to compare them to a https://pypi.org/project/jsonschema/ - # of how a log of this kind should be populated. - # Example log: - # {"created_at": "2024-01-15T19:35:29.470825", "namespace": "test-1", "event": "Something", "trace_id": "not-implemented", "span_id": "not-implemented", "severity": 0, "data": {}} +def test_warning_no_arbitrary_data(log_output): + """ + Test that we get the expected structure when constructing + a warning log. + """ + do_something("warning") + output_dict = log_output.entries[0] + # Validate output_dict against schema + assert jsonschema.validate(instance=output_dict, schema=schema) is None - # Note: - # Don't manually write json schemas - # 1.) Use the logger - # 2.) Careful check the logs match the spec - # 3.) Use a site like this to get you started and tweak as needed: - # https://www.liquid-technologies.com/online-json-to-schema-converter -# Also write a test for: -# - logger.info -# - logger.warning +def test_error_no_arbitrary_data(log_output): + """ + Test that we get the expected structure when constructing + an error log. + """ + do_something("error") + output_dict = log_output.entries[0] + # Validate output_dict against schema + assert jsonschema.validate(instance=output_dict, schema=schema_with_error) is None -# And also -# - one of the above with arbitrary data key values -# - one of the above with a "raw" field - -# Lastly for: -# - error -# - critical ("fatal" in go terms) -# but for these two remember you'd be passing in an error. - -# And that's it, as long as the logger is constructing logging structures -# that validate against the json schemas we're done. -# NOTE: ignore hpp and auth fields for now, we'll pick those up later. - +def test_critical_no_arbitrary_data(log_output): + """ + Test that we get the expected structure when constructing + a critical log. + """ + do_something("critical") + output_dict = log_output.entries[0] + # Validate output_dict against schema + assert jsonschema.validate(instance=output_dict, schema=schema_with_error) is None +def test_debug_with_arbitrary_data(log_output): + """ + Test that we get the expected structure when constructing + a debug log with arbitrary data. + """ + do_something("arbitrary") + output_dict = log_output.entries[0] + # Validate output_dict against schema + assert jsonschema.validate(instance=output_dict, schema=schema) is None +def test_debug_with_raw_data(log_output): + """ + Test that we get the expected structure when constructing + a debug log with raw data. + """ + do_something("raw") + output_dict = log_output.entries[0] + # Validate output_dict against schema + assert jsonschema.validate(instance=output_dict, schema=schema) is None From 345ab0180d53d3efac5af02f527a15638f0fdce7 Mon Sep 17 00:00:00 2001 From: Sarah Johnson Date: Mon, 22 Jan 2024 14:49:26 +0000 Subject: [PATCH 33/48] Added timezone to `created_at` datetime --- dpytools/logger/logger.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dpytools/logger/logger.py b/dpytools/logger/logger.py index e4e5c45..b3b3a4f 100644 --- a/dpytools/logger/logger.py +++ b/dpytools/logger/logger.py @@ -2,7 +2,7 @@ import structlog import traceback import json -from datetime import datetime +from datetime import datetime, timezone def level_to_severity(level: int) -> int: @@ -81,7 +81,9 @@ def _log( def _create_log_entry(self, event, level, data, error, raw) -> Dict: log_entry = { - "created_at": datetime.now().isoformat(), # TODO - might not be quite the right ISOtime, investigate + "created_at": datetime.now( + timezone.utc + ).isoformat(), # TODO - might not be quite the right ISOtime, investigate "namespace": self.namespace, "event": event, "trace_id": "not-implemented", From a878a81de8241da96e6e5aba16d96102d4837780 Mon Sep 17 00:00:00 2001 From: Sarah Johnson Date: Mon, 22 Jan 2024 14:51:22 +0000 Subject: [PATCH 34/48] Tidy up --- dpytools/logger/logger.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dpytools/logger/logger.py b/dpytools/logger/logger.py index b3b3a4f..4e0f43c 100644 --- a/dpytools/logger/logger.py +++ b/dpytools/logger/logger.py @@ -81,9 +81,7 @@ def _log( def _create_log_entry(self, event, level, data, error, raw) -> Dict: log_entry = { - "created_at": datetime.now( - timezone.utc - ).isoformat(), # TODO - might not be quite the right ISOtime, investigate + "created_at": datetime.now(timezone.utc).isoformat(), "namespace": self.namespace, "event": event, "trace_id": "not-implemented", From 5c8fe551fca3226e9f3f59755fcd92296daa9914 Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Tue, 23 Jan 2024 12:44:24 +0000 Subject: [PATCH 35/48] Made the requested changes --- dpytools/slack/slack.py | 32 ++++++++++++++++------------ tests/test_slack.py | 46 ++++++++++++++--------------------------- 2 files changed, 34 insertions(+), 44 deletions(-) diff --git a/dpytools/slack/slack.py b/dpytools/slack/slack.py index ab56432..13eb617 100644 --- a/dpytools/slack/slack.py +++ b/dpytools/slack/slack.py @@ -1,25 +1,31 @@ -import os import logging from dpytools.http_clients.base import BaseHttpClient class SlackNotifier: - def __init__(self): - self.webhook_url = os.getenv("SLACK_WEBHOOK_URL") - if not self.webhook_url: - raise ValueError('SLACK_WEBHOOK_URL is not set') + def __init__(self, webhook_url): + if not webhook_url: + raise ValueError('webhook_url is not set') + self.webhook_url = webhook_url self.http_client = BaseHttpClient() - self.validate_webhook_url() - def validate_webhook_url(self): - response = self.http_client.get(self.webhook_url) - if response.status_code != 200: - logging.error(f'Invalid SLACK_WEBHOOK_URL: {response.status_code}') - raise ValueError('Invalid SLACK_WEBHOOK_URL') + def notify(self, msg_dict: dict): + """ + Send a message to the Slack webhook. - def notify(self, msg: dict): + The msg_dict parameter should be a dictionary that matches the + structure documented at https://api.slack.com/messaging/webhooks + """ try: - response = self.http_client.post(self.webhook_url, json=msg) + 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}') + + def msg_str(self, msg: str): + """ + Send a string message to the Slack webhook. + + The msg parameter is wrapped into a dictionary before being sent. + """ + self.notify({'text': msg}) \ No newline at end of file diff --git a/tests/test_slack.py b/tests/test_slack.py index 376e2bc..32b85db 100644 --- a/tests/test_slack.py +++ b/tests/test_slack.py @@ -4,48 +4,32 @@ from dpytools.http_clients.base import BaseHttpClient from dpytools.slack.slack import SlackNotifier -@patch('os.getenv') -@patch.object(BaseHttpClient, 'get') -def test_validate_webhook_url(mock_get, mock_getenv): - """ - Test that the validate_webhook_url method raises an exception for invalid URLs - """ - mock_getenv.return_value = 'http://example.com' - mock_response = MagicMock(Response) - mock_response.status_code = 404 - mock_get.return_value = mock_response - - with pytest.raises(ValueError): - notifier = SlackNotifier() - -@patch('os.getenv') -@patch.object(BaseHttpClient, 'get') -def test_validate_webhook_url_success(mock_get, mock_getenv): +@patch.object(BaseHttpClient, 'post') +def test_notify(mock_post): """ - Test that the validate_webhook_url method does not raise an exception for valid URLs + Test that the notify method sends a POST request """ - mock_getenv.return_value = 'http://example.com' + webhook_url = 'http://example.com' mock_response = MagicMock(Response) mock_response.status_code = 200 - mock_get.return_value = mock_response + mock_post.return_value = mock_response + + notifier = SlackNotifier(webhook_url) + notifier.notify({'text': 'Test message'}) - try: - notifier = SlackNotifier() - except ValueError: - pytest.fail("Unexpected ValueError ..") + mock_post.assert_called_once_with(webhook_url, json={'text': 'Test message'}) -@patch('os.getenv') @patch.object(BaseHttpClient, 'post') -def test_notify(mock_post, mock_getenv): +def test_msg_str(mock_post): """ - Test that the notify method sends a POST request + Test that the msg_str method sends a POST request with a string message """ - mock_getenv.return_value = 'http://example.com' + webhook_url = 'http://example.com' mock_response = MagicMock(Response) mock_response.status_code = 200 mock_post.return_value = mock_response - notifier = SlackNotifier() - notifier.notify({'text': 'Test message'}) + notifier = SlackNotifier(webhook_url) + notifier.msg_str('Test message') - mock_post.assert_called_once_with('http://example.com', json={'text': 'Test message'}) \ No newline at end of file + mock_post.assert_called_once_with(webhook_url, json={'text': 'Test message'}) \ No newline at end of file From 41d4c924a88c425a5649c8e9287bc25db8b37692 Mon Sep 17 00:00:00 2001 From: Sarah Johnson Date: Wed, 24 Jan 2024 10:53:14 +0000 Subject: [PATCH 36/48] Fixed typo --- dpytools/logger/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dpytools/logger/logger.py b/dpytools/logger/logger.py index 4e0f43c..523b97f 100644 --- a/dpytools/logger/logger.py +++ b/dpytools/logger/logger.py @@ -103,7 +103,7 @@ def debug(self, event: str, raw: str = None, data: Dict = None): Log at the debug level. event: the thing that's happened, a simple short english statement - raw : a raw string of any log messages captured for a third paty library + raw : a raw string of any log messages captured for a third party library data : arbitrary key values pairs that may be of use in providing context """ self._log(event, 10, raw=raw, data=data) From b11c30a84fea0dcebdf1222bbe7756014884667c Mon Sep 17 00:00:00 2001 From: Sarah Johnson Date: Thu, 25 Jan 2024 08:10:38 +0000 Subject: [PATCH 37/48] Setting up verified commits --- dpytools/logger/logger.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/dpytools/logger/logger.py b/dpytools/logger/logger.py index 523b97f..3c4a10a 100644 --- a/dpytools/logger/logger.py +++ b/dpytools/logger/logger.py @@ -104,7 +104,7 @@ def debug(self, event: str, raw: str = None, data: Dict = None): event: the thing that's happened, a simple short english statement raw : a raw string of any log messages captured for a third party library - data : arbitrary key values pairs that may be of use in providing context + data : arbitrary key-value pairs that may be of use in providing context """ self._log(event, 10, raw=raw, data=data) @@ -113,8 +113,8 @@ def info(self, event: str, raw: str = None, data: Dict = None): Log at the info level. event: the thing that's happened, a simple short english statement - raw : a raw string of any log messages captured for a third paty library - data : arbitrary key values pairs that may be of use in providing context + raw : a raw string of any log messages captured for a third party library + data : arbitrary key-value pairs that may be of use in providing context """ self._log(event, 20, raw=raw, data=data) @@ -123,8 +123,8 @@ def warning(self, event: str, raw: str = None, data: Dict = None): Log at the warning level. event: the thing that's happened, a simple short english statement - raw : a raw string of any log messages captured for a third paty library - data : arbitrary key values pairs that may be of use in providing context + raw : a raw string of any log messages captured for a third party library + data : arbitrary key-value pairs that may be of use in providing context """ self._log(event, 30, raw=raw, data=data) @@ -134,8 +134,8 @@ def error(self, event: str, error: Exception, raw: str = None, data: Dict = None event: the thing that's happened, a simple short english statement error: a caught python Exceotion - raw : a raw string of any log messages captured for a third paty library - data : arbitrary key values pairs that may be of use in providing context + raw : a raw string of any log messages captured for a third party library + data : arbitrary key-value pairs that may be of use in providing context """ self._log(event, 40, error=error, raw=raw, data=data) @@ -151,7 +151,7 @@ def critical( event: the thing that's happened, a simple short english statement error: a caught python Exceotion - raw : a raw string of any log messages captured for a third paty library - data : arbitrary key values pairs that may be of use in providing context + raw : a raw string of any log messages captured for a third party library + data : arbitrary key-value pairs that may be of use in providing context """ self._log(event, 50, error=error, raw=raw, data=data) From 90848d8cc5ac3a1322def9a493a2ab00a34f86e7 Mon Sep 17 00:00:00 2001 From: NickPapONS Date: Mon, 29 Jan 2024 10:15:24 +0000 Subject: [PATCH 38/48] Adding tests and more config work --- .vscode/settings.json | 7 ++ .../config/__pycache__/config.cpython-311.pyc | Bin 0 -> 3114 bytes .../test_config.cpython-311-pytest-7.4.4.pyc | Bin 0 -> 952 bytes dpytools/config/config.py | 74 +++++++++++-- dpytools/config/properties/__init__.py | 3 +- .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 329 bytes .../__pycache__/base.cpython-311.pyc | Bin 0 -> 2021 bytes .../__pycache__/intproperty.cpython-311.pyc | Bin 0 -> 2207 bytes .../__pycache__/string.cpython-311.pyc | Bin 0 -> 2445 bytes ...t_intproperty.cpython-311-pytest-7.4.4.pyc | Bin 0 -> 8242 bytes ...tringproperty.cpython-311-pytest-7.4.4.pyc | Bin 0 -> 7036 bytes .../properties/{integer.py => intproperty.py} | 20 ++-- dpytools/config/properties/string.py | 16 +-- .../config/properties/test_intproperty.py | 99 ++++++++++++++++++ .../config/properties/test_stringproperty.py | 83 +++++++++++++++ dpytools/config/test_config.py | 65 ++++++++++++ .../test_nothing.cpython-311-pytest-7.4.4.pyc | Bin 0 -> 503 bytes 17 files changed, 337 insertions(+), 30 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 dpytools/config/__pycache__/config.cpython-311.pyc create mode 100644 dpytools/config/__pycache__/test_config.cpython-311-pytest-7.4.4.pyc create mode 100644 dpytools/config/properties/__pycache__/__init__.cpython-311.pyc create mode 100644 dpytools/config/properties/__pycache__/base.cpython-311.pyc create mode 100644 dpytools/config/properties/__pycache__/intproperty.cpython-311.pyc create mode 100644 dpytools/config/properties/__pycache__/string.cpython-311.pyc create mode 100644 dpytools/config/properties/__pycache__/test_intproperty.cpython-311-pytest-7.4.4.pyc create mode 100644 dpytools/config/properties/__pycache__/test_stringproperty.cpython-311-pytest-7.4.4.pyc rename dpytools/config/properties/{integer.py => intproperty.py} (66%) create mode 100644 dpytools/config/properties/test_intproperty.py create mode 100644 dpytools/config/properties/test_stringproperty.py create mode 100644 dpytools/config/test_config.py create mode 100644 tests/__pycache__/test_nothing.cpython-311-pytest-7.4.4.pyc diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f8322ec --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.pytestArgs": [ + "dpytools" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/dpytools/config/__pycache__/config.cpython-311.pyc b/dpytools/config/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..97ce87d06099c63ad945acec025bf876e7f3c3c1 GIT binary patch literal 3114 zcmb7G&2JM&6rZ*C%Z@{!arg)!**FPcDY2nZfI}2$NtLE*qqY=iEnr!^n`DW-c4s#f zN47v!j!1PSBz&|~NP~hZuDwHI3!)OnxQNxxG9P^=CvxW(b%~G%rm5*7$u{aNAb#(LMx0_ z*m=*1mX4d3J*)H@^lh~IW1Q?&KtrZi=@>6FU^#2DS5w-&x?pz1ty+}9gE-aFL5T2P zC_4cl5I$-Z<-ib7_RPgwk$^rPN;etE!tiG(OBJE<@8q^=8s(|m_iw>GIZw`;1>bi4 zF&wimSv-CTBOD&%uifpULt}Sk*}Dc#Y_y!p(Ff9^+q9SiX)$eDtj5YHM?+ztum%Zb zBf)&^uD4llmd-L+_7%6rfVEKUl;hmva(osQ9kVpYA(Z9ZYg5SZIeJl+hY_H;(bA^S zlRsc~XUwg8WNS5I{>1G!zUXf66La2eOU`#7Enl0KUn7z8LGc?M4qw_M=gazE`Pbm4 zZ!~W^%5}hsj`;pZ+&bK=?h&_+_PFJ#C$|kinr4#Q=J?URB7r%Ri6{Gl*O#$Rjw?`>&$c!5%%Tsqv+>luj)hy0oO~(dkrt znK+?GXH?_U<$F;%J(kWGiz#i)NTm`wv@u`6-leuJ`m;ap&0pW@ zK3+KXbn3;OO80oFd%Sk6ukh{DM|H&YoT&*T#l`1co5FZS7=Jxe7AC90WPz!L&aCs# zqnn|LN@(J>S`J;QhOQL2eZ&QBOBg0fr^yf??|2kCR~r~E#;OD7UNovkPMd9mearwD4CE~#`I-8mU4yrR#~bF}EC(m5 z!HH6EqG|4~1iId_=;TOgMpS|EC6Yfp%5SfT3C)xF0l4S@IH{SPyy_XbuS6eR`Xt$F4 zU4{P_iagOPvcGw8KC98pvr(7#1Fs% z7!YZ7&cVM=C&3p{)oqY4gSrBjOO|lP7D$Gg(jd%zU@$%n#2IExp23ygMuhhOo0~)- z0tAA682LIk`Q8fOTju+!d|!$0tMP$Nez3w1mieJ7KU8XFEA(?=AGzHGTtw0Ii()b* zFDD2e5XJA8r9`7ApaYz#@uUKHA!D*LrWYUzlb0Be5JIfP!-SA6wGId-$G{>41$7edh5RFA)LI{DNY9m2FP20gjvRt1@tooPkF0|>? z0Y)S?mhOm+KP4N{O_i9kS?bn_J3FZe5_jk4?|tvyckjLLrIG>o`f~Bp`>X))TR5|& zO~K_S4!!{d5Db6?v%eI|t^`52U@0(AYUydo;0B~(Vsc@$O7Wvhg$))InU(5y{FNu4HS>dQF#;P*IM z>Qd}Sy>~cHJWPk2DYql)^*lVIm=Zam_wcmbZiEhX`wgc-`#fbLT5n2B!Tk0K(?UP8 z121aIOmPE;5T++nQKlXpJGe)3rA@5!#8abBX+oZuX6t^=Kbr6fg2`&GS`cy{onYXx zLg<|2kZaevZ{KX$-M!s*XWQQD9M}ghcG+@9?(e?-hiP~AX3Rtux@D#x4*Y=n5n;vW z$UJ`u9-%E>IUj@{M=k6fW1sT(uPLU@ID;pP>$ZcLnnR|#*-)`~iB+b}`yOeL zY{Ym$@HhYB{fN#+ghlVHk}KHAcSQvb9x{0H16=YLxH>TRd1b_meD^4El6XLgbL12A zRqPHzFQUZU_UX>x&_u}wuc#kK8#Ims!u??ma&Af7XLy}I1NldO zag)jr!Z846|J$XrY+r!-X#R}BgVFpMgU!+WNp~QqHpVwrFILvax9X`P*NhYhqiJ{5 z6bMtD-YC|-zE5RI#MC;E39}5UYa+XTXIx*M%N92@Zq{zENGafE1S;ucpr~h1Y;XCTS&D^5SOIJKx)KQA*myCAV3zqF({F*~zZ-z7h}G&eP`q*&iQ zv&5q`Nk64Px1h2lBR@~KBtJi=7|5@LFp~50(lXQap{8f17VF2yXXa&=#K-FuRQ}?y q$<0qG%}KQ@0tEud1I7A4;sY}yBjXJQr3s0RQE4qzhy literal 0 HcmV?d00001 diff --git a/dpytools/config/properties/__pycache__/base.cpython-311.pyc b/dpytools/config/properties/__pycache__/base.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1209990446e8ed1957f0d67156cf87fd7a117bbd GIT binary patch literal 2021 zcmbVN&5zqe6rZu<#Lh>zv?=W_qIJ2HNObcLR7fpIWmh5~Een+oShMzQV%A^GI8=#x zNTnS1P;q3raVuJtzl2kcD~m%VPTW#0r=ECkoMe;kA}Yqo&+lvO-+O+~{ zS`ZAlZ0`Zz&2*4kZkJ~+@*TdroWthAj)Ww3%!r(2C=4k-)g|ez`69lL&eL~#14;pueC>jDQ z-_pbtI4MFB_t|zq#&YGqRAX+x0nri@foe3@4@=LiLpc67o9l3;8JpaS`D0h*yTYle zI9*sEB&8hhEwQ?G6IfLb;V4*L*nc?ZFy0iJR~tU6+OzdF3j>%lZf8A~0dRZPG6OqI zk^^an!2w@H2br{!eHG>I9F-YQq6ZX4rt9vqhPuPQ`Z%0A5m)Vb548)Vg9?_3&)@4B~Gn?7P==4Rg zOI3Sw5!w;AfZw^EZb}Z@d?p4(mTK?t_DZT3g|wD3uAXJwX*F`wRYU5!FfeYE`2CQB zTzB2?`z*xq1x<%nX1?aSERK^*-BY@*z(*}`CIt4b*g$w60p9&Y*a#mWs7!FG)F_A= zN(cu0<&OZPrQ>C_G1?k!J>5E~T^enVwx4cKO=_%9$pXsM*g$m#R~C}8f%g`AXHX{P z=2SC``jh~?(m@@6TNs*it8$%z>IFp*KMwGjC_WCIRxQ`ro+t2Cpi<0hi!TS40<+1> z&}Tv1ElN|tKobFbt;l<|7O%~FMa4fBA3+EXl>7-`s#8i&$eqbWzY}tMvidtAo0I?g hy`pcf8lQF;K00|zes2OE?_v{IxRa05V zfK;G^rUqdR1euQMKt z1Gf`PkUjM_d5d_E`|h|z#M}3%r*&lrkAjEpMaj>Q`(BSAD-X4Pzjx_c!c_0kZ|MqM zA*;$4WQFFn_U9?q8FPkhL6$l5mJJUGXN^vFneDnY$!x2}VfJQb)HWI zs^s=!pl~5%0`AY)OQSd|xsXN2s@57t!)^he{*R>_ct%#|*&Z4zc?A*`dvIsGBZ(C@zf)TunZb|&8mf4;<@#PDBYAhGYf!rrY zDP`zOdXUa+sz0WFnAw>5K73vrWX6vO1ywIS$WHpX*~jlaxe70se|`3MIpTosX6N>@ zbN;;Ur}Y;wjubQpMvlQrYh&Z&LF!sk`Ss#8MR}@d!1jR;p%Q{m1z>Fv00%5YLymhf za5_N3#D)Wb^ib&s&RQST#5=vddO=tVJ&5zYD`$F-p?=DHtf_^Zm^e3f^|W`a-Kc)s z%@~^{*NM=22YQH!$VB8ruVF2>$?De9vd2~o2O#anHlpb;W67`_K(oOj1T{ru6+t^J z2TQV=<(P%ouAS|Gr&9onu3+s^X1ZK5EDyeFNw3g;lYyn}u5f|%2S?c`m=KI()tmJ~ zo8g9K2<_AkV>R4S%4=c-zCneQ^Dkj~|ac$^DbN;^(e}M)Q%;yg!9=H(S}uR{V4&Hqy^BszgQ=e+uVr zc405O;HMX`eDtvTbPE1q9z&?4lurgpUB_0V>p@D_>#o_fkx%RT*G34k>IBSPbz=X7e558d z>=iw9EUjVuXVd6lJhPM^39Qn)Sbd-bl z1p@^lMScea5=iMjne_YTKDp@k&wcWS-#-s&>L@WmG5oXXru{tB{1?*a3P}vFUpQ?N G2Khfe$S(l^ literal 0 HcmV?d00001 diff --git a/dpytools/config/properties/__pycache__/string.cpython-311.pyc b/dpytools/config/properties/__pycache__/string.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f011bcff324ebe1ead9fe614e79b49acaecb96f1 GIT binary patch literal 2445 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 A7XSbN literal 0 HcmV?d00001 diff --git a/dpytools/config/properties/__pycache__/test_stringproperty.cpython-311-pytest-7.4.4.pyc b/dpytools/config/properties/__pycache__/test_stringproperty.cpython-311-pytest-7.4.4.pyc new file mode 100644 index 0000000000000000000000000000000000000000..404df9793127ac31d4ddfd197e1630ecb1fc9981 GIT binary patch literal 7036 zcmeGhU2ojRarchAFMV2)kvMS!!ma^d68R)6P8`5#lfX_})Kv^PHWDQO!8@)bDtF{z z$+IQ(4ls%Wc}O4HhaxY12>ReaP*CJgq|dhDA_C!u0DaQi39zd8BwJ2`KVZ z@s4w|v$Hd^!{yA*-Zvv7B?8w!_r7CZfsp@VBfK&JvU?qnuZT%ZS|2T85H_ zvY=AglUv9^Po|!46c!3V%l?@x6io$S$y5OjnK^*NCcR0@`E3+EU#9-(O^;jl(hcr3 z8TZy|flBd{2bhI>_cnm9h(`rg0F#;-GixfQ`fcvMO2{VM^6HQ_`8viK(h0c_=w{Re z3-e$}VO=k2aDBvF&(*9&&Cpz7k=6`M(k$Ec47P_`!Lsf82WJ0YlnE|Ab-dnG~QY>BOI;|#XX1K3b~#Wotu zR|S)=ihe)eux!1~Y(R`vMCQvGj?VI5c3I7_O@puLfnkrKOTnF$Ub@xU5AoSch# zv^}eG%VpfH*jDZHrqOg-o@;z=xs|I0*qYcaPX^vL;z+AcKL{HYi{GJ{Wo84BF)djRf{XT#*=*{!j2TjOW9#!qie zOm0m~b%$~%b2|jUQv~<^&>bi0#1G2!BW3!bQhBUY9)#ar5JD15sR#tIlqLWkeJOc3 zCb(9@*MWGskOqqi9Jq;26xSuvp|JdLCaEjE{5qL7UFj{{tT)md)XpX)ca(Oft=u6U zwXOb@x~ezU<6VIlS6*hJtu9ith`fuL`(VdrWJ_WM`a@jpOj3_o@=k_*gscpOc+&bi z#66XeHyrYL5w_nqJUJq=VDJc=nk94Si1tkNx2H{$^W#ar$cp`ZpnE7GvB$aZ$qe99 zl6uYIf2ixQZaO)0#2jtsEGk-g@8!^Qgd`-5hV*tmDJj|d6E(+HX(t1>3U{`nm}B_4 z536-ETCou9#%_|cWR|$f-OOtC6LObQ@(H2v!JhK?`WJ_VK@r;^0h)P}BqQgbZ-XB*r%hmQ7xk3GmWpB{+-=90*T$?RJn71`VFPGiY9|J^tN08>q z?s=rV8Fm2@biGk_FUWaz%B7BIU~u=ryBW*EwtQn$u_+ zocTrFH5M6C{9)kKt1YYULE7?jbqAPT|3%$hc3O2)jMA6tPSvREBCCwPCu{`w3&sAV z4xVioo(@m1ZrBDswv7Tmfu`V6^@mV_oY+jfaVg$-E8e&$8&dAu(n8sooptdEWB$d9 zPoRQV`XvZmtU63+{4HR@5apI3ILA}P#Nbr%Y@C$F|MH8!uRkoFd0afxC8~Oxer^0% zIQc_i_EBN>VPWoZVeUa;uB*`EZQ3P0_~gc|ZWf!*V#FPRKaEl1L(;h9mlB>_4Fsmy z(v-NOVEFZ5c)Uvv3>ZGVwId8pn-?&=bL*L47%1WTK!&)5M^`5uc!0V=42gO5oA{wV zkrJfpxbbN`&LhPJ!r$-X@bp~L0uP}SaSh6qDB+G+l}`nX(+|d>16krXg(S?B)H$1z?n7cQZx=31_k|s(y`*=V*P~u-n0p@-Dk)3hoINmLygS(^$ zcLe@4Mv3QZpoCrBS6=%Y_uID+c3|A4``ieR3lZc<>O}8&?zz|2P;P?~=>`!>0z8n*GcR z7=CA9sad51C44-PA#U^0)ky~)pl%RDVqX0weyC64Zj2kB#-(|2je+p_J`PXMB`xp} z3Y40-V0sWsO?+xSO1yUv9+a9%QE6t8yvrkgAv}iSxV!UD>GBNgf;>FJ13%90a?6AB ztBB<10~*KwKKMBtSnB<$AP&yNp9SI=NNgR&Te?B`6b`^Dcdu}z;h3#D`xXBHns_+x zehC1cJf&N>#T@V%xhFnb= self.min_val + if self.min_val and self.value < self.min_val: + raise ValueError(f"Integer value for {self.name} is lower than allowed minimum.") - if self.max_val: - assert self.value <= self.max_val \ No newline at end of file + 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 diff --git a/dpytools/config/properties/string.py b/dpytools/config/properties/string.py index 5ea0931..e4d72df 100644 --- a/dpytools/config/properties/string.py +++ b/dpytools/config/properties/string.py @@ -1,8 +1,8 @@ from typing import Optional - +from dataclasses import dataclass from .base import BaseProperty - +@dataclass class StringProperty(BaseProperty): regex: Optional[str] min_len: Optional[int] @@ -23,20 +23,20 @@ def secondary_validation_passed(self): Non type based validation you might want to run against a configuration value of this kind. """ + 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 - ... - assert self.value == self.regex + pass if self.min_len: # TODO - confirm the string matches of exceeds the minimum length - ... - assert len(self.value) >= self.min_len + if len(self.value) < self.min_len: + raise ValueError(f"Str value for {self.name} is shorter than minimum length {self.min_len}") if self.max_len: # TODO - confirm the value matches or is less than the max length - ... - assert len(self.value) <= self.max_len \ No newline at end of file + if len(self.value) > self.max_len: + raise ValueError(f"Str value for {self.name} is longer than maximum length {self.max_len}") \ No newline at end of file diff --git a/dpytools/config/properties/test_intproperty.py b/dpytools/config/properties/test_intproperty.py new file mode 100644 index 0000000..f32495b --- /dev/null +++ b/dpytools/config/properties/test_intproperty.py @@ -0,0 +1,99 @@ +import pytest +from properties import IntegerProperty + +def test_int_property(): + """ + Tests if an integer property instance can be created + and validated with no errors. + """ + + test_property = IntegerProperty( + name = "Test Integer property", + value = 24, + min_val = 0, + max_val = 101 + ) + + test_property.type_is_valid() + test_property.secondary_validation_passed() + + +def test_int_property_type_invalid(): + """ + Tests if an integer property with a type of value that + cannot be cast to string raises an exception. + """ + + test_property = IntegerProperty( + name = "Test Integer Property", + value = "Not an integer", + min_val = 0, + max_val = 101 + ) + + with pytest.raises(Exception) as e: + + test_property.type_is_valid() + + assert "Cannot cast Test Integer Property value Not an integer to integer." in str(e.value) + + +def test_int_property_empty_val(): + """ + Tests if an integer property with nothing as the value + raises the expected exception from the secondary validation. + """ + + test_property = IntegerProperty( + name = "Test Integer Property", + value = None, + min_val = 0, + max_val = 101 + ) + + with pytest.raises(ValueError) as e: + + test_property.secondary_validation_passed() + + assert "Integer value for Test Integer property does not exist." in str(e.value) + + +def test_int_property_min_val(): + """ + Tests if an integer property with a value lower than the allowed minimum + raises the expected exception from the secondary validation. + """ + + test_property = IntegerProperty( + name = "Test Integer Property", + value = 9, + min_val = 10, + max_val = 101 + ) + + with pytest.raises(ValueError) as e: + + test_property.secondary_validation_passed() + + assert "Integer value for Test Integer property is lower than allowed minimum." in str(e.value) + + + +def test_int_property_max_val(): + """ + Tests if an integer property with a value higher than the allowed maximum + raises the expected exception from the secondary validation. + """ + + test_property = IntegerProperty( + name = "Test Integer Property", + value = 102, + min_val = 0, + max_val = 101 + ) + + with pytest.raises(ValueError) as e: + + test_property.secondary_validation_passed() + + assert "Integer value for Test Integer property is higher than allowed maximum." in str(e.value) \ No newline at end of file diff --git a/dpytools/config/properties/test_stringproperty.py b/dpytools/config/properties/test_stringproperty.py new file mode 100644 index 0000000..1e96981 --- /dev/null +++ b/dpytools/config/properties/test_stringproperty.py @@ -0,0 +1,83 @@ +import pytest +from properties import StringProperty + +def test_string_property(): + """ + Tests if a string property instance can be created + and validated with no errors. + """ + + test_property = StringProperty( + name = "Test String Property", + value = "Test string value", + regex = "Test regex", + min_len = 1, + max_len = 40 + ) + + test_property.secondary_validation_passed() + + +def test_string_property_empty_val(): + """ + Tests if a string property with an empty string as the value + raises the expected exception from the secondary validation. + """ + + test_property = StringProperty( + name = "Test String Property", + value = "", + regex = "Test regex", + min_len = 1, + max_len = 40 + ) + + with pytest.raises(ValueError) as e: + + test_property.secondary_validation_passed() + + assert ( + f"Str value for Test String Property is an empty string") in str(e.value) + + +def test_string_property_min_len(): + """ + Tests if a string property instance with a non-matching minimum + length string raises the expected error from secondary validation. + """ + + test_property = StringProperty( + name = "Test String Property", + value = "Test string value", + regex = "Test regex", + min_len = 50, + max_len = 51 + ) + + with pytest.raises(ValueError) as e: + + test_property.secondary_validation_passed() + + assert "Str value for Test String Property is shorter than minimum length 50" in str(e.value) + + +def test_string_property_max_len(): + """ + Tests if a string property instance with a non-matching maximum + length string raises the expected error from secondary validation. + """ + + test_property = StringProperty( + name = "Test String Property", + value = "Test string value", + regex = "Test regex", + min_len = 1, + max_len = 2 + ) + + with pytest.raises(ValueError) as e: + + test_property.secondary_validation_passed() + + assert ( + "Str value for Test String Property is longer than maximum length 2") in str(e.value) \ No newline at end of file diff --git a/dpytools/config/test_config.py b/dpytools/config/test_config.py new file mode 100644 index 0000000..71c63a9 --- /dev/null +++ b/dpytools/config/test_config.py @@ -0,0 +1,65 @@ +import pytest + +from config import Config +from properties.string import StringProperty +from properties.intproperty import IntegerProperty + +# def test_config_loader(): + +# config_dictionary = { +# "SOME_STRING_ENV_VAR": { +# "class": StringProperty, +# "property": "name1", +# "kwargs": { +# "regex": "I match a thing", +# "min_len": 10 +# }, +# }, +# "SOME_URL_ENV_VAR": { +# "class": StringProperty, +# "property": "name2", +# "kwargs": { +# "regex": "https://.*" +# }, +# }, +# "SOME_INT_ENV_VAR": { +# "class": IntegerProperty, +# "property": "name3", +# "kwargs": { +# "min_value": 5, +# "max_value": 27 +# } +# }, +# } + +# config = Config.from_env(config_dictionary) +# pass + + +config_dictionary = { + "SOME_STRING_ENV_VAR": { + "class": StringProperty, + "property": "name1", + "kwargs": { + "regex": "I match a thing", + "min_len": 10 + }, + }, + "SOME_URL_ENV_VAR": { + "class": StringProperty, + "property": "name2", + "kwargs": { + "regex": "https://.*" + }, + }, + "SOME_INT_ENV_VAR": { + "class": IntegerProperty, + "property": "name3", + "kwargs": { + "min_value": 5, + "max_value": 27 + } + }, +} + +x = 3 \ No newline at end of file diff --git a/tests/__pycache__/test_nothing.cpython-311-pytest-7.4.4.pyc b/tests/__pycache__/test_nothing.cpython-311-pytest-7.4.4.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c075661570d54b955a54a22d6f51f9cab258e4a8 GIT binary patch literal 503 zcmZutJ5R$f5Vn&((yC==^Mur)4FekpApwGgE{LT|l_qX$HL)$*0fngpD>GYH#Q)$| zWMyKD)U6X|({}1v_uWHx_nq&v+ifF|)%S|NgZ;6=PQ0zNJ^|+gB`Cou8ewO>%#S^c zvf8MQQR2O!Q6s^RsOSIMreVBobp`|apz8(z%&~Re>sgzC+-A}g#87RB5kwmy$uweV zuBawcp~xwT#WPjWjJopS7g9Al3KVS~!Y56Y4mpb#GLj-UI$AIt-iSEQC^tI1W9B}e zgo*6S!psHl8zE91TE2fAb7AI;&jzw6J2t&bs3tJjt#u1viK+nAgXOiVL)2@Q!6av? zVO*D;D_M;9(eW6FV9X%Wu$p0l4-|b-%+Rv6PnFGGt_{tOHByeyea>+xd#p`xk%=Tv g>7_b?&>m8s15`f7xN0Dbm$2HQSD_jX4uKo~2aEZIBLDyZ literal 0 HcmV?d00001 From 0a099404edf627a5642d2c93a25b6214aded28f9 Mon Sep 17 00:00:00 2001 From: NickPapONS Date: Fri, 2 Feb 2024 16:51:57 +0000 Subject: [PATCH 39/48] Config and tests completed --- .vscode/settings.json | 3 +- .../config/__pycache__/config.cpython-311.pyc | Bin 3114 -> 3570 bytes .../test_config.cpython-311-pytest-7.4.4.pyc | Bin 952 -> 0 bytes dpytools/config/config.py | 153 ++++++++---------- .../__pycache__/__init__.cpython-311.pyc | Bin 329 -> 329 bytes .../__pycache__/string.cpython-311.pyc | Bin 2445 -> 2445 bytes ...t_intproperty.cpython-311-pytest-7.4.4.pyc | Bin 8242 -> 0 bytes ...tringproperty.cpython-311-pytest-7.4.4.pyc | Bin 7036 -> 0 bytes dpytools/config/test_config.py | 65 -------- .../test_nothing.cpython-311-pytest-7.4.4.pyc | Bin 503 -> 503 bytes tests/test_config.py | 116 +++++++++++++ .../properties => tests}/test_intproperty.py | 3 +- .../test_stringproperty.py | 2 +- 13 files changed, 190 insertions(+), 152 deletions(-) delete mode 100644 dpytools/config/__pycache__/test_config.cpython-311-pytest-7.4.4.pyc delete mode 100644 dpytools/config/properties/__pycache__/test_intproperty.cpython-311-pytest-7.4.4.pyc delete mode 100644 dpytools/config/properties/__pycache__/test_stringproperty.cpython-311-pytest-7.4.4.pyc delete mode 100644 dpytools/config/test_config.py create mode 100644 tests/test_config.py rename {dpytools/config/properties => tests}/test_intproperty.py (96%) rename {dpytools/config/properties => tests}/test_stringproperty.py (97%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9b38853..aa5d433 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "python.testing.pytestArgs": [ - "tests" + "tests", + "dpytools" ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true diff --git a/dpytools/config/__pycache__/config.cpython-311.pyc b/dpytools/config/__pycache__/config.cpython-311.pyc index 97ce87d06099c63ad945acec025bf876e7f3c3c1..ed7fc3e5f2565273595b04d2142a9bc67965be23 100644 GIT binary patch literal 3570 zcmbVP-ESMm5#J;4j$bF~BssDy)67}2(b69zY}seW@Pi)wzyL;NpeaBUiPAzrpvS)Se>e&RL>y2+K#_;M(U9}vmv-h>qT1# z>(r^jypaz~M_~FTr%E8f?UiBe)$O+YUXW?6+W_Zr}Dqsyh{G2BH!L1%6ci1ayr! z0}%^GxX#r`b3lqKvl1M4jWoF1Uw#77u7~PjXYbVF$a{PHN=S2nyQc(RH8{V)^H=9< z{L1jp;iT6>e}*&umvC^cKXSFO7CzEf;rO?Qb?^J9n0^Z*`27*|X0ZQiOEh5}Jl*Jf zTNmDFmUv*6_(roL2WCY~Q6qH$9(+ffMBhCE`l9>3)cBfE6Kj!gV(S7}3lA&xXmDIT z<_z^@5M{0wt3}sgFE;x7!2$PPq&KFCXX2#-a{@8p$m1Viox$jj?N8c^{^!96&`ZJo zYoZ?yu2he|(X7OQSqYPe_{HH#IOF@4fv#W-MZ<{t090BK>16&%uv7EfAe_Ao9t|_|t0N|G*#wdVI6Vf<>ub=vnAg%erEh^@6czfD4Nj zReDP*V4<;OqC1&%f+Es+g5l^T+Y4E?7eRNaWqRQy-Jy8By@;*DC&;067<*#AT-HsE z#;_CnH7xW#r4~&OQQUJ+WYFkIpa-Cws_33?uQ%rVsDd#)#IGK7I1VrhA1Soeiy>vx z`<{9c!*q~u;{{U&3q`p0qKnijsjzk*(tC^%5ZnKO8?{1y5Xso&=aWw+SH+!~%=XNM zt(gn0nYs4NT$f~HA9K%q+LGR0{pjh*?~c!WIrVkwuc=MxA6o19)%NkLu9WJGpLEC1 zVV$kGH-C@RudVTOZJ)n0a;Wj!$A9=LwKbA;N3xyq$)}S~-s_Uc=u9Vhy!qoVezBd* zZY8sukyi4ZcJiIZ`<>KG^ZFOocIw<#>fC0!mAc$cU2ZJAhTT}$Nh&yL8ixS+?%3(h zQKdQ6K6<8;%)lC*)Jdq_gqRw8Mu0p=(uj9ua%{SJs+~OBi2X}G{8Vhl{`QlWJlB@z zTzRe|PqgLCwtR6*zPNd>C0}XFS6sjDj7~H@dy@Lftc{X72QcIq1%k!>04-MGn!-}i?t~HTsPvl%V zwN(c7pxE~J;4X!<26wm_$ zGJPRrk?bB1Ax5Z%&GIepkgdbb)$+93OTYm1(jki1h9-g7=$fLiYc>ub0-D)Sm0kJe zDQ0En6|(E7;6orQq#GwkjKF(g{~O;d9bkQo1Fb zZcC?KzwZ1fJ&=m|8+ii|5pjoNj!6;OeMCz~Fu6nMQB^OlswGRS6tOL->Zg@_kxA$% zjQ1oP6YN4shn!KP8B~F3@ZX8QfbXxb(}62)(zG!zrb-2y;sv`KL7b^q`pW01w}1RApigX literal 3114 zcmb7G&2JM&6rZ*C%Z@{!arg)!**FPcDY2nZfI}2$NtLE*qqY=iEnr!^n`DW-c4s#f zN47v!j!1PSBz&|~NP~hZuDwHI3!)OnxQNxxG9P^=CvxW(b%~G%rm5*7$u{aNAb#(LMx0_ z*m=*1mX4d3J*)H@^lh~IW1Q?&KtrZi=@>6FU^#2DS5w-&x?pz1ty+}9gE-aFL5T2P zC_4cl5I$-Z<-ib7_RPgwk$^rPN;etE!tiG(OBJE<@8q^=8s(|m_iw>GIZw`;1>bi4 zF&wimSv-CTBOD&%uifpULt}Sk*}Dc#Y_y!p(Ff9^+q9SiX)$eDtj5YHM?+ztum%Zb zBf)&^uD4llmd-L+_7%6rfVEKUl;hmva(osQ9kVpYA(Z9ZYg5SZIeJl+hY_H;(bA^S zlRsc~XUwg8WNS5I{>1G!zUXf66La2eOU`#7Enl0KUn7z8LGc?M4qw_M=gazE`Pbm4 zZ!~W^%5}hsj`;pZ+&bK=?h&_+_PFJ#C$|kinr4#Q=J?URB7r%Ri6{Gl*O#$Rjw?`>&$c!5%%Tsqv+>luj)hy0oO~(dkrt znK+?GXH?_U<$F;%J(kWGiz#i)NTm`wv@u`6-leuJ`m;ap&0pW@ zK3+KXbn3;OO80oFd%Sk6ukh{DM|H&YoT&*T#l`1co5FZS7=Jxe7AC90WPz!L&aCs# zqnn|LN@(J>S`J;QhOQL2eZ&QBOBg0fr^yf??|2kCR~r~E#;OD7UNovkPMd9mearwD4CE~#`I-8mU4yrR#~bF}EC(m5 z!HH6EqG|4~1iId_=;TOgMpS|EC6Yfp%5SfT3C)xF0l4S@IH{SPyy_XbuS6eR`Xt$F4 zU4{P_iagOPvcGw8KC98pvr(7#1Fs% z7!YZ7&cVM=C&3p{)oqY4gSrBjOO|lP7D$Gg(jd%zU@$%n#2IExp23ygMuhhOo0~)- z0tAA682LIk`Q8fOTju+!d|!$0tMP$Nez3w1mieJ7KU8XFEA(?=AGzHGTtw0Ii()b* zFDD2e5XJA8r9`7ApaYz#@uUKHA!D*LrWYUzlb0Be5JIfP!-SA6wGId-$G{>41$7edh5RFA)LI{DNY9m2FP20gjvRt1@tooPkF0|>? z0Y)S?mhOm+KP4N{O_i9kS?bn_J3FZe5_jk4?|tvyckjLLrIG>o`f~Bp`>X))TR5|& zO~K_S4!!{d5Db6?v%eI|t^`52U@0(AYUydo;0B~(Vsc@$O7Wvhg$))InU(5y{FNu4HS>dQF#;P*IM z>Qd}Sy>~cHJWPk2DYql)^*lVIm=Zam_wcmbZiEhX`wgc-`#fbLT5n2B!Tk0K(?UP8 z121aIOmPE;5T++nQKlXpJGe)3rA@5!#8abBX+oZuX6t^=Kbr6fg2`&GS`cy{onYXx zLg<|2kZaevZ{KX$-M!s*XWQQD9M}ghcG+@9?(e?-hiP~AX3Rtux@D#x4*Y=n5n;vW z$UJ`u9-%E>IUj@{M=k6fW1sT(uPLU@ID;pP>$ZcLnnR|#*-)`~iB+b}`yOeL zY{Ym$@HhYB{fN#+ghlVHk}KHAcSQvb9x{0H16=YLxH>TRd1b_meD^4El6XLgbL12A zRqPHzFQUZU_UX>x&_u}wuc#kK8#Ims!u??ma&Af7XLy}I1NldO zag)jr!Z846|J$XrY+r!-X#R}BgVFpMgU!+WNp~QqHpVwrFILvax9X`P*NhYhqiJ{5 z6bMtD-YC|-zE5RI#MC;E39}5UYa+XTXIx*M%N92@Zq{zENGafE1S;ucpr~h1Y 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, f'Required envionrment value "{env_var_name}" could not be found.' + + if value["class"] == StringProperty: + if value["kwargs"]: + regex = value["kwargs"].get("regex") + min_len = value["kwargs"].get("min_len") + max_len = value["kwargs"].get("max_len") + else: + regex = None + min_len = None + max_len = None + + stringprop = StringProperty( + name = value["property"], + value = value_for_property, + regex = regex, + min_len = min_len, + max_len = max_len + ) + + prop_name = value["property"] + setattr(config, prop_name, stringprop) + config._properties_to_validate.append(stringprop) + elif value["class"] == IntegerProperty: + if value["kwargs"]: + min_val = value["kwargs"].get("min_val") + max_val = value["kwargs"].get("max_val") else: - raise ValueError("Incorrect value type specified for property assignment.") - - + min_val = None + max_val = None - def assert_valid_config(self, prop: BaseProperty): + intprop = IntegerProperty( + name = value["property"], + value = value_for_property, + min_val = min_val, + max_val = max_val + ) + + prop_name = value["property"] + setattr(config, prop_name, intprop) + config._properties_to_validate.append(intprop) + + else: + raise TypeError("Incorrect value type specified for property assignment.") + + return config + + + def assert_valid_config(self): """ Assert that then Config class has the properties that provided properties. """ - # For each of the properties you imbided above, run - # self.type_is_valid() - # self.secondary_validation() - prop.type_is_valid() - prop.secondary_validation() + for property in self._properties_to_validate: + property.type_is_valid() + property.secondary_validation() + + self._properties_to_validate = [] \ No newline at end of file diff --git a/dpytools/config/properties/__pycache__/__init__.cpython-311.pyc b/dpytools/config/properties/__pycache__/__init__.cpython-311.pyc index 9074554816273738b4a41b2d399c78630d8e6f57..06e33a73d446581b25fbaa09110fdd2dbfbdd87d 100644 GIT binary patch delta 20 acmX@fbdrgCIWI340}y<8vS%Z=9U}lecm?`�da3fd!)g diff --git a/dpytools/config/properties/__pycache__/test_intproperty.cpython-311-pytest-7.4.4.pyc b/dpytools/config/properties/__pycache__/test_intproperty.cpython-311-pytest-7.4.4.pyc deleted file mode 100644 index b0f6693d7ffe6b71ac14881b5de8b8585b9252a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8242 zcmeHM&u<&Y6`m!RP@k~ICLq(xD|ZQ3FdP!MbGQe21J zC1#eE#FBtp^w>jzw#cOgd~zVTJ@+qY&!j=a!uDdIC*2e{0Suq|-puZ3cS%|bkRA$X zx%~L%n>TNUv%~pjh96H)7YJOxYW&0ccb<^{U?RLyf${8TV0=VOVk!-?q43XCGhIz7 zq^WLX6p@qN$U;u4k!xxj8gS{~k&V2m0v5~+;3+c;IA!Jlr%mO3QquOZg0+(3&-}>o ztWAsFqHfEg-cB{BnSW#f)9`-Q1^9?~3THW(ikULgruw(c1C@}6@D^8(bjkgQU&s^k z0L+Insi0r@H6@Ei($CC7gL56Dpu_cZi+N19Yr5g+wrIKD3YrcC^9-kI=~c+R4Ik7p zJj>L1Ny9PqyGFw{Ikjbbb=`4wi&B@eCGOFg$LM;{Au{a#E6^y4P2INuc2!?>jHcyh zAX?kX@xRcwZ&ZTjb#RdpTHpgV#vJll0DErT)3ga({NH-C`N%x6C#0G}e<`^iC(Wagfz7apk>2I|64UFfR|d+O{H_39(_>Od_I z)p9?)pFvC#8*1)G zcD+K-V&=`lG23%? ze0#b|q7`vJ*@~ne)Ln>0JoHRFnF(HMyk7IvUossSH@&PmWlndqw!%|6?|j&Egv25h zL%y4fM~bI@tC+=YrI&(t2HxqOY8LVJ07mOWIbvbXF1}ALk!8Zvt<-jUoop!zStkm- zFsC%rVMkAYd`>fToON`!hLc^}(!ILji3t!UMaT8{ylB8Q=eaubsO@a()UcVwa7MMZ zt5yrAB{3nsh);;Of_b)6T!T1=6EL3}DgNka4xsbB?~9tMSWRq74I>0?i-*A$1ylOc zuHvU`XIJU`c=1YWXSoDZzN4dgv&0I&4h;QmPOX&K6=ZINSzrWN*Gp`PcSE69D)js` zd|_X)b~An&f+DOSTt~QpaP!3sPRG>aftRn9n6HB8wYAcWn9{Q_t->_zXGNd+`PHz; zDE_c0jzaon0Kf1Z;aV}MKU=99jYfr9En0P(ErVKqzQT-}g^WK1o|W5eyWxR;`PqgG z&dh(M!s>3jVe(Ry&4zp1XjHiF6u--x2v2Dh#mhfaskyXicol2A)i4|`E4v!S0ZLK2 z{8Lzg_+HndiPxivH=>DeiiC*!Ezv@eSYBp0o1qlrMRA(*zdE8G6(M~NS3bG|PB?3^ zG>rJDl;{(rqB+UH&P@DF1T%`Sa&5cM!SH=3p@VeN|a~GyTsu)zx=$ z|4J!#xMR;s0nofNO#1dLRWGsnp1NAT@D406C>%=Qqb( z4}j#?QREErI9bi|8FkL|6B7k4`C9ZL} z3{qU2AL=iC6_bkyml3{(a0Q`+@EXF`5xxO1M$*oYN}APdd3@_eZ$cRiGooi_kI=Ku zAb)X~zlgKox^my(dba#XTOMdDLv5w6tvuB}=<|EX@iE7N_JK%>EQ!E_!`LkF5v8Pq z3?dkr1W&;LN^+DDl};)vtCP{9%qS`1Bz&NDV4}BRz&YVB+ZlC^+NfviT+c#8JzG~$ z&q78#gqLHWXGayG*I_W6sMm>lB~^)@>}JRBX8(9h^)iB;V#D3CDDGt)E=sVQ0>aec z=*@1nn_r-w?c+*o9}i|D8`^h|t7$gVBP~s|@;DN=MsHx%6GZ9Us3--8kMu1l_BOmn zBx!bU4peFMv@YCYMI@;{L6SE68s0;Wk2wy)q{xy8JU9qXNlHVMk`6M6U}O^f1p_F_ zQAUKFR2Fg*WuuI&LXskmOoAb7VMv6SV1skQU)Ci(69!6Bol8=PC`olLNg;z`bd()P z(ot?Fl5}#w3e&;pM(IS7CLFN->2pd_-QKK^)g)v4i`JwXJjdid>%GsRNztZCQ4-Dk z--wd6-0^^=Ua{60{N%C!R`;o_`~GOOSoGJ?RMgh%kTSq1xVJo4uM7mTcH A7XSbN diff --git a/dpytools/config/properties/__pycache__/test_stringproperty.cpython-311-pytest-7.4.4.pyc b/dpytools/config/properties/__pycache__/test_stringproperty.cpython-311-pytest-7.4.4.pyc deleted file mode 100644 index 404df9793127ac31d4ddfd197e1630ecb1fc9981..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7036 zcmeGhU2ojRarchAFMV2)kvMS!!ma^d68R)6P8`5#lfX_})Kv^PHWDQO!8@)bDtF{z z$+IQ(4ls%Wc}O4HhaxY12>ReaP*CJgq|dhDA_C!u0DaQi39zd8BwJ2`KVZ z@s4w|v$Hd^!{yA*-Zvv7B?8w!_r7CZfsp@VBfK&JvU?qnuZT%ZS|2T85H_ zvY=AglUv9^Po|!46c!3V%l?@x6io$S$y5OjnK^*NCcR0@`E3+EU#9-(O^;jl(hcr3 z8TZy|flBd{2bhI>_cnm9h(`rg0F#;-GixfQ`fcvMO2{VM^6HQ_`8viK(h0c_=w{Re z3-e$}VO=k2aDBvF&(*9&&Cpz7k=6`M(k$Ec47P_`!Lsf82WJ0YlnE|Ab-dnG~QY>BOI;|#XX1K3b~#Wotu zR|S)=ihe)eux!1~Y(R`vMCQvGj?VI5c3I7_O@puLfnkrKOTnF$Ub@xU5AoSch# zv^}eG%VpfH*jDZHrqOg-o@;z=xs|I0*qYcaPX^vL;z+AcKL{HYi{GJ{Wo84BF)djRf{XT#*=*{!j2TjOW9#!qie zOm0m~b%$~%b2|jUQv~<^&>bi0#1G2!BW3!bQhBUY9)#ar5JD15sR#tIlqLWkeJOc3 zCb(9@*MWGskOqqi9Jq;26xSuvp|JdLCaEjE{5qL7UFj{{tT)md)XpX)ca(Oft=u6U zwXOb@x~ezU<6VIlS6*hJtu9ith`fuL`(VdrWJ_WM`a@jpOj3_o@=k_*gscpOc+&bi z#66XeHyrYL5w_nqJUJq=VDJc=nk94Si1tkNx2H{$^W#ar$cp`ZpnE7GvB$aZ$qe99 zl6uYIf2ixQZaO)0#2jtsEGk-g@8!^Qgd`-5hV*tmDJj|d6E(+HX(t1>3U{`nm}B_4 z536-ETCou9#%_|cWR|$f-OOtC6LObQ@(H2v!JhK?`WJ_VK@r;^0h)P}BqQgbZ-XB*r%hmQ7xk3GmWpB{+-=90*T$?RJn71`VFPGiY9|J^tN08>q z?s=rV8Fm2@biGk_FUWaz%B7BIU~u=ryBW*EwtQn$u_+ zocTrFH5M6C{9)kKt1YYULE7?jbqAPT|3%$hc3O2)jMA6tPSvREBCCwPCu{`w3&sAV z4xVioo(@m1ZrBDswv7Tmfu`V6^@mV_oY+jfaVg$-E8e&$8&dAu(n8sooptdEWB$d9 zPoRQV`XvZmtU63+{4HR@5apI3ILA}P#Nbr%Y@C$F|MH8!uRkoFd0afxC8~Oxer^0% zIQc_i_EBN>VPWoZVeUa;uB*`EZQ3P0_~gc|ZWf!*V#FPRKaEl1L(;h9mlB>_4Fsmy z(v-NOVEFZ5c)Uvv3>ZGVwId8pn-?&=bL*L47%1WTK!&)5M^`5uc!0V=42gO5oA{wV zkrJfpxbbN`&LhPJ!r$-X@bp~L0uP}SaSh6qDB+G+l}`nX(+|d>16krXg(S?B)H$1z?n7cQZx=31_k|s(y`*=V*P~u-n0p@-Dk)3hoINmLygS(^$ zcLe@4Mv3QZpoCrBS6=%Y_uID+c3|A4``ieR3lZc<>O}8&?zz|2P;P?~=>`!>0z8n*GcR z7=CA9sad51C44-PA#U^0)ky~)pl%RDVqX0weyC64Zj2kB#-(|2je+p_J`PXMB`xp} z3Y40-V0sWsO?+xSO1yUv9+a9%QE6t8yvrkgAv}iSxV!UD>GBNgf;>FJ13%90a?6AB ztBB<10~*KwKKMBtSnB<$AP&yNp9SI=NNgR&Te?B`6b`^Dcdu}z;h3#D`xXBHns_+x zehC1cJf&N>#T@V%xhFnb Date: Tue, 6 Feb 2024 11:17:25 +0000 Subject: [PATCH 40/48] Addressing feedback --- .../__pycache__/base.cpython-311.pyc | Bin 2021 -> 1919 bytes .../__pycache__/intproperty.cpython-311.pyc | Bin 2207 -> 2200 bytes .../__pycache__/string.cpython-311.pyc | Bin 2445 -> 2694 bytes dpytools/config/properties/base.py | 28 +++------- dpytools/config/properties/intproperty.py | 18 +++---- dpytools/config/properties/string.py | 20 +++---- .../test_nothing.cpython-311-pytest-7.4.4.pyc | Bin 503 -> 0 bytes tests/test_intproperty.py | 28 +++++----- tests/test_stringproperty.py | 50 +++++++++++++----- 9 files changed, 78 insertions(+), 66 deletions(-) delete mode 100644 tests/__pycache__/test_nothing.cpython-311-pytest-7.4.4.pyc diff --git a/dpytools/config/properties/__pycache__/base.cpython-311.pyc b/dpytools/config/properties/__pycache__/base.cpython-311.pyc index 1209990446e8ed1957f0d67156cf87fd7a117bbd..f3828255bd68b596ce51276a39620f86e809755d 100644 GIT binary patch delta 892 zcmZuuO=uHA7@eK{NwV1_O_PI36$SYFt~Ju`*4a%VQOH(u z5cCpOP>^2hp=XbRc+-q$aep?3>y5y?Niy?q&X_(;rhQ6|r?T z|J8ac4${Ab&B5<;JxejgKH6b{U5N5kk6E#&@yVAdoe)o$@-bF5l|HH`&DbrZ$3yL^ z;aWGTvtd!M6Uq(SShqr@v1zQet)?(B557xU@DS%EU8w27w(zVcfv}sMzFh(eigy8g zG|c4QbbrJP`{2^Vfa4{zU1ab6B7=9xJIaHQDZCIqAQNwkJB-~uio6tbvDRI@O*<`n zm3TdeG@lsuszo3v*%&uKJcsboN%t~&)b>cnX_Ka5+m1KU8~?@ps`1#fC~4WQXV_lL z@LG;t(uGjH3lq3bsY7*4=UAPg%$;3mgwzSmvYpN|IAIEDM!Odo_-HRvh!9%1fxp~2 z+^2Db&|DcGaAdq%#xxgvmd*$B(gQ66CNKb&D|jxI@{r6;aMwsf3GJ20)r0IQ#?cvh zLtZMLnUbqxIiqU27JQPg^uqXLYP^e3aV?LHf=Xak09XLv-Z?n&hLdmtfPd(|X5ddf z!(`g8_?4~7Pc7qD{pwbAD5oMU$i)aT7>P(p_wRFpL<&;W{tA4QF@(z);@Jrm1j;2L z7kpDzinBHRi7%}?W_!(ArWbKf1fwO=_ASO~f|ZixI6SFCq$hN)ENqgHn+tbQUjd)6TYPLm^wp zAxKfm9Q5W*3HmSi4|wV^EFQcF9=B8wyy$ylQzt~7VLvnPy?JlGGqXQ4U$U9Ep63u~ zpFX}0TKYleE4_d4eQuFyU9xO49XP=Zpv6p}9XM>FOWr`jJIs=8In6AHxy)W7H79m& zH^X3&w>AR4wG->>&9xvl#4~zT@6(08_&_(W+y0|q%g0E&sIHzr^}A3xL}3vbWT*Cb z4z1~9+)TWnmpVKPvp(e>4Ezirq<|`)0>+N4@wmCV9kfg1#bp5VmFn$7tIP}FTS)uNY`3SWNl%Nj5E^^5yf|;V!aSov)s)!2+xdT3@{2_Q6X9xqn@Hrq-`e(QQ~KK1gSxM%37ABo;Nw z2|0yHjS8BGDln(mU_y8n!@nV}nAd1Qte6ifqcVP2H`ZEgdo{SpFN=Z6?p&m9LdNmx Xp7b7J=o{ilBdK(^oTNt9$~*l9uUFJ~ diff --git a/dpytools/config/properties/__pycache__/intproperty.cpython-311.pyc b/dpytools/config/properties/__pycache__/intproperty.cpython-311.pyc index e7776451ab01dfb43f51b2c2a0c62bb1ff5ea5b5..7bd533ca44b55f8d3ce1d78ac68dd99a6611abd4 100644 GIT binary patch delta 474 zcmbO)I75(kIWI340}vRA97?s`$eYO|$`)Uim{Xd1i_^6tIkli9Ge7SZYy9L+rgd3a zKy?iaAGnw~lNj&v@K0cw!hMlP<_eF@1sM8qmtW!o10zr#nY=3`eL*ICg~=K#AO%D_ zQubs+SU~ckP{b9XhzoIv7kCnX0Ko@lp!Uskn1h)ZMJ9h>6=AAUnasF35t=MWOgBLh%=P;z7<% z0Q)+D5vUZ1Ccj`?pbZKL4pulR!ovzugq57UnSGfXNLGpw8#y88BUt7OnEc4hz{K4m N`GE;tagjJMhybz=nArdT delta 522 zcmbOsIA4%=IWI340}x~|Y)rM>$eYO|%vzS1Q<{2<)3qWwwV)(3KkpXHEGku0fs;B@=JVRU<67blXr!rFUW+hFj->>q=0Bg$et(& z3rJoR3cDf{b|EJI0#E!8Ao#!x(hg)4ivVqE5|Ln-!R5%oyhzcJnfV|y>*l@8mP~Te z#i_~pc`1oSmGMAlW~L;992j4aSX`W%Ql&kamrbP}$^#ivyd3Ig1_6=mKd3p}xKP4Qq&7HGO+A-ZBO$b!&Cp|~qTaTj>vK)#I!2Ua{I&;lTu{E%&d zHYof!SmC4y4=YF!R&w$h_GNA$St&+rR+YiiQ|t zh{1&MNc5ly2Z9<*1OkZ%F8CL8WCyai#EVz6i-s5vzPDxz2mSi``+l##@B8|`etp^e zyv6;_?QQ_XqTVmYZrWfiH&evbw9)fSVDxVAYeZi z;ZSuy#I=k3F!@%)AS~cJ+-gwI8kRPb&1sp*+*DG_B@J!*_I+bUGmUBEK@>Nk0Vv`d zNQ;#i(1@gDLr4?FP|>g;GGPpo6) z{Bz)^3}j_KxA3SSsYOYBeRp5dY)Qj^Q0D|ICg69q{Y`9TWOZ!AyR8<~{-WAHf90S# zNT%aXz_L%XPu$w*+71)vtb#9I^u_JQ_%}&e=&Jxm%)qkZS?patTTp^UCAiYH+P$wt zZ6%5a_->UE)Bn|9@S!G-2GBBTc;IC1@z+$9bnpR~0phCtnF3`LcpMcjouR*vz}3;) z!qFoCMyCqRCDZsMoQn8swagC6>M!{%^kuU8y|i)83=x6O-&(+ocwPYFT=l{i#LFy= F{|gt^(LMkG delta 597 zcmZn@?G@%-&dbZi00e7a?@3)Wkyny2Y@)iRUMe#agF8bCYYRgPTPo`^W(J1UKnwxP zfYfRjharkBg?(b1`Nn;B7#Ue7&tck{#SN4zP6ZMT3?I0dIpZ1c^6*b!nZniS-{ao_ zhClA|OMGBp1WF;3cZH-c$b_sgSz`&LfM`d^o+t4zm1H<&mb&Tqh895nh7_wL=-(@idI!BOImz4#~KglWsVz7ZF*d|X0 zO0a_YCs}1V*@A)I3}(<|pZuP6$>gnUPK@l6f3PXY6@fyg2;?74#v(zGw}5srC=>|; z$y?Hs1KCA5;|mgti&Imov?iysE2~3!q9CPrfJ6hsR~`l){*IEKs!sbJ`x%LsxaBW! z%THdzuB^tyDfoa#-~l&(kIe@LX3kv3r~E<_Or}_M*7wve5LqH~iC^~uzwTsx4joBm z&Kw-FWgKdP>>z275jl(xAbQqv1W8K(tzXGdBnhOz?k(Z~u|NdtWGPM$UQpO`utLen z`J4(oV5J}he!zg3#i^|U)*%nl1~=swhfQvNN@-52U6C?S0u&s@&67WI+E2FP@?Zfe F0{~Zan_U0^ diff --git a/dpytools/config/properties/base.py b/dpytools/config/properties/base.py index c058ce8..f323dad 100644 --- a/dpytools/config/properties/base.py +++ b/dpytools/config/properties/base.py @@ -4,28 +4,16 @@ @dataclass class BaseProperty(metaclass=ABCMeta): - name: str - value: Any + _name: str + _value: Any - # TODO: getter - # allow someone to get the property - def _get_name(self): - return self.name + @property + def name(self): + return self._name - def _get_value(self): - return self.value - - # TODO: setter - # categorically disallow anyone from - # changing a property after the class - # has been instantiated. - # Refuse to do it, and log an error. - - def _set_name(self, value): - self.name = value - - def _set_value(self, new_value): - self.value = new_value + @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.") @abstractmethod def type_is_valid(self): diff --git a/dpytools/config/properties/intproperty.py b/dpytools/config/properties/intproperty.py index 2e0015f..f776738 100644 --- a/dpytools/config/properties/intproperty.py +++ b/dpytools/config/properties/intproperty.py @@ -13,20 +13,20 @@ def type_is_valid(self): its of the correct type """ try: - int(self.value) + 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_passed(self): + def secondary_validation(self): """ Non type based validation you might want to run against a configuration value of this kind. """ - if not self.value: - raise ValueError(f"Integer value for {self.name} does not exist.") + if not self._value: + 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.") + if self.min_val and self._value < self.min_val: + 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 + 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 diff --git a/dpytools/config/properties/string.py b/dpytools/config/properties/string.py index e4d72df..e56a92f 100644 --- a/dpytools/config/properties/string.py +++ b/dpytools/config/properties/string.py @@ -2,6 +2,8 @@ from dataclasses import dataclass from .base import BaseProperty +import re + @dataclass class StringProperty(BaseProperty): regex: Optional[str] @@ -14,29 +16,29 @@ def type_is_valid(self): its of the correct type """ try: - str(self.value) + 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): + def secondary_validation(self): """ Non type based validation you might want to run against a configuration value of this kind. """ - if len(self.value) == 0: + 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 - pass + regex_search = re.search(self.regex, self._value) + if not regex_search: + raise ValueError(f"Str value for {self.name} does not match the given regex.") if self.min_len: - # TODO - confirm the string matches of exceeds the minimum length - if len(self.value) < self.min_len: + if len(self._value) < self.min_len: raise ValueError(f"Str value for {self.name} is shorter than minimum length {self.min_len}") if self.max_len: - # TODO - confirm the value matches or is less than the max length - if len(self.value) > self.max_len: + if len(self._value) > self.max_len: raise ValueError(f"Str value for {self.name} is longer than maximum length {self.max_len}") \ No newline at end of file diff --git a/tests/__pycache__/test_nothing.cpython-311-pytest-7.4.4.pyc b/tests/__pycache__/test_nothing.cpython-311-pytest-7.4.4.pyc deleted file mode 100644 index 5694b58800fc78630ab8c5f1bb1fd0e833ce7890..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 503 zcmZuty-ve05Vn*4q*cq#<_W1o8wNHILIMN}T@Xu`DoxzhYGPZq0}4|IR%RXnG4KXF zNmeGr5~*7!&Zh0uv+lbe-Q9P-_ine1Kt6xJ=quQ78*IhfIIAOY-cW)PoT3qS*310Z z!zinb>KG;73mP>Ne2;qmk8K*pn^tEqpbxrg0Kgnu=RKda3CMLOO+gIRh8RJ#5t2+J zmgb6TG8Kv(lUO`b70swCAATTJv!g)K`YwFXROyhjcp)Pxa-*XK)8Unf^Nez%!y9I9 z^GTS{uh^2;Ju#hqA|72WOc` h@|2#d0|@OQ^(jE*V~nc?!gvX*?Ryof;b0%Q;eRd>g?9h| diff --git a/tests/test_intproperty.py b/tests/test_intproperty.py index a02f32d..a393a77 100644 --- a/tests/test_intproperty.py +++ b/tests/test_intproperty.py @@ -8,14 +8,14 @@ def test_int_property(): """ test_property = IntegerProperty( - name = "Test Integer property", - value = 24, + _name = "Test Integer property", + _value = 24, min_val = 0, max_val = 101 ) test_property.type_is_valid() - test_property.secondary_validation_passed() + test_property.secondary_validation() assert test_property @@ -26,8 +26,8 @@ def test_int_property_type_invalid(): """ test_property = IntegerProperty( - name = "Test Integer Property", - value = "Not an integer", + _name = "Test Integer Property", + _value = "Not an integer", min_val = 0, max_val = 101 ) @@ -46,15 +46,15 @@ def test_int_property_empty_val(): """ test_property = IntegerProperty( - name = "Test Integer Property", - value = None, + _name = "Test Integer Property", + _value = None, min_val = 0, max_val = 101 ) with pytest.raises(ValueError) as e: - test_property.secondary_validation_passed() + test_property.secondary_validation() assert "Integer value for Test Integer property does not exist." in str(e.value) @@ -66,15 +66,15 @@ def test_int_property_min_val(): """ test_property = IntegerProperty( - name = "Test Integer Property", - value = 9, + _name = "Test Integer Property", + _value = 9, min_val = 10, max_val = 101 ) with pytest.raises(ValueError) as e: - test_property.secondary_validation_passed() + test_property.secondary_validation() assert "Integer value for Test Integer property is lower than allowed minimum." in str(e.value) @@ -87,14 +87,14 @@ def test_int_property_max_val(): """ test_property = IntegerProperty( - name = "Test Integer Property", - value = 102, + _name = "Test Integer Property", + _value = 102, min_val = 0, max_val = 101 ) with pytest.raises(ValueError) as e: - test_property.secondary_validation_passed() + test_property.secondary_validation() assert "Integer value for Test Integer property is higher than allowed maximum." in str(e.value) \ No newline at end of file diff --git a/tests/test_stringproperty.py b/tests/test_stringproperty.py index a0caec6..c1b48ef 100644 --- a/tests/test_stringproperty.py +++ b/tests/test_stringproperty.py @@ -8,14 +8,14 @@ def test_string_property(): """ test_property = StringProperty( - name = "Test String Property", - value = "Test string value", - regex = "Test regex", + _name = "Test String Property", + _value = "Test string value", + regex = "Test", min_len = 1, max_len = 40 ) - test_property.secondary_validation_passed() + test_property.secondary_validation() def test_string_property_empty_val(): @@ -25,8 +25,8 @@ def test_string_property_empty_val(): """ test_property = StringProperty( - name = "Test String Property", - value = "", + _name = "Test String Property", + _value = "", regex = "Test regex", min_len = 1, max_len = 40 @@ -34,7 +34,7 @@ def test_string_property_empty_val(): with pytest.raises(ValueError) as e: - test_property.secondary_validation_passed() + test_property.secondary_validation() assert ( f"Str value for Test String Property is an empty string") in str(e.value) @@ -47,8 +47,8 @@ def test_string_property_min_len(): """ test_property = StringProperty( - name = "Test String Property", - value = "Test string value", + _name = "Test String Property", + _value = "Test string value", regex = "Test regex", min_len = 50, max_len = 51 @@ -56,7 +56,7 @@ def test_string_property_min_len(): with pytest.raises(ValueError) as e: - test_property.secondary_validation_passed() + test_property.secondary_validation() assert "Str value for Test String Property is shorter than minimum length 50" in str(e.value) @@ -68,8 +68,8 @@ def test_string_property_max_len(): """ test_property = StringProperty( - name = "Test String Property", - value = "Test string value", + _name = "Test String Property", + _value = "Test string value", regex = "Test regex", min_len = 1, max_len = 2 @@ -77,7 +77,29 @@ def test_string_property_max_len(): with pytest.raises(ValueError) as e: - test_property.secondary_validation_passed() + test_property.secondary_validation() + + assert ( + "Str value for Test String Property is longer than maximum length 2") in str(e.value) + + +def test_string_property_regex_no_match(): + """ + Tests if a string property instance with a non-matching regex/value + raises the expected error from secondary validation. + """ + + test_property = StringProperty( + _name = "Test String Property", + _value = "Test string value", + regex = "Test regex", + min_len = 1, + max_len = 50 + ) + + with pytest.raises(ValueError) as e: + + test_property.secondary_validation() assert ( - "Str value for Test String Property is longer than maximum length 2") in str(e.value) \ No newline at end of file + "Str value for Test String Property does not match the given regex.") in str(e.value) \ No newline at end of file From affdfdd8ac3b970d4ab22cc7b83a79049d3cb51a Mon Sep 17 00:00:00 2001 From: NickPapONS Date: Wed, 7 Feb 2024 10:55:03 +0000 Subject: [PATCH 41/48] Fixing forgotten variable names --- .../config/__pycache__/config.cpython-311.pyc | Bin 3570 -> 3574 bytes dpytools/config/config.py | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dpytools/config/__pycache__/config.cpython-311.pyc b/dpytools/config/__pycache__/config.cpython-311.pyc index ed7fc3e5f2565273595b04d2142a9bc67965be23..26d410a4f82274a2da4dae66dd802870e360ad6d 100644 GIT binary patch delta 122 zcmew){Y{#8IWI340}z-e9!{;?$Xmw7$r_)Rn45ZwEq-zr+g`g7*$Xmw7&XSjyn|h0Nau3^G)*?Be)aC;AWJWG&g#}Vq sq;+n{=uN)Mu^NfHc@k#}6Pq4TVUf{f8J_=)=96W3B^YfdoAMq60G;m~h5!Hn diff --git a/dpytools/config/config.py b/dpytools/config/config.py index a4add5f..69c669c 100644 --- a/dpytools/config/config.py +++ b/dpytools/config/config.py @@ -33,8 +33,8 @@ def from_env(config_dict: Dict[str, Dict[str, Any]]) -> Config: max_len = None stringprop = StringProperty( - name = value["property"], - value = value_for_property, + _name = value["property"], + _value = value_for_property, regex = regex, min_len = min_len, max_len = max_len @@ -53,8 +53,8 @@ def from_env(config_dict: Dict[str, Dict[str, Any]]) -> Config: max_val = None intprop = IntegerProperty( - name = value["property"], - value = value_for_property, + _name = value["property"], + _value = value_for_property, min_val = min_val, max_val = max_val ) From 5b6105ac31f573b7ff91b06d8f4590fead65ea20 Mon Sep 17 00:00:00 2001 From: NickPapONS Date: Wed, 7 Feb 2024 11:00:50 +0000 Subject: [PATCH 42/48] Fixing unit tests again --- tests/test_config.py | 14 +++++++------- tests/test_intproperty.py | 6 +++++- tests/test_stringproperty.py | 6 ++++++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 6174625..2c9c5bd 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -48,19 +48,19 @@ def test_config_loader(monkeypatch): # Assertions - assert config.name1.name == "name1" - assert config.name1.value == "Some string value" + assert config.name1._name == "name1" + assert config.name1._value == "Some string value" assert config.name1.min_len == 10 assert config.name1.regex == "I match a thing" - assert config.name2.name == "name2" - assert config.name2.value == "https://test.com/some-url" + assert config.name2._name == "name2" + assert config.name2._value == "https://test.com/some-url" assert config.name2.regex == "https://.*" assert config.name2.max_len == 100 - assert config.name3.name == "name3" - assert config.name3.min_val == 5 - assert config.name3.max_val == 27 + assert config.name3._name == "name3" + assert config.name3._min_val == 5 + assert config.name3._max_val == 27 def test_config_loader_no_values_error(): diff --git a/tests/test_intproperty.py b/tests/test_intproperty.py index a393a77..64ff0be 100644 --- a/tests/test_intproperty.py +++ b/tests/test_intproperty.py @@ -16,7 +16,11 @@ def test_int_property(): test_property.type_is_valid() test_property.secondary_validation() - assert test_property + + assert test_property._name == "Test Integer property" + assert test_property._value == 24 + assert test_property.min_val == 0 + assert test_property.max_val == 101 def test_int_property_type_invalid(): diff --git a/tests/test_stringproperty.py b/tests/test_stringproperty.py index c1b48ef..61d8d50 100644 --- a/tests/test_stringproperty.py +++ b/tests/test_stringproperty.py @@ -17,6 +17,12 @@ def test_string_property(): test_property.secondary_validation() + assert test_property._name == "Test String Property" + assert test_property._value == "Test string value" + assert test_property.regex == "Test" + assert test_property.min_len == 1 + assert test_property.max_len == 40 + def test_string_property_empty_val(): """ From ff4e4c359c8da5d74ef92d2f09a0800e69949b04 Mon Sep 17 00:00:00 2001 From: NickPapONS Date: Wed, 7 Feb 2024 11:02:52 +0000 Subject: [PATCH 43/48] Forgot a test --- tests/test_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 2c9c5bd..0b8e67d 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -59,8 +59,8 @@ def test_config_loader(monkeypatch): assert config.name2.max_len == 100 assert config.name3._name == "name3" - assert config.name3._min_val == 5 - assert config.name3._max_val == 27 + assert config.name3.min_val == 5 + assert config.name3.max_val == 27 def test_config_loader_no_values_error(): From 497feba3199cc68db77a4da8dbb53ea4d63a8e78 Mon Sep 17 00:00:00 2001 From: Sarah Johnson Date: Wed, 7 Feb 2024 14:55:50 +0000 Subject: [PATCH 44/48] 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 1b1dcecbfe44e4a6a948e6a8370818c6bdf6f625 Mon Sep 17 00:00:00 2001 From: NickPapONS Date: Thu, 8 Feb 2024 15:02:37 +0000 Subject: [PATCH 45/48] Addressing new feedback --- .../config/__pycache__/config.cpython-311.pyc | Bin 3574 -> 0 bytes dpytools/config/config.py | 3 ++- .../__pycache__/__init__.cpython-311.pyc | Bin 329 -> 0 bytes .../properties/__pycache__/base.cpython-311.pyc | Bin 1919 -> 0 bytes .../__pycache__/intproperty.cpython-311.pyc | Bin 2200 -> 0 bytes .../__pycache__/string.cpython-311.pyc | Bin 2694 -> 0 bytes dpytools/config/properties/base.py | 8 ++++++++ tests/test_config.py | 16 ++++++++-------- tests/test_intproperty.py | 4 ++-- tests/test_stringproperty.py | 4 ++-- 10 files changed, 22 insertions(+), 13 deletions(-) delete mode 100644 dpytools/config/__pycache__/config.cpython-311.pyc delete mode 100644 dpytools/config/properties/__pycache__/__init__.cpython-311.pyc delete mode 100644 dpytools/config/properties/__pycache__/base.cpython-311.pyc delete mode 100644 dpytools/config/properties/__pycache__/intproperty.cpython-311.pyc delete mode 100644 dpytools/config/properties/__pycache__/string.cpython-311.pyc diff --git a/dpytools/config/__pycache__/config.cpython-311.pyc b/dpytools/config/__pycache__/config.cpython-311.pyc deleted file mode 100644 index 26d410a4f82274a2da4dae66dd802870e360ad6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3574 zcmbVP|8LvI6+cQO^^LY^J9V5?Rv9}%r=+ovIB|k4bCD&5)2+zXq{A?^8NrCO!$cz0 zBb7#mG#EpE@DKSx1%f$1JHQxPcY}+;KkoPa1C$}a0)YVrivG~w6gd4QzijXEELn6C zAlvEqc=ztzyZ7GRefadvz(A5fxuE^as6y*I>~ssy8r*IX8mmMnI#(nmF3)k;jua#L z2)v`kXo=7BB_S`A#JmV&ye<@DrFcFbw8dhgl*}iCcC45xNqLDQ5i(Eo_(P&6)+2;$ zz{5&@K#$xc+2jw{&2oN9vn<=yT+_Ck>)EIuy=+x|{;FAUef|T}aev^Lob(ILF@8hs zvO(P{%o}>&a*aiU_OS+Uy4185yPN`BkkKf6al2=su}Tb*=X3(zaSus8YVdko7asEK zFt))2f#wBWgi%qCfxQV|ykc7m=3)Wu=@#?~G;_O$ps`98NsXx8F^C|{kV8Cuf<3du6R`S6oNCXF9p7cA-)-EZ51+ zo9?yB?VMgdU9P%!Z0odZ+eHWZRn{m3Cg#{{x?J_+s%l!MtEx*w%>3ytLxLq88OSne z|Lo^0d@Fw33+gU9;HQ`lRfmmuegwDUW5=cMDElqgr(3stks5A=TA`@KK|v6eKLcGQ zZePU05w3GJveq|>E3*=oyGk0|t*<_TXxAh4sJnM+QS$EIz7n$5$K6u`uNs`+@cFCr zHGX;UmvGW+kuTtk|0Nn;>rY%Qsz;CXRJg(IVcmPdDW?Ab34ULK-U#Rg0eT_a ze?|0@;g#yi*P4|&Fe_#85WgfmDR*?=GSC%{VX~I`R$7M>xzRh7btx?Mk_bxwM+Bv? zg&f?JN2~4|`?l!=VF@_j9N^O6^`-O;s4P7y;`vAWbF-8Gwsu;@T(i@XX!LAf_ z1<+Z!Z72(N#nPvJp-|Ku#~1H?rqM;m7bsxx13y+WEwyM^(9j+PjrbnoRkMOGsFqeT zd=YW4Vo*G7^eB=yfv~wS!sbFF%hL%Q%^-OTi2`Kl!uwXirqn2OZFH+;LvhMR!CWxG zi3OV~-9;6!(Ok69q3LXjBG&nW=^7=+kJyeMLx-tt`O!tgrFg;pm}9_4$fa};d!kk@ z8)K<$zWtM_2N%NT-T=Ra_(mdTGI85#e4{PFmTxHC1q zJ#}tt>RfYbwly`|A-TlI+|z(IrMFf-cyi+VV^d!p{U-C*%%=2Dy?N|P>(~`f%CtvM zc%x^q&Q!b`e?S`4=IEJLz~31<)cD<_KYpFr8p?S?x%TMzlkvyzc1Ub^s+~Tz_LDDv zy`9c&rE{CHX8K|)eX;RgJ2SO*?Mr(*b7m`ZW;5H&Txw-5HRfNzZp`nb6`VAQB0#>M zIE9K&w$i8D6FF$MGbf;SQetN0DFN~fNh8^j$;4zJ)=2zYKKw*nOZ@GpO?kE@&wBD~ zTOMo4)7$d-E&2TB-KKoGC13V}x-&f1`0R1!>vy+?&w9gWdkzWU(En=rzR^ds{YEp3 z6T+KdF_33S8nMpcixyvK$}=r_#*=447B4pCxt2WV$#c+vZ9c4c%I}+F`PNw8lk+?B z*vcS9;0%405t-rPb{{}v6*85(@Mh&Ixpfl0#oX(BixJn?df8l81HQxGQNZ$32l64r zoyd}&?4IY#4x~G-;@;7a7?#3P6eWNd<&Necw|l1{bqXYVv*CW+H+8u9cu_zP2u$|| zA(Lb`c>qyDH6507`G*_>?yj!UYBvc3;LAoRUK^SQ;-G7a!mimUzzAq&qAI)eOGlZN z(=Qpjh6;WKWSMl5^-2-jlB#}M(TZ$@4uiZe zIhbS@N(SVOI?bVq86bZr!2*H5fzE&+Bsw>NEcbpLo)hLeq(?*|j&vgDg~J`vB~PQk zd-w_Cmbrv*vO``ZY(0eJ>>wJwP=N#zjHY-%=_C+egiQl{q&#vk2caUehx&Hm(yNCM*8R!OZ&pf?|n(>|l-an6DV)2`!bHKwy&DjLfiEM-l882Mh Config: config._properties_to_validate.append(intprop) else: - raise TypeError("Incorrect value type specified for property assignment.") + prop_type = value["class"] + raise TypeError(f"Unsupported property type specified via 'property' field, got {prop_type}. Should be of type StringProperty or IntegerProperty") return config diff --git a/dpytools/config/properties/__pycache__/__init__.cpython-311.pyc b/dpytools/config/properties/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 06e33a73d446581b25fbaa09110fdd2dbfbdd87d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 329 zcmZ3^%ge<81RtL4N%aQOk3k$5V1hC}%K;hF8B!Qh7;_kM8KW2(L2RZRrd;MIW+0n6 zm_d`}B_mLgCgUx>;F6-uy!3#g{DRb?l1iWmzh_=aYIV+zDs^`X>Mv>NwL0r zW{F2>l732oZb4;9Mt+`dNq&A#F_2#gVI=40rDdk;Lru?2E!K~Z&&) diff --git a/dpytools/config/properties/__pycache__/base.cpython-311.pyc b/dpytools/config/properties/__pycache__/base.cpython-311.pyc deleted file mode 100644 index f3828255bd68b596ce51276a39620f86e809755d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1919 zcma)6&2Jk;6rb7ku0NAB)CbanwB^$J&{&+QkXk@(5@C>NpjsqWlii8E&3>4f*&k3jqdIYxVmgg9_>l%9Ixz1i4tJ|q~gfAi+eo0<3VoB3^Fp+=y+ z)BKbFXcF=l4yMgM6;65}?2>>4G$Ju|X>7O##jz2Yamg)d*^I2%cI~+Amg9ld@IH&HKq9w>Fb7O>*mT){l5z`J}I4$RdBXI|$QrPoBYGmvG|J zfVjpc;U+Hy^d4xr1(Km9J21!Of`?^Zep;DsiBVmeuLjm7Z7s0J#H|<6<&6hS@=rzD z=R$$sUczFY+uk!4W!$IJ&7nsbT|T)6uuGnkr_dJ-(nLEgDjG0+N%NBA(Y8c;=e673 zlw8Pm68bxR)=x7f*-j|iAEtg5^F+z^!%#iSw%S4eZhrvzOzx^QjUjcGzwA z3l~E!+gsrAYJZTI(AVMed;qJcLJCQQ5jk4A{=@K~vT_0=;v0yk&4ONsa|eW7qUJ&k z=yUoDu<2KG548KUnQv*!>25IK_ zEJ;!|wP$C>dMVphTsUDO6-!jekSeRds@msRgO7wrMavMF_1w}sON_&D#4>c^4S*|h zun21TF2IQVy|gkRdNB{go{U^-p24r%{gu0 zX9zcdJF3SNz+5t-G~JO-6z=eub*QA1Zfi3?6@vS)WYFi%%%Jms+W;nY=w0aqTSP zPOFhuJbj-%FRyuCoCaCML2h{7*BOhDdoVKLG0T@cPg7K%DLqeMsq@N|)S6|vEx7`w ziV}Lif^ZW-2ZN+32w6u70dJW69$+;0JE|{^)<^3v)(;y?qm9wVi;W{|VPa70#)JSI zo5WrmKPeP8v1{WmQC>4}cFn-)<(UfRn-jyb8WRG5bItKrg`zpt>Zvo(T+rU(vkeb; z@oaZm_1t7zzQ7kj$CuY F{{msF-CqCz diff --git a/dpytools/config/properties/__pycache__/intproperty.cpython-311.pyc b/dpytools/config/properties/__pycache__/intproperty.cpython-311.pyc deleted file mode 100644 index 7bd533ca44b55f8d3ce1d78ac68dd99a6611abd4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2200 zcmaJ?L2nyH6rSRmd@i91y7ly>Lj2(gOz$9Js(QSfd=Gl_Evr)LW`>sCwePU3+CGQOD13-Znz1 zR-=R~DRPZi+6J-uxVEDZvJ3xs?7(BUqmbD?tN$h`q@UtUfrjbnns0lq>3}?7nZ8+e zOevqzcvb(zl>CP9YFzk@a^eJpj{D>5pllOPW~oJHQ@0=#uAWPFa?PSspq-jc;jE=> zU_dJHeoKR}`oiJQT%Rv<(K$5iuiCEhjp=}3uAxxI*g7K(0)}$@Bd~2UK>V)NyW~!y z?SQnKXplYiCV7kako(SHaNz1%nMbcZ)C||G z@t2ut+-B0!`W5LKf z!0wZyj54rD5A@+J^@qWhp5N2+L5H3n438cW3hUkcAbUQ@fARQ(Cm+HC=nw19N-P0y zWoP!XGr_zW=;m`6M+%yQAaiisdTx|F$XreoY#6%cW0bkn$Fai_t zkfVNrpEjN_yU{>AJyd$}v)+R^@lLm|ZY)+J58~zCl{393h(F~$S*b*l7<*~#>S^y- z!f_cl>j^|lo*RR95n73b0L21;mctreomK7S6`!q|E+E@WY((8<=CWzK0BV!PFsh5# zDn@uz6}Ds*+qEVWyJohHpmrTBd4e@ZnB{TFuuAxvE&a*nn+z-+Z;9*X3 zxQk_qMhy7qLd}Vjs6T5L0#QB!hTtb7xy^48-8bk#HXn?aew+CHU3kRQ%1-ZPrvrWZ zaBz5Y3bz^b!Ihsse?0Nzy?^qTg8Ze(Xf8II3&xRJ+4;Tfe4x)KMhj;d&BsRb!8lSY zYwTr>KsT^p7NUAvpx__q7(ykbLOM(vQHL>%Fk={1&#F5dWZf{nuA2__9>Wk=B0>ii z4jV?)&!WsoSb7DQUIX?k5wD|+i4@rNBZj3XzXEo@>vuTx>Q;GkE>OpSTI$&T3H?Y- zYuFvScuv}FrHZ|^oM=Wlx%l73OXE2zL;DJteQ|om<#-b$5G}{N+N%eo{Dqu zv0$)(*pS}?g9K8#PtFIuv`;Psy|ho>2zu#|W{y&06vIE8Z#mDRmH#6COd+YkjdQ0> Hq9FeV8QUvP diff --git a/dpytools/config/properties/__pycache__/string.cpython-311.pyc b/dpytools/config/properties/__pycache__/string.cpython-311.pyc deleted file mode 100644 index 98cdffb7601895d67b58ddf9589234e7375aeca8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2694 zcma)8L2naB6rT0&I(F>fGzn?Ip&6G5$x=x!TxdfjN)bIkS}7!I-6YH6o!FaQ@0yu4 ziCw57#DPN&IiP|>kx+$Fs&L@Ip$9JQFIXcVqLm^=s(RpNL|Y+FeY0zCZ5l<4$8X=v zy!YnKoAp+ESb-J&h#)gmx=kcKqDL?t072%L|Yk&>7bffqH& zluB|=_UC0Ynu`)Kg3ci=dJ}0qMBWq;+Jb-3HsP_=9-v&0CeNU3>;<363SR8XvZLFU zY62hER7cI5Dq}B%U^V%f%E(vLE)(ihr}Lo|VA}Ru-V&4(G?a_XA|ev$P$)@cO_+ia z%tpIrBYVz@d{&cg`hEh9X;JXD$2&0NP~9rDUGZc}3S`ymDe0DC5(^l0l{0znc)P`b z;cmYA0mvN`M^4A;HMAU#Hi6p;M-WzN1|3C?)Ha0BIHJd{3l3aayJ-SgXa@n^gA3}k z2xIZS+->)oa3i8g*M;we6=4Ofir=6WAuHFu?!~P(rIMm=b%$Yl$rsDpl#)ECRLca10=48B)v{~{=T+w5S`WUeniYa;3GCRI`SF~X&PtvL zycbpcRWE*aHBbDM@FYtuk!-}17%`VTkx`rlCf*mjGwTM=#V}jxNhiUr1Z}wuE2vYZ zGgmiZ&n_VpqPUW6KZd7<^p01R`GtyRKeoVKl?JTC*HYP=MAsRQ!js$Ib)y-;-U zifTa(+o2AvSXeEnx&`H{;-F+IG|&oa*{^?{VMDjHiU0KYW!5-kwn~1kYc^q+qeMw{@CUb=EphE{`eyF`^;0X628}p*@UV$$R{~%r> z{1PIJa!~LjUbmEEDIMX&_xwZ7g{OHFn+k&SDLn=Y{Ck3}1A#Y(AZ(+w+ugTO#_jIg r=mWRA@1ppc|JxOMn~`yWpJi{lG5^wE`OBv-MHETgJoJAR-`jryWsjJ< diff --git a/dpytools/config/properties/base.py b/dpytools/config/properties/base.py index f323dad..a5cfe2b 100644 --- a/dpytools/config/properties/base.py +++ b/dpytools/config/properties/base.py @@ -15,6 +15,14 @@ def name(self): def name(self, value): raise ValueError(f"Trying to change name property to value {value} but you cannot change a property name after instantiation.") + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + 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/tests/test_config.py b/tests/test_config.py index 0b8e67d..3f8934c 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -22,7 +22,7 @@ def test_config_loader(monkeypatch): "class": StringProperty, "property": "name1", "kwargs": { - "regex": "I match a thing", + "regex": "string value", "min_len": 10 }, }, @@ -48,17 +48,17 @@ def test_config_loader(monkeypatch): # Assertions - assert config.name1._name == "name1" - assert config.name1._value == "Some string value" + assert config.name1.name == "name1" + assert config.name1.value == "Some string value" assert config.name1.min_len == 10 - assert config.name1.regex == "I match a thing" + assert config.name1.regex == "string value" - assert config.name2._name == "name2" - assert config.name2._value == "https://test.com/some-url" + assert config.name2.name == "name2" + assert config.name2.value == "https://test.com/some-url" assert config.name2.regex == "https://.*" assert config.name2.max_len == 100 - assert config.name3._name == "name3" + assert config.name3.name == "name3" assert config.name3.min_val == 5 assert config.name3.max_val == 27 @@ -113,4 +113,4 @@ def test_config_loader_incorrect_type_error(monkeypatch): config = Config.from_env(config_dictionary) - assert "Incorrect value type specified for property assignment." in str(e.value) \ No newline at end of file + assert "Unsupported property type specified via 'property' field, got . Should be of type StringProperty or IntegerProperty" in str(e.value) \ No newline at end of file diff --git a/tests/test_intproperty.py b/tests/test_intproperty.py index 64ff0be..04efea6 100644 --- a/tests/test_intproperty.py +++ b/tests/test_intproperty.py @@ -17,8 +17,8 @@ def test_int_property(): test_property.type_is_valid() test_property.secondary_validation() - assert test_property._name == "Test Integer property" - assert test_property._value == 24 + assert test_property.name == "Test Integer property" + assert test_property.value == 24 assert test_property.min_val == 0 assert test_property.max_val == 101 diff --git a/tests/test_stringproperty.py b/tests/test_stringproperty.py index 61d8d50..a976980 100644 --- a/tests/test_stringproperty.py +++ b/tests/test_stringproperty.py @@ -17,8 +17,8 @@ def test_string_property(): test_property.secondary_validation() - assert test_property._name == "Test String Property" - assert test_property._value == "Test string value" + assert test_property.name == "Test String Property" + assert test_property.value == "Test string value" assert test_property.regex == "Test" assert test_property.min_len == 1 assert test_property.max_len == 40 From e64697a6e186a03809c3dae1f932e05b72ccb360 Mon Sep 17 00:00:00 2001 From: Sarah Johnson Date: Fri, 9 Feb 2024 09:43:36 +0000 Subject: [PATCH 46/48] 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 47/48] 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" From 09b6af53fa9e4350806dd6d0ccd31ac16d6685a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 07:22:53 +0000 Subject: [PATCH 48/48] --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- poetry.lock | 12 ++++++------ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index fc596ef..3e2a63a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "attrs" @@ -483,13 +483,13 @@ rpds-py = ">=0.7.0" [[package]] name = "requests" -version = "2.31.0" +version = "2.32.0" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.0-py3-none-any.whl", hash = "sha256:f2c3881dddb70d056c5bd7600a4fae312b2a300e39be6a118d30b90bd27262b5"}, + {file = "requests-2.32.0.tar.gz", hash = "sha256:fa5490319474c82ef1d2c9bc459d3652e3ae4ef4c4ebdd18a21145a47ca4b6b8"}, ] [package.dependencies] @@ -695,4 +695,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = ">=3.9, <3.12" -content-hash = "3787a66b3361f30ff714f018623a0e219c492cff7de767b0ac48b2fa386285e7" +content-hash = "3d46b73402bc0d35859f3e0e706ee178c9fcd53507b97268c128331d7e40498a" diff --git a/pyproject.toml b/pyproject.toml index e0404d7..56d52d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ readme = "README.md" python = ">=3.9, <3.12" structlog = "^23.2.0" jsonschema = "^4.21.1" -requests = "^2.31.0" +requests = "^2.32.0" pytest = "^7.4.4" backoff = "^2.2.1"

>^8C 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 literal 0 HcmV?d00001 From 4b3b006dc010248894c187e4785807b65b0b5729 Mon Sep 17 00:00:00 2001 From: Moasib-Arif Date: Mon, 8 Jan 2024 11:11:31 +0000 Subject: [PATCH 21/48] Minor changes --- poetry.lock | 37 +----------------- ....45.2-cp39-cp39-macosx_10_9_universal2.whl | Bin 199395 -> 0 bytes pyproject.toml | 1 - 3 files changed, 1 insertion(+), 37 deletions(-) delete mode 100644 pycurl-7.45.2-cp39-cp39-macosx_10_9_universal2.whl diff --git a/poetry.lock b/poetry.lock index b659ab9..41e3eab 100644 --- a/poetry.lock +++ b/poetry.lock @@ -269,20 +269,6 @@ files = [ [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "http-client" -version = "0.1.22" -description = "Fast and robust HTTP client based on PyCurl" -optional = false -python-versions = "*" -files = [ - {file = "http_client-0.1.22.tar.gz", hash = "sha256:f33d77e4e08b70659e1497eeb3d2121a3c6e8a4c252b3c6a56e87a16bb442f91"}, -] - -[package.dependencies] -pycurl = "*" -six = "*" - [[package]] name = "idna" version = "3.6" @@ -385,16 +371,6 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] -[[package]] -name = "pycurl" -version = "7.45.2" -description = "PycURL -- A Python Interface To The cURL library" -optional = false -python-versions = ">=3.5" -files = [ - {file = "pycurl-7.45.2.tar.gz", hash = "sha256:5730590be0271364a5bddd9e245c9cc0fb710c4cbacbdd95264a3122d23224ca"}, -] - [[package]] name = "pytest" version = "7.4.4" @@ -482,17 +458,6 @@ files = [ {file = "ruff-0.1.6.tar.gz", hash = "sha256:1b09f29b16c6ead5ea6b097ef2764b42372aebe363722f1605ecbcd2b9207184"}, ] -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - [[package]] name = "tomli" version = "2.0.1" @@ -534,4 +499,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "eb6fcf9517e94cc494a812a0f83d8907331f360ab237c66c9251a081aaf8289d" +content-hash = "e6fba5c3c204253c8f04dfe78bb5a5e7253af4df9bdf24455f53b5c90645f7fa" diff --git a/pycurl-7.45.2-cp39-cp39-macosx_10_9_universal2.whl b/pycurl-7.45.2-cp39-cp39-macosx_10_9_universal2.whl deleted file mode 100644 index 4cf35119ad44408ceafb38eb344a195444b469af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 199395 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