diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 94a6ce5db..880ce1bcc 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -125,13 +125,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
@@ -142,6 +143,7 @@ jobs:
- tests/mock_vws/test_docker.py
- README.rst
- docs/
+ - tests/mock_vws/test_validator_errors.py
steps:
- uses: actions/checkout@v7
diff --git a/pyproject.toml b/pyproject.toml
index 100d0b4bd..227608202 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -90,7 +90,7 @@ dev = [
"pylint-per-file-ignores==4.0.0",
"pyproject-fmt==2.30.3",
"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..717df7148 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -63,9 +63,24 @@ class ValidatorError(Exception):
client endpoints.
"""
- status_code: HTTPStatus
- response_text: str
- headers: Mapping[str, str]
+ 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
@@ -74,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
@@ -90,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
@@ -109,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
@@ -129,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
@@ -150,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
@@ -172,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
@@ -191,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
@@ -211,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
@@ -230,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
@@ -247,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
@@ -264,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
@@ -282,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
@@ -307,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
@@ -332,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
@@ -356,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
@@ -377,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
@@ -393,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
@@ -409,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
@@ -429,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
@@ -447,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
@@ -467,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 (
"",
@@ -485,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
@@ -499,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
@@ -512,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
@@ -530,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="""\
@@ -552,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 6df987e56..04ebcfac6 100644
--- a/src/mock_vws/_services_validators/exceptions.py
+++ b/src/mock_vws/_services_validators/exceptions.py
@@ -30,9 +30,24 @@ class ValidatorError(Exception):
endpoints.
"""
- status_code: HTTPStatus
- response_text: str
- headers: Mapping[str, str]
+ 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
@@ -43,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
@@ -63,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
@@ -86,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
@@ -109,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(),
@@ -123,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
@@ -131,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
@@ -149,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
@@ -167,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
@@ -187,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
@@ -211,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
@@ -231,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
@@ -251,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
@@ -271,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
@@ -291,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
@@ -311,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
@@ -331,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
@@ -352,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
@@ -375,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 (
"",
@@ -393,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
@@ -410,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
@@ -422,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
@@ -437,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
@@ -457,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
@@ -475,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
@@ -493,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
@@ -513,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
@@ -531,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 == ()
diff --git a/uv.lock b/uv.lock
index 337cc283d..3a0b94ea2 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1526,21 +1526,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]]
@@ -2803,7 +2803,7 @@ dev = [
{ name = "pylint-per-file-ignores", specifier = "==4.0.0" },
{ name = "pyproject-fmt", specifier = "==2.30.3" },
{ 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" },