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): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, message): | ||||
|     def __init__(self, message: str): | ||||
|         super().__init__("MessageRejected", message) | ||||
| 
 | ||||
| 
 | ||||
| class ConfigurationSetDoesNotExist(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, message): | ||||
|     def __init__(self, message: str): | ||||
|         super().__init__("ConfigurationSetDoesNotExist", message) | ||||
| 
 | ||||
| 
 | ||||
| class ConfigurationSetAlreadyExists(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, message): | ||||
|     def __init__(self, message: str): | ||||
|         super().__init__("ConfigurationSetAlreadyExists", message) | ||||
| 
 | ||||
| 
 | ||||
| class EventDestinationAlreadyExists(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, message): | ||||
|     def __init__(self, message: str): | ||||
|         super().__init__("EventDestinationAlreadyExists", message) | ||||
| 
 | ||||
| 
 | ||||
| class TemplateNameAlreadyExists(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, message): | ||||
|     def __init__(self, message: str): | ||||
|         super().__init__("TemplateNameAlreadyExists", message) | ||||
| 
 | ||||
| 
 | ||||
| class ValidationError(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, message): | ||||
|     def __init__(self, message: str): | ||||
|         super().__init__("ValidationError", message) | ||||
| 
 | ||||
| 
 | ||||
| class InvalidParameterValue(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, message): | ||||
|     def __init__(self, message: str): | ||||
|         super().__init__("InvalidParameterValue", message) | ||||
| 
 | ||||
| 
 | ||||
| class InvalidRenderingParameterException: | ||||
| class InvalidRenderingParameterException(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, message): | ||||
|     def __init__(self, message: str): | ||||
|         super().__init__("InvalidRenderingParameterException", message) | ||||
| 
 | ||||
| 
 | ||||
| class TemplateDoesNotExist(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, message): | ||||
|     def __init__(self, message: str): | ||||
|         super().__init__("TemplateDoesNotExist", message) | ||||
| 
 | ||||
| 
 | ||||
| class RuleSetNameAlreadyExists(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, message): | ||||
|     def __init__(self, message: str): | ||||
|         super().__init__("RuleSetNameAlreadyExists", message) | ||||
| 
 | ||||
| 
 | ||||
| class RuleAlreadyExists(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, message): | ||||
|     def __init__(self, message: str): | ||||
|         super().__init__("RuleAlreadyExists", message) | ||||
| 
 | ||||
| 
 | ||||
| class RuleSetDoesNotExist(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, message): | ||||
|     def __init__(self, message: str): | ||||
|         super().__init__("RuleSetDoesNotExist", message) | ||||
| 
 | ||||
| 
 | ||||
| class RuleDoesNotExist(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, message): | ||||
|     def __init__(self, message: str): | ||||
|         super().__init__("RuleDoesNotExist", message) | ||||
| 
 | ||||
| 
 | ||||
| class MissingRenderingAttributeException(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, var): | ||||
|     def __init__(self, var: str): | ||||
|         super().__init__( | ||||
|             "MissingRenderingAttributeException", | ||||
|             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.mime.multipart import MIMEMultipart | ||||
| 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.sns.models import sns_backends | ||||
| @ -48,8 +48,8 @@ class SESFeedback(BaseModel): | ||||
|     FORWARDING_ENABLED = "feedback_forwarding_enabled" | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def generate_message(account_id, msg_type): | ||||
|         msg = dict(COMMON_MAIL) | ||||
|     def generate_message(account_id: str, msg_type: str) -> Dict[str, Any]:  # type: ignore[misc] | ||||
|         msg: Dict[str, Any] = dict(COMMON_MAIL) | ||||
|         msg["mail"]["sendingAccountId"] = account_id | ||||
|         if msg_type == SESFeedback.BOUNCE: | ||||
|             msg["bounce"] = BOUNCE | ||||
| @ -62,7 +62,14 @@ class SESFeedback(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.source = source | ||||
|         self.subject = subject | ||||
| @ -71,7 +78,14 @@ class Message(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.source = source | ||||
|         self.template = template | ||||
| @ -80,7 +94,14 @@ class TemplateMessage(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.source = source | ||||
|         self.template = template | ||||
| @ -89,7 +110,9 @@ class BulkTemplateMessage(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.source = source | ||||
|         self.destinations = destinations | ||||
| @ -97,11 +120,11 @@ class RawMessage(BaseModel): | ||||
| 
 | ||||
| 
 | ||||
| class SESQuota(BaseModel): | ||||
|     def __init__(self, sent): | ||||
|     def __init__(self, sent: int): | ||||
|         self.sent = sent | ||||
| 
 | ||||
|     @property | ||||
|     def sent_past_24(self): | ||||
|     def sent_past_24(self) -> int: | ||||
|         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. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, region_name, account_id): | ||||
|     def __init__(self, region_name: str, account_id: str): | ||||
|         super().__init__(region_name, account_id) | ||||
|         self.addresses = [] | ||||
|         self.email_addresses = [] | ||||
|         self.domains = [] | ||||
|         self.sent_messages = [] | ||||
|         self.addresses: List[str] = [] | ||||
|         self.email_addresses: List[str] = [] | ||||
|         self.domains: List[str] = [] | ||||
|         self.sent_messages: List[Any] = [] | ||||
|         self.sent_message_count = 0 | ||||
|         self.rejected_messages_count = 0 | ||||
|         self.sns_topics = {} | ||||
|         self.config_set = {} | ||||
|         self.config_set_event_destination = {} | ||||
|         self.event_destinations = {} | ||||
|         self.identity_mail_from_domains = {} | ||||
|         self.templates = {} | ||||
|         self.receipt_rule_set = {} | ||||
|         self.sns_topics: Dict[str, Dict[str, Any]] = {} | ||||
|         self.config_set: Dict[str, int] = {} | ||||
|         self.config_set_event_destination: Dict[str, Dict[str, Any]] = {} | ||||
|         self.event_destinations: Dict[str, int] = {} | ||||
|         self.identity_mail_from_domains: Dict[str, Dict[str, Any]] = {} | ||||
|         self.templates: Dict[str, Dict[str, str]] = {} | ||||
|         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) | ||||
|         if address in self.addresses: | ||||
|             return True | ||||
| @ -146,32 +169,34 @@ class SESBackend(BaseBackend): | ||||
|         _, host = address.split("@", 1) | ||||
|         return host in self.domains | ||||
| 
 | ||||
|     def verify_email_identity(self, address): | ||||
|     def verify_email_identity(self, address: str) -> None: | ||||
|         _, address = parseaddr(address) | ||||
|         if address not in self.addresses: | ||||
|             self.addresses.append(address) | ||||
| 
 | ||||
|     def verify_email_address(self, address): | ||||
|     def verify_email_address(self, address: str) -> None: | ||||
|         _, address = parseaddr(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: | ||||
|             self.domains.append(domain.lower()) | ||||
| 
 | ||||
|     def list_identities(self): | ||||
|     def list_identities(self) -> List[str]: | ||||
|         return self.domains + self.addresses | ||||
| 
 | ||||
|     def list_verified_email_addresses(self): | ||||
|     def list_verified_email_addresses(self) -> List[str]: | ||||
|         return self.email_addresses | ||||
| 
 | ||||
|     def delete_identity(self, identity): | ||||
|     def delete_identity(self, identity: str) -> None: | ||||
|         if "@" in identity: | ||||
|             self.addresses.remove(identity) | ||||
|         else: | ||||
|             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())) | ||||
|         if recipient_count > RECIPIENT_LIMIT: | ||||
|             raise MessageRejectedError("Too many recipients.") | ||||
| @ -182,11 +207,11 @@ class SESBackend(BaseBackend): | ||||
|             address for addresses in destinations.values() for address in addresses | ||||
|         ] | ||||
|         for address in [source, *destination_addresses]: | ||||
|             valid, msg = is_valid_address(address) | ||||
|             if not valid: | ||||
|             msg = is_valid_address(address) | ||||
|             if msg is not None: | ||||
|                 raise InvalidParameterValue(msg) | ||||
| 
 | ||||
|         self.__process_sns_feedback__(source, destinations, region) | ||||
|         self.__process_sns_feedback__(source, destinations) | ||||
| 
 | ||||
|         message_id = get_random_message_id() | ||||
|         message = Message(message_id, source, subject, body, destinations) | ||||
| @ -195,14 +220,18 @@ class SESBackend(BaseBackend): | ||||
|         return message | ||||
| 
 | ||||
|     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) | ||||
|         if recipient_count > RECIPIENT_LIMIT: | ||||
|             raise MessageRejectedError("Too many destinations.") | ||||
| 
 | ||||
|         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: | ||||
|             raise MessageRejectedError("Too many destinations.") | ||||
| @ -214,7 +243,7 @@ class SESBackend(BaseBackend): | ||||
|         if not self.templates.get(template[0]): | ||||
|             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 = TemplateMessage( | ||||
| @ -227,8 +256,12 @@ class SESBackend(BaseBackend): | ||||
|         return BulkTemplateMessage(ids, source, template, template_data, destinations) | ||||
| 
 | ||||
|     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())) | ||||
|         if recipient_count > RECIPIENT_LIMIT: | ||||
|             raise MessageRejectedError("Too many recipients.") | ||||
| @ -239,14 +272,14 @@ class SESBackend(BaseBackend): | ||||
|             address for addresses in destinations.values() for address in addresses | ||||
|         ] | ||||
|         for address in [source, *destination_addresses]: | ||||
|             valid, msg = is_valid_address(address) | ||||
|             if not valid: | ||||
|             msg = is_valid_address(address) | ||||
|             if msg is not None: | ||||
|                 raise InvalidParameterValue(msg) | ||||
| 
 | ||||
|         if not self.templates.get(template[0]): | ||||
|             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 = TemplateMessage( | ||||
| @ -256,7 +289,7 @@ class SESBackend(BaseBackend): | ||||
|         self.sent_message_count += recipient_count | ||||
|         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, | ||||
|         complaint or bounce like in SES simulator""" | ||||
|         if isinstance(destinations, list): | ||||
| @ -278,11 +311,11 @@ class SESBackend(BaseBackend): | ||||
| 
 | ||||
|         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""" | ||||
|         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) | ||||
|         if "@" in domain: | ||||
|             domain = domain.split("@")[1] | ||||
| @ -293,11 +326,13 @@ class SESBackend(BaseBackend): | ||||
|                 if sns_topic is not None: | ||||
|                     message = self.__generate_feedback__(msg_type) | ||||
|                     if message: | ||||
|                         sns_backends[self.account_id][region].publish( | ||||
|                         sns_backends[self.account_id][self.region_name].publish( | ||||
|                             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: | ||||
|             _, source_email_address = parseaddr(source) | ||||
|             if not self._is_verified_address(source_email_address): | ||||
| @ -324,33 +359,39 @@ class SESBackend(BaseBackend): | ||||
|         if recipient_count > RECIPIENT_LIMIT: | ||||
|             raise MessageRejectedError("Too many recipients.") | ||||
|         for address in [addr for addr in [source, *destinations] if addr is not None]: | ||||
|             valid, msg = is_valid_address(address) | ||||
|             if not valid: | ||||
|             msg = is_valid_address(address) | ||||
|             if msg is not None: | ||||
|                 raise InvalidParameterValue(msg) | ||||
| 
 | ||||
|         self.__process_sns_feedback__(source, destinations, region) | ||||
|         self.__process_sns_feedback__(source, destinations) | ||||
| 
 | ||||
|         self.sent_message_count += recipient_count | ||||
|         message_id = get_random_message_id() | ||||
|         message = RawMessage(message_id, source, destinations, raw_data) | ||||
|         self.sent_messages.append(message) | ||||
|         return message | ||||
|         raw_message = RawMessage(message_id, source, destinations, raw_data) | ||||
|         self.sent_messages.append(raw_message) | ||||
|         return raw_message | ||||
| 
 | ||||
|     def get_send_quota(self): | ||||
|     def get_send_quota(self) -> SESQuota: | ||||
|         return SESQuota(self.sent_message_count) | ||||
| 
 | ||||
|     def get_identity_notification_attributes(self, identities): | ||||
|         response = {} | ||||
|     def get_identity_notification_attributes( | ||||
|         self, identities: List[str] | ||||
|     ) -> Dict[str, Dict[str, Any]]: | ||||
|         response: Dict[str, Dict[str, Any]] = {} | ||||
|         for identity in identities: | ||||
|             response[identity] = self.sns_topics.get(identity, {}) | ||||
|         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[SESFeedback.FORWARDING_ENABLED] = enabled | ||||
|         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, {}) | ||||
|         if sns_topic is None: | ||||
|             del identity_sns_topics[notification_type] | ||||
| @ -359,26 +400,22 @@ class SESBackend(BaseBackend): | ||||
| 
 | ||||
|         self.sns_topics[identity] = identity_sns_topics | ||||
| 
 | ||||
|         return {} | ||||
| 
 | ||||
|     def create_configuration_set(self, configuration_set_name): | ||||
|     def create_configuration_set(self, configuration_set_name: str) -> None: | ||||
|         if configuration_set_name in self.config_set: | ||||
|             raise ConfigurationSetAlreadyExists( | ||||
|                 f"Configuration set <{configuration_set_name}> already exists" | ||||
|             ) | ||||
|         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: | ||||
|             raise ConfigurationSetDoesNotExist( | ||||
|                 f"Configuration set <{configuration_set_name}> does not exist" | ||||
|             ) | ||||
|         return {} | ||||
| 
 | ||||
|     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: | ||||
|             raise ConfigurationSetDoesNotExist("Invalid Configuration Set Name.") | ||||
| @ -389,19 +426,16 @@ class SESBackend(BaseBackend): | ||||
|         self.config_set_event_destination[configuration_set_name] = event_destination | ||||
|         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): | ||||
| 
 | ||||
|         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): | ||||
|     def add_template(self, template_info: Dict[str, str]) -> None: | ||||
|         template_name = template_info["template_name"] | ||||
|         if not template_name: | ||||
|             raise ValidationError( | ||||
| @ -418,7 +452,7 @@ class SESBackend(BaseBackend): | ||||
|             raise InvalidParameterValue("The subject must be specified.") | ||||
|         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"] | ||||
|         if not template_name: | ||||
|             raise ValidationError( | ||||
| @ -435,15 +469,15 @@ class SESBackend(BaseBackend): | ||||
|             raise InvalidParameterValue("The subject must be specified.") | ||||
|         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): | ||||
|             raise TemplateDoesNotExist("Invalid Template Name.") | ||||
|         return self.templates[template_name] | ||||
| 
 | ||||
|     def list_templates(self): | ||||
|     def list_templates(self) -> List[Dict[str, str]]: | ||||
|         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 = self.templates.get(template_name, None) | ||||
|         if not template: | ||||
| @ -451,7 +485,7 @@ class SESBackend(BaseBackend): | ||||
| 
 | ||||
|         template_data = render_data.get("data") | ||||
|         try: | ||||
|             template_data = json.loads(template_data) | ||||
|             template_data = json.loads(template_data)  # type: ignore | ||||
|         except ValueError: | ||||
|             raise InvalidRenderingParameterException( | ||||
|                 "Template rendering data is invalid" | ||||
| @ -484,12 +518,12 @@ class SESBackend(BaseBackend): | ||||
|         ) | ||||
|         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: | ||||
|             raise RuleSetNameAlreadyExists("Duplicate Receipt 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) | ||||
|         if rule_set is None: | ||||
|             raise RuleSetDoesNotExist("Invalid Rule Set Name.") | ||||
| @ -498,7 +532,7 @@ class SESBackend(BaseBackend): | ||||
|         rule_set.append(rule) | ||||
|         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) | ||||
| 
 | ||||
|         if rule_set is None: | ||||
| @ -506,7 +540,9 @@ class SESBackend(BaseBackend): | ||||
| 
 | ||||
|         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) | ||||
| 
 | ||||
|         if rule_set is None: | ||||
| @ -518,7 +554,7 @@ class SESBackend(BaseBackend): | ||||
| 
 | ||||
|         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) | ||||
| 
 | ||||
|         if rule_set is None: | ||||
| @ -532,8 +568,11 @@ class SESBackend(BaseBackend): | ||||
|             raise RuleDoesNotExist(f"Rule does not exist: {rule['name']}") | ||||
| 
 | ||||
|     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): | ||||
|             raise InvalidParameterValue(f"Identity '{identity}' does not exist.") | ||||
| 
 | ||||
| @ -560,7 +599,9 @@ class SESBackend(BaseBackend): | ||||
|             "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: | ||||
|             identities = [] | ||||
| 
 | ||||
| @ -573,11 +614,13 @@ class SESBackend(BaseBackend): | ||||
| 
 | ||||
|         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: | ||||
|             identities = [] | ||||
| 
 | ||||
|         attributes_by_identity = {} | ||||
|         attributes_by_identity: Dict[str, str] = {} | ||||
|         for identity in identities: | ||||
|             if identity in (self.domains + self.addresses): | ||||
|                 attributes_by_identity[identity] = "Success" | ||||
| @ -585,4 +628,4 @@ class SESBackend(BaseBackend): | ||||
|         return attributes_by_identity | ||||
| 
 | ||||
| 
 | ||||
| ses_backends: Mapping[str, SESBackend] = BackendDict(SESBackend, "ses") | ||||
| ses_backends = BackendDict(SESBackend, "ses") | ||||
|  | ||||
| @ -1,66 +1,71 @@ | ||||
| import base64 | ||||
| from typing import Any, Dict, List | ||||
| 
 | ||||
| from moto.core.responses import BaseResponse | ||||
| from .models import ses_backends | ||||
| from .models import ses_backends, SESBackend | ||||
| from datetime import datetime | ||||
| 
 | ||||
| 
 | ||||
| class EmailResponse(BaseResponse): | ||||
|     def __init__(self): | ||||
|     def __init__(self) -> None: | ||||
|         super().__init__(service_name="ses") | ||||
| 
 | ||||
|     @property | ||||
|     def backend(self): | ||||
|     def backend(self) -> SESBackend: | ||||
|         return ses_backends[self.current_account][self.region] | ||||
| 
 | ||||
|     def verify_email_identity(self): | ||||
|         address = self.querystring.get("EmailAddress")[0] | ||||
|     def verify_email_identity(self) -> str: | ||||
|         address = self.querystring.get("EmailAddress")[0]  # type: ignore | ||||
|         self.backend.verify_email_identity(address) | ||||
|         template = self.response_template(VERIFY_EMAIL_IDENTITY) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def verify_email_address(self): | ||||
|         address = self.querystring.get("EmailAddress")[0] | ||||
|     def verify_email_address(self) -> str: | ||||
|         address = self.querystring.get("EmailAddress")[0]  # type: ignore | ||||
|         self.backend.verify_email_address(address) | ||||
|         template = self.response_template(VERIFY_EMAIL_ADDRESS) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def list_identities(self): | ||||
|     def list_identities(self) -> str: | ||||
|         identities = self.backend.list_identities() | ||||
|         template = self.response_template(LIST_IDENTITIES_RESPONSE) | ||||
|         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() | ||||
|         template = self.response_template(LIST_VERIFIED_EMAIL_RESPONSE) | ||||
|         return template.render(email_addresses=email_addresses) | ||||
| 
 | ||||
|     def verify_domain_dkim(self): | ||||
|         domain = self.querystring.get("Domain")[0] | ||||
|     def verify_domain_dkim(self) -> str: | ||||
|         domain = self.querystring.get("Domain")[0]  # type: ignore | ||||
|         self.backend.verify_domain(domain) | ||||
|         template = self.response_template(VERIFY_DOMAIN_DKIM_RESPONSE) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def verify_domain_identity(self): | ||||
|         domain = self.querystring.get("Domain")[0] | ||||
|     def verify_domain_identity(self) -> str: | ||||
|         domain = self.querystring.get("Domain")[0]  # type: ignore | ||||
|         self.backend.verify_domain(domain) | ||||
|         template = self.response_template(VERIFY_DOMAIN_IDENTITY_RESPONSE) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def delete_identity(self): | ||||
|         domain = self.querystring.get("Identity")[0] | ||||
|     def delete_identity(self) -> str: | ||||
|         domain = self.querystring.get("Identity")[0]  # type: ignore | ||||
|         self.backend.delete_identity(domain) | ||||
|         template = self.response_template(DELETE_IDENTITY_RESPONSE) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def send_email(self): | ||||
|     def send_email(self) -> str: | ||||
|         bodydatakey = "Message.Body.Text.Data" | ||||
|         if "Message.Body.Html.Data" in self.querystring: | ||||
|             bodydatakey = "Message.Body.Html.Data" | ||||
|         body = self.querystring.get(bodydatakey)[0] | ||||
|         source = self.querystring.get("Source")[0] | ||||
|         subject = self.querystring.get("Message.Subject.Data")[0] | ||||
|         destinations = {"ToAddresses": [], "CcAddresses": [], "BccAddresses": []} | ||||
|         body = self.querystring.get(bodydatakey)[0]  # type: ignore | ||||
|         source = self.querystring.get("Source")[0]  # type: ignore | ||||
|         subject = self.querystring.get("Message.Subject.Data")[0]  # type: ignore | ||||
|         destinations: Dict[str, List[str]] = { | ||||
|             "ToAddresses": [], | ||||
|             "CcAddresses": [], | ||||
|             "BccAddresses": [], | ||||
|         } | ||||
|         for dest_type in destinations: | ||||
|             # consume up to 51 to allow exception | ||||
|             for i in range(1, 52): | ||||
| @ -70,18 +75,20 @@ class EmailResponse(BaseResponse): | ||||
|                     break | ||||
|                 destinations[dest_type].append(address[0]) | ||||
| 
 | ||||
|         message = self.backend.send_email( | ||||
|             source, subject, body, destinations, self.region | ||||
|         ) | ||||
|         message = self.backend.send_email(source, subject, body, destinations) | ||||
|         template = self.response_template(SEND_EMAIL_RESPONSE) | ||||
|         return template.render(message=message) | ||||
| 
 | ||||
|     def send_templated_email(self): | ||||
|         source = self.querystring.get("Source")[0] | ||||
|         template = self.querystring.get("Template") | ||||
|         template_data = self.querystring.get("TemplateData") | ||||
|     def send_templated_email(self) -> str: | ||||
|         source = self.querystring.get("Source")[0]  # type: ignore | ||||
|         template: List[str] = self.querystring.get("Template")  # type: ignore | ||||
|         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: | ||||
|             # consume up to 51 to allow exception | ||||
|             for i in range(1, 52): | ||||
| @ -92,13 +99,14 @@ class EmailResponse(BaseResponse): | ||||
|                 destinations[dest_type].append(address[0]) | ||||
| 
 | ||||
|         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): | ||||
|         source = self.querystring.get("Source")[0] | ||||
|     def send_bulk_templated_email(self) -> str: | ||||
|         source = self.querystring.get("Source")[0]  # type: ignore | ||||
|         template = self.querystring.get("Template") | ||||
|         template_data = self.querystring.get("DefaultTemplateData") | ||||
| 
 | ||||
| @ -109,7 +117,11 @@ class EmailResponse(BaseResponse): | ||||
|             ) | ||||
|             if self.querystring.get(destination_field) is None: | ||||
|                 break | ||||
|             destination = {"ToAddresses": [], "CcAddresses": [], "BccAddresses": []} | ||||
|             destination: Dict[str, List[str]] = { | ||||
|                 "ToAddresses": [], | ||||
|                 "CcAddresses": [], | ||||
|                 "BccAddresses": [], | ||||
|             } | ||||
|             for dest_type in destination: | ||||
|                 # consume up to 51 to allow exception | ||||
|                 for j in range(1, 52): | ||||
| @ -123,18 +135,18 @@ class EmailResponse(BaseResponse): | ||||
|             destinations.append({"Destination": destination}) | ||||
| 
 | ||||
|         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) | ||||
|         result = template.render(message=message) | ||||
|         return result | ||||
| 
 | ||||
|     def send_raw_email(self): | ||||
|     def send_raw_email(self) -> str: | ||||
|         source = self.querystring.get("Source") | ||||
|         if source is not None: | ||||
|             (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 = raw_data.decode("utf-8") | ||||
|         destinations = [] | ||||
| @ -146,35 +158,33 @@ class EmailResponse(BaseResponse): | ||||
|                 break | ||||
|             destinations.append(address[0]) | ||||
| 
 | ||||
|         message = self.backend.send_raw_email( | ||||
|             source, destinations, raw_data, self.region | ||||
|         ) | ||||
|         message = self.backend.send_raw_email(source, destinations, raw_data) | ||||
|         template = self.response_template(SEND_RAW_EMAIL_RESPONSE) | ||||
|         return template.render(message=message) | ||||
| 
 | ||||
|     def get_send_quota(self): | ||||
|     def get_send_quota(self) -> str: | ||||
|         quota = self.backend.get_send_quota() | ||||
|         template = self.response_template(GET_SEND_QUOTA_RESPONSE) | ||||
|         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.backend.get_identity_notification_attributes(identities) | ||||
|         template = self.response_template(GET_IDENTITY_NOTIFICATION_ATTRIBUTES) | ||||
|         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") | ||||
|         enabled = self._get_bool_param("ForwardingEnabled") | ||||
|         self.backend.set_identity_feedback_forwarding_enabled(identity, enabled) | ||||
|         template = self.response_template(SET_IDENTITY_FORWARDING_ENABLED_RESPONSE) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def set_identity_notification_topic(self): | ||||
|     def set_identity_notification_topic(self) -> str: | ||||
| 
 | ||||
|         identity = self.querystring.get("Identity")[0] | ||||
|         not_type = self.querystring.get("NotificationType")[0] | ||||
|         sns_topic = self.querystring.get("SnsTopic") | ||||
|         identity = self.querystring.get("Identity")[0]  # type: ignore | ||||
|         not_type = self.querystring.get("NotificationType")[0]  # type: ignore | ||||
|         sns_topic = self.querystring.get("SnsTopic")  # type: ignore | ||||
|         if sns_topic: | ||||
|             sns_topic = sns_topic[0] | ||||
| 
 | ||||
| @ -182,33 +192,35 @@ class EmailResponse(BaseResponse): | ||||
|         template = self.response_template(SET_IDENTITY_NOTIFICATION_TOPIC_RESPONSE) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def get_send_statistics(self): | ||||
|     def get_send_statistics(self) -> str: | ||||
|         statistics = self.backend.get_send_statistics() | ||||
|         template = self.response_template(GET_SEND_STATISTICS) | ||||
|         return template.render(all_statistics=[statistics]) | ||||
| 
 | ||||
|     def create_configuration_set(self): | ||||
|         configuration_set_name = self.querystring.get("ConfigurationSet.Name")[0] | ||||
|     def create_configuration_set(self) -> str: | ||||
|         configuration_set_name = self.querystring.get("ConfigurationSet.Name")[0]  # type: ignore | ||||
|         self.backend.create_configuration_set( | ||||
|             configuration_set_name=configuration_set_name | ||||
|         ) | ||||
|         template = self.response_template(CREATE_CONFIGURATION_SET) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def describe_configuration_set(self): | ||||
|         configuration_set_name = self.querystring.get("ConfigurationSetName")[0] | ||||
|     def describe_configuration_set(self) -> str: | ||||
|         configuration_set_name = self.querystring.get("ConfigurationSetName")[0]  # type: ignore | ||||
|         self.backend.describe_configuration_set(configuration_set_name) | ||||
|         template = self.response_template(DESCRIBE_CONFIGURATION_SET) | ||||
|         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( | ||||
|             "EventDestination.Enabled" | ||||
|         )[0] | ||||
|         configuration_event_name = self.querystring.get("EventDestination.Name")[0] | ||||
|         event_topic_arn = self.querystring.get( | ||||
|         )[ | ||||
|             0 | ||||
|         ]  # type: ignore | ||||
|         configuration_event_name = self.querystring.get("EventDestination.Name")[0]  # type: ignore | ||||
|         event_topic_arn = self.querystring.get(  # type: ignore | ||||
|             "EventDestination.SNSDestination.TopicARN" | ||||
|         )[0] | ||||
|         event_matching_types = self._get_multi_param( | ||||
| @ -230,7 +242,7 @@ class EmailResponse(BaseResponse): | ||||
|         template = self.response_template(CREATE_CONFIGURATION_SET_EVENT_DESTINATION) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def create_template(self): | ||||
|     def create_template(self) -> str: | ||||
|         template_data = self._get_dict_param("Template") | ||||
|         template_info = {} | ||||
|         template_info["text_part"] = template_data.get("._text_part", "") | ||||
| @ -242,7 +254,7 @@ class EmailResponse(BaseResponse): | ||||
|         template = self.response_template(CREATE_TEMPLATE) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def update_template(self): | ||||
|     def update_template(self) -> str: | ||||
|         template_data = self._get_dict_param("Template") | ||||
|         template_info = {} | ||||
|         template_info["text_part"] = template_data.get("._text_part", "") | ||||
| @ -254,43 +266,43 @@ class EmailResponse(BaseResponse): | ||||
|         template = self.response_template(UPDATE_TEMPLATE) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def get_template(self): | ||||
|     def get_template(self) -> str: | ||||
|         template_name = self._get_param("TemplateName") | ||||
|         template_data = self.backend.get_template(template_name) | ||||
|         template = self.response_template(GET_TEMPLATE) | ||||
|         return template.render(template_data=template_data) | ||||
| 
 | ||||
|     def list_templates(self): | ||||
|     def list_templates(self) -> str: | ||||
|         email_templates = self.backend.list_templates() | ||||
|         template = self.response_template(LIST_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") | ||||
|         rendered_template = self.backend.render_template(render_info) | ||||
|         template = self.response_template(RENDER_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") | ||||
|         self.backend.create_receipt_rule_set(rule_set_name) | ||||
|         template = self.response_template(CREATE_RECEIPT_RULE_SET) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def create_receipt_rule(self): | ||||
|     def create_receipt_rule(self) -> str: | ||||
|         rule_set_name = self._get_param("RuleSetName") | ||||
|         rule = self._get_dict_param("Rule.") | ||||
|         self.backend.create_receipt_rule(rule_set_name, rule) | ||||
|         template = self.response_template(CREATE_RECEIPT_RULE) | ||||
|         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 = self.backend.describe_receipt_rule_set(rule_set_name) | ||||
| 
 | ||||
|         for i, rule in enumerate(rule_set): | ||||
|             formatted_rule = {} | ||||
|             formatted_rule: Dict[str, Any] = {} | ||||
| 
 | ||||
|             for k, v in rule.items(): | ||||
|                 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) | ||||
| 
 | ||||
|     def describe_receipt_rule(self): | ||||
|     def describe_receipt_rule(self) -> str: | ||||
|         rule_set_name = self._get_param("RuleSetName") | ||||
|         rule_name = self._get_param("RuleName") | ||||
| 
 | ||||
|         receipt_rule = self.backend.describe_receipt_rule(rule_set_name, rule_name) | ||||
| 
 | ||||
|         rule = {} | ||||
|         rule: Dict[str, Any] = {} | ||||
| 
 | ||||
|         for k, v in receipt_rule.items(): | ||||
|             self._parse_param(k, v, rule) | ||||
| @ -315,7 +327,7 @@ class EmailResponse(BaseResponse): | ||||
|         template = self.response_template(DESCRIBE_RECEIPT_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 = self._get_dict_param("Rule.") | ||||
| 
 | ||||
| @ -324,7 +336,7 @@ class EmailResponse(BaseResponse): | ||||
|         template = self.response_template(UPDATE_RECEIPT_RULE) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def set_identity_mail_from_domain(self): | ||||
|     def set_identity_mail_from_domain(self) -> str: | ||||
|         identity = self._get_param("Identity") | ||||
|         mail_from_domain = self._get_param("MailFromDomain") | ||||
|         behavior_on_mx_failure = self._get_param("BehaviorOnMXFailure") | ||||
| @ -336,14 +348,16 @@ class EmailResponse(BaseResponse): | ||||
|         template = self.response_template(SET_IDENTITY_MAIL_FROM_DOMAIN) | ||||
|         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.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) | ||||
| 
 | ||||
|         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() | ||||
|         identities = params.get("Identities") | ||||
|         verification_attributes = self.backend.get_identity_verification_attributes( | ||||
|  | ||||
| @ -1,8 +1,15 @@ | ||||
| from typing import Any, Dict, Optional | ||||
| 
 | ||||
| from .exceptions import MissingRenderingAttributeException | ||||
| 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) | ||||
|     state = "" | ||||
|     parsed = "" | ||||
| @ -27,7 +34,7 @@ def parse_template(template, template_data, tokenizer=None, until=None): | ||||
|                 if state == "VAR": | ||||
|                     if template_data.get(var_name) is None: | ||||
|                         raise MissingRenderingAttributeException(var_name) | ||||
|                     parsed += template_data.get(var_name) | ||||
|                     parsed += template_data.get(var_name)  # type: ignore | ||||
|                 else: | ||||
|                     current_position = tokenizer.token_pos | ||||
|                     for item in template_data.get(var_name, []): | ||||
|  | ||||
| @ -1,19 +1,21 @@ | ||||
| import string | ||||
| from typing import Optional | ||||
| 
 | ||||
| from email.utils import parseaddr | ||||
| 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)) | ||||
| 
 | ||||
| 
 | ||||
| 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)}" | ||||
| 
 | ||||
| 
 | ||||
| def is_valid_address(addr): | ||||
| def is_valid_address(addr: str) -> Optional[str]: | ||||
|     _, address = parseaddr(addr) | ||||
|     address = address.split("@") | ||||
|     if len(address) != 2 or not address[1]: | ||||
|         return False, "Missing domain" | ||||
|     return True, None | ||||
|     address_parts = address.split("@") | ||||
|     if len(address_parts) != 2 or not address_parts[1]: | ||||
|         return "Missing domain" | ||||
|     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 | ||||
| 
 | ||||
| [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_error_codes = True | ||||
| disable_error_code=abstract | ||||
|  | ||||
| @ -4,14 +4,11 @@ from moto.ses.utils import is_valid_address | ||||
| 
 | ||||
| 
 | ||||
| def test_is_valid_address(): | ||||
|     valid, msg = is_valid_address("test@example.com") | ||||
|     valid.should.equal(True) | ||||
|     msg = is_valid_address("test@example.com") | ||||
|     msg.should.equal(None) | ||||
| 
 | ||||
|     valid, msg = is_valid_address("test@") | ||||
|     valid.should.equal(False) | ||||
|     msg = is_valid_address("test@") | ||||
|     msg.should.be.a(str) | ||||
| 
 | ||||
|     valid, msg = is_valid_address("test") | ||||
|     valid.should.equal(False) | ||||
|     msg = is_valid_address("test") | ||||
|     msg.should.be.a(str) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user