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 <anandkarancompsci@gmail.com>
This commit is contained in:
Karan Anand
2026-02-22 14:52:49 -05:00
parent cf664df7c5
commit 180a4de8a9
2 changed files with 86 additions and 69 deletions

View File

@@ -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:

View File

@@ -12,4 +12,5 @@ ignore_missing_imports = true
files = [
"ironicclient/common/i18n.py",
"ironicclient/common/filecache.py",
"ironicclient/common/apiclient/exceptions.py",
]