From 58da7434ed4738a844a5a3c5ad39f184a81e8512 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 19 Oct 2026 10:17:28 +0100 Subject: [PATCH 1/3] Upgrade Pyrefly to 1.4.0 --- pyproject.toml | 2 +- src/mock_vws/_query_validators/exceptions.py | 7 +++-- .../_services_validators/exceptions.py | 7 +++-- uv.lock | 28 +++++++++---------- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fa31cd09b..a04345415 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,7 +90,7 @@ dev = [ "pylint-per-file-ignores==4.0.0", "pyproject-fmt==2.30.1", "pyprojroot==0.3.0", - "pyrefly==1.3.2", + "pyrefly==1.4.0", "pyright==1.1.414", "pyroma==5.0.1", "pytest==9.1.1", diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py index 0fd4a04b8..82de2f9c5 100644 --- a/src/mock_vws/_query_validators/exceptions.py +++ b/src/mock_vws/_query_validators/exceptions.py @@ -63,9 +63,10 @@ class ValidatorError(Exception): client endpoints. """ - status_code: HTTPStatus - response_text: str - headers: Mapping[str, str] + # Concrete validator errors initialize these response fields. + status_code: HTTPStatus # pyrefly: ignore[uninitialized-instance-variable] + response_text: str # pyrefly: ignore[uninitialized-instance-variable] + headers: Mapping[str, str] # pyrefly: ignore[uninitialized-instance-variable] @beartype diff --git a/src/mock_vws/_services_validators/exceptions.py b/src/mock_vws/_services_validators/exceptions.py index 6df987e56..2c4eaa12e 100644 --- a/src/mock_vws/_services_validators/exceptions.py +++ b/src/mock_vws/_services_validators/exceptions.py @@ -30,9 +30,10 @@ class ValidatorError(Exception): endpoints. """ - status_code: HTTPStatus - response_text: str - headers: Mapping[str, str] + # Concrete validator errors initialize these response fields. + status_code: HTTPStatus # pyrefly: ignore[uninitialized-instance-variable] + response_text: str # pyrefly: ignore[uninitialized-instance-variable] + headers: Mapping[str, str] # pyrefly: ignore[uninitialized-instance-variable] @beartype diff --git a/uv.lock b/uv.lock index e4d301551..d8b2b555e 100644 --- a/uv.lock +++ b/uv.lock @@ -1428,21 +1428,21 @@ wheels = [ [[package]] name = "pyrefly" -version = "1.3.2" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4d/78/b6ec12acb586606618c71ebe1008af59e1ca50e667895f1946fa7b656619/pyrefly-1.3.2.tar.gz", hash = "sha256:30a68ff5429a1546ec08a2d913dfe442c9553a2940d274de18833d8eda939b5e", size = 6679591, upload-time = "2026-09-28T22:10:06.292Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/3e/ea6893be167fa14307d7f9a924bed9ecbbbd91b01953890b9b3c58ed5359/pyrefly-1.4.0.tar.gz", hash = "sha256:bb57c004a4842cfe93f93aebb5203ca6209a5f0c8bda47e8bc289e70940fd910", size = 6962062, upload-time = "2026-10-15T16:54:11.819Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/24/01/144a78839822abad55628881bcb45f8173e6d8b87976897c9105884b5a6b/pyrefly-1.3.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:626dbaee16ae1169a10a4ba3293583b5ab0320102f35efaad9e03457a2fc646c", size = 15088488, upload-time = "2026-09-28T22:09:40.743Z" }, - { url = "https://files.pythonhosted.org/packages/23/aa/599756bd42e6533ee9261ebd288f846ab453b00debaef2b4789893967809/pyrefly-1.3.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2d5fc704dbf0e990dc4f15cb1ead64f1a325ae27383a50c76cfcaf0387f926bb", size = 14429996, upload-time = "2026-09-28T22:09:43.316Z" }, - { url = "https://files.pythonhosted.org/packages/90/0c/4b0678889136b1514ecd25cec11974d5bfdaeaaf9f5fe8a766ca45ec4ab5/pyrefly-1.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89dbe8a33e735fa96023c33ce5dfd81af9b0bdd2ee771e8d69b2ebffc7e0fbc7", size = 14871810, upload-time = "2026-09-28T22:09:45.42Z" }, - { url = "https://files.pythonhosted.org/packages/a6/4e/a96236391c6bb0d8b50af85e0ea6c0c56206f739931ac3031f9e000325ee/pyrefly-1.3.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ec0c31715d48bff279f548b85a93b5bc43d2c7bd1492a8cd703a9185cb089e5", size = 16113462, upload-time = "2026-09-28T22:09:47.619Z" }, - { url = "https://files.pythonhosted.org/packages/ad/c3/d4d0966aa7008ebd8481a6d05aa53202ad40c9597705eeee87ba6e0e2025/pyrefly-1.3.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b3de468bed691e40dc6271284c8dcc05e649d7ed45280ef3d07332741efe34c", size = 16043341, upload-time = "2026-09-28T22:09:50.083Z" }, - { url = "https://files.pythonhosted.org/packages/4f/d1/52d9ea1b7048c7316d9ff7594c170b1c06d37547a75d140c8deea7b9ddfd/pyrefly-1.3.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb94365fe5f854cb4fe82f600c4e2453160d328610b12981004851ee0b6e8aeb", size = 15437183, upload-time = "2026-09-28T22:09:52.56Z" }, - { url = "https://files.pythonhosted.org/packages/08/ec/24f83e4a397fa9d5c416bb4d378ee1bb18fe48bdd5698fe541636f5352f3/pyrefly-1.3.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4f730d2e515ac25827fc68f343a7aa02b1072e0062df6a430346a4ffdfb5db2f", size = 14892976, upload-time = "2026-09-28T22:09:54.916Z" }, - { url = "https://files.pythonhosted.org/packages/29/c7/a473f8e9ec31ba0682ebe44f4e51167a22a4cbdc8bfd87365b9aca23be20/pyrefly-1.3.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:60f440e1626bd5cc4a41ee90cecba80796ed01ebd6c15d9f933efb8d07ffe2b8", size = 15469689, upload-time = "2026-09-28T22:09:57.541Z" }, - { url = "https://files.pythonhosted.org/packages/d0/df/779092365e8e469fc8e7349e1c26c35b8a73df9e6be80a00265fc26a2b2b/pyrefly-1.3.2-py3-none-win32.whl", hash = "sha256:b31684189de44a25a5bf00bdf3ad16be44a7b14654702d6018bce1043357202c", size = 14135550, upload-time = "2026-09-28T22:09:59.757Z" }, - { url = "https://files.pythonhosted.org/packages/9d/20/466333e7f2891f8c357b5c226559c39ba8dde4f90be09189ecc771a086f5/pyrefly-1.3.2-py3-none-win_amd64.whl", hash = "sha256:693960cdb247d1f76b9e09b3f2554dae20034f7d442ccd4e9b3e8962a3622363", size = 15067523, upload-time = "2026-09-28T22:10:02.099Z" }, - { url = "https://files.pythonhosted.org/packages/18/32/ee66df92c692c0686d82c9ecef3876ccb0386aa318bf507016f0121a8f78/pyrefly-1.3.2-py3-none-win_arm64.whl", hash = "sha256:f7424b21331670522980ff13e08bd143659879a4d92f4832282c7d7c41a9b5d6", size = 14348271, upload-time = "2026-09-28T22:10:04.253Z" }, + { url = "https://files.pythonhosted.org/packages/6a/17/dfce6611acc00cb26667e5e1011438cc510f3067f961e0f07f7a0c68f416/pyrefly-1.4.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:460cec39bc8743915e9c2c8c10ba926ad0b29b099d89c1859fb97e0059f42fa7", size = 15705624, upload-time = "2026-10-15T16:53:43.412Z" }, + { url = "https://files.pythonhosted.org/packages/a6/78/a7b96442123fa6ed733de0d1ea933e35d6fc6b7c9cf16e05279abb493493/pyrefly-1.4.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5a6370d686b6cbbac8869832f09a52012888371956fbc68d446a349bc85ccd43", size = 14978669, upload-time = "2026-10-15T16:53:46.546Z" }, + { url = "https://files.pythonhosted.org/packages/23/97/dfcfec6a9d8948ab4b2746f7e87558a82bffabb7a54c22e7d3ce4bdc1932/pyrefly-1.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f1b16923a6583a6764e8aec07787e2ef4df644c7030294cba71339783a9451c", size = 15438858, upload-time = "2026-10-15T16:53:48.904Z" }, + { url = "https://files.pythonhosted.org/packages/31/12/6fa414aa87e398d6ddc7145a5d993b0975fc3511198dbb72c47873653ab6/pyrefly-1.4.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:688ad4cb4d6e711d67026fdcfb37b599f86061efe11ba5704287330e7041a69e", size = 16852241, upload-time = "2026-10-15T16:53:51.496Z" }, + { url = "https://files.pythonhosted.org/packages/a8/73/2d7312262cf6481d6ad46eed55b8f2592c3cff60f95cc2f30a114cd1484e/pyrefly-1.4.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5f3c9dddb6f3128beb49cf4e0f5cb88d4d17f039008d8e0c2be653966acd234", size = 16751917, upload-time = "2026-10-15T16:53:54.39Z" }, + { url = "https://files.pythonhosted.org/packages/2c/65/b44a1cb23667bcbcdc88558cbef79fa9e07a1152c2a1da148036b1a2e0d1/pyrefly-1.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6951f51687304caebfedf13dd1a943aeafdbc95abcc4e24a683b97467a77007", size = 16066345, upload-time = "2026-10-15T16:53:57.436Z" }, + { url = "https://files.pythonhosted.org/packages/a6/07/7ca6ff0c53e3a90ecdab05790d33f430fca5c48687613a0b182744166d98/pyrefly-1.4.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:838de4d4a8b9b8ed39bedd0b8b55629c69661d73a6fa3a3a4adff24b9dbeb399", size = 15461223, upload-time = "2026-10-15T16:54:00.364Z" }, + { url = "https://files.pythonhosted.org/packages/61/be/ec7b2f38a898ce5b2219dda730e87d31fc8cfb2108a3d5844f9d590f1281/pyrefly-1.4.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:afb9ce2e0ae3e6106fb45f6d47536ac92b6423e601e08401cdefa850066444f5", size = 16119099, upload-time = "2026-10-15T16:54:02.664Z" }, + { url = "https://files.pythonhosted.org/packages/6e/16/1b9549168b38a575d9c168e17b4b4ee30028d602ec0f7b07c198ef265dbe/pyrefly-1.4.0-py3-none-win32.whl", hash = "sha256:839db536d0ac29f1b23e2d974fb911075c2283e3e623755c45d826aafac175bc", size = 14858880, upload-time = "2026-10-15T16:54:05.114Z" }, + { url = "https://files.pythonhosted.org/packages/03/02/18f075d4de0e255f184f42fc28c1f0ecc9f49dd528678479fc90cf12246c/pyrefly-1.4.0-py3-none-win_amd64.whl", hash = "sha256:7aff37c2b711c8240ed650da0c6148237b327bf7e5dfacee65884f1adea22305", size = 15726220, upload-time = "2026-10-15T16:54:07.452Z" }, + { url = "https://files.pythonhosted.org/packages/92/62/92947076681550904c72a9dd03b24e92694ede9a48548f5e487c849e66ea/pyrefly-1.4.0-py3-none-win_arm64.whl", hash = "sha256:88879f0cb749e5fea81d9ff4c2d5431a70349c458bcdf0b61de72c79214d90a5", size = 14943690, upload-time = "2026-10-15T16:54:09.689Z" }, ] [[package]] @@ -2668,7 +2668,7 @@ dev = [ { name = "pylint-per-file-ignores", specifier = "==4.0.0" }, { name = "pyproject-fmt", specifier = "==2.30.1" }, { name = "pyprojroot", specifier = "==0.3.0" }, - { name = "pyrefly", specifier = "==1.3.2" }, + { name = "pyrefly", specifier = "==1.4.0" }, { name = "pyright", specifier = "==1.1.414" }, { name = "pyroma", specifier = "==5.0.1" }, { name = "pytest", specifier = "==9.1.1" }, From 5b12521bb91929408cf105f45be6ae027e4d5e07 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 19 Oct 2026 10:35:07 +0100 Subject: [PATCH 2/3] Initialize validator response fields in base constructors --- .github/workflows/test.yml | 1 + src/mock_vws/_query_validators/exceptions.py | 346 +++++++++++------ .../_services_validators/exceptions.py | 355 ++++++++++++------ tests/mock_vws/test_validator_errors.py | 38 ++ 4 files changed, 496 insertions(+), 244 deletions(-) create mode 100644 tests/mock_vws/test_validator_errors.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 676995431..c5695b786 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -61,6 +61,7 @@ jobs: - tests/mock_vws/authorization_header/test_malformed.py::test_missing_signature - tests/mock_vws/authorization_header/test_bad_key.py - tests/mock_vws/test_cloud_query_failure_response.py + - tests/mock_vws/test_validator_errors.py - tests/mock_vws/content_length/test_incorrect.py::test_not_integer - tests/mock_vws/content_length/test_incorrect.py::test_too_large - tests/mock_vws/content_length/test_incorrect.py::test_too_small diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py index 82de2f9c5..717df7148 100644 --- a/src/mock_vws/_query_validators/exceptions.py +++ b/src/mock_vws/_query_validators/exceptions.py @@ -63,10 +63,24 @@ class ValidatorError(Exception): client endpoints. """ - # Concrete validator errors initialize these response fields. - status_code: HTTPStatus # pyrefly: ignore[uninitialized-instance-variable] - response_text: str # pyrefly: ignore[uninitialized-instance-variable] - headers: Mapping[str, str] # pyrefly: ignore[uninitialized-instance-variable] + def __init__( + self, + *, + status_code: HTTPStatus, + response_text: str, + headers: Mapping[str, str], + ) -> None: + """Initialize the HTTP response represented by this error. + + Args: + status_code: The response's HTTP status. + response_text: The response body. + headers: The response headers. + """ + super().__init__() + self.status_code = status_code + self.response_text = response_text + self.headers = headers @beartype @@ -75,14 +89,18 @@ class DateHeaderNotGivenError(ValidatorError): def __init__(self) -> None: """Initialize a missing date header response.""" - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = "Date header required." - self.headers = { + status_code = HTTPStatus.BAD_REQUEST + response_text = "Date header required." + headers = { **_TEXT_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -91,15 +109,19 @@ class DateFormatNotValidError(ValidatorError): def __init__(self) -> None: """Initialize a malformed date header response.""" - super().__init__() - self.status_code = HTTPStatus.UNAUTHORIZED - self.response_text = "Malformed date header." - self.headers = { + status_code = HTTPStatus.UNAUTHORIZED + response_text = "Malformed date header." + headers = { **_TEXT_HEADERS, "Date": http_date(), "WWW-Authenticate": "KWS", - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -110,16 +132,20 @@ class RequestTimeTooSkewedError(ValidatorError): def __init__(self) -> None: """Initialize a ``RequestTimeTooSkewed`` response.""" - super().__init__() - self.status_code = HTTPStatus.FORBIDDEN - self.response_text = result_code_response_text( + status_code = HTTPStatus.FORBIDDEN + response_text = result_code_response_text( result_code=ResultCodes.REQUEST_TIME_TOO_SKEWED, ) - self.headers = { + headers = { **_JSON_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -130,17 +156,21 @@ class BadImageError(ValidatorError): def __init__(self) -> None: """Initialize a ``BadImage`` response.""" - super().__init__() - self.status_code = HTTPStatus.UNPROCESSABLE_ENTITY - self.response_text = _unusual_separators_response_text( + status_code = HTTPStatus.UNPROCESSABLE_ENTITY + response_text = _unusual_separators_response_text( result_code=ResultCodes.BAD_IMAGE, space_after_transaction_id_key=True, ) - self.headers = { + headers = { **_JSON_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -151,18 +181,22 @@ class AuthenticationFailureError(ValidatorError): def __init__(self) -> None: """Initialize an ``AuthenticationFailure`` response.""" - super().__init__() - self.status_code = HTTPStatus.UNAUTHORIZED - self.response_text = _unusual_separators_response_text( + status_code = HTTPStatus.UNAUTHORIZED + response_text = _unusual_separators_response_text( result_code=ResultCodes.AUTHENTICATION_FAILURE, space_after_transaction_id_key=False, ) - self.headers = { + headers = { **_JSON_HEADERS, "Date": http_date(), "WWW-Authenticate": "VWS", - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -173,17 +207,21 @@ class AuthenticationFailureGoodFormattingError(ValidatorError): def __init__(self) -> None: """Initialize a well formatted ``AuthenticationFailure`` body.""" - super().__init__() - self.status_code = HTTPStatus.UNAUTHORIZED - self.response_text = result_code_response_text( + status_code = HTTPStatus.UNAUTHORIZED + response_text = result_code_response_text( result_code=ResultCodes.AUTHENTICATION_FAILURE, ) - self.headers = { + headers = { **_JSON_HEADERS, "Date": http_date(), "WWW-Authenticate": "VWS", - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -192,14 +230,18 @@ class ImageNotGivenError(ValidatorError): def __init__(self) -> None: """Initialize a missing image response.""" - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = "No image." - self.headers = { + status_code = HTTPStatus.BAD_REQUEST + response_text = "No image." + headers = { **_JSON_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -212,17 +254,19 @@ def __init__(self) -> None: """Initialize a missing ``Content-Disposition`` header response. """ - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = ( - "Could find no Content-Disposition header within part" - ) - self.headers = { + status_code = HTTPStatus.BAD_REQUEST + response_text = "Could find no Content-Disposition header within part" + headers = { **_BASE_HEADERS, "Content-Type": "text/plain;charset=utf-8", "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -231,15 +275,19 @@ class AuthHeaderMissingError(ValidatorError): def __init__(self) -> None: """Initialize a missing authorization header response.""" - super().__init__() - self.status_code = HTTPStatus.UNAUTHORIZED - self.response_text = "Authorization header missing." - self.headers = { + status_code = HTTPStatus.UNAUTHORIZED + response_text = "Authorization header missing." + headers = { **_TEXT_HEADERS, "Date": http_date(), "WWW-Authenticate": "KWS", - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -248,15 +296,19 @@ class MalformedAuthHeaderError(ValidatorError): def __init__(self) -> None: """Initialize a malformed authorization header response.""" - super().__init__() - self.status_code = HTTPStatus.UNAUTHORIZED - self.response_text = "Malformed authorization header." - self.headers = { + status_code = HTTPStatus.UNAUTHORIZED + response_text = "Malformed authorization header." + headers = { **_TEXT_HEADERS, "Date": http_date(), "WWW-Authenticate": "KWS", - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -265,14 +317,18 @@ class UnknownParametersError(ValidatorError): def __init__(self) -> None: """Initialize an unknown parameters response.""" - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = "Unknown parameters in the request." - self.headers = { + status_code = HTTPStatus.BAD_REQUEST + response_text = "Unknown parameters in the request." + headers = { **_JSON_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -283,17 +339,21 @@ class InactiveProjectError(ValidatorError): def __init__(self) -> None: """Initialize an ``InactiveProject`` response.""" - super().__init__() - self.status_code = HTTPStatus.FORBIDDEN - self.response_text = _unusual_separators_response_text( + status_code = HTTPStatus.FORBIDDEN + response_text = _unusual_separators_response_text( result_code=ResultCodes.INACTIVE_PROJECT, space_after_transaction_id_key=True, ) - self.headers = { + headers = { **_JSON_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -308,17 +368,21 @@ def __init__(self, given_value: str) -> None: Args: given_value: The given value of the "max_num_results" field. """ - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = ( + status_code = HTTPStatus.BAD_REQUEST + response_text = ( f"Invalid value '{given_value}' in form data part 'max_result'. " "Expecting integer value in range from 1 to 50 (inclusive)." ) - self.headers = { + headers = { **_JSON_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -333,17 +397,21 @@ def __init__(self, given_value: str) -> None: Args: given_value: The given value of the "max_num_results" field. """ - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = ( + status_code = HTTPStatus.BAD_REQUEST + response_text = ( f"Integer out of range ({given_value}) in form data part " "'max_result'. Accepted range is from 1 to 50 (inclusive)." ) - self.headers = { + headers = { **_JSON_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -357,19 +425,23 @@ def __init__(self, given_value: str) -> None: Args: given_value: The given "include_target_data" value. """ - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = ( + status_code = HTTPStatus.BAD_REQUEST + response_text = ( f"Invalid value '{given_value.lower()}' in form data part " "'include_target_data'. " "Expecting one of the (unquoted) string values 'all', 'none' or " "'top'." ) - self.headers = { + headers = { **_JSON_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -378,14 +450,18 @@ class UnsupportedMediaTypeError(ValidatorError): def __init__(self) -> None: """Initialize an unsupported media type response.""" - super().__init__() - self.status_code = HTTPStatus.UNSUPPORTED_MEDIA_TYPE - self.response_text = "" - self.headers = { + status_code = HTTPStatus.UNSUPPORTED_MEDIA_TYPE + response_text = "" + headers = { **_BASE_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -394,14 +470,18 @@ class InvalidAcceptHeaderError(ValidatorError): def __init__(self) -> None: """Initialize an invalid accept header response.""" - super().__init__() - self.status_code = HTTPStatus.NOT_ACCEPTABLE - self.response_text = "" - self.headers = { + status_code = HTTPStatus.NOT_ACCEPTABLE + response_text = "" + headers = { **_BASE_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -410,15 +490,19 @@ class NoBoundaryFoundError(ValidatorError): def __init__(self) -> None: """Initialize a missing multipart boundary response.""" - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = "Unable to get boundary for multipart" - self.headers = { + status_code = HTTPStatus.BAD_REQUEST + response_text = "Unable to get boundary for multipart" + headers = { **_BASE_HEADERS, "Content-Type": "text/plain;charset=utf-8", "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -430,13 +514,17 @@ class ContentLengthHeaderTooLargeError(ValidatorError): def __init__(self) -> None: """Initialize a gateway timeout response.""" - super().__init__() - self.status_code = HTTPStatus.GATEWAY_TIMEOUT - self.response_text = "" - self.headers = { + status_code = HTTPStatus.GATEWAY_TIMEOUT + response_text = "" + headers = { "Connection": "keep-alive", - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -448,13 +536,17 @@ class ContentLengthHeaderNotIntError(ValidatorError): def __init__(self) -> None: """Initialize an empty bad request response.""" - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = "" - self.headers = { + status_code = HTTPStatus.BAD_REQUEST + response_text = "" + headers = { "Connection": "Close", - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -468,9 +560,8 @@ class RequestHeaderOrCookieTooLargeError(ValidatorError): def __init__(self) -> None: """Initialize an NGINX request header too large response.""" - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = "".join( + status_code = HTTPStatus.BAD_REQUEST + response_text = "".join( f"{line}\r\n" for line in ( "", @@ -486,12 +577,17 @@ def __init__(self) -> None: "", ) ) - self.headers = { + headers = { **_BASE_HEADERS, "Content-Type": "text/html", "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -500,9 +596,8 @@ class RequestEntityTooLargeError(ValidatorError): def __init__(self) -> None: """Initialize a request entity too large response.""" - super().__init__() - self.status_code = HTTPStatus.REQUEST_ENTITY_TOO_LARGE - self.response_text = textwrap.dedent( + status_code = HTTPStatus.REQUEST_ENTITY_TOO_LARGE + response_text = textwrap.dedent( text="""\ \r 413 Request Entity Too Large\r @@ -513,13 +608,18 @@ def __init__(self) -> None: \r """, ) - self.headers = { + headers = { "Connection": "Close", "Date": http_date(), "Server": "nginx", "Content-Type": "text/html", - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -531,8 +631,7 @@ class NoContentTypeError(ValidatorError): def __init__(self) -> None: """Initialize a missing content type response.""" - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST + status_code = HTTPStatus.BAD_REQUEST jetty_content_type_error = textwrap.dedent( text="""\ @@ -553,12 +652,17 @@ def __init__(self) -> None: """, # noqa: E501 ) - self.response_text = jetty_content_type_error - self.headers = { + response_text = jetty_content_type_error + headers = { "Connection": "keep-alive", "Content-Type": "text/html;charset=iso-8859-1", "Server": "nginx", "Cache-Control": "must-revalidate,no-cache,no-store", "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) diff --git a/src/mock_vws/_services_validators/exceptions.py b/src/mock_vws/_services_validators/exceptions.py index 2c4eaa12e..04ebcfac6 100644 --- a/src/mock_vws/_services_validators/exceptions.py +++ b/src/mock_vws/_services_validators/exceptions.py @@ -30,10 +30,24 @@ class ValidatorError(Exception): endpoints. """ - # Concrete validator errors initialize these response fields. - status_code: HTTPStatus # pyrefly: ignore[uninitialized-instance-variable] - response_text: str # pyrefly: ignore[uninitialized-instance-variable] - headers: Mapping[str, str] # pyrefly: ignore[uninitialized-instance-variable] + def __init__( + self, + *, + status_code: HTTPStatus, + response_text: str, + headers: Mapping[str, str], + ) -> None: + """Initialize the HTTP response represented by this error. + + Args: + status_code: The response's HTTP status. + response_text: The response body. + headers: The response headers. + """ + super().__init__() + self.status_code = status_code + self.response_text = response_text + self.headers = headers @beartype @@ -44,16 +58,20 @@ class UnknownTargetError(ValidatorError): def __init__(self) -> None: """Initialize an ``UnknownTarget`` response.""" - super().__init__() - self.status_code = HTTPStatus.NOT_FOUND - self.response_text = result_code_response_text( + status_code = HTTPStatus.NOT_FOUND + response_text = result_code_response_text( result_code=ResultCodes.UNKNOWN_TARGET, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -64,16 +82,20 @@ class ProjectInactiveError(ValidatorError): def __init__(self) -> None: """Initialize a ``ProjectInactive`` response.""" - super().__init__() - self.status_code = HTTPStatus.FORBIDDEN - self.response_text = result_code_response_text( + status_code = HTTPStatus.FORBIDDEN + response_text = result_code_response_text( result_code=ResultCodes.PROJECT_INACTIVE, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -87,16 +109,20 @@ class RequestQuotaReachedError(ValidatorError): def __init__(self) -> None: """Initialize a ``RequestQuotaReached`` response.""" - super().__init__() - self.status_code = HTTPStatus.FORBIDDEN - self.response_text = result_code_response_text( + status_code = HTTPStatus.FORBIDDEN + response_text = result_code_response_text( result_code=ResultCodes.REQUEST_QUOTA_REACHED, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -110,10 +136,9 @@ class TooManyRequestsError(ValidatorError): def __init__(self) -> None: """Initialize a ``429 Too Many Requests`` response.""" - super().__init__() - self.status_code = HTTPStatus.TOO_MANY_REQUESTS - self.response_text = "" - self.headers = { + status_code = HTTPStatus.TOO_MANY_REQUESTS + response_text = "" + headers = { "Connection": "keep-alive", "Content-Length": "0", "Date": http_date(), @@ -124,6 +149,11 @@ def __init__(self) -> None: "x-envoy-ratelimited": "true", "x-envoy-upstream-service-time": "5", } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -132,16 +162,20 @@ class TargetQuotaReachedError(ValidatorError): def __init__(self) -> None: """Initialize a ``TargetQuotaReached`` response.""" - super().__init__() - self.status_code = HTTPStatus.FORBIDDEN - self.response_text = result_code_response_text( + status_code = HTTPStatus.FORBIDDEN + response_text = result_code_response_text( result_code=ResultCodes.TARGET_QUOTA_REACHED, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -150,16 +184,20 @@ class ProjectSuspendedError(ValidatorError): def __init__(self) -> None: """Initialize a ``ProjectSuspended`` response.""" - super().__init__() - self.status_code = HTTPStatus.FORBIDDEN - self.response_text = result_code_response_text( + status_code = HTTPStatus.FORBIDDEN + response_text = result_code_response_text( result_code=ResultCodes.PROJECT_SUSPENDED, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -168,16 +206,20 @@ class ProjectHasNoApiAccessError(ValidatorError): def __init__(self) -> None: """Initialize a ``ProjectHasNoApiAccess`` response.""" - super().__init__() - self.status_code = HTTPStatus.FORBIDDEN - self.response_text = result_code_response_text( + status_code = HTTPStatus.FORBIDDEN + response_text = result_code_response_text( result_code=ResultCodes.PROJECT_HAS_NO_API_ACCESS, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -188,16 +230,20 @@ class AuthenticationFailureError(ValidatorError): def __init__(self) -> None: """Initialize an ``AuthenticationFailure`` response.""" - super().__init__() - self.status_code = HTTPStatus.UNAUTHORIZED - self.response_text = result_code_response_text( + status_code = HTTPStatus.UNAUTHORIZED + response_text = result_code_response_text( result_code=ResultCodes.AUTHENTICATION_FAILURE, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -212,16 +258,19 @@ def __init__(self, *, status_code: HTTPStatus) -> None: status_code: The status code to use in a response if this is raised. """ - super().__init__() - self.status_code = status_code - self.response_text = result_code_response_text( + response_text = result_code_response_text( result_code=ResultCodes.FAIL, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -232,16 +281,20 @@ class BadRequestError(ValidatorError): def __init__(self) -> None: """Initialize a ``BadRequest`` response.""" - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = result_code_response_text( + status_code = HTTPStatus.BAD_REQUEST + response_text = result_code_response_text( result_code=ResultCodes.BAD_REQUEST, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -252,16 +305,20 @@ class MetadataTooLargeError(ValidatorError): def __init__(self) -> None: """Initialize a ``MetadataTooLarge`` response.""" - super().__init__() - self.status_code = HTTPStatus.UNPROCESSABLE_ENTITY - self.response_text = result_code_response_text( + status_code = HTTPStatus.UNPROCESSABLE_ENTITY + response_text = result_code_response_text( result_code=ResultCodes.METADATA_TOO_LARGE, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -272,16 +329,20 @@ class TargetNameExistError(ValidatorError): def __init__(self) -> None: """Initialize a ``TargetNameExist`` response.""" - super().__init__() - self.status_code = HTTPStatus.FORBIDDEN - self.response_text = result_code_response_text( + status_code = HTTPStatus.FORBIDDEN + response_text = result_code_response_text( result_code=ResultCodes.TARGET_NAME_EXIST, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -292,16 +353,20 @@ class BadImageError(ValidatorError): def __init__(self) -> None: """Initialize a ``BadImage`` response.""" - super().__init__() - self.status_code = HTTPStatus.UNPROCESSABLE_ENTITY - self.response_text = result_code_response_text( + status_code = HTTPStatus.UNPROCESSABLE_ENTITY + response_text = result_code_response_text( result_code=ResultCodes.BAD_IMAGE, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -312,16 +377,20 @@ class ImageTooLargeError(ValidatorError): def __init__(self) -> None: """Initialize an ``ImageTooLarge`` response.""" - super().__init__() - self.status_code = HTTPStatus.UNPROCESSABLE_ENTITY - self.response_text = result_code_response_text( + status_code = HTTPStatus.UNPROCESSABLE_ENTITY + response_text = result_code_response_text( result_code=ResultCodes.IMAGE_TOO_LARGE, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -332,16 +401,20 @@ class RequestTimeTooSkewedError(ValidatorError): def __init__(self) -> None: """Initialize a ``RequestTimeTooSkewed`` response.""" - super().__init__() - self.status_code = HTTPStatus.FORBIDDEN - self.response_text = result_code_response_text( + status_code = HTTPStatus.FORBIDDEN + response_text = result_code_response_text( result_code=ResultCodes.REQUEST_TIME_TOO_SKEWED, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -353,16 +426,20 @@ class ContentLengthHeaderTooLargeError(ValidatorError): def __init__(self) -> None: """Initialize a stream timeout response.""" - super().__init__() - self.status_code = HTTPStatus.REQUEST_TIMEOUT - self.response_text = "stream timeout" - self.headers = { - "Content-Length": str(object=len(self.response_text)), + status_code = HTTPStatus.REQUEST_TIMEOUT + response_text = "stream timeout" + headers = { + "Content-Length": str(object=len(response_text)), "Date": http_date(), "server": "envoy", "Content-Type": "text/plain", "Connection": "close", } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -376,9 +453,8 @@ class RequestHeaderOrCookieTooLargeError(ValidatorError): def __init__(self) -> None: """Initialize an NGINX request header too large response.""" - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = "".join( + status_code = HTTPStatus.BAD_REQUEST + response_text = "".join( f"{line}\r\n" for line in ( "", @@ -394,12 +470,17 @@ def __init__(self) -> None: "", ) ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Content-Type": "text/html", "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -411,9 +492,8 @@ class ContentLengthHeaderNotIntError(ValidatorError): def __init__(self) -> None: """Initialize a load balancer bad request response.""" - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = textwrap.dedent( + status_code = HTTPStatus.BAD_REQUEST + response_text = textwrap.dedent( text="""\ \r 400 Bad Request\r @@ -423,13 +503,18 @@ def __init__(self) -> None: \r """, ) - self.headers = { + headers = { "Connection": "close", - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), "Date": http_date(), "Server": "awselb/2.0", "Content-Type": "text/html", } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -438,15 +523,19 @@ class UnnecessaryRequestBodyError(ValidatorError): def __init__(self) -> None: """Initialize an empty bad request response.""" - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = "" - self.headers = { + status_code = HTTPStatus.BAD_REQUEST + response_text = "" + headers = { "server": "envoy", "Date": http_date(), "x-envoy-upstream-service-time": "5", - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -458,16 +547,20 @@ class TargetStatusNotSuccessError(ValidatorError): def __init__(self) -> None: """Initialize a ``TargetStatusNotSuccess`` response.""" - super().__init__() - self.status_code = HTTPStatus.FORBIDDEN - self.response_text = result_code_response_text( + status_code = HTTPStatus.FORBIDDEN + response_text = result_code_response_text( result_code=ResultCodes.TARGET_STATUS_NOT_SUCCESS, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -476,16 +569,20 @@ class InvalidAcceptHeaderError(ValidatorError): def __init__(self) -> None: """Initialize an ``InvalidAcceptHeader`` response.""" - super().__init__() - self.status_code = HTTPStatus.BAD_REQUEST - self.response_text = result_code_response_text( + status_code = HTTPStatus.BAD_REQUEST + response_text = result_code_response_text( result_code=ResultCodes.INVALID_ACCEPT_HEADER, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -494,16 +591,20 @@ class InvalidInstanceIdError(ValidatorError): def __init__(self) -> None: """Initialize an ``InvalidInstanceId`` response.""" - super().__init__() - self.status_code = HTTPStatus.UNPROCESSABLE_ENTITY - self.response_text = result_code_response_text( + status_code = HTTPStatus.UNPROCESSABLE_ENTITY + response_text = result_code_response_text( result_code=ResultCodes.INVALID_INSTANCE_ID, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -514,16 +615,20 @@ class InvalidTargetTypeError(ValidatorError): def __init__(self) -> None: """Initialize an ``InvalidTargetType`` response.""" - super().__init__() - self.status_code = HTTPStatus.UNPROCESSABLE_ENTITY - self.response_text = result_code_response_text( + status_code = HTTPStatus.UNPROCESSABLE_ENTITY + response_text = result_code_response_text( result_code=ResultCodes.INVALID_TARGET_TYPE, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) @beartype @@ -532,13 +637,17 @@ class TargetStatusProcessingError(ValidatorError): def __init__(self) -> None: """Initialize a ``TargetStatusProcessing`` response.""" - super().__init__() - self.status_code = HTTPStatus.FORBIDDEN - self.response_text = result_code_response_text( + status_code = HTTPStatus.FORBIDDEN + response_text = result_code_response_text( result_code=ResultCodes.TARGET_STATUS_PROCESSING, ) - self.headers = { + headers = { **_STANDARD_HEADERS, "Date": http_date(), - "Content-Length": str(object=len(self.response_text)), + "Content-Length": str(object=len(response_text)), } + super().__init__( + status_code=status_code, + response_text=response_text, + headers=headers, + ) diff --git a/tests/mock_vws/test_validator_errors.py b/tests/mock_vws/test_validator_errors.py new file mode 100644 index 000000000..c2076c675 --- /dev/null +++ b/tests/mock_vws/test_validator_errors.py @@ -0,0 +1,38 @@ +"""Validator errors always carry a complete HTTP response.""" + +from http import HTTPStatus + +import pytest + +from mock_vws._query_validators.exceptions import ( + ValidatorError as QueryValidatorError, +) +from mock_vws._services_validators.exceptions import ( + ValidatorError as ServicesValidatorError, +) + + +@pytest.mark.parametrize( + argnames="error_type", + argvalues=[QueryValidatorError, ServicesValidatorError], + ids=["query", "services"], +) +def test_complete_response( + error_type: type[QueryValidatorError | ServicesValidatorError], +) -> None: + """A validator error can be consumed without uninitialized fields.""" + response_text = "Invalid request" + headers = { + "Content-Type": "text/plain", + "Content-Length": str(object=len(response_text)), + } + error = error_type( + status_code=HTTPStatus.BAD_REQUEST, + response_text=response_text, + headers=headers, + ) + + assert error.status_code is HTTPStatus.BAD_REQUEST + assert error.response_text == response_text + assert error.headers == headers + assert error.args == () From 62309c1a9fe93bb32f49ec23b09326c93bfcaf9a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 19 Oct 2026 11:07:57 +0100 Subject: [PATCH 3/3] Keep live CI jobs on distinct Vuforia credential slots --- .github/workflows/test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c5695b786..59b830221 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -61,7 +61,6 @@ jobs: - tests/mock_vws/authorization_header/test_malformed.py::test_missing_signature - tests/mock_vws/authorization_header/test_bad_key.py - tests/mock_vws/test_cloud_query_failure_response.py - - tests/mock_vws/test_validator_errors.py - tests/mock_vws/content_length/test_incorrect.py::test_not_integer - tests/mock_vws/content_length/test_incorrect.py::test_too_large - tests/mock_vws/content_length/test_incorrect.py::test_too_small @@ -124,13 +123,14 @@ jobs: - tests/mock_vws/update_target/test_update.py - tests/mock_vws/update_target/test_width.py - tests/mock_vws/update_target/test_inactive_project.py + # Live jobs must keep distinct credential indices below 100. + - tests/mock_vws/model_target_web_api + - tests/mock_vws/vumark_generation_api - tests/mock_vws/test_model_target_generation_failure.py - tests/mock_vws/test_model_target_generation_warning.py - tests/mock_vws/test_model_target_failure_response.py - tests/mock_vws/test_model_target_training_allowance.py - - tests/mock_vws/model_target_web_api - tests/mock_vws/test_model_target_retries.py - - tests/mock_vws/vumark_generation_api - tests/mock_vws/test_vumark_generation_failure.py - tests/mock_vws/test_target_validators.py - tests/mock_vws/requests_mock @@ -141,6 +141,7 @@ jobs: - tests/mock_vws/test_docker.py - README.rst - docs/ + - tests/mock_vws/test_validator_errors.py steps: - uses: actions/checkout@v7