Speed up exception handling by re-using Environments (#7143)

This commit is contained in:
Joe Gordon 2023-12-19 16:34:47 -08:00 committed by GitHub
parent 0bbe1f1717
commit eccb2b0e66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 46 additions and 24 deletions

View File

@ -15,10 +15,11 @@ EXCEPTION_RESPONSE = """<?xml version="1.0"?>
class CloudFrontException(RESTError): class CloudFrontException(RESTError):
code = 400 code = 400
extended_templates = {"cferror": EXCEPTION_RESPONSE}
env = RESTError.extended_environment(extended_templates)
def __init__(self, error_type: str, message: str, **kwargs: Any): def __init__(self, error_type: str, message: str, **kwargs: Any):
kwargs.setdefault("template", "cferror") kwargs.setdefault("template", "cferror")
self.templates["cferror"] = EXCEPTION_RESPONSE
super().__init__(error_type, message, **kwargs) super().__init__(error_type, message, **kwargs)

View File

@ -1,5 +1,5 @@
import json import json
from typing import Any, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
from jinja2 import DictLoader, Environment from jinja2 import DictLoader, Environment
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
@ -54,6 +54,7 @@ class RESTError(HTTPException):
"wrapped_single_error": WRAPPED_SINGLE_ERROR_RESPONSE, "wrapped_single_error": WRAPPED_SINGLE_ERROR_RESPONSE,
"error": ERROR_RESPONSE, "error": ERROR_RESPONSE,
} }
env = Environment(loader=DictLoader(templates))
def __init__( def __init__(
self, error_type: str, message: str, template: str = "error", **kwargs: Any self, error_type: str, message: str, template: str = "error", **kwargs: Any
@ -62,9 +63,8 @@ class RESTError(HTTPException):
self.error_type = error_type self.error_type = error_type
self.message = message self.message = message
if template in self.templates.keys(): if template in self.env.list_templates():
env = Environment(loader=DictLoader(self.templates)) self.description: str = self.__class__.env.get_template(template).render(
self.description: str = env.get_template(template).render(
error_type=error_type, error_type=error_type,
message=message, message=message,
request_id_tag=self.request_id_tag_name, request_id_tag=self.request_id_tag_name,
@ -95,6 +95,13 @@ class RESTError(HTTPException):
err.code = self.code err.code = self.code
return err return err
@classmethod
def extended_environment(cls, extended_templates: Dict[str, str]) -> Environment:
# Can be simplified to cls.templates | extended_templates when we drop Python 3.8 support
# https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
templates = dict(cls.templates.items() | extended_templates.items())
return Environment(loader=DictLoader(templates))
class DryRunClientError(RESTError): class DryRunClientError(RESTError):
code = 412 code = 412

View File

@ -22,10 +22,11 @@ class EC2ClientError(RESTError):
code = 400 code = 400
# EC2 uses <RequestID> as tag name in the XML response # EC2 uses <RequestID> as tag name in the XML response
request_id_tag_name = "RequestID" request_id_tag_name = "RequestID"
extended_templates = {"custom_response": EC2_ERROR_RESPONSE}
env = RESTError.extended_environment(extended_templates)
def __init__(self, *args: Any, **kwargs: Any): def __init__(self, *args: Any, **kwargs: Any):
kwargs.setdefault("template", "custom_response") kwargs.setdefault("template", "custom_response")
self.templates["custom_response"] = EC2_ERROR_RESPONSE
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

@ -16,10 +16,11 @@ EXCEPTION_RESPONSE = """<?xml version="1.0"?>
class ElastiCacheException(RESTError): class ElastiCacheException(RESTError):
code = 400 code = 400
extended_templates = {"ecerror": EXCEPTION_RESPONSE}
env = RESTError.extended_environment(extended_templates)
def __init__(self, code: str, message: str, **kwargs: Any): def __init__(self, code: str, message: str, **kwargs: Any):
kwargs.setdefault("template", "ecerror") kwargs.setdefault("template", "ecerror")
self.templates["ecerror"] = EXCEPTION_RESPONSE
super().__init__(code, message) super().__init__(code, message)

View File

@ -16,10 +16,11 @@ EXCEPTION_RESPONSE = """<?xml version="1.0"?>
class ElasticBeanstalkException(RESTError): class ElasticBeanstalkException(RESTError):
code = 400 code = 400
extended_templates = {"ecerror": EXCEPTION_RESPONSE}
env = RESTError.extended_environment(extended_templates)
def __init__(self, code: str, message: str, **kwargs: Any): def __init__(self, code: str, message: str, **kwargs: Any):
kwargs.setdefault("template", "ecerror") kwargs.setdefault("template", "ecerror")
self.templates["ecerror"] = EXCEPTION_RESPONSE
super().__init__(code, message) super().__init__(code, message)

View File

@ -41,9 +41,19 @@ class S3ClientError(RESTError):
# S3 API uses <RequestID> as the XML tag in response messages # S3 API uses <RequestID> as the XML tag in response messages
request_id_tag_name = "RequestID" request_id_tag_name = "RequestID"
extended_templates = {
"bucket_error": ERROR_WITH_BUCKET_NAME,
"key_error": ERROR_WITH_KEY_NAME,
"argument_error": ERROR_WITH_ARGUMENT,
"error_uploadid": ERROR_WITH_UPLOADID,
"condition_error": ERROR_WITH_CONDITION_NAME,
"range_error": ERROR_WITH_RANGE,
"storage_error": ERROR_WITH_STORAGE_CLASS,
}
env = RESTError.extended_environment(extended_templates)
def __init__(self, *args: Any, **kwargs: Any): def __init__(self, *args: Any, **kwargs: Any):
kwargs.setdefault("template", "single_error") kwargs.setdefault("template", "single_error")
self.templates["bucket_error"] = ERROR_WITH_BUCKET_NAME
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -54,7 +64,6 @@ class InvalidArgumentError(S3ClientError):
kwargs.setdefault("template", "argument_error") kwargs.setdefault("template", "argument_error")
kwargs["name"] = name kwargs["name"] = name
kwargs["value"] = value kwargs["value"] = value
self.templates["argument_error"] = ERROR_WITH_ARGUMENT
super().__init__("InvalidArgument", message, *args, **kwargs) super().__init__("InvalidArgument", message, *args, **kwargs)
@ -75,7 +84,6 @@ class BadRequest(S3ClientError):
class BucketError(S3ClientError): class BucketError(S3ClientError):
def __init__(self, *args: Any, **kwargs: Any): def __init__(self, *args: Any, **kwargs: Any):
kwargs.setdefault("template", "bucket_error") kwargs.setdefault("template", "bucket_error")
self.templates["bucket_error"] = ERROR_WITH_BUCKET_NAME
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -84,7 +92,6 @@ class BucketAlreadyExists(BucketError):
def __init__(self, *args: Any, **kwargs: Any): def __init__(self, *args: Any, **kwargs: Any):
kwargs.setdefault("template", "bucket_error") kwargs.setdefault("template", "bucket_error")
self.templates["bucket_error"] = ERROR_WITH_BUCKET_NAME
super().__init__( super().__init__(
"BucketAlreadyExists", "BucketAlreadyExists",
( (
@ -111,7 +118,6 @@ class MissingKey(S3ClientError):
def __init__(self, **kwargs: Any): def __init__(self, **kwargs: Any):
kwargs.setdefault("template", "key_error") kwargs.setdefault("template", "key_error")
self.templates["key_error"] = ERROR_WITH_KEY_NAME
super().__init__("NoSuchKey", "The specified key does not exist.", **kwargs) super().__init__("NoSuchKey", "The specified key does not exist.", **kwargs)
@ -129,7 +135,6 @@ class InvalidVersion(S3ClientError):
kwargs.setdefault("template", "argument_error") kwargs.setdefault("template", "argument_error")
kwargs["name"] = "versionId" kwargs["name"] = "versionId"
kwargs["value"] = version_id kwargs["value"] = version_id
self.templates["argument_error"] = ERROR_WITH_ARGUMENT
super().__init__( super().__init__(
"InvalidArgument", "Invalid version id specified", *args, **kwargs "InvalidArgument", "Invalid version id specified", *args, **kwargs
) )
@ -436,7 +441,6 @@ class NoSuchUpload(S3ClientError):
def __init__(self, upload_id: Union[int, str], *args: Any, **kwargs: Any): def __init__(self, upload_id: Union[int, str], *args: Any, **kwargs: Any):
kwargs.setdefault("template", "error_uploadid") kwargs.setdefault("template", "error_uploadid")
kwargs["upload_id"] = upload_id kwargs["upload_id"] = upload_id
self.templates["error_uploadid"] = ERROR_WITH_UPLOADID
super().__init__( super().__init__(
"NoSuchUpload", "NoSuchUpload",
"The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.", "The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.",
@ -450,7 +454,6 @@ class PreconditionFailed(S3ClientError):
def __init__(self, failed_condition: str, **kwargs: Any): def __init__(self, failed_condition: str, **kwargs: Any):
kwargs.setdefault("template", "condition_error") kwargs.setdefault("template", "condition_error")
self.templates["condition_error"] = ERROR_WITH_CONDITION_NAME
super().__init__( super().__init__(
"PreconditionFailed", "PreconditionFailed",
"At least one of the pre-conditions you specified did not hold", "At least one of the pre-conditions you specified did not hold",
@ -464,7 +467,6 @@ class InvalidRange(S3ClientError):
def __init__(self, range_requested: str, actual_size: str, **kwargs: Any): def __init__(self, range_requested: str, actual_size: str, **kwargs: Any):
kwargs.setdefault("template", "range_error") kwargs.setdefault("template", "range_error")
self.templates["range_error"] = ERROR_WITH_RANGE
super().__init__( super().__init__(
"InvalidRange", "InvalidRange",
"The requested range is not satisfiable", "The requested range is not satisfiable",
@ -488,7 +490,6 @@ class InvalidObjectState(BucketError):
def __init__(self, storage_class: Optional[str], **kwargs: Any): def __init__(self, storage_class: Optional[str], **kwargs: Any):
kwargs.setdefault("template", "storage_error") kwargs.setdefault("template", "storage_error")
self.templates["storage_error"] = ERROR_WITH_STORAGE_CLASS
super().__init__( super().__init__(
error_type="InvalidObjectState", error_type="InvalidObjectState",
message="The operation is not valid for the object's storage class", message="The operation is not valid for the object's storage class",

View File

@ -13,6 +13,12 @@ ERROR_WITH_ACCESS_POINT_POLICY = """{% extends 'wrapped_single_error' %}
class S3ControlError(RESTError): class S3ControlError(RESTError):
extended_templates = {
"ap_not_found": ERROR_WITH_ACCESS_POINT_NAME,
"apf_not_found": ERROR_WITH_ACCESS_POINT_POLICY,
}
env = RESTError.extended_environment(extended_templates)
def __init__(self, *args: Any, **kwargs: Any): def __init__(self, *args: Any, **kwargs: Any):
kwargs.setdefault("template", "single_error") kwargs.setdefault("template", "single_error")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -24,7 +30,6 @@ class AccessPointNotFound(S3ControlError):
def __init__(self, name: str, **kwargs: Any): def __init__(self, name: str, **kwargs: Any):
kwargs.setdefault("template", "ap_not_found") kwargs.setdefault("template", "ap_not_found")
kwargs["name"] = name kwargs["name"] = name
self.templates["ap_not_found"] = ERROR_WITH_ACCESS_POINT_NAME
super().__init__( super().__init__(
"NoSuchAccessPoint", "The specified accesspoint does not exist", **kwargs "NoSuchAccessPoint", "The specified accesspoint does not exist", **kwargs
) )
@ -36,7 +41,6 @@ class AccessPointPolicyNotFound(S3ControlError):
def __init__(self, name: str, **kwargs: Any): def __init__(self, name: str, **kwargs: Any):
kwargs.setdefault("template", "apf_not_found") kwargs.setdefault("template", "apf_not_found")
kwargs["name"] = name kwargs["name"] = name
self.templates["apf_not_found"] = ERROR_WITH_ACCESS_POINT_POLICY
super().__init__( super().__init__(
"NoSuchAccessPointPolicy", "NoSuchAccessPointPolicy",
"The specified accesspoint policy does not exist", "The specified accesspoint policy does not exist",

View File

@ -8,16 +8,20 @@ ERROR_WITH_MODEL_NAME = """{% extends 'single_error' %}
class SagemakerClientError(RESTError): class SagemakerClientError(RESTError):
extended_templates = {"model_error": ERROR_WITH_MODEL_NAME}
env = RESTError.extended_environment(extended_templates)
def __init__(self, *args: Any, **kwargs: Any): def __init__(self, *args: Any, **kwargs: Any):
kwargs.setdefault("template", "single_error") kwargs.setdefault("template", "single_error")
self.templates["model_error"] = ERROR_WITH_MODEL_NAME
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
class ModelError(RESTError): class ModelError(RESTError):
extended_templates = {"model_error": ERROR_WITH_MODEL_NAME}
env = RESTError.extended_environment(extended_templates)
def __init__(self, *args: Any, **kwargs: Any): def __init__(self, *args: Any, **kwargs: Any):
kwargs.setdefault("template", "model_error") kwargs.setdefault("template", "model_error")
self.templates["model_error"] = ERROR_WITH_MODEL_NAME
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

@ -18,10 +18,11 @@ SDB_ERROR = """<?xml version="1.0"?>
class InvalidParameterError(RESTError): class InvalidParameterError(RESTError):
code = 400 code = 400
extended_templates = {"sdb_error": SDB_ERROR}
env = RESTError.extended_environment(extended_templates)
def __init__(self, **kwargs: Any): def __init__(self, **kwargs: Any):
kwargs.setdefault("template", "sdb_error") kwargs.setdefault("template", "sdb_error")
self.templates["sdb_error"] = SDB_ERROR
kwargs["error_type"] = "InvalidParameterValue" kwargs["error_type"] = "InvalidParameterValue"
super().__init__(**kwargs) super().__init__(**kwargs)
@ -37,10 +38,11 @@ class InvalidDomainName(InvalidParameterError):
class UnknownDomainName(RESTError): class UnknownDomainName(RESTError):
code = 400 code = 400
extended_templates = {"sdb_error": SDB_ERROR}
env = RESTError.extended_environment(extended_templates)
def __init__(self, **kwargs: Any): def __init__(self, **kwargs: Any):
kwargs.setdefault("template", "sdb_error") kwargs.setdefault("template", "sdb_error")
self.templates["sdb_error"] = SDB_ERROR
kwargs["error_type"] = "NoSuchDomain" kwargs["error_type"] = "NoSuchDomain"
kwargs["message"] = "The specified domain does not exist." kwargs["message"] = "The specified domain does not exist."
super().__init__(**kwargs) super().__init__(**kwargs)