Techdebt: MyPy SES (#6252)
This commit is contained in:
parent
fe7b15c9f9
commit
65611082be
@ -4,98 +4,98 @@ from moto.core.exceptions import RESTError
|
|||||||
class MessageRejectedError(RESTError):
|
class MessageRejectedError(RESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message: str):
|
||||||
super().__init__("MessageRejected", message)
|
super().__init__("MessageRejected", message)
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationSetDoesNotExist(RESTError):
|
class ConfigurationSetDoesNotExist(RESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message: str):
|
||||||
super().__init__("ConfigurationSetDoesNotExist", message)
|
super().__init__("ConfigurationSetDoesNotExist", message)
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationSetAlreadyExists(RESTError):
|
class ConfigurationSetAlreadyExists(RESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message: str):
|
||||||
super().__init__("ConfigurationSetAlreadyExists", message)
|
super().__init__("ConfigurationSetAlreadyExists", message)
|
||||||
|
|
||||||
|
|
||||||
class EventDestinationAlreadyExists(RESTError):
|
class EventDestinationAlreadyExists(RESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message: str):
|
||||||
super().__init__("EventDestinationAlreadyExists", message)
|
super().__init__("EventDestinationAlreadyExists", message)
|
||||||
|
|
||||||
|
|
||||||
class TemplateNameAlreadyExists(RESTError):
|
class TemplateNameAlreadyExists(RESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message: str):
|
||||||
super().__init__("TemplateNameAlreadyExists", message)
|
super().__init__("TemplateNameAlreadyExists", message)
|
||||||
|
|
||||||
|
|
||||||
class ValidationError(RESTError):
|
class ValidationError(RESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message: str):
|
||||||
super().__init__("ValidationError", message)
|
super().__init__("ValidationError", message)
|
||||||
|
|
||||||
|
|
||||||
class InvalidParameterValue(RESTError):
|
class InvalidParameterValue(RESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message: str):
|
||||||
super().__init__("InvalidParameterValue", message)
|
super().__init__("InvalidParameterValue", message)
|
||||||
|
|
||||||
|
|
||||||
class InvalidRenderingParameterException:
|
class InvalidRenderingParameterException(RESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message: str):
|
||||||
super().__init__("InvalidRenderingParameterException", message)
|
super().__init__("InvalidRenderingParameterException", message)
|
||||||
|
|
||||||
|
|
||||||
class TemplateDoesNotExist(RESTError):
|
class TemplateDoesNotExist(RESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message: str):
|
||||||
super().__init__("TemplateDoesNotExist", message)
|
super().__init__("TemplateDoesNotExist", message)
|
||||||
|
|
||||||
|
|
||||||
class RuleSetNameAlreadyExists(RESTError):
|
class RuleSetNameAlreadyExists(RESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message: str):
|
||||||
super().__init__("RuleSetNameAlreadyExists", message)
|
super().__init__("RuleSetNameAlreadyExists", message)
|
||||||
|
|
||||||
|
|
||||||
class RuleAlreadyExists(RESTError):
|
class RuleAlreadyExists(RESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message: str):
|
||||||
super().__init__("RuleAlreadyExists", message)
|
super().__init__("RuleAlreadyExists", message)
|
||||||
|
|
||||||
|
|
||||||
class RuleSetDoesNotExist(RESTError):
|
class RuleSetDoesNotExist(RESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message: str):
|
||||||
super().__init__("RuleSetDoesNotExist", message)
|
super().__init__("RuleSetDoesNotExist", message)
|
||||||
|
|
||||||
|
|
||||||
class RuleDoesNotExist(RESTError):
|
class RuleDoesNotExist(RESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message: str):
|
||||||
super().__init__("RuleDoesNotExist", message)
|
super().__init__("RuleDoesNotExist", message)
|
||||||
|
|
||||||
|
|
||||||
class MissingRenderingAttributeException(RESTError):
|
class MissingRenderingAttributeException(RESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
def __init__(self, var):
|
def __init__(self, var: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"MissingRenderingAttributeException",
|
"MissingRenderingAttributeException",
|
||||||
f"Attribute '{var}' is not present in the rendering data.",
|
f"Attribute '{var}' is not present in the rendering data.",
|
||||||
|
@ -5,7 +5,7 @@ from email.mime.base import MIMEBase
|
|||||||
from email.utils import parseaddr
|
from email.utils import parseaddr
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.encoders import encode_7or8bit
|
from email.encoders import encode_7or8bit
|
||||||
from typing import Mapping
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from moto.core import BaseBackend, BackendDict, BaseModel
|
from moto.core import BaseBackend, BackendDict, BaseModel
|
||||||
from moto.sns.models import sns_backends
|
from moto.sns.models import sns_backends
|
||||||
@ -48,8 +48,8 @@ class SESFeedback(BaseModel):
|
|||||||
FORWARDING_ENABLED = "feedback_forwarding_enabled"
|
FORWARDING_ENABLED = "feedback_forwarding_enabled"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_message(account_id, msg_type):
|
def generate_message(account_id: str, msg_type: str) -> Dict[str, Any]: # type: ignore[misc]
|
||||||
msg = dict(COMMON_MAIL)
|
msg: Dict[str, Any] = dict(COMMON_MAIL)
|
||||||
msg["mail"]["sendingAccountId"] = account_id
|
msg["mail"]["sendingAccountId"] = account_id
|
||||||
if msg_type == SESFeedback.BOUNCE:
|
if msg_type == SESFeedback.BOUNCE:
|
||||||
msg["bounce"] = BOUNCE
|
msg["bounce"] = BOUNCE
|
||||||
@ -62,7 +62,14 @@ class SESFeedback(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Message(BaseModel):
|
class Message(BaseModel):
|
||||||
def __init__(self, message_id, source, subject, body, destinations):
|
def __init__(
|
||||||
|
self,
|
||||||
|
message_id: str,
|
||||||
|
source: str,
|
||||||
|
subject: str,
|
||||||
|
body: str,
|
||||||
|
destinations: Dict[str, List[str]],
|
||||||
|
):
|
||||||
self.id = message_id
|
self.id = message_id
|
||||||
self.source = source
|
self.source = source
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
@ -71,7 +78,14 @@ class Message(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class TemplateMessage(BaseModel):
|
class TemplateMessage(BaseModel):
|
||||||
def __init__(self, message_id, source, template, template_data, destinations):
|
def __init__(
|
||||||
|
self,
|
||||||
|
message_id: str,
|
||||||
|
source: str,
|
||||||
|
template: List[str],
|
||||||
|
template_data: List[str],
|
||||||
|
destinations: Any,
|
||||||
|
):
|
||||||
self.id = message_id
|
self.id = message_id
|
||||||
self.source = source
|
self.source = source
|
||||||
self.template = template
|
self.template = template
|
||||||
@ -80,7 +94,14 @@ class TemplateMessage(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class BulkTemplateMessage(BaseModel):
|
class BulkTemplateMessage(BaseModel):
|
||||||
def __init__(self, message_ids, source, template, template_data, destinations):
|
def __init__(
|
||||||
|
self,
|
||||||
|
message_ids: List[str],
|
||||||
|
source: str,
|
||||||
|
template: List[str],
|
||||||
|
template_data: List[str],
|
||||||
|
destinations: Any,
|
||||||
|
):
|
||||||
self.ids = message_ids
|
self.ids = message_ids
|
||||||
self.source = source
|
self.source = source
|
||||||
self.template = template
|
self.template = template
|
||||||
@ -89,7 +110,9 @@ class BulkTemplateMessage(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class RawMessage(BaseModel):
|
class RawMessage(BaseModel):
|
||||||
def __init__(self, message_id, source, destinations, raw_data):
|
def __init__(
|
||||||
|
self, message_id: str, source: str, destinations: List[str], raw_data: str
|
||||||
|
):
|
||||||
self.id = message_id
|
self.id = message_id
|
||||||
self.source = source
|
self.source = source
|
||||||
self.destinations = destinations
|
self.destinations = destinations
|
||||||
@ -97,11 +120,11 @@ class RawMessage(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class SESQuota(BaseModel):
|
class SESQuota(BaseModel):
|
||||||
def __init__(self, sent):
|
def __init__(self, sent: int):
|
||||||
self.sent = sent
|
self.sent = sent
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sent_past_24(self):
|
def sent_past_24(self) -> int:
|
||||||
return self.sent
|
return self.sent
|
||||||
|
|
||||||
|
|
||||||
@ -121,23 +144,23 @@ class SESBackend(BaseBackend):
|
|||||||
Note that, as this is an internal API, the exact format may differ per versions.
|
Note that, as this is an internal API, the exact format may differ per versions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, region_name, account_id):
|
def __init__(self, region_name: str, account_id: str):
|
||||||
super().__init__(region_name, account_id)
|
super().__init__(region_name, account_id)
|
||||||
self.addresses = []
|
self.addresses: List[str] = []
|
||||||
self.email_addresses = []
|
self.email_addresses: List[str] = []
|
||||||
self.domains = []
|
self.domains: List[str] = []
|
||||||
self.sent_messages = []
|
self.sent_messages: List[Any] = []
|
||||||
self.sent_message_count = 0
|
self.sent_message_count = 0
|
||||||
self.rejected_messages_count = 0
|
self.rejected_messages_count = 0
|
||||||
self.sns_topics = {}
|
self.sns_topics: Dict[str, Dict[str, Any]] = {}
|
||||||
self.config_set = {}
|
self.config_set: Dict[str, int] = {}
|
||||||
self.config_set_event_destination = {}
|
self.config_set_event_destination: Dict[str, Dict[str, Any]] = {}
|
||||||
self.event_destinations = {}
|
self.event_destinations: Dict[str, int] = {}
|
||||||
self.identity_mail_from_domains = {}
|
self.identity_mail_from_domains: Dict[str, Dict[str, Any]] = {}
|
||||||
self.templates = {}
|
self.templates: Dict[str, Dict[str, str]] = {}
|
||||||
self.receipt_rule_set = {}
|
self.receipt_rule_set: Dict[str, List[Dict[str, Any]]] = {}
|
||||||
|
|
||||||
def _is_verified_address(self, source):
|
def _is_verified_address(self, source: str) -> bool:
|
||||||
_, address = parseaddr(source)
|
_, address = parseaddr(source)
|
||||||
if address in self.addresses:
|
if address in self.addresses:
|
||||||
return True
|
return True
|
||||||
@ -146,32 +169,34 @@ class SESBackend(BaseBackend):
|
|||||||
_, host = address.split("@", 1)
|
_, host = address.split("@", 1)
|
||||||
return host in self.domains
|
return host in self.domains
|
||||||
|
|
||||||
def verify_email_identity(self, address):
|
def verify_email_identity(self, address: str) -> None:
|
||||||
_, address = parseaddr(address)
|
_, address = parseaddr(address)
|
||||||
if address not in self.addresses:
|
if address not in self.addresses:
|
||||||
self.addresses.append(address)
|
self.addresses.append(address)
|
||||||
|
|
||||||
def verify_email_address(self, address):
|
def verify_email_address(self, address: str) -> None:
|
||||||
_, address = parseaddr(address)
|
_, address = parseaddr(address)
|
||||||
self.email_addresses.append(address)
|
self.email_addresses.append(address)
|
||||||
|
|
||||||
def verify_domain(self, domain):
|
def verify_domain(self, domain: str) -> None:
|
||||||
if domain.lower() not in self.domains:
|
if domain.lower() not in self.domains:
|
||||||
self.domains.append(domain.lower())
|
self.domains.append(domain.lower())
|
||||||
|
|
||||||
def list_identities(self):
|
def list_identities(self) -> List[str]:
|
||||||
return self.domains + self.addresses
|
return self.domains + self.addresses
|
||||||
|
|
||||||
def list_verified_email_addresses(self):
|
def list_verified_email_addresses(self) -> List[str]:
|
||||||
return self.email_addresses
|
return self.email_addresses
|
||||||
|
|
||||||
def delete_identity(self, identity):
|
def delete_identity(self, identity: str) -> None:
|
||||||
if "@" in identity:
|
if "@" in identity:
|
||||||
self.addresses.remove(identity)
|
self.addresses.remove(identity)
|
||||||
else:
|
else:
|
||||||
self.domains.remove(identity)
|
self.domains.remove(identity)
|
||||||
|
|
||||||
def send_email(self, source, subject, body, destinations, region):
|
def send_email(
|
||||||
|
self, source: str, subject: str, body: str, destinations: Dict[str, List[str]]
|
||||||
|
) -> Message:
|
||||||
recipient_count = sum(map(len, destinations.values()))
|
recipient_count = sum(map(len, destinations.values()))
|
||||||
if recipient_count > RECIPIENT_LIMIT:
|
if recipient_count > RECIPIENT_LIMIT:
|
||||||
raise MessageRejectedError("Too many recipients.")
|
raise MessageRejectedError("Too many recipients.")
|
||||||
@ -182,11 +207,11 @@ class SESBackend(BaseBackend):
|
|||||||
address for addresses in destinations.values() for address in addresses
|
address for addresses in destinations.values() for address in addresses
|
||||||
]
|
]
|
||||||
for address in [source, *destination_addresses]:
|
for address in [source, *destination_addresses]:
|
||||||
valid, msg = is_valid_address(address)
|
msg = is_valid_address(address)
|
||||||
if not valid:
|
if msg is not None:
|
||||||
raise InvalidParameterValue(msg)
|
raise InvalidParameterValue(msg)
|
||||||
|
|
||||||
self.__process_sns_feedback__(source, destinations, region)
|
self.__process_sns_feedback__(source, destinations)
|
||||||
|
|
||||||
message_id = get_random_message_id()
|
message_id = get_random_message_id()
|
||||||
message = Message(message_id, source, subject, body, destinations)
|
message = Message(message_id, source, subject, body, destinations)
|
||||||
@ -195,14 +220,18 @@ class SESBackend(BaseBackend):
|
|||||||
return message
|
return message
|
||||||
|
|
||||||
def send_bulk_templated_email(
|
def send_bulk_templated_email(
|
||||||
self, source, template, template_data, destinations, region
|
self,
|
||||||
):
|
source: str,
|
||||||
|
template: List[str],
|
||||||
|
template_data: List[str],
|
||||||
|
destinations: List[Dict[str, Dict[str, List[str]]]],
|
||||||
|
) -> BulkTemplateMessage:
|
||||||
recipient_count = len(destinations)
|
recipient_count = len(destinations)
|
||||||
if recipient_count > RECIPIENT_LIMIT:
|
if recipient_count > RECIPIENT_LIMIT:
|
||||||
raise MessageRejectedError("Too many destinations.")
|
raise MessageRejectedError("Too many destinations.")
|
||||||
|
|
||||||
total_recipient_count = sum(
|
total_recipient_count = sum(
|
||||||
map(lambda d: sum(map(len, d["Destination"].values())), destinations)
|
map(lambda d: sum(map(len, d["Destination"].values())), destinations) # type: ignore
|
||||||
)
|
)
|
||||||
if total_recipient_count > RECIPIENT_LIMIT:
|
if total_recipient_count > RECIPIENT_LIMIT:
|
||||||
raise MessageRejectedError("Too many destinations.")
|
raise MessageRejectedError("Too many destinations.")
|
||||||
@ -214,7 +243,7 @@ class SESBackend(BaseBackend):
|
|||||||
if not self.templates.get(template[0]):
|
if not self.templates.get(template[0]):
|
||||||
raise TemplateDoesNotExist(f"Template ({template[0]}) does not exist")
|
raise TemplateDoesNotExist(f"Template ({template[0]}) does not exist")
|
||||||
|
|
||||||
self.__process_sns_feedback__(source, destinations, region)
|
self.__process_sns_feedback__(source, destinations)
|
||||||
|
|
||||||
message_id = get_random_message_id()
|
message_id = get_random_message_id()
|
||||||
message = TemplateMessage(
|
message = TemplateMessage(
|
||||||
@ -227,8 +256,12 @@ class SESBackend(BaseBackend):
|
|||||||
return BulkTemplateMessage(ids, source, template, template_data, destinations)
|
return BulkTemplateMessage(ids, source, template, template_data, destinations)
|
||||||
|
|
||||||
def send_templated_email(
|
def send_templated_email(
|
||||||
self, source, template, template_data, destinations, region
|
self,
|
||||||
):
|
source: str,
|
||||||
|
template: List[str],
|
||||||
|
template_data: List[str],
|
||||||
|
destinations: Dict[str, List[str]],
|
||||||
|
) -> TemplateMessage:
|
||||||
recipient_count = sum(map(len, destinations.values()))
|
recipient_count = sum(map(len, destinations.values()))
|
||||||
if recipient_count > RECIPIENT_LIMIT:
|
if recipient_count > RECIPIENT_LIMIT:
|
||||||
raise MessageRejectedError("Too many recipients.")
|
raise MessageRejectedError("Too many recipients.")
|
||||||
@ -239,14 +272,14 @@ class SESBackend(BaseBackend):
|
|||||||
address for addresses in destinations.values() for address in addresses
|
address for addresses in destinations.values() for address in addresses
|
||||||
]
|
]
|
||||||
for address in [source, *destination_addresses]:
|
for address in [source, *destination_addresses]:
|
||||||
valid, msg = is_valid_address(address)
|
msg = is_valid_address(address)
|
||||||
if not valid:
|
if msg is not None:
|
||||||
raise InvalidParameterValue(msg)
|
raise InvalidParameterValue(msg)
|
||||||
|
|
||||||
if not self.templates.get(template[0]):
|
if not self.templates.get(template[0]):
|
||||||
raise TemplateDoesNotExist(f"Template ({template[0]}) does not exist")
|
raise TemplateDoesNotExist(f"Template ({template[0]}) does not exist")
|
||||||
|
|
||||||
self.__process_sns_feedback__(source, destinations, region)
|
self.__process_sns_feedback__(source, destinations)
|
||||||
|
|
||||||
message_id = get_random_message_id()
|
message_id = get_random_message_id()
|
||||||
message = TemplateMessage(
|
message = TemplateMessage(
|
||||||
@ -256,7 +289,7 @@ class SESBackend(BaseBackend):
|
|||||||
self.sent_message_count += recipient_count
|
self.sent_message_count += recipient_count
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def __type_of_message__(self, destinations):
|
def __type_of_message__(self, destinations: Any) -> Optional[str]:
|
||||||
"""Checks the destination for any special address that could indicate delivery,
|
"""Checks the destination for any special address that could indicate delivery,
|
||||||
complaint or bounce like in SES simulator"""
|
complaint or bounce like in SES simulator"""
|
||||||
if isinstance(destinations, list):
|
if isinstance(destinations, list):
|
||||||
@ -278,11 +311,11 @@ class SESBackend(BaseBackend):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __generate_feedback__(self, msg_type):
|
def __generate_feedback__(self, msg_type: str) -> Dict[str, Any]:
|
||||||
"""Generates the SNS message for the feedback"""
|
"""Generates the SNS message for the feedback"""
|
||||||
return SESFeedback.generate_message(self.account_id, msg_type)
|
return SESFeedback.generate_message(self.account_id, msg_type)
|
||||||
|
|
||||||
def __process_sns_feedback__(self, source, destinations, region):
|
def __process_sns_feedback__(self, source: str, destinations: Any) -> None:
|
||||||
domain = str(source)
|
domain = str(source)
|
||||||
if "@" in domain:
|
if "@" in domain:
|
||||||
domain = domain.split("@")[1]
|
domain = domain.split("@")[1]
|
||||||
@ -293,11 +326,13 @@ class SESBackend(BaseBackend):
|
|||||||
if sns_topic is not None:
|
if sns_topic is not None:
|
||||||
message = self.__generate_feedback__(msg_type)
|
message = self.__generate_feedback__(msg_type)
|
||||||
if message:
|
if message:
|
||||||
sns_backends[self.account_id][region].publish(
|
sns_backends[self.account_id][self.region_name].publish(
|
||||||
message, arn=sns_topic
|
message, arn=sns_topic
|
||||||
)
|
)
|
||||||
|
|
||||||
def send_raw_email(self, source, destinations, raw_data, region):
|
def send_raw_email(
|
||||||
|
self, source: str, destinations: List[str], raw_data: str
|
||||||
|
) -> RawMessage:
|
||||||
if source is not None:
|
if source is not None:
|
||||||
_, source_email_address = parseaddr(source)
|
_, source_email_address = parseaddr(source)
|
||||||
if not self._is_verified_address(source_email_address):
|
if not self._is_verified_address(source_email_address):
|
||||||
@ -324,33 +359,39 @@ class SESBackend(BaseBackend):
|
|||||||
if recipient_count > RECIPIENT_LIMIT:
|
if recipient_count > RECIPIENT_LIMIT:
|
||||||
raise MessageRejectedError("Too many recipients.")
|
raise MessageRejectedError("Too many recipients.")
|
||||||
for address in [addr for addr in [source, *destinations] if addr is not None]:
|
for address in [addr for addr in [source, *destinations] if addr is not None]:
|
||||||
valid, msg = is_valid_address(address)
|
msg = is_valid_address(address)
|
||||||
if not valid:
|
if msg is not None:
|
||||||
raise InvalidParameterValue(msg)
|
raise InvalidParameterValue(msg)
|
||||||
|
|
||||||
self.__process_sns_feedback__(source, destinations, region)
|
self.__process_sns_feedback__(source, destinations)
|
||||||
|
|
||||||
self.sent_message_count += recipient_count
|
self.sent_message_count += recipient_count
|
||||||
message_id = get_random_message_id()
|
message_id = get_random_message_id()
|
||||||
message = RawMessage(message_id, source, destinations, raw_data)
|
raw_message = RawMessage(message_id, source, destinations, raw_data)
|
||||||
self.sent_messages.append(message)
|
self.sent_messages.append(raw_message)
|
||||||
return message
|
return raw_message
|
||||||
|
|
||||||
def get_send_quota(self):
|
def get_send_quota(self) -> SESQuota:
|
||||||
return SESQuota(self.sent_message_count)
|
return SESQuota(self.sent_message_count)
|
||||||
|
|
||||||
def get_identity_notification_attributes(self, identities):
|
def get_identity_notification_attributes(
|
||||||
response = {}
|
self, identities: List[str]
|
||||||
|
) -> Dict[str, Dict[str, Any]]:
|
||||||
|
response: Dict[str, Dict[str, Any]] = {}
|
||||||
for identity in identities:
|
for identity in identities:
|
||||||
response[identity] = self.sns_topics.get(identity, {})
|
response[identity] = self.sns_topics.get(identity, {})
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def set_identity_feedback_forwarding_enabled(self, identity, enabled):
|
def set_identity_feedback_forwarding_enabled(
|
||||||
|
self, identity: str, enabled: bool
|
||||||
|
) -> None:
|
||||||
identity_sns_topics = self.sns_topics.get(identity, {})
|
identity_sns_topics = self.sns_topics.get(identity, {})
|
||||||
identity_sns_topics[SESFeedback.FORWARDING_ENABLED] = enabled
|
identity_sns_topics[SESFeedback.FORWARDING_ENABLED] = enabled
|
||||||
self.sns_topics[identity] = identity_sns_topics
|
self.sns_topics[identity] = identity_sns_topics
|
||||||
|
|
||||||
def set_identity_notification_topic(self, identity, notification_type, sns_topic):
|
def set_identity_notification_topic(
|
||||||
|
self, identity: str, notification_type: str, sns_topic: Optional[str]
|
||||||
|
) -> None:
|
||||||
identity_sns_topics = self.sns_topics.get(identity, {})
|
identity_sns_topics = self.sns_topics.get(identity, {})
|
||||||
if sns_topic is None:
|
if sns_topic is None:
|
||||||
del identity_sns_topics[notification_type]
|
del identity_sns_topics[notification_type]
|
||||||
@ -359,26 +400,22 @@ class SESBackend(BaseBackend):
|
|||||||
|
|
||||||
self.sns_topics[identity] = identity_sns_topics
|
self.sns_topics[identity] = identity_sns_topics
|
||||||
|
|
||||||
return {}
|
def create_configuration_set(self, configuration_set_name: str) -> None:
|
||||||
|
|
||||||
def create_configuration_set(self, configuration_set_name):
|
|
||||||
if configuration_set_name in self.config_set:
|
if configuration_set_name in self.config_set:
|
||||||
raise ConfigurationSetAlreadyExists(
|
raise ConfigurationSetAlreadyExists(
|
||||||
f"Configuration set <{configuration_set_name}> already exists"
|
f"Configuration set <{configuration_set_name}> already exists"
|
||||||
)
|
)
|
||||||
self.config_set[configuration_set_name] = 1
|
self.config_set[configuration_set_name] = 1
|
||||||
return {}
|
|
||||||
|
|
||||||
def describe_configuration_set(self, configuration_set_name):
|
def describe_configuration_set(self, configuration_set_name: str) -> None:
|
||||||
if configuration_set_name not in self.config_set:
|
if configuration_set_name not in self.config_set:
|
||||||
raise ConfigurationSetDoesNotExist(
|
raise ConfigurationSetDoesNotExist(
|
||||||
f"Configuration set <{configuration_set_name}> does not exist"
|
f"Configuration set <{configuration_set_name}> does not exist"
|
||||||
)
|
)
|
||||||
return {}
|
|
||||||
|
|
||||||
def create_configuration_set_event_destination(
|
def create_configuration_set_event_destination(
|
||||||
self, configuration_set_name, event_destination
|
self, configuration_set_name: str, event_destination: Dict[str, Any]
|
||||||
):
|
) -> None:
|
||||||
|
|
||||||
if self.config_set.get(configuration_set_name) is None:
|
if self.config_set.get(configuration_set_name) is None:
|
||||||
raise ConfigurationSetDoesNotExist("Invalid Configuration Set Name.")
|
raise ConfigurationSetDoesNotExist("Invalid Configuration Set Name.")
|
||||||
@ -389,19 +426,16 @@ class SESBackend(BaseBackend):
|
|||||||
self.config_set_event_destination[configuration_set_name] = event_destination
|
self.config_set_event_destination[configuration_set_name] = event_destination
|
||||||
self.event_destinations[event_destination["Name"]] = 1
|
self.event_destinations[event_destination["Name"]] = 1
|
||||||
|
|
||||||
return {}
|
def get_send_statistics(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"DeliveryAttempts": self.sent_message_count,
|
||||||
|
"Rejects": self.rejected_messages_count,
|
||||||
|
"Complaints": 0,
|
||||||
|
"Bounces": 0,
|
||||||
|
"Timestamp": datetime.datetime.utcnow(),
|
||||||
|
}
|
||||||
|
|
||||||
def get_send_statistics(self):
|
def add_template(self, template_info: Dict[str, str]) -> None:
|
||||||
|
|
||||||
statistics = {}
|
|
||||||
statistics["DeliveryAttempts"] = self.sent_message_count
|
|
||||||
statistics["Rejects"] = self.rejected_messages_count
|
|
||||||
statistics["Complaints"] = 0
|
|
||||||
statistics["Bounces"] = 0
|
|
||||||
statistics["Timestamp"] = datetime.datetime.utcnow()
|
|
||||||
return statistics
|
|
||||||
|
|
||||||
def add_template(self, template_info):
|
|
||||||
template_name = template_info["template_name"]
|
template_name = template_info["template_name"]
|
||||||
if not template_name:
|
if not template_name:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
@ -418,7 +452,7 @@ class SESBackend(BaseBackend):
|
|||||||
raise InvalidParameterValue("The subject must be specified.")
|
raise InvalidParameterValue("The subject must be specified.")
|
||||||
self.templates[template_name] = template_info
|
self.templates[template_name] = template_info
|
||||||
|
|
||||||
def update_template(self, template_info):
|
def update_template(self, template_info: Dict[str, str]) -> None:
|
||||||
template_name = template_info["template_name"]
|
template_name = template_info["template_name"]
|
||||||
if not template_name:
|
if not template_name:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
@ -435,15 +469,15 @@ class SESBackend(BaseBackend):
|
|||||||
raise InvalidParameterValue("The subject must be specified.")
|
raise InvalidParameterValue("The subject must be specified.")
|
||||||
self.templates[template_name] = template_info
|
self.templates[template_name] = template_info
|
||||||
|
|
||||||
def get_template(self, template_name):
|
def get_template(self, template_name: str) -> Dict[str, str]:
|
||||||
if not self.templates.get(template_name, None):
|
if not self.templates.get(template_name, None):
|
||||||
raise TemplateDoesNotExist("Invalid Template Name.")
|
raise TemplateDoesNotExist("Invalid Template Name.")
|
||||||
return self.templates[template_name]
|
return self.templates[template_name]
|
||||||
|
|
||||||
def list_templates(self):
|
def list_templates(self) -> List[Dict[str, str]]:
|
||||||
return list(self.templates.values())
|
return list(self.templates.values())
|
||||||
|
|
||||||
def render_template(self, render_data):
|
def render_template(self, render_data: Dict[str, Any]) -> str:
|
||||||
template_name = render_data.get("name", "")
|
template_name = render_data.get("name", "")
|
||||||
template = self.templates.get(template_name, None)
|
template = self.templates.get(template_name, None)
|
||||||
if not template:
|
if not template:
|
||||||
@ -451,7 +485,7 @@ class SESBackend(BaseBackend):
|
|||||||
|
|
||||||
template_data = render_data.get("data")
|
template_data = render_data.get("data")
|
||||||
try:
|
try:
|
||||||
template_data = json.loads(template_data)
|
template_data = json.loads(template_data) # type: ignore
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise InvalidRenderingParameterException(
|
raise InvalidRenderingParameterException(
|
||||||
"Template rendering data is invalid"
|
"Template rendering data is invalid"
|
||||||
@ -484,12 +518,12 @@ class SESBackend(BaseBackend):
|
|||||||
)
|
)
|
||||||
return rendered_template
|
return rendered_template
|
||||||
|
|
||||||
def create_receipt_rule_set(self, rule_set_name):
|
def create_receipt_rule_set(self, rule_set_name: str) -> None:
|
||||||
if self.receipt_rule_set.get(rule_set_name) is not None:
|
if self.receipt_rule_set.get(rule_set_name) is not None:
|
||||||
raise RuleSetNameAlreadyExists("Duplicate Receipt Rule Set Name.")
|
raise RuleSetNameAlreadyExists("Duplicate Receipt Rule Set Name.")
|
||||||
self.receipt_rule_set[rule_set_name] = []
|
self.receipt_rule_set[rule_set_name] = []
|
||||||
|
|
||||||
def create_receipt_rule(self, rule_set_name, rule):
|
def create_receipt_rule(self, rule_set_name: str, rule: Dict[str, Any]) -> None:
|
||||||
rule_set = self.receipt_rule_set.get(rule_set_name)
|
rule_set = self.receipt_rule_set.get(rule_set_name)
|
||||||
if rule_set is None:
|
if rule_set is None:
|
||||||
raise RuleSetDoesNotExist("Invalid Rule Set Name.")
|
raise RuleSetDoesNotExist("Invalid Rule Set Name.")
|
||||||
@ -498,7 +532,7 @@ class SESBackend(BaseBackend):
|
|||||||
rule_set.append(rule)
|
rule_set.append(rule)
|
||||||
self.receipt_rule_set[rule_set_name] = rule_set
|
self.receipt_rule_set[rule_set_name] = rule_set
|
||||||
|
|
||||||
def describe_receipt_rule_set(self, rule_set_name):
|
def describe_receipt_rule_set(self, rule_set_name: str) -> List[Dict[str, Any]]:
|
||||||
rule_set = self.receipt_rule_set.get(rule_set_name)
|
rule_set = self.receipt_rule_set.get(rule_set_name)
|
||||||
|
|
||||||
if rule_set is None:
|
if rule_set is None:
|
||||||
@ -506,7 +540,9 @@ class SESBackend(BaseBackend):
|
|||||||
|
|
||||||
return rule_set
|
return rule_set
|
||||||
|
|
||||||
def describe_receipt_rule(self, rule_set_name, rule_name):
|
def describe_receipt_rule(
|
||||||
|
self, rule_set_name: str, rule_name: str
|
||||||
|
) -> Dict[str, Any]:
|
||||||
rule_set = self.receipt_rule_set.get(rule_set_name)
|
rule_set = self.receipt_rule_set.get(rule_set_name)
|
||||||
|
|
||||||
if rule_set is None:
|
if rule_set is None:
|
||||||
@ -518,7 +554,7 @@ class SESBackend(BaseBackend):
|
|||||||
|
|
||||||
raise RuleDoesNotExist("Invalid Rule Name.")
|
raise RuleDoesNotExist("Invalid Rule Name.")
|
||||||
|
|
||||||
def update_receipt_rule(self, rule_set_name, rule):
|
def update_receipt_rule(self, rule_set_name: str, rule: Dict[str, Any]) -> None:
|
||||||
rule_set = self.receipt_rule_set.get(rule_set_name)
|
rule_set = self.receipt_rule_set.get(rule_set_name)
|
||||||
|
|
||||||
if rule_set is None:
|
if rule_set is None:
|
||||||
@ -532,8 +568,11 @@ class SESBackend(BaseBackend):
|
|||||||
raise RuleDoesNotExist(f"Rule does not exist: {rule['name']}")
|
raise RuleDoesNotExist(f"Rule does not exist: {rule['name']}")
|
||||||
|
|
||||||
def set_identity_mail_from_domain(
|
def set_identity_mail_from_domain(
|
||||||
self, identity, mail_from_domain=None, behavior_on_mx_failure=None
|
self,
|
||||||
):
|
identity: str,
|
||||||
|
mail_from_domain: Optional[str] = None,
|
||||||
|
behavior_on_mx_failure: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
if identity not in (self.domains + self.addresses):
|
if identity not in (self.domains + self.addresses):
|
||||||
raise InvalidParameterValue(f"Identity '{identity}' does not exist.")
|
raise InvalidParameterValue(f"Identity '{identity}' does not exist.")
|
||||||
|
|
||||||
@ -560,7 +599,9 @@ class SESBackend(BaseBackend):
|
|||||||
"behavior_on_mx_failure": behavior_on_mx_failure,
|
"behavior_on_mx_failure": behavior_on_mx_failure,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_identity_mail_from_domain_attributes(self, identities=None):
|
def get_identity_mail_from_domain_attributes(
|
||||||
|
self, identities: Optional[List[str]] = None
|
||||||
|
) -> Dict[str, Dict[str, str]]:
|
||||||
if identities is None:
|
if identities is None:
|
||||||
identities = []
|
identities = []
|
||||||
|
|
||||||
@ -573,11 +614,13 @@ class SESBackend(BaseBackend):
|
|||||||
|
|
||||||
return attributes_by_identity
|
return attributes_by_identity
|
||||||
|
|
||||||
def get_identity_verification_attributes(self, identities=None):
|
def get_identity_verification_attributes(
|
||||||
|
self, identities: Optional[List[str]] = None
|
||||||
|
) -> Dict[str, str]:
|
||||||
if identities is None:
|
if identities is None:
|
||||||
identities = []
|
identities = []
|
||||||
|
|
||||||
attributes_by_identity = {}
|
attributes_by_identity: Dict[str, str] = {}
|
||||||
for identity in identities:
|
for identity in identities:
|
||||||
if identity in (self.domains + self.addresses):
|
if identity in (self.domains + self.addresses):
|
||||||
attributes_by_identity[identity] = "Success"
|
attributes_by_identity[identity] = "Success"
|
||||||
@ -585,4 +628,4 @@ class SESBackend(BaseBackend):
|
|||||||
return attributes_by_identity
|
return attributes_by_identity
|
||||||
|
|
||||||
|
|
||||||
ses_backends: Mapping[str, SESBackend] = BackendDict(SESBackend, "ses")
|
ses_backends = BackendDict(SESBackend, "ses")
|
||||||
|
@ -1,66 +1,71 @@
|
|||||||
import base64
|
import base64
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from moto.core.responses import BaseResponse
|
from moto.core.responses import BaseResponse
|
||||||
from .models import ses_backends
|
from .models import ses_backends, SESBackend
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class EmailResponse(BaseResponse):
|
class EmailResponse(BaseResponse):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__(service_name="ses")
|
super().__init__(service_name="ses")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def backend(self):
|
def backend(self) -> SESBackend:
|
||||||
return ses_backends[self.current_account][self.region]
|
return ses_backends[self.current_account][self.region]
|
||||||
|
|
||||||
def verify_email_identity(self):
|
def verify_email_identity(self) -> str:
|
||||||
address = self.querystring.get("EmailAddress")[0]
|
address = self.querystring.get("EmailAddress")[0] # type: ignore
|
||||||
self.backend.verify_email_identity(address)
|
self.backend.verify_email_identity(address)
|
||||||
template = self.response_template(VERIFY_EMAIL_IDENTITY)
|
template = self.response_template(VERIFY_EMAIL_IDENTITY)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def verify_email_address(self):
|
def verify_email_address(self) -> str:
|
||||||
address = self.querystring.get("EmailAddress")[0]
|
address = self.querystring.get("EmailAddress")[0] # type: ignore
|
||||||
self.backend.verify_email_address(address)
|
self.backend.verify_email_address(address)
|
||||||
template = self.response_template(VERIFY_EMAIL_ADDRESS)
|
template = self.response_template(VERIFY_EMAIL_ADDRESS)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def list_identities(self):
|
def list_identities(self) -> str:
|
||||||
identities = self.backend.list_identities()
|
identities = self.backend.list_identities()
|
||||||
template = self.response_template(LIST_IDENTITIES_RESPONSE)
|
template = self.response_template(LIST_IDENTITIES_RESPONSE)
|
||||||
return template.render(identities=identities)
|
return template.render(identities=identities)
|
||||||
|
|
||||||
def list_verified_email_addresses(self):
|
def list_verified_email_addresses(self) -> str:
|
||||||
email_addresses = self.backend.list_verified_email_addresses()
|
email_addresses = self.backend.list_verified_email_addresses()
|
||||||
template = self.response_template(LIST_VERIFIED_EMAIL_RESPONSE)
|
template = self.response_template(LIST_VERIFIED_EMAIL_RESPONSE)
|
||||||
return template.render(email_addresses=email_addresses)
|
return template.render(email_addresses=email_addresses)
|
||||||
|
|
||||||
def verify_domain_dkim(self):
|
def verify_domain_dkim(self) -> str:
|
||||||
domain = self.querystring.get("Domain")[0]
|
domain = self.querystring.get("Domain")[0] # type: ignore
|
||||||
self.backend.verify_domain(domain)
|
self.backend.verify_domain(domain)
|
||||||
template = self.response_template(VERIFY_DOMAIN_DKIM_RESPONSE)
|
template = self.response_template(VERIFY_DOMAIN_DKIM_RESPONSE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def verify_domain_identity(self):
|
def verify_domain_identity(self) -> str:
|
||||||
domain = self.querystring.get("Domain")[0]
|
domain = self.querystring.get("Domain")[0] # type: ignore
|
||||||
self.backend.verify_domain(domain)
|
self.backend.verify_domain(domain)
|
||||||
template = self.response_template(VERIFY_DOMAIN_IDENTITY_RESPONSE)
|
template = self.response_template(VERIFY_DOMAIN_IDENTITY_RESPONSE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def delete_identity(self):
|
def delete_identity(self) -> str:
|
||||||
domain = self.querystring.get("Identity")[0]
|
domain = self.querystring.get("Identity")[0] # type: ignore
|
||||||
self.backend.delete_identity(domain)
|
self.backend.delete_identity(domain)
|
||||||
template = self.response_template(DELETE_IDENTITY_RESPONSE)
|
template = self.response_template(DELETE_IDENTITY_RESPONSE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def send_email(self):
|
def send_email(self) -> str:
|
||||||
bodydatakey = "Message.Body.Text.Data"
|
bodydatakey = "Message.Body.Text.Data"
|
||||||
if "Message.Body.Html.Data" in self.querystring:
|
if "Message.Body.Html.Data" in self.querystring:
|
||||||
bodydatakey = "Message.Body.Html.Data"
|
bodydatakey = "Message.Body.Html.Data"
|
||||||
body = self.querystring.get(bodydatakey)[0]
|
body = self.querystring.get(bodydatakey)[0] # type: ignore
|
||||||
source = self.querystring.get("Source")[0]
|
source = self.querystring.get("Source")[0] # type: ignore
|
||||||
subject = self.querystring.get("Message.Subject.Data")[0]
|
subject = self.querystring.get("Message.Subject.Data")[0] # type: ignore
|
||||||
destinations = {"ToAddresses": [], "CcAddresses": [], "BccAddresses": []}
|
destinations: Dict[str, List[str]] = {
|
||||||
|
"ToAddresses": [],
|
||||||
|
"CcAddresses": [],
|
||||||
|
"BccAddresses": [],
|
||||||
|
}
|
||||||
for dest_type in destinations:
|
for dest_type in destinations:
|
||||||
# consume up to 51 to allow exception
|
# consume up to 51 to allow exception
|
||||||
for i in range(1, 52):
|
for i in range(1, 52):
|
||||||
@ -70,18 +75,20 @@ class EmailResponse(BaseResponse):
|
|||||||
break
|
break
|
||||||
destinations[dest_type].append(address[0])
|
destinations[dest_type].append(address[0])
|
||||||
|
|
||||||
message = self.backend.send_email(
|
message = self.backend.send_email(source, subject, body, destinations)
|
||||||
source, subject, body, destinations, self.region
|
|
||||||
)
|
|
||||||
template = self.response_template(SEND_EMAIL_RESPONSE)
|
template = self.response_template(SEND_EMAIL_RESPONSE)
|
||||||
return template.render(message=message)
|
return template.render(message=message)
|
||||||
|
|
||||||
def send_templated_email(self):
|
def send_templated_email(self) -> str:
|
||||||
source = self.querystring.get("Source")[0]
|
source = self.querystring.get("Source")[0] # type: ignore
|
||||||
template = self.querystring.get("Template")
|
template: List[str] = self.querystring.get("Template") # type: ignore
|
||||||
template_data = self.querystring.get("TemplateData")
|
template_data: List[str] = self.querystring.get("TemplateData") # type: ignore
|
||||||
|
|
||||||
destinations = {"ToAddresses": [], "CcAddresses": [], "BccAddresses": []}
|
destinations: Dict[str, List[str]] = {
|
||||||
|
"ToAddresses": [],
|
||||||
|
"CcAddresses": [],
|
||||||
|
"BccAddresses": [],
|
||||||
|
}
|
||||||
for dest_type in destinations:
|
for dest_type in destinations:
|
||||||
# consume up to 51 to allow exception
|
# consume up to 51 to allow exception
|
||||||
for i in range(1, 52):
|
for i in range(1, 52):
|
||||||
@ -92,13 +99,14 @@ class EmailResponse(BaseResponse):
|
|||||||
destinations[dest_type].append(address[0])
|
destinations[dest_type].append(address[0])
|
||||||
|
|
||||||
message = self.backend.send_templated_email(
|
message = self.backend.send_templated_email(
|
||||||
source, template, template_data, destinations, self.region
|
source, template, template_data, destinations
|
||||||
|
)
|
||||||
|
return self.response_template(SEND_TEMPLATED_EMAIL_RESPONSE).render(
|
||||||
|
message=message
|
||||||
)
|
)
|
||||||
template = self.response_template(SEND_TEMPLATED_EMAIL_RESPONSE)
|
|
||||||
return template.render(message=message)
|
|
||||||
|
|
||||||
def send_bulk_templated_email(self):
|
def send_bulk_templated_email(self) -> str:
|
||||||
source = self.querystring.get("Source")[0]
|
source = self.querystring.get("Source")[0] # type: ignore
|
||||||
template = self.querystring.get("Template")
|
template = self.querystring.get("Template")
|
||||||
template_data = self.querystring.get("DefaultTemplateData")
|
template_data = self.querystring.get("DefaultTemplateData")
|
||||||
|
|
||||||
@ -109,7 +117,11 @@ class EmailResponse(BaseResponse):
|
|||||||
)
|
)
|
||||||
if self.querystring.get(destination_field) is None:
|
if self.querystring.get(destination_field) is None:
|
||||||
break
|
break
|
||||||
destination = {"ToAddresses": [], "CcAddresses": [], "BccAddresses": []}
|
destination: Dict[str, List[str]] = {
|
||||||
|
"ToAddresses": [],
|
||||||
|
"CcAddresses": [],
|
||||||
|
"BccAddresses": [],
|
||||||
|
}
|
||||||
for dest_type in destination:
|
for dest_type in destination:
|
||||||
# consume up to 51 to allow exception
|
# consume up to 51 to allow exception
|
||||||
for j in range(1, 52):
|
for j in range(1, 52):
|
||||||
@ -123,18 +135,18 @@ class EmailResponse(BaseResponse):
|
|||||||
destinations.append({"Destination": destination})
|
destinations.append({"Destination": destination})
|
||||||
|
|
||||||
message = self.backend.send_bulk_templated_email(
|
message = self.backend.send_bulk_templated_email(
|
||||||
source, template, template_data, destinations, self.region
|
source, template, template_data, destinations # type: ignore
|
||||||
)
|
)
|
||||||
template = self.response_template(SEND_BULK_TEMPLATED_EMAIL_RESPONSE)
|
template = self.response_template(SEND_BULK_TEMPLATED_EMAIL_RESPONSE)
|
||||||
result = template.render(message=message)
|
result = template.render(message=message)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def send_raw_email(self):
|
def send_raw_email(self) -> str:
|
||||||
source = self.querystring.get("Source")
|
source = self.querystring.get("Source")
|
||||||
if source is not None:
|
if source is not None:
|
||||||
(source,) = source
|
(source,) = source
|
||||||
|
|
||||||
raw_data = self.querystring.get("RawMessage.Data")[0]
|
raw_data = self.querystring.get("RawMessage.Data")[0] # type: ignore
|
||||||
raw_data = base64.b64decode(raw_data)
|
raw_data = base64.b64decode(raw_data)
|
||||||
raw_data = raw_data.decode("utf-8")
|
raw_data = raw_data.decode("utf-8")
|
||||||
destinations = []
|
destinations = []
|
||||||
@ -146,35 +158,33 @@ class EmailResponse(BaseResponse):
|
|||||||
break
|
break
|
||||||
destinations.append(address[0])
|
destinations.append(address[0])
|
||||||
|
|
||||||
message = self.backend.send_raw_email(
|
message = self.backend.send_raw_email(source, destinations, raw_data)
|
||||||
source, destinations, raw_data, self.region
|
|
||||||
)
|
|
||||||
template = self.response_template(SEND_RAW_EMAIL_RESPONSE)
|
template = self.response_template(SEND_RAW_EMAIL_RESPONSE)
|
||||||
return template.render(message=message)
|
return template.render(message=message)
|
||||||
|
|
||||||
def get_send_quota(self):
|
def get_send_quota(self) -> str:
|
||||||
quota = self.backend.get_send_quota()
|
quota = self.backend.get_send_quota()
|
||||||
template = self.response_template(GET_SEND_QUOTA_RESPONSE)
|
template = self.response_template(GET_SEND_QUOTA_RESPONSE)
|
||||||
return template.render(quota=quota)
|
return template.render(quota=quota)
|
||||||
|
|
||||||
def get_identity_notification_attributes(self):
|
def get_identity_notification_attributes(self) -> str:
|
||||||
identities = self._get_params()["Identities"]
|
identities = self._get_params()["Identities"]
|
||||||
identities = self.backend.get_identity_notification_attributes(identities)
|
identities = self.backend.get_identity_notification_attributes(identities)
|
||||||
template = self.response_template(GET_IDENTITY_NOTIFICATION_ATTRIBUTES)
|
template = self.response_template(GET_IDENTITY_NOTIFICATION_ATTRIBUTES)
|
||||||
return template.render(identities=identities)
|
return template.render(identities=identities)
|
||||||
|
|
||||||
def set_identity_feedback_forwarding_enabled(self):
|
def set_identity_feedback_forwarding_enabled(self) -> str:
|
||||||
identity = self._get_param("Identity")
|
identity = self._get_param("Identity")
|
||||||
enabled = self._get_bool_param("ForwardingEnabled")
|
enabled = self._get_bool_param("ForwardingEnabled")
|
||||||
self.backend.set_identity_feedback_forwarding_enabled(identity, enabled)
|
self.backend.set_identity_feedback_forwarding_enabled(identity, enabled)
|
||||||
template = self.response_template(SET_IDENTITY_FORWARDING_ENABLED_RESPONSE)
|
template = self.response_template(SET_IDENTITY_FORWARDING_ENABLED_RESPONSE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def set_identity_notification_topic(self):
|
def set_identity_notification_topic(self) -> str:
|
||||||
|
|
||||||
identity = self.querystring.get("Identity")[0]
|
identity = self.querystring.get("Identity")[0] # type: ignore
|
||||||
not_type = self.querystring.get("NotificationType")[0]
|
not_type = self.querystring.get("NotificationType")[0] # type: ignore
|
||||||
sns_topic = self.querystring.get("SnsTopic")
|
sns_topic = self.querystring.get("SnsTopic") # type: ignore
|
||||||
if sns_topic:
|
if sns_topic:
|
||||||
sns_topic = sns_topic[0]
|
sns_topic = sns_topic[0]
|
||||||
|
|
||||||
@ -182,33 +192,35 @@ class EmailResponse(BaseResponse):
|
|||||||
template = self.response_template(SET_IDENTITY_NOTIFICATION_TOPIC_RESPONSE)
|
template = self.response_template(SET_IDENTITY_NOTIFICATION_TOPIC_RESPONSE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def get_send_statistics(self):
|
def get_send_statistics(self) -> str:
|
||||||
statistics = self.backend.get_send_statistics()
|
statistics = self.backend.get_send_statistics()
|
||||||
template = self.response_template(GET_SEND_STATISTICS)
|
template = self.response_template(GET_SEND_STATISTICS)
|
||||||
return template.render(all_statistics=[statistics])
|
return template.render(all_statistics=[statistics])
|
||||||
|
|
||||||
def create_configuration_set(self):
|
def create_configuration_set(self) -> str:
|
||||||
configuration_set_name = self.querystring.get("ConfigurationSet.Name")[0]
|
configuration_set_name = self.querystring.get("ConfigurationSet.Name")[0] # type: ignore
|
||||||
self.backend.create_configuration_set(
|
self.backend.create_configuration_set(
|
||||||
configuration_set_name=configuration_set_name
|
configuration_set_name=configuration_set_name
|
||||||
)
|
)
|
||||||
template = self.response_template(CREATE_CONFIGURATION_SET)
|
template = self.response_template(CREATE_CONFIGURATION_SET)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def describe_configuration_set(self):
|
def describe_configuration_set(self) -> str:
|
||||||
configuration_set_name = self.querystring.get("ConfigurationSetName")[0]
|
configuration_set_name = self.querystring.get("ConfigurationSetName")[0] # type: ignore
|
||||||
self.backend.describe_configuration_set(configuration_set_name)
|
self.backend.describe_configuration_set(configuration_set_name)
|
||||||
template = self.response_template(DESCRIBE_CONFIGURATION_SET)
|
template = self.response_template(DESCRIBE_CONFIGURATION_SET)
|
||||||
return template.render(name=configuration_set_name)
|
return template.render(name=configuration_set_name)
|
||||||
|
|
||||||
def create_configuration_set_event_destination(self):
|
def create_configuration_set_event_destination(self) -> str:
|
||||||
|
|
||||||
configuration_set_name = self._get_param("ConfigurationSetName")
|
configuration_set_name = self._get_param("ConfigurationSetName") # type: ignore
|
||||||
is_configuration_event_enabled = self.querystring.get(
|
is_configuration_event_enabled = self.querystring.get(
|
||||||
"EventDestination.Enabled"
|
"EventDestination.Enabled"
|
||||||
)[0]
|
)[
|
||||||
configuration_event_name = self.querystring.get("EventDestination.Name")[0]
|
0
|
||||||
event_topic_arn = self.querystring.get(
|
] # type: ignore
|
||||||
|
configuration_event_name = self.querystring.get("EventDestination.Name")[0] # type: ignore
|
||||||
|
event_topic_arn = self.querystring.get( # type: ignore
|
||||||
"EventDestination.SNSDestination.TopicARN"
|
"EventDestination.SNSDestination.TopicARN"
|
||||||
)[0]
|
)[0]
|
||||||
event_matching_types = self._get_multi_param(
|
event_matching_types = self._get_multi_param(
|
||||||
@ -230,7 +242,7 @@ class EmailResponse(BaseResponse):
|
|||||||
template = self.response_template(CREATE_CONFIGURATION_SET_EVENT_DESTINATION)
|
template = self.response_template(CREATE_CONFIGURATION_SET_EVENT_DESTINATION)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def create_template(self):
|
def create_template(self) -> str:
|
||||||
template_data = self._get_dict_param("Template")
|
template_data = self._get_dict_param("Template")
|
||||||
template_info = {}
|
template_info = {}
|
||||||
template_info["text_part"] = template_data.get("._text_part", "")
|
template_info["text_part"] = template_data.get("._text_part", "")
|
||||||
@ -242,7 +254,7 @@ class EmailResponse(BaseResponse):
|
|||||||
template = self.response_template(CREATE_TEMPLATE)
|
template = self.response_template(CREATE_TEMPLATE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def update_template(self):
|
def update_template(self) -> str:
|
||||||
template_data = self._get_dict_param("Template")
|
template_data = self._get_dict_param("Template")
|
||||||
template_info = {}
|
template_info = {}
|
||||||
template_info["text_part"] = template_data.get("._text_part", "")
|
template_info["text_part"] = template_data.get("._text_part", "")
|
||||||
@ -254,43 +266,43 @@ class EmailResponse(BaseResponse):
|
|||||||
template = self.response_template(UPDATE_TEMPLATE)
|
template = self.response_template(UPDATE_TEMPLATE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def get_template(self):
|
def get_template(self) -> str:
|
||||||
template_name = self._get_param("TemplateName")
|
template_name = self._get_param("TemplateName")
|
||||||
template_data = self.backend.get_template(template_name)
|
template_data = self.backend.get_template(template_name)
|
||||||
template = self.response_template(GET_TEMPLATE)
|
template = self.response_template(GET_TEMPLATE)
|
||||||
return template.render(template_data=template_data)
|
return template.render(template_data=template_data)
|
||||||
|
|
||||||
def list_templates(self):
|
def list_templates(self) -> str:
|
||||||
email_templates = self.backend.list_templates()
|
email_templates = self.backend.list_templates()
|
||||||
template = self.response_template(LIST_TEMPLATES)
|
template = self.response_template(LIST_TEMPLATES)
|
||||||
return template.render(templates=email_templates)
|
return template.render(templates=email_templates)
|
||||||
|
|
||||||
def test_render_template(self):
|
def test_render_template(self) -> str:
|
||||||
render_info = self._get_dict_param("Template")
|
render_info = self._get_dict_param("Template")
|
||||||
rendered_template = self.backend.render_template(render_info)
|
rendered_template = self.backend.render_template(render_info)
|
||||||
template = self.response_template(RENDER_TEMPLATE)
|
template = self.response_template(RENDER_TEMPLATE)
|
||||||
return template.render(template=rendered_template)
|
return template.render(template=rendered_template)
|
||||||
|
|
||||||
def create_receipt_rule_set(self):
|
def create_receipt_rule_set(self) -> str:
|
||||||
rule_set_name = self._get_param("RuleSetName")
|
rule_set_name = self._get_param("RuleSetName")
|
||||||
self.backend.create_receipt_rule_set(rule_set_name)
|
self.backend.create_receipt_rule_set(rule_set_name)
|
||||||
template = self.response_template(CREATE_RECEIPT_RULE_SET)
|
template = self.response_template(CREATE_RECEIPT_RULE_SET)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def create_receipt_rule(self):
|
def create_receipt_rule(self) -> str:
|
||||||
rule_set_name = self._get_param("RuleSetName")
|
rule_set_name = self._get_param("RuleSetName")
|
||||||
rule = self._get_dict_param("Rule.")
|
rule = self._get_dict_param("Rule.")
|
||||||
self.backend.create_receipt_rule(rule_set_name, rule)
|
self.backend.create_receipt_rule(rule_set_name, rule)
|
||||||
template = self.response_template(CREATE_RECEIPT_RULE)
|
template = self.response_template(CREATE_RECEIPT_RULE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def describe_receipt_rule_set(self):
|
def describe_receipt_rule_set(self) -> str:
|
||||||
rule_set_name = self._get_param("RuleSetName")
|
rule_set_name = self._get_param("RuleSetName")
|
||||||
|
|
||||||
rule_set = self.backend.describe_receipt_rule_set(rule_set_name)
|
rule_set = self.backend.describe_receipt_rule_set(rule_set_name)
|
||||||
|
|
||||||
for i, rule in enumerate(rule_set):
|
for i, rule in enumerate(rule_set):
|
||||||
formatted_rule = {}
|
formatted_rule: Dict[str, Any] = {}
|
||||||
|
|
||||||
for k, v in rule.items():
|
for k, v in rule.items():
|
||||||
self._parse_param(k, v, formatted_rule)
|
self._parse_param(k, v, formatted_rule)
|
||||||
@ -301,13 +313,13 @@ class EmailResponse(BaseResponse):
|
|||||||
|
|
||||||
return template.render(rule_set=rule_set, rule_set_name=rule_set_name)
|
return template.render(rule_set=rule_set, rule_set_name=rule_set_name)
|
||||||
|
|
||||||
def describe_receipt_rule(self):
|
def describe_receipt_rule(self) -> str:
|
||||||
rule_set_name = self._get_param("RuleSetName")
|
rule_set_name = self._get_param("RuleSetName")
|
||||||
rule_name = self._get_param("RuleName")
|
rule_name = self._get_param("RuleName")
|
||||||
|
|
||||||
receipt_rule = self.backend.describe_receipt_rule(rule_set_name, rule_name)
|
receipt_rule = self.backend.describe_receipt_rule(rule_set_name, rule_name)
|
||||||
|
|
||||||
rule = {}
|
rule: Dict[str, Any] = {}
|
||||||
|
|
||||||
for k, v in receipt_rule.items():
|
for k, v in receipt_rule.items():
|
||||||
self._parse_param(k, v, rule)
|
self._parse_param(k, v, rule)
|
||||||
@ -315,7 +327,7 @@ class EmailResponse(BaseResponse):
|
|||||||
template = self.response_template(DESCRIBE_RECEIPT_RULE)
|
template = self.response_template(DESCRIBE_RECEIPT_RULE)
|
||||||
return template.render(rule=rule)
|
return template.render(rule=rule)
|
||||||
|
|
||||||
def update_receipt_rule(self):
|
def update_receipt_rule(self) -> str:
|
||||||
rule_set_name = self._get_param("RuleSetName")
|
rule_set_name = self._get_param("RuleSetName")
|
||||||
rule = self._get_dict_param("Rule.")
|
rule = self._get_dict_param("Rule.")
|
||||||
|
|
||||||
@ -324,7 +336,7 @@ class EmailResponse(BaseResponse):
|
|||||||
template = self.response_template(UPDATE_RECEIPT_RULE)
|
template = self.response_template(UPDATE_RECEIPT_RULE)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def set_identity_mail_from_domain(self):
|
def set_identity_mail_from_domain(self) -> str:
|
||||||
identity = self._get_param("Identity")
|
identity = self._get_param("Identity")
|
||||||
mail_from_domain = self._get_param("MailFromDomain")
|
mail_from_domain = self._get_param("MailFromDomain")
|
||||||
behavior_on_mx_failure = self._get_param("BehaviorOnMXFailure")
|
behavior_on_mx_failure = self._get_param("BehaviorOnMXFailure")
|
||||||
@ -336,14 +348,16 @@ class EmailResponse(BaseResponse):
|
|||||||
template = self.response_template(SET_IDENTITY_MAIL_FROM_DOMAIN)
|
template = self.response_template(SET_IDENTITY_MAIL_FROM_DOMAIN)
|
||||||
return template.render()
|
return template.render()
|
||||||
|
|
||||||
def get_identity_mail_from_domain_attributes(self):
|
def get_identity_mail_from_domain_attributes(self) -> str:
|
||||||
identities = self._get_multi_param("Identities.member.")
|
identities = self._get_multi_param("Identities.member.")
|
||||||
identities = self.backend.get_identity_mail_from_domain_attributes(identities)
|
attributes_by_identity = self.backend.get_identity_mail_from_domain_attributes(
|
||||||
|
identities
|
||||||
|
)
|
||||||
template = self.response_template(GET_IDENTITY_MAIL_FROM_DOMAIN_ATTRIBUTES)
|
template = self.response_template(GET_IDENTITY_MAIL_FROM_DOMAIN_ATTRIBUTES)
|
||||||
|
|
||||||
return template.render(identities=identities)
|
return template.render(identities=attributes_by_identity)
|
||||||
|
|
||||||
def get_identity_verification_attributes(self):
|
def get_identity_verification_attributes(self) -> str:
|
||||||
params = self._get_params()
|
params = self._get_params()
|
||||||
identities = params.get("Identities")
|
identities = params.get("Identities")
|
||||||
verification_attributes = self.backend.get_identity_verification_attributes(
|
verification_attributes = self.backend.get_identity_verification_attributes(
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from .exceptions import MissingRenderingAttributeException
|
from .exceptions import MissingRenderingAttributeException
|
||||||
from moto.utilities.tokenizer import GenericTokenizer
|
from moto.utilities.tokenizer import GenericTokenizer
|
||||||
|
|
||||||
|
|
||||||
def parse_template(template, template_data, tokenizer=None, until=None):
|
def parse_template(
|
||||||
|
template: str,
|
||||||
|
template_data: Dict[str, Any],
|
||||||
|
tokenizer: Optional[GenericTokenizer] = None,
|
||||||
|
until: Optional[str] = None,
|
||||||
|
) -> str:
|
||||||
tokenizer = tokenizer or GenericTokenizer(template)
|
tokenizer = tokenizer or GenericTokenizer(template)
|
||||||
state = ""
|
state = ""
|
||||||
parsed = ""
|
parsed = ""
|
||||||
@ -27,7 +34,7 @@ def parse_template(template, template_data, tokenizer=None, until=None):
|
|||||||
if state == "VAR":
|
if state == "VAR":
|
||||||
if template_data.get(var_name) is None:
|
if template_data.get(var_name) is None:
|
||||||
raise MissingRenderingAttributeException(var_name)
|
raise MissingRenderingAttributeException(var_name)
|
||||||
parsed += template_data.get(var_name)
|
parsed += template_data.get(var_name) # type: ignore
|
||||||
else:
|
else:
|
||||||
current_position = tokenizer.token_pos
|
current_position = tokenizer.token_pos
|
||||||
for item in template_data.get(var_name, []):
|
for item in template_data.get(var_name, []):
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
import string
|
import string
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from email.utils import parseaddr
|
from email.utils import parseaddr
|
||||||
from moto.moto_api._internal import mock_random as random
|
from moto.moto_api._internal import mock_random as random
|
||||||
|
|
||||||
|
|
||||||
def random_hex(length):
|
def random_hex(length: int) -> str:
|
||||||
return "".join(random.choice(string.ascii_lowercase) for x in range(length))
|
return "".join(random.choice(string.ascii_lowercase) for x in range(length))
|
||||||
|
|
||||||
|
|
||||||
def get_random_message_id():
|
def get_random_message_id() -> str:
|
||||||
return f"{random_hex(16)}-{random_hex(8)}-{random_hex(4)}-{random_hex(4)}-{random_hex(4)}-{random_hex(12)}-{random_hex(6)}"
|
return f"{random_hex(16)}-{random_hex(8)}-{random_hex(4)}-{random_hex(4)}-{random_hex(4)}-{random_hex(12)}-{random_hex(6)}"
|
||||||
|
|
||||||
|
|
||||||
def is_valid_address(addr):
|
def is_valid_address(addr: str) -> Optional[str]:
|
||||||
_, address = parseaddr(addr)
|
_, address = parseaddr(addr)
|
||||||
address = address.split("@")
|
address_parts = address.split("@")
|
||||||
if len(address) != 2 or not address[1]:
|
if len(address_parts) != 2 or not address_parts[1]:
|
||||||
return False, "Missing domain"
|
return "Missing domain"
|
||||||
return True, None
|
return None
|
||||||
|
@ -239,7 +239,7 @@ disable = W,C,R,E
|
|||||||
enable = anomalous-backslash-in-string, arguments-renamed, dangerous-default-value, deprecated-module, function-redefined, import-self, redefined-builtin, redefined-outer-name, reimported, pointless-statement, super-with-arguments, unused-argument, unused-import, unused-variable, useless-else-on-loop, wildcard-import
|
enable = anomalous-backslash-in-string, arguments-renamed, dangerous-default-value, deprecated-module, function-redefined, import-self, redefined-builtin, redefined-outer-name, reimported, pointless-statement, super-with-arguments, unused-argument, unused-import, unused-variable, useless-else-on-loop, wildcard-import
|
||||||
|
|
||||||
[mypy]
|
[mypy]
|
||||||
files= moto/a*,moto/b*,moto/c*,moto/d*,moto/e*,moto/f*,moto/g*,moto/i*,moto/k*,moto/l*,moto/m*,moto/n*,moto/o*,moto/p*,moto/q*,moto/r*,moto/s3*,moto/sagemaker,moto/secretsmanager,moto/sqs,moto/ssm,moto/scheduler
|
files= moto/a*,moto/b*,moto/c*,moto/d*,moto/e*,moto/f*,moto/g*,moto/i*,moto/k*,moto/l*,moto/m*,moto/n*,moto/o*,moto/p*,moto/q*,moto/r*,moto/s3*,moto/sagemaker,moto/secretsmanager,moto/ses,moto/sqs,moto/ssm,moto/scheduler
|
||||||
show_column_numbers=True
|
show_column_numbers=True
|
||||||
show_error_codes = True
|
show_error_codes = True
|
||||||
disable_error_code=abstract
|
disable_error_code=abstract
|
||||||
|
@ -4,14 +4,11 @@ from moto.ses.utils import is_valid_address
|
|||||||
|
|
||||||
|
|
||||||
def test_is_valid_address():
|
def test_is_valid_address():
|
||||||
valid, msg = is_valid_address("test@example.com")
|
msg = is_valid_address("test@example.com")
|
||||||
valid.should.equal(True)
|
|
||||||
msg.should.equal(None)
|
msg.should.equal(None)
|
||||||
|
|
||||||
valid, msg = is_valid_address("test@")
|
msg = is_valid_address("test@")
|
||||||
valid.should.equal(False)
|
|
||||||
msg.should.be.a(str)
|
msg.should.be.a(str)
|
||||||
|
|
||||||
valid, msg = is_valid_address("test")
|
msg = is_valid_address("test")
|
||||||
valid.should.equal(False)
|
|
||||||
msg.should.be.a(str)
|
msg.should.be.a(str)
|
||||||
|
Loading…
Reference in New Issue
Block a user