From 180a4de8a9376dfb81e2b92264ae7c1d954059dd Mon Sep 17 00:00:00 2001 From: Karan Anand Date: Sun, 22 Feb 2026 14:52:49 -0500 Subject: [PATCH] Add type annotations to `ironicclient/common/apiclient/exceptions.py` Changes: Add type annotations following code conventions. Added file to the migrated files list. Change-Id: I0479edf2bff4174e7bb813c6fd123529ff287635 Signed-off-by: Karan Anand --- ironicclient/common/apiclient/exceptions.py | 154 +++++++++++--------- pyproject.toml | 1 + 2 files changed, 86 insertions(+), 69 deletions(-) diff --git a/ironicclient/common/apiclient/exceptions.py b/ironicclient/common/apiclient/exceptions.py index 10bca538e..a0a863ed9 100644 --- a/ironicclient/common/apiclient/exceptions.py +++ b/ironicclient/common/apiclient/exceptions.py @@ -20,9 +20,15 @@ Exception definitions. """ +from __future__ import annotations + from http import client as http_client import inspect import sys +from typing import Any +from typing import cast + +import requests # type: ignore[import-untyped] from ironicclient.common.i18n import _ @@ -64,7 +70,7 @@ class ConnectionRefused(ConnectionError): class AuthPluginOptionsMissing(AuthorizationFailure): """Auth plugin misses some options.""" - def __init__(self, opt_names): + def __init__(self, opt_names: list[str]) -> None: super(AuthPluginOptionsMissing, self).__init__( _("Authentication failed. Missing options: %s") % ", ".join(opt_names)) @@ -73,7 +79,7 @@ class AuthPluginOptionsMissing(AuthorizationFailure): class AuthSystemNotFound(AuthorizationFailure): """User has specified an AuthSystem that is not installed.""" - def __init__(self, auth_system): + def __init__(self, auth_system: str) -> None: super(AuthSystemNotFound, self).__init__( _("AuthSystemNotFound: %r") % auth_system) self.auth_system = auth_system @@ -96,7 +102,7 @@ class EndpointNotFound(EndpointException): class AmbiguousEndpoints(EndpointException): """Found more than one matching endpoint in Service Catalog.""" - def __init__(self, endpoints=None): + def __init__(self, endpoints: object | None = None) -> None: super(AmbiguousEndpoints, self).__init__( _("AmbiguousEndpoints: %r") % endpoints) self.endpoints = endpoints @@ -104,12 +110,19 @@ class AmbiguousEndpoints(EndpointException): class HttpError(ClientException): """The base exception class for all HTTP exceptions.""" - http_status = 0 - message = _("HTTP Error") + http_status: int = 0 + message: str = _("HTTP Error") - def __init__(self, message=None, details=None, - response=None, request_id=None, - url=None, method=None, http_status=None): + def __init__( + self, + message: str | None = None, + details: str | None = None, + response: requests.Response | None = None, + request_id: str | None = None, + url: str | None = None, + method: str | None = None, + http_status: int | None = None, + ) -> None: self.http_status = http_status or self.http_status self.message = message or self.message self.details = details @@ -125,7 +138,7 @@ class HttpError(ClientException): class HTTPRedirection(HttpError): """HTTP Redirection.""" - message = _("HTTP Redirection") + message: str = _("HTTP Redirection") class HTTPClientError(HttpError): @@ -133,7 +146,7 @@ class HTTPClientError(HttpError): Exception for cases in which the client seems to have erred. """ - message = _("HTTP Client Error") + message: str = _("HTTP Client Error") class HttpServerError(HttpError): @@ -142,7 +155,7 @@ class HttpServerError(HttpError): Exception for cases in which the server is aware that it has erred or is incapable of performing the request. """ - message = _("HTTP Server Error") + message: str = _("HTTP Server Error") class MultipleChoices(HTTPRedirection): @@ -151,8 +164,8 @@ class MultipleChoices(HTTPRedirection): Indicates multiple options for the resource that the client may follow. """ - http_status = http_client.MULTIPLE_CHOICES - message = _("Multiple Choices") + http_status: int = http_client.MULTIPLE_CHOICES + message: str = _("Multiple Choices") class BadRequest(HTTPClientError): @@ -160,8 +173,8 @@ class BadRequest(HTTPClientError): The request cannot be fulfilled due to bad syntax. """ - http_status = http_client.BAD_REQUEST - message = _("Bad Request") + http_status: int = http_client.BAD_REQUEST + message: str = _("Bad Request") class Unauthorized(HTTPClientError): @@ -170,8 +183,8 @@ class Unauthorized(HTTPClientError): Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. """ - http_status = http_client.UNAUTHORIZED - message = _("Unauthorized") + http_status: int = http_client.UNAUTHORIZED + message: str = _("Unauthorized") class PaymentRequired(HTTPClientError): @@ -179,8 +192,8 @@ class PaymentRequired(HTTPClientError): Reserved for future use. """ - http_status = http_client.PAYMENT_REQUIRED - message = _("Payment Required") + http_status: int = http_client.PAYMENT_REQUIRED + message: str = _("Payment Required") class Forbidden(HTTPClientError): @@ -189,8 +202,8 @@ class Forbidden(HTTPClientError): The request was a valid request, but the server is refusing to respond to it. """ - http_status = http_client.FORBIDDEN - message = _("Forbidden") + http_status: int = http_client.FORBIDDEN + message: str = _("Forbidden") class NotFound(HTTPClientError): @@ -199,8 +212,8 @@ class NotFound(HTTPClientError): The requested resource could not be found but may be available again in the future. """ - http_status = http_client.NOT_FOUND - message = _("Not Found") + http_status: int = http_client.NOT_FOUND + message: str = _("Not Found") class MethodNotAllowed(HTTPClientError): @@ -209,8 +222,8 @@ class MethodNotAllowed(HTTPClientError): A request was made of a resource using a request method not supported by that resource. """ - http_status = http_client.METHOD_NOT_ALLOWED - message = _("Method Not Allowed") + http_status: int = http_client.METHOD_NOT_ALLOWED + message: str = _("Method Not Allowed") class NotAcceptable(HTTPClientError): @@ -219,8 +232,8 @@ class NotAcceptable(HTTPClientError): The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request. """ - http_status = http_client.NOT_ACCEPTABLE - message = _("Not Acceptable") + http_status: int = http_client.NOT_ACCEPTABLE + message: str = _("Not Acceptable") class ProxyAuthenticationRequired(HTTPClientError): @@ -228,8 +241,8 @@ class ProxyAuthenticationRequired(HTTPClientError): The client must first authenticate itself with the proxy. """ - http_status = http_client.PROXY_AUTHENTICATION_REQUIRED - message = _("Proxy Authentication Required") + http_status: int = http_client.PROXY_AUTHENTICATION_REQUIRED + message: str = _("Proxy Authentication Required") class RequestTimeout(HTTPClientError): @@ -237,8 +250,8 @@ class RequestTimeout(HTTPClientError): The server timed out waiting for the request. """ - http_status = http_client.REQUEST_TIMEOUT - message = _("Request Timeout") + http_status: int = http_client.REQUEST_TIMEOUT + message: str = _("Request Timeout") class Conflict(HTTPClientError): @@ -247,8 +260,8 @@ class Conflict(HTTPClientError): Indicates that the request could not be processed because of conflict in the request, such as an edit conflict. """ - http_status = http_client.CONFLICT - message = _("Conflict") + http_status: int = http_client.CONFLICT + message: str = _("Conflict") class Gone(HTTPClientError): @@ -257,8 +270,8 @@ class Gone(HTTPClientError): Indicates that the resource requested is no longer available and will not be available again. """ - http_status = http_client.GONE - message = _("Gone") + http_status: int = http_client.GONE + message: str = _("Gone") class LengthRequired(HTTPClientError): @@ -267,8 +280,8 @@ class LengthRequired(HTTPClientError): The request did not specify the length of its content, which is required by the requested resource. """ - http_status = http_client.LENGTH_REQUIRED - message = _("Length Required") + http_status: int = http_client.LENGTH_REQUIRED + message: str = _("Length Required") class PreconditionFailed(HTTPClientError): @@ -277,8 +290,8 @@ class PreconditionFailed(HTTPClientError): The server does not meet one of the preconditions that the requester put on the request. """ - http_status = http_client.PRECONDITION_FAILED - message = _("Precondition Failed") + http_status: int = http_client.PRECONDITION_FAILED + message: str = _("Precondition Failed") class RequestEntityTooLarge(HTTPClientError): @@ -286,10 +299,10 @@ class RequestEntityTooLarge(HTTPClientError): The request is larger than the server is willing or able to process. """ - http_status = http_client.REQUEST_ENTITY_TOO_LARGE - message = _("Request Entity Too Large") + http_status: int = http_client.REQUEST_ENTITY_TOO_LARGE + message: str = _("Request Entity Too Large") - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: try: self.retry_after = int(kwargs.pop('retry_after')) except (KeyError, ValueError): @@ -303,8 +316,8 @@ class RequestUriTooLong(HTTPClientError): The URI provided was too long for the server to process. """ - http_status = http_client.REQUEST_URI_TOO_LONG - message = _("Request-URI Too Long") + http_status: int = http_client.REQUEST_URI_TOO_LONG + message: str = _("Request-URI Too Long") class UnsupportedMediaType(HTTPClientError): @@ -313,8 +326,8 @@ class UnsupportedMediaType(HTTPClientError): The request entity has a media type which the server or resource does not support. """ - http_status = http_client.UNSUPPORTED_MEDIA_TYPE - message = _("Unsupported Media Type") + http_status: int = http_client.UNSUPPORTED_MEDIA_TYPE + message: str = _("Unsupported Media Type") class RequestedRangeNotSatisfiable(HTTPClientError): @@ -323,8 +336,8 @@ class RequestedRangeNotSatisfiable(HTTPClientError): The client has asked for a portion of the file, but the server cannot supply that portion. """ - http_status = http_client.REQUESTED_RANGE_NOT_SATISFIABLE - message = _("Requested Range Not Satisfiable") + http_status: int = http_client.REQUESTED_RANGE_NOT_SATISFIABLE + message: str = _("Requested Range Not Satisfiable") class ExpectationFailed(HTTPClientError): @@ -332,8 +345,8 @@ class ExpectationFailed(HTTPClientError): The server cannot meet the requirements of the Expect request-header field. """ - http_status = http_client.EXPECTATION_FAILED - message = _("Expectation Failed") + http_status: int = http_client.EXPECTATION_FAILED + message: str = _("Expectation Failed") class UnprocessableEntity(HTTPClientError): @@ -342,8 +355,8 @@ class UnprocessableEntity(HTTPClientError): The request was well-formed but was unable to be followed due to semantic errors. """ - http_status = http_client.UNPROCESSABLE_ENTITY - message = _("Unprocessable Entity") + http_status: int = http_client.UNPROCESSABLE_ENTITY + message: str = _("Unprocessable Entity") class InternalServerError(HttpServerError): @@ -351,8 +364,8 @@ class InternalServerError(HttpServerError): A generic error message, given when no more specific message is suitable. """ - http_status = http_client.INTERNAL_SERVER_ERROR - message = _("Internal Server Error") + http_status: int = http_client.INTERNAL_SERVER_ERROR + message: str = _("Internal Server Error") # NotImplemented is a python keyword. @@ -362,8 +375,8 @@ class HttpNotImplemented(HttpServerError): The server either does not recognize the request method, or it lacks the ability to fulfill the request. """ - http_status = http_client.NOT_IMPLEMENTED - message = _("Not Implemented") + http_status: int = http_client.NOT_IMPLEMENTED + message: str = _("Not Implemented") class BadGateway(HttpServerError): @@ -372,8 +385,8 @@ class BadGateway(HttpServerError): The server was acting as a gateway or proxy and received an invalid response from the upstream server. """ - http_status = http_client.BAD_GATEWAY - message = _("Bad Gateway") + http_status: int = http_client.BAD_GATEWAY + message: str = _("Bad Gateway") class ServiceUnavailable(HttpServerError): @@ -381,8 +394,8 @@ class ServiceUnavailable(HttpServerError): The server is currently unavailable. """ - http_status = http_client.SERVICE_UNAVAILABLE - message = _("Service Unavailable") + http_status: int = http_client.SERVICE_UNAVAILABLE + message: str = _("Service Unavailable") class GatewayTimeout(HttpServerError): @@ -391,8 +404,8 @@ class GatewayTimeout(HttpServerError): The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. """ - http_status = http_client.GATEWAY_TIMEOUT - message = _("Gateway Timeout") + http_status: int = http_client.GATEWAY_TIMEOUT + message: str = _("Gateway Timeout") class HttpVersionNotSupported(HttpServerError): @@ -400,19 +413,21 @@ class HttpVersionNotSupported(HttpServerError): The server does not support the HTTP protocol version used in the request. """ - http_status = http_client.HTTP_VERSION_NOT_SUPPORTED - message = _("HTTP Version Not Supported") + http_status: int = http_client.HTTP_VERSION_NOT_SUPPORTED + message: str = _("HTTP Version Not Supported") # _code_map contains all the classes that have http_status attribute. -_code_map = dict( - (getattr(obj, 'http_status', None), obj) +_code_map: dict[int, type[HttpError]] = { + cast(int, getattr(obj, 'http_status')): cast(type[HttpError], obj) for name, obj in vars(sys.modules[__name__]).items() if inspect.isclass(obj) and getattr(obj, 'http_status', False) -) +} -def from_response(response, method, url): +def from_response( + response: requests.Response, method: str, url: str +) -> HttpError: """Returns an instance of :class:`HttpError` or subclass based on response. :param response: instance of `requests.Response` class @@ -424,7 +439,7 @@ def from_response(response, method, url): # NOTE(hdd) true for older versions of nova and cinder if not req_id: req_id = response.headers.get("x-compute-request-id") - kwargs = { + kwargs: dict[str, Any] = { "http_status": response.status_code, "response": response, "method": method, @@ -451,6 +466,7 @@ def from_response(response, method, url): elif content_type.startswith("text/"): kwargs["details"] = getattr(response, 'text', '') + cls: type[HttpError] try: cls = _code_map[response.status_code] except KeyError: diff --git a/pyproject.toml b/pyproject.toml index f3ecc31ff..b46162f4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,4 +12,5 @@ ignore_missing_imports = true files = [ "ironicclient/common/i18n.py", "ironicclient/common/filecache.py", + "ironicclient/common/apiclient/exceptions.py", ] \ No newline at end of file