Techdebt: MyPy SNS (#6258)
This commit is contained in:
parent
9b969f7e3f
commit
37f1456747
@ -1,8 +1,9 @@
|
||||
from typing import Any, Optional
|
||||
from moto.core.exceptions import RESTError
|
||||
|
||||
|
||||
class SNSException(RESTError):
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, *args: Any, **kwargs: Any):
|
||||
kwargs["template"] = "wrapped_single_error"
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@ -10,54 +11,54 @@ class SNSException(RESTError):
|
||||
class SNSNotFoundError(SNSException):
|
||||
code = 404
|
||||
|
||||
def __init__(self, message, **kwargs):
|
||||
super().__init__("NotFound", message, **kwargs)
|
||||
def __init__(self, message: str, template: Optional[str] = None):
|
||||
super().__init__("NotFound", message, template=template)
|
||||
|
||||
|
||||
class TopicNotFound(SNSNotFoundError):
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(message="Topic does not exist")
|
||||
|
||||
|
||||
class ResourceNotFoundError(SNSException):
|
||||
code = 404
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("ResourceNotFound", "Resource does not exist")
|
||||
|
||||
|
||||
class DuplicateSnsEndpointError(SNSException):
|
||||
code = 400
|
||||
|
||||
def __init__(self, message):
|
||||
def __init__(self, message: str):
|
||||
super().__init__("DuplicateEndpoint", message)
|
||||
|
||||
|
||||
class SnsEndpointDisabled(SNSException):
|
||||
code = 400
|
||||
|
||||
def __init__(self, message):
|
||||
def __init__(self, message: str):
|
||||
super().__init__("EndpointDisabled", message)
|
||||
|
||||
|
||||
class SNSInvalidParameter(SNSException):
|
||||
code = 400
|
||||
|
||||
def __init__(self, message):
|
||||
def __init__(self, message: str):
|
||||
super().__init__("InvalidParameter", message)
|
||||
|
||||
|
||||
class InvalidParameterValue(SNSException):
|
||||
code = 400
|
||||
|
||||
def __init__(self, message):
|
||||
def __init__(self, message: str):
|
||||
super().__init__("InvalidParameterValue", message)
|
||||
|
||||
|
||||
class TagLimitExceededError(SNSException):
|
||||
code = 400
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
"TagLimitExceeded",
|
||||
"Could not complete request: tag quota of per resource exceeded",
|
||||
@ -67,14 +68,14 @@ class TagLimitExceededError(SNSException):
|
||||
class InternalError(SNSException):
|
||||
code = 500
|
||||
|
||||
def __init__(self, message):
|
||||
def __init__(self, message: str):
|
||||
super().__init__("InternalFailure", message)
|
||||
|
||||
|
||||
class TooManyEntriesInBatchRequest(SNSException):
|
||||
code = 400
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
"TooManyEntriesInBatchRequest",
|
||||
"The batch request contains more entries than permissible.",
|
||||
@ -84,7 +85,7 @@ class TooManyEntriesInBatchRequest(SNSException):
|
||||
class BatchEntryIdsNotDistinct(SNSException):
|
||||
code = 400
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
"BatchEntryIdsNotDistinct",
|
||||
"Two or more batch entries in the request have the same Id.",
|
||||
|
@ -1,10 +1,11 @@
|
||||
import datetime
|
||||
import json
|
||||
|
||||
import requests
|
||||
import re
|
||||
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Dict, List, Iterable, Optional, Tuple
|
||||
|
||||
from moto.core import BaseBackend, BackendDict, BaseModel, CloudFormationModel
|
||||
from moto.core.utils import (
|
||||
iso_8601_datetime_with_milliseconds,
|
||||
@ -36,7 +37,7 @@ MAXIMUM_SMS_MESSAGE_BYTES = 1600 # Amazon limit for a single publish SMS action
|
||||
|
||||
|
||||
class Topic(CloudFormationModel):
|
||||
def __init__(self, name, sns_backend):
|
||||
def __init__(self, name: str, sns_backend: "SNSBackend"):
|
||||
self.name = name
|
||||
self.sns_backend = sns_backend
|
||||
self.account_id = sns_backend.account_id
|
||||
@ -49,23 +50,25 @@ class Topic(CloudFormationModel):
|
||||
self.subscriptions_pending = 0
|
||||
self.subscriptions_confimed = 0
|
||||
self.subscriptions_deleted = 0
|
||||
self.sent_notifications = []
|
||||
self.sent_notifications: List[
|
||||
Tuple[str, str, Optional[str], Optional[Dict[str, Any]], Optional[str]]
|
||||
] = []
|
||||
|
||||
self._policy_json = self._create_default_topic_policy(
|
||||
sns_backend.region_name, self.account_id, name
|
||||
)
|
||||
self._tags = {}
|
||||
self._tags: Dict[str, str] = {}
|
||||
self.fifo_topic = "false"
|
||||
self.content_based_deduplication = "false"
|
||||
|
||||
def publish(
|
||||
self,
|
||||
message,
|
||||
subject=None,
|
||||
message_attributes=None,
|
||||
group_id=None,
|
||||
deduplication_id=None,
|
||||
):
|
||||
message: str,
|
||||
subject: Optional[str] = None,
|
||||
message_attributes: Optional[Dict[str, Any]] = None,
|
||||
group_id: Optional[str] = None,
|
||||
deduplication_id: Optional[str] = None,
|
||||
) -> str:
|
||||
message_id = str(mock_random.uuid4())
|
||||
subscriptions, _ = self.sns_backend.list_subscriptions(self.arn)
|
||||
for subscription in subscriptions:
|
||||
@ -83,10 +86,10 @@ class Topic(CloudFormationModel):
|
||||
return message_id
|
||||
|
||||
@classmethod
|
||||
def has_cfn_attr(cls, attr):
|
||||
def has_cfn_attr(cls, attr: str) -> bool:
|
||||
return attr in ["TopicName"]
|
||||
|
||||
def get_cfn_attribute(self, attribute_name):
|
||||
def get_cfn_attribute(self, attribute_name: str) -> str:
|
||||
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
||||
|
||||
if attribute_name == "TopicName":
|
||||
@ -94,30 +97,35 @@ class Topic(CloudFormationModel):
|
||||
raise UnformattedGetAttTemplateException()
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
def physical_resource_id(self) -> str:
|
||||
return self.arn
|
||||
|
||||
@property
|
||||
def policy(self):
|
||||
def policy(self) -> str:
|
||||
return json.dumps(self._policy_json, separators=(",", ":"))
|
||||
|
||||
@policy.setter
|
||||
def policy(self, policy):
|
||||
def policy(self, policy: Any) -> None: # type: ignore[misc]
|
||||
self._policy_json = json.loads(policy)
|
||||
|
||||
@staticmethod
|
||||
def cloudformation_name_type():
|
||||
def cloudformation_name_type() -> str:
|
||||
return "TopicName"
|
||||
|
||||
@staticmethod
|
||||
def cloudformation_type():
|
||||
def cloudformation_type() -> str:
|
||||
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sns-topic.html
|
||||
return "AWS::SNS::Topic"
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(
|
||||
cls, resource_name, cloudformation_json, account_id, region_name, **kwargs
|
||||
):
|
||||
def create_from_cloudformation_json( # type: ignore[misc]
|
||||
cls,
|
||||
resource_name: str,
|
||||
cloudformation_json: Any,
|
||||
account_id: str,
|
||||
region_name: str,
|
||||
**kwargs: Any,
|
||||
) -> "Topic":
|
||||
sns_backend = sns_backends[account_id][region_name]
|
||||
properties = cloudformation_json["Properties"]
|
||||
|
||||
@ -129,14 +137,14 @@ class Topic(CloudFormationModel):
|
||||
return topic
|
||||
|
||||
@classmethod
|
||||
def update_from_cloudformation_json(
|
||||
def update_from_cloudformation_json( # type: ignore[misc]
|
||||
cls,
|
||||
original_resource,
|
||||
new_resource_name,
|
||||
cloudformation_json,
|
||||
account_id,
|
||||
region_name,
|
||||
):
|
||||
original_resource: Any,
|
||||
new_resource_name: str,
|
||||
cloudformation_json: Any,
|
||||
account_id: str,
|
||||
region_name: str,
|
||||
) -> "Topic":
|
||||
cls.delete_from_cloudformation_json(
|
||||
original_resource.name, cloudformation_json, account_id, region_name
|
||||
)
|
||||
@ -145,9 +153,13 @@ class Topic(CloudFormationModel):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def delete_from_cloudformation_json(
|
||||
cls, resource_name, cloudformation_json, account_id, region_name
|
||||
):
|
||||
def delete_from_cloudformation_json( # type: ignore[misc]
|
||||
cls,
|
||||
resource_name: str,
|
||||
cloudformation_json: Any,
|
||||
account_id: str,
|
||||
region_name: str,
|
||||
) -> None:
|
||||
sns_backend = sns_backends[account_id][region_name]
|
||||
properties = cloudformation_json["Properties"]
|
||||
|
||||
@ -158,7 +170,9 @@ class Topic(CloudFormationModel):
|
||||
sns_backend.unsubscribe(subscription.arn)
|
||||
sns_backend.delete_topic(topic_arn)
|
||||
|
||||
def _create_default_topic_policy(self, region_name, account_id, name):
|
||||
def _create_default_topic_policy(
|
||||
self, region_name: str, account_id: str, name: str
|
||||
) -> Dict[str, Any]:
|
||||
return {
|
||||
"Version": "2008-10-17",
|
||||
"Id": "__default_policy_ID",
|
||||
@ -186,25 +200,25 @@ class Topic(CloudFormationModel):
|
||||
|
||||
|
||||
class Subscription(BaseModel):
|
||||
def __init__(self, account_id, topic, endpoint, protocol):
|
||||
def __init__(self, account_id: str, topic: Topic, endpoint: str, protocol: str):
|
||||
self.account_id = account_id
|
||||
self.topic = topic
|
||||
self.endpoint = endpoint
|
||||
self.protocol = protocol
|
||||
self.arn = make_arn_for_subscription(self.topic.arn)
|
||||
self.attributes = {}
|
||||
self.attributes: Dict[str, Any] = {}
|
||||
self._filter_policy = None # filter policy as a dict, not json.
|
||||
self.confirmed = False
|
||||
|
||||
def publish(
|
||||
self,
|
||||
message,
|
||||
message_id,
|
||||
subject=None,
|
||||
message_attributes=None,
|
||||
group_id=None,
|
||||
deduplication_id=None,
|
||||
):
|
||||
message: str,
|
||||
message_id: str,
|
||||
subject: Optional[str] = None,
|
||||
message_attributes: Optional[Dict[str, Any]] = None,
|
||||
group_id: Optional[str] = None,
|
||||
deduplication_id: Optional[str] = None,
|
||||
) -> None:
|
||||
if not self._matches_filter_policy(message_attributes):
|
||||
return
|
||||
|
||||
@ -230,7 +244,7 @@ class Subscription(BaseModel):
|
||||
)
|
||||
else:
|
||||
raw_message_attributes = {}
|
||||
for key, value in message_attributes.items():
|
||||
for key, value in message_attributes.items(): # type: ignore
|
||||
attr_type = "string_value"
|
||||
type_value = value["Value"]
|
||||
if value["Type"].startswith("Binary"):
|
||||
@ -279,14 +293,18 @@ class Subscription(BaseModel):
|
||||
function_name, message, subject=subject, qualifier=qualifier
|
||||
)
|
||||
|
||||
def _matches_filter_policy(self, message_attributes):
|
||||
def _matches_filter_policy(
|
||||
self, message_attributes: Optional[Dict[str, Any]]
|
||||
) -> bool:
|
||||
if not self._filter_policy:
|
||||
return True
|
||||
|
||||
if message_attributes is None:
|
||||
message_attributes = {}
|
||||
|
||||
def _field_match(field, rules, message_attributes):
|
||||
def _field_match(
|
||||
field: str, rules: List[Any], message_attributes: Dict[str, Any]
|
||||
) -> bool:
|
||||
for rule in rules:
|
||||
# TODO: boolean value matching is not supported, SNS behavior unknown
|
||||
if isinstance(rule, str):
|
||||
@ -400,8 +418,14 @@ class Subscription(BaseModel):
|
||||
for field, rules in self._filter_policy.items()
|
||||
)
|
||||
|
||||
def get_post_data(self, message, message_id, subject, message_attributes=None):
|
||||
post_data = {
|
||||
def get_post_data(
|
||||
self,
|
||||
message: str,
|
||||
message_id: str,
|
||||
subject: Optional[str],
|
||||
message_attributes: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
post_data: Dict[str, Any] = {
|
||||
"Type": "Notification",
|
||||
"MessageId": message_id,
|
||||
"TopicArn": self.topic.arn,
|
||||
@ -422,7 +446,14 @@ class Subscription(BaseModel):
|
||||
|
||||
|
||||
class PlatformApplication(BaseModel):
|
||||
def __init__(self, account_id, region, name, platform, attributes):
|
||||
def __init__(
|
||||
self,
|
||||
account_id: str,
|
||||
region: str,
|
||||
name: str,
|
||||
platform: str,
|
||||
attributes: Dict[str, str],
|
||||
):
|
||||
self.region = region
|
||||
self.name = name
|
||||
self.platform = platform
|
||||
@ -432,7 +463,13 @@ class PlatformApplication(BaseModel):
|
||||
|
||||
class PlatformEndpoint(BaseModel):
|
||||
def __init__(
|
||||
self, account_id, region, application, custom_user_data, token, attributes
|
||||
self,
|
||||
account_id: str,
|
||||
region: str,
|
||||
application: PlatformApplication,
|
||||
custom_user_data: str,
|
||||
token: str,
|
||||
attributes: Dict[str, str],
|
||||
):
|
||||
self.region = region
|
||||
self.application = application
|
||||
@ -441,10 +478,10 @@ class PlatformEndpoint(BaseModel):
|
||||
self.attributes = attributes
|
||||
self.id = mock_random.uuid4()
|
||||
self.arn = f"arn:aws:sns:{region}:{account_id}:endpoint/{self.application.platform}/{self.application.name}/{self.id}"
|
||||
self.messages = OrderedDict()
|
||||
self.messages: Dict[str, str] = OrderedDict()
|
||||
self.__fixup_attributes()
|
||||
|
||||
def __fixup_attributes(self):
|
||||
def __fixup_attributes(self) -> None:
|
||||
# When AWS returns the attributes dict, it always contains these two elements, so we need to
|
||||
# automatically ensure they exist as well.
|
||||
if "Token" not in self.attributes:
|
||||
@ -456,10 +493,10 @@ class PlatformEndpoint(BaseModel):
|
||||
self.attributes["Enabled"] = "true"
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
def enabled(self) -> bool:
|
||||
return json.loads(self.attributes.get("Enabled", "true").lower())
|
||||
|
||||
def publish(self, message):
|
||||
def publish(self, message: str) -> str:
|
||||
if not self.enabled:
|
||||
raise SnsEndpointDisabled(f"Endpoint {self.id} disabled")
|
||||
|
||||
@ -485,15 +522,15 @@ class SNSBackend(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.topics = OrderedDict()
|
||||
self.topics: Dict[str, Topic] = OrderedDict()
|
||||
self.subscriptions: OrderedDict[str, Subscription] = OrderedDict()
|
||||
self.applications = {}
|
||||
self.platform_endpoints = {}
|
||||
self.applications: Dict[str, PlatformApplication] = {}
|
||||
self.platform_endpoints: Dict[str, PlatformEndpoint] = {}
|
||||
self.region_name = region_name
|
||||
self.sms_attributes = {}
|
||||
self.sms_messages = OrderedDict()
|
||||
self.sms_attributes: Dict[str, str] = {}
|
||||
self.sms_messages: Dict[str, Tuple[str, str]] = OrderedDict()
|
||||
self.opt_out_numbers = [
|
||||
"+447420500600",
|
||||
"+447420505401",
|
||||
@ -506,23 +543,27 @@ class SNSBackend(BaseBackend):
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def default_vpc_endpoint_service(service_region, zones):
|
||||
def default_vpc_endpoint_service(
|
||||
service_region: str, zones: List[str]
|
||||
) -> List[Dict[str, str]]:
|
||||
"""List of dicts representing default VPC endpoints for this service."""
|
||||
return BaseBackend.default_vpc_endpoint_service_factory(
|
||||
service_region, zones, "sns"
|
||||
)
|
||||
|
||||
def update_sms_attributes(self, attrs):
|
||||
def update_sms_attributes(self, attrs: Dict[str, str]) -> None:
|
||||
self.sms_attributes.update(attrs)
|
||||
|
||||
def create_topic(self, name, attributes=None, tags=None):
|
||||
def create_topic(
|
||||
self,
|
||||
name: str,
|
||||
attributes: Optional[Dict[str, str]] = None,
|
||||
tags: Optional[Dict[str, str]] = None,
|
||||
) -> Topic:
|
||||
|
||||
if attributes is None:
|
||||
attributes = {}
|
||||
if (
|
||||
attributes.get("FifoTopic")
|
||||
and attributes.get("FifoTopic").lower() == "true"
|
||||
):
|
||||
if attributes.get("FifoTopic") and attributes["FifoTopic"].lower() == "true":
|
||||
fails_constraints = not re.match(r"^[a-zA-Z0-9_-]{1,256}\.fifo$", name)
|
||||
msg = "Fifo Topic names must end with .fifo and must be made up of only uppercase and lowercase ASCII letters, numbers, underscores, and hyphens, and must be between 1 and 256 characters long."
|
||||
|
||||
@ -549,29 +590,33 @@ class SNSBackend(BaseBackend):
|
||||
self.topics[candidate_topic.arn] = candidate_topic
|
||||
return candidate_topic
|
||||
|
||||
def _get_values_nexttoken(self, values_map, next_token=None):
|
||||
if next_token is None or not next_token:
|
||||
next_token = 0
|
||||
next_token = int(next_token)
|
||||
values = list(values_map.values())[next_token : next_token + DEFAULT_PAGE_SIZE]
|
||||
def _get_values_nexttoken(
|
||||
self, values_map: Dict[str, Any], next_token: Optional[str] = None
|
||||
) -> Tuple[List[Any], Optional[int]]:
|
||||
i_next_token = int(next_token or "0")
|
||||
values = list(values_map.values())[
|
||||
i_next_token : i_next_token + DEFAULT_PAGE_SIZE
|
||||
]
|
||||
if len(values) == DEFAULT_PAGE_SIZE:
|
||||
next_token = next_token + DEFAULT_PAGE_SIZE
|
||||
i_next_token = i_next_token + DEFAULT_PAGE_SIZE
|
||||
else:
|
||||
next_token = None
|
||||
return values, next_token
|
||||
i_next_token = None # type: ignore
|
||||
return values, i_next_token
|
||||
|
||||
def _get_topic_subscriptions(self, topic):
|
||||
def _get_topic_subscriptions(self, topic: Topic) -> List[Subscription]:
|
||||
return [sub for sub in self.subscriptions.values() if sub.topic == topic]
|
||||
|
||||
def list_topics(self, next_token=None):
|
||||
def list_topics(
|
||||
self, next_token: Optional[str] = None
|
||||
) -> Tuple[List[Topic], Optional[int]]:
|
||||
return self._get_values_nexttoken(self.topics, next_token)
|
||||
|
||||
def delete_topic_subscriptions(self, topic):
|
||||
def delete_topic_subscriptions(self, topic: Topic) -> None:
|
||||
for key, value in dict(self.subscriptions).items():
|
||||
if value.topic == topic:
|
||||
self.subscriptions.pop(key)
|
||||
|
||||
def delete_topic(self, arn):
|
||||
def delete_topic(self, arn: str) -> None:
|
||||
try:
|
||||
topic = self.get_topic(arn)
|
||||
self.delete_topic_subscriptions(topic)
|
||||
@ -579,17 +624,19 @@ class SNSBackend(BaseBackend):
|
||||
except KeyError:
|
||||
raise SNSNotFoundError(f"Topic with arn {arn} not found")
|
||||
|
||||
def get_topic(self, arn):
|
||||
def get_topic(self, arn: str) -> Topic:
|
||||
try:
|
||||
return self.topics[arn]
|
||||
except KeyError:
|
||||
raise SNSNotFoundError(f"Topic with arn {arn} not found")
|
||||
|
||||
def set_topic_attribute(self, topic_arn, attribute_name, attribute_value):
|
||||
def set_topic_attribute(
|
||||
self, topic_arn: str, attribute_name: str, attribute_value: str
|
||||
) -> None:
|
||||
topic = self.get_topic(topic_arn)
|
||||
setattr(topic, attribute_name, attribute_value)
|
||||
|
||||
def subscribe(self, topic_arn, endpoint, protocol):
|
||||
def subscribe(self, topic_arn: str, endpoint: str, protocol: str) -> Subscription:
|
||||
if protocol == "sms":
|
||||
if re.search(r"[./-]{2,}", endpoint) or re.search(
|
||||
r"(^[./-]|[./-]$)", endpoint
|
||||
@ -625,7 +672,9 @@ class SNSBackend(BaseBackend):
|
||||
self.subscriptions[subscription.arn] = subscription
|
||||
return subscription
|
||||
|
||||
def _find_subscription(self, topic_arn, endpoint, protocol):
|
||||
def _find_subscription(
|
||||
self, topic_arn: str, endpoint: str, protocol: str
|
||||
) -> Optional[Subscription]:
|
||||
for subscription in self.subscriptions.values():
|
||||
if (
|
||||
subscription.topic.arn == topic_arn
|
||||
@ -635,10 +684,12 @@ class SNSBackend(BaseBackend):
|
||||
return subscription
|
||||
return None
|
||||
|
||||
def unsubscribe(self, subscription_arn):
|
||||
def unsubscribe(self, subscription_arn: str) -> None:
|
||||
self.subscriptions.pop(subscription_arn, None)
|
||||
|
||||
def list_subscriptions(self, topic_arn=None, next_token=None):
|
||||
def list_subscriptions(
|
||||
self, topic_arn: Optional[str] = None, next_token: Optional[str] = None
|
||||
) -> Tuple[List[Subscription], Optional[int]]:
|
||||
if topic_arn:
|
||||
topic = self.get_topic(topic_arn)
|
||||
filtered = OrderedDict(
|
||||
@ -650,14 +701,14 @@ class SNSBackend(BaseBackend):
|
||||
|
||||
def publish(
|
||||
self,
|
||||
message,
|
||||
arn=None,
|
||||
phone_number=None,
|
||||
subject=None,
|
||||
message_attributes=None,
|
||||
group_id=None,
|
||||
deduplication_id=None,
|
||||
):
|
||||
message: str,
|
||||
arn: Optional[str],
|
||||
phone_number: Optional[str] = None,
|
||||
subject: Optional[str] = None,
|
||||
message_attributes: Optional[Dict[str, Any]] = None,
|
||||
group_id: Optional[str] = None,
|
||||
deduplication_id: Optional[str] = None,
|
||||
) -> str:
|
||||
if subject is not None and len(subject) > 100:
|
||||
# Note that the AWS docs around length are wrong: https://github.com/getmoto/moto/issues/1503
|
||||
raise ValueError("Subject must be less than 100 characters")
|
||||
@ -677,7 +728,7 @@ class SNSBackend(BaseBackend):
|
||||
)
|
||||
|
||||
try:
|
||||
topic = self.get_topic(arn)
|
||||
topic = self.get_topic(arn) # type: ignore
|
||||
|
||||
fifo_topic = topic.fifo_topic == "true"
|
||||
if fifo_topic:
|
||||
@ -705,40 +756,48 @@ class SNSBackend(BaseBackend):
|
||||
deduplication_id=deduplication_id,
|
||||
)
|
||||
except SNSNotFoundError:
|
||||
endpoint = self.get_endpoint(arn)
|
||||
endpoint = self.get_endpoint(arn) # type: ignore
|
||||
message_id = endpoint.publish(message)
|
||||
return message_id
|
||||
|
||||
def create_platform_application(self, name, platform, attributes):
|
||||
def create_platform_application(
|
||||
self, name: str, platform: str, attributes: Dict[str, str]
|
||||
) -> PlatformApplication:
|
||||
application = PlatformApplication(
|
||||
self.account_id, self.region_name, name, platform, attributes
|
||||
)
|
||||
self.applications[application.arn] = application
|
||||
return application
|
||||
|
||||
def get_application(self, arn):
|
||||
def get_application(self, arn: str) -> PlatformApplication:
|
||||
try:
|
||||
return self.applications[arn]
|
||||
except KeyError:
|
||||
raise SNSNotFoundError(f"Application with arn {arn} not found")
|
||||
|
||||
def set_application_attributes(self, arn, attributes):
|
||||
def set_application_attributes(
|
||||
self, arn: str, attributes: Dict[str, Any]
|
||||
) -> PlatformApplication:
|
||||
application = self.get_application(arn)
|
||||
application.attributes.update(attributes)
|
||||
return application
|
||||
|
||||
def list_platform_applications(self):
|
||||
def list_platform_applications(self) -> Iterable[PlatformApplication]:
|
||||
return self.applications.values()
|
||||
|
||||
def delete_platform_application(self, platform_arn):
|
||||
def delete_platform_application(self, platform_arn: str) -> None:
|
||||
self.applications.pop(platform_arn)
|
||||
endpoints = self.list_endpoints_by_platform_application(platform_arn)
|
||||
for endpoint in endpoints:
|
||||
self.platform_endpoints.pop(endpoint.arn)
|
||||
|
||||
def create_platform_endpoint(
|
||||
self, application, custom_user_data, token, attributes
|
||||
):
|
||||
self,
|
||||
application: PlatformApplication,
|
||||
custom_user_data: str,
|
||||
token: str,
|
||||
attributes: Dict[str, str],
|
||||
) -> PlatformEndpoint:
|
||||
for endpoint in self.platform_endpoints.values():
|
||||
if token == endpoint.token:
|
||||
if (
|
||||
@ -760,33 +819,37 @@ class SNSBackend(BaseBackend):
|
||||
self.platform_endpoints[platform_endpoint.arn] = platform_endpoint
|
||||
return platform_endpoint
|
||||
|
||||
def list_endpoints_by_platform_application(self, application_arn):
|
||||
def list_endpoints_by_platform_application(
|
||||
self, application_arn: str
|
||||
) -> List[PlatformEndpoint]:
|
||||
return [
|
||||
endpoint
|
||||
for endpoint in self.platform_endpoints.values()
|
||||
if endpoint.application.arn == application_arn
|
||||
]
|
||||
|
||||
def get_endpoint(self, arn):
|
||||
def get_endpoint(self, arn: str) -> PlatformEndpoint:
|
||||
try:
|
||||
return self.platform_endpoints[arn]
|
||||
except KeyError:
|
||||
raise SNSNotFoundError("Endpoint does not exist")
|
||||
|
||||
def set_endpoint_attributes(self, arn, attributes):
|
||||
def set_endpoint_attributes(
|
||||
self, arn: str, attributes: Dict[str, Any]
|
||||
) -> PlatformEndpoint:
|
||||
endpoint = self.get_endpoint(arn)
|
||||
if "Enabled" in attributes:
|
||||
attributes["Enabled"] = attributes["Enabled"].lower()
|
||||
endpoint.attributes.update(attributes)
|
||||
return endpoint
|
||||
|
||||
def delete_endpoint(self, arn):
|
||||
def delete_endpoint(self, arn: str) -> None:
|
||||
try:
|
||||
del self.platform_endpoints[arn]
|
||||
except KeyError:
|
||||
raise SNSNotFoundError(f"Endpoint with arn {arn} not found")
|
||||
|
||||
def get_subscription_attributes(self, arn):
|
||||
def get_subscription_attributes(self, arn: str) -> Dict[str, Any]:
|
||||
subscription = self.subscriptions.get(arn)
|
||||
|
||||
if not subscription:
|
||||
@ -796,7 +859,7 @@ class SNSBackend(BaseBackend):
|
||||
|
||||
return subscription.attributes
|
||||
|
||||
def set_subscription_attributes(self, arn, name, value):
|
||||
def set_subscription_attributes(self, arn: str, name: str, value: Any) -> None:
|
||||
if name not in [
|
||||
"RawMessageDelivery",
|
||||
"DeliveryPolicy",
|
||||
@ -819,7 +882,7 @@ class SNSBackend(BaseBackend):
|
||||
self._validate_filter_policy(filter_policy)
|
||||
subscription._filter_policy = filter_policy
|
||||
|
||||
def _validate_filter_policy(self, value):
|
||||
def _validate_filter_policy(self, value: Any) -> None:
|
||||
# TODO: extend validation checks
|
||||
combinations = 1
|
||||
for rules in value.values():
|
||||
@ -931,7 +994,13 @@ class SNSBackend(BaseBackend):
|
||||
"Invalid parameter: FilterPolicy: Match value must be String, number, true, false, or null"
|
||||
)
|
||||
|
||||
def add_permission(self, topic_arn, label, aws_account_ids, action_names):
|
||||
def add_permission(
|
||||
self,
|
||||
topic_arn: str,
|
||||
label: str,
|
||||
aws_account_ids: List[str],
|
||||
action_names: List[str],
|
||||
) -> None:
|
||||
if topic_arn not in self.topics:
|
||||
raise SNSNotFoundError("Topic does not exist")
|
||||
|
||||
@ -966,7 +1035,7 @@ class SNSBackend(BaseBackend):
|
||||
|
||||
self.topics[topic_arn]._policy_json["Statement"].append(statement)
|
||||
|
||||
def remove_permission(self, topic_arn, label):
|
||||
def remove_permission(self, topic_arn: str, label: str) -> None:
|
||||
if topic_arn not in self.topics:
|
||||
raise SNSNotFoundError("Topic does not exist")
|
||||
|
||||
@ -977,13 +1046,13 @@ class SNSBackend(BaseBackend):
|
||||
|
||||
self.topics[topic_arn]._policy_json["Statement"] = statements
|
||||
|
||||
def list_tags_for_resource(self, resource_arn):
|
||||
def list_tags_for_resource(self, resource_arn: str) -> Dict[str, str]:
|
||||
if resource_arn not in self.topics:
|
||||
raise ResourceNotFoundError
|
||||
|
||||
return self.topics[resource_arn]._tags
|
||||
|
||||
def tag_resource(self, resource_arn, tags):
|
||||
def tag_resource(self, resource_arn: str, tags: Dict[str, str]) -> None:
|
||||
if resource_arn not in self.topics:
|
||||
raise ResourceNotFoundError
|
||||
|
||||
@ -995,14 +1064,16 @@ class SNSBackend(BaseBackend):
|
||||
|
||||
self.topics[resource_arn]._tags = updated_tags
|
||||
|
||||
def untag_resource(self, resource_arn, tag_keys):
|
||||
def untag_resource(self, resource_arn: str, tag_keys: List[str]) -> None:
|
||||
if resource_arn not in self.topics:
|
||||
raise ResourceNotFoundError
|
||||
|
||||
for key in tag_keys:
|
||||
self.topics[resource_arn]._tags.pop(key, None)
|
||||
|
||||
def publish_batch(self, topic_arn, publish_batch_request_entries):
|
||||
def publish_batch(
|
||||
self, topic_arn: str, publish_batch_request_entries: List[Dict[str, Any]]
|
||||
) -> Tuple[List[Dict[str, str]], List[Dict[str, Any]]]:
|
||||
"""
|
||||
The MessageStructure and MessageDeduplicationId-parameters have not yet been implemented.
|
||||
"""
|
||||
@ -1027,8 +1098,8 @@ class SNSBackend(BaseBackend):
|
||||
"Invalid parameter: The MessageGroupId parameter is required for FIFO topics"
|
||||
)
|
||||
|
||||
successful = []
|
||||
failed = []
|
||||
successful: List[Dict[str, str]] = []
|
||||
failed: List[Dict[str, Any]] = []
|
||||
|
||||
for entry in publish_batch_request_entries:
|
||||
try:
|
||||
|
@ -1,10 +1,11 @@
|
||||
import json
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from typing import Any, Dict, Tuple, Union
|
||||
|
||||
from moto.core.responses import BaseResponse
|
||||
from moto.core.utils import camelcase_to_underscores
|
||||
from .models import sns_backends
|
||||
from .models import sns_backends, SNSBackend
|
||||
from .exceptions import InvalidParameterValue, SNSNotFoundError
|
||||
from .utils import is_e164
|
||||
|
||||
@ -15,32 +16,34 @@ class SNSResponse(BaseResponse):
|
||||
)
|
||||
OPT_OUT_PHONE_NUMBER_REGEX = re.compile(r"^\+?\d+$")
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(service_name="sns")
|
||||
|
||||
@property
|
||||
def backend(self):
|
||||
def backend(self) -> SNSBackend:
|
||||
return sns_backends[self.current_account][self.region]
|
||||
|
||||
def _error(self, code, message, sender="Sender"):
|
||||
def _error(self, code: str, message: str, sender: str = "Sender") -> str:
|
||||
template = self.response_template(ERROR_RESPONSE)
|
||||
return template.render(code=code, message=message, sender=sender)
|
||||
|
||||
def _get_attributes(self):
|
||||
def _get_attributes(self) -> Dict[str, str]:
|
||||
attributes = self._get_list_prefix("Attributes.entry")
|
||||
return dict((attribute["key"], attribute["value"]) for attribute in attributes)
|
||||
|
||||
def _get_tags(self):
|
||||
def _get_tags(self) -> Dict[str, str]:
|
||||
tags = self._get_list_prefix("Tags.member")
|
||||
return {tag["key"]: tag["value"] for tag in tags}
|
||||
|
||||
def _parse_message_attributes(self):
|
||||
def _parse_message_attributes(self) -> Dict[str, Any]:
|
||||
message_attributes = self._get_object_map(
|
||||
"MessageAttributes.entry", name="Name", value="Value"
|
||||
)
|
||||
return self._transform_message_attributes(message_attributes)
|
||||
|
||||
def _transform_message_attributes(self, message_attributes):
|
||||
def _transform_message_attributes(
|
||||
self, message_attributes: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
# SNS converts some key names before forwarding messages
|
||||
# DataType -> Type, StringValue -> Value, BinaryValue -> Value
|
||||
transformed_message_attributes = {}
|
||||
@ -96,7 +99,7 @@ class SNSResponse(BaseResponse):
|
||||
|
||||
return transformed_message_attributes
|
||||
|
||||
def create_topic(self):
|
||||
def create_topic(self) -> str:
|
||||
name = self._get_param("Name")
|
||||
attributes = self._get_attributes()
|
||||
tags = self._get_tags()
|
||||
@ -117,7 +120,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(CREATE_TOPIC_TEMPLATE)
|
||||
return template.render(topic=topic)
|
||||
|
||||
def list_topics(self):
|
||||
def list_topics(self) -> str:
|
||||
next_token = self._get_param("NextToken")
|
||||
topics, next_token = self.backend.list_topics(next_token=next_token)
|
||||
|
||||
@ -139,7 +142,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(LIST_TOPICS_TEMPLATE)
|
||||
return template.render(topics=topics, next_token=next_token)
|
||||
|
||||
def delete_topic(self):
|
||||
def delete_topic(self) -> str:
|
||||
topic_arn = self._get_param("TopicArn")
|
||||
self.backend.delete_topic(topic_arn)
|
||||
|
||||
@ -157,7 +160,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(DELETE_TOPIC_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def get_topic_attributes(self):
|
||||
def get_topic_attributes(self) -> str:
|
||||
topic_arn = self._get_param("TopicArn")
|
||||
topic = self.backend.get_topic(topic_arn)
|
||||
|
||||
@ -193,7 +196,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(GET_TOPIC_ATTRIBUTES_TEMPLATE)
|
||||
return template.render(topic=topic)
|
||||
|
||||
def set_topic_attributes(self):
|
||||
def set_topic_attributes(self) -> str:
|
||||
topic_arn = self._get_param("TopicArn")
|
||||
attribute_name = self._get_param("AttributeName")
|
||||
attribute_name = camelcase_to_underscores(attribute_name)
|
||||
@ -214,7 +217,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(SET_TOPIC_ATTRIBUTES_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def subscribe(self):
|
||||
def subscribe(self) -> str:
|
||||
topic_arn = self._get_param("TopicArn")
|
||||
endpoint = self._get_param("Endpoint")
|
||||
protocol = self._get_param("Protocol")
|
||||
@ -243,7 +246,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(SUBSCRIBE_TEMPLATE)
|
||||
return template.render(subscription=subscription)
|
||||
|
||||
def unsubscribe(self):
|
||||
def unsubscribe(self) -> str:
|
||||
subscription_arn = self._get_param("SubscriptionArn")
|
||||
self.backend.unsubscribe(subscription_arn)
|
||||
|
||||
@ -261,7 +264,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(UNSUBSCRIBE_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def list_subscriptions(self):
|
||||
def list_subscriptions(self) -> str:
|
||||
next_token = self._get_param("NextToken")
|
||||
subscriptions, next_token = self.backend.list_subscriptions(
|
||||
next_token=next_token
|
||||
@ -294,7 +297,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(LIST_SUBSCRIPTIONS_TEMPLATE)
|
||||
return template.render(subscriptions=subscriptions, next_token=next_token)
|
||||
|
||||
def list_subscriptions_by_topic(self):
|
||||
def list_subscriptions_by_topic(self) -> str:
|
||||
topic_arn = self._get_param("TopicArn")
|
||||
next_token = self._get_param("NextToken")
|
||||
subscriptions, next_token = self.backend.list_subscriptions(
|
||||
@ -328,7 +331,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(LIST_SUBSCRIPTIONS_BY_TOPIC_TEMPLATE)
|
||||
return template.render(subscriptions=subscriptions, next_token=next_token)
|
||||
|
||||
def publish(self):
|
||||
def publish(self) -> Union[str, Tuple[str, Dict[str, int]]]:
|
||||
target_arn = self._get_param("TargetArn")
|
||||
topic_arn = self._get_param("TopicArn")
|
||||
phone_number = self._get_param("PhoneNumber")
|
||||
@ -384,7 +387,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(PUBLISH_TEMPLATE)
|
||||
return template.render(message_id=message_id)
|
||||
|
||||
def publish_batch(self):
|
||||
def publish_batch(self) -> str:
|
||||
topic_arn = self._get_param("TopicArn")
|
||||
publish_batch_request_entries = self._get_multi_param(
|
||||
"PublishBatchRequestEntries.member"
|
||||
@ -406,7 +409,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(PUBLISH_BATCH_TEMPLATE)
|
||||
return template.render(successful=successful, failed=failed)
|
||||
|
||||
def create_platform_application(self):
|
||||
def create_platform_application(self) -> str:
|
||||
name = self._get_param("Name")
|
||||
platform = self._get_param("Platform")
|
||||
attributes = self._get_attributes()
|
||||
@ -431,7 +434,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(CREATE_PLATFORM_APPLICATION_TEMPLATE)
|
||||
return template.render(platform_application=platform_application)
|
||||
|
||||
def get_platform_application_attributes(self):
|
||||
def get_platform_application_attributes(self) -> str:
|
||||
arn = self._get_param("PlatformApplicationArn")
|
||||
application = self.backend.get_application(arn)
|
||||
|
||||
@ -452,7 +455,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(GET_PLATFORM_APPLICATION_ATTRIBUTES_TEMPLATE)
|
||||
return template.render(application=application)
|
||||
|
||||
def set_platform_application_attributes(self):
|
||||
def set_platform_application_attributes(self) -> str:
|
||||
arn = self._get_param("PlatformApplicationArn")
|
||||
attributes = self._get_attributes()
|
||||
|
||||
@ -472,7 +475,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(SET_PLATFORM_APPLICATION_ATTRIBUTES_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def list_platform_applications(self):
|
||||
def list_platform_applications(self) -> str:
|
||||
applications = self.backend.list_platform_applications()
|
||||
|
||||
if self.request_json:
|
||||
@ -499,7 +502,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(LIST_PLATFORM_APPLICATIONS_TEMPLATE)
|
||||
return template.render(applications=applications)
|
||||
|
||||
def delete_platform_application(self):
|
||||
def delete_platform_application(self) -> str:
|
||||
platform_arn = self._get_param("PlatformApplicationArn")
|
||||
self.backend.delete_platform_application(platform_arn)
|
||||
|
||||
@ -517,7 +520,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(DELETE_PLATFORM_APPLICATION_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def create_platform_endpoint(self):
|
||||
def create_platform_endpoint(self) -> str:
|
||||
application_arn = self._get_param("PlatformApplicationArn")
|
||||
application = self.backend.get_application(application_arn)
|
||||
|
||||
@ -546,7 +549,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(CREATE_PLATFORM_ENDPOINT_TEMPLATE)
|
||||
return template.render(platform_endpoint=platform_endpoint)
|
||||
|
||||
def list_endpoints_by_platform_application(self):
|
||||
def list_endpoints_by_platform_application(self) -> str:
|
||||
application_arn = self._get_param("PlatformApplicationArn")
|
||||
endpoints = self.backend.list_endpoints_by_platform_application(application_arn)
|
||||
|
||||
@ -576,7 +579,7 @@ class SNSResponse(BaseResponse):
|
||||
)
|
||||
return template.render(endpoints=endpoints)
|
||||
|
||||
def get_endpoint_attributes(self):
|
||||
def get_endpoint_attributes(self) -> Union[str, Tuple[str, Dict[str, int]]]:
|
||||
arn = self._get_param("EndpointArn")
|
||||
try:
|
||||
endpoint = self.backend.get_endpoint(arn)
|
||||
@ -601,7 +604,7 @@ class SNSResponse(BaseResponse):
|
||||
error_response = self._error("NotFound", "Endpoint does not exist")
|
||||
return error_response, dict(status=404)
|
||||
|
||||
def set_endpoint_attributes(self):
|
||||
def set_endpoint_attributes(self) -> Union[str, Tuple[str, Dict[str, int]]]:
|
||||
arn = self._get_param("EndpointArn")
|
||||
attributes = self._get_attributes()
|
||||
|
||||
@ -621,7 +624,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(SET_ENDPOINT_ATTRIBUTES_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def delete_endpoint(self):
|
||||
def delete_endpoint(self) -> str:
|
||||
arn = self._get_param("EndpointArn")
|
||||
self.backend.delete_endpoint(arn)
|
||||
|
||||
@ -639,13 +642,13 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(DELETE_ENDPOINT_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def get_subscription_attributes(self):
|
||||
def get_subscription_attributes(self) -> str:
|
||||
arn = self._get_param("SubscriptionArn")
|
||||
attributes = self.backend.get_subscription_attributes(arn)
|
||||
template = self.response_template(GET_SUBSCRIPTION_ATTRIBUTES_TEMPLATE)
|
||||
return template.render(attributes=attributes)
|
||||
|
||||
def set_subscription_attributes(self):
|
||||
def set_subscription_attributes(self) -> str:
|
||||
arn = self._get_param("SubscriptionArn")
|
||||
attr_name = self._get_param("AttributeName")
|
||||
attr_value = self._get_param("AttributeValue")
|
||||
@ -653,12 +656,12 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(SET_SUBSCRIPTION_ATTRIBUTES_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def set_sms_attributes(self):
|
||||
def set_sms_attributes(self) -> str:
|
||||
# attributes.entry.1.key
|
||||
# attributes.entry.1.value
|
||||
# to
|
||||
# 1: {key:X, value:Y}
|
||||
temp_dict = defaultdict(dict)
|
||||
temp_dict: Dict[str, Any] = defaultdict(dict)
|
||||
for key, value in self.querystring.items():
|
||||
match = self.SMS_ATTR_REGEX.match(key)
|
||||
if match is not None:
|
||||
@ -678,7 +681,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(SET_SMS_ATTRIBUTES_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def get_sms_attributes(self):
|
||||
def get_sms_attributes(self) -> str:
|
||||
filter_list = set()
|
||||
for key, value in self.querystring.items():
|
||||
if key.startswith("attributes.member.1"):
|
||||
@ -694,7 +697,9 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(GET_SMS_ATTRIBUTES_TEMPLATE)
|
||||
return template.render(attributes=result)
|
||||
|
||||
def check_if_phone_number_is_opted_out(self):
|
||||
def check_if_phone_number_is_opted_out(
|
||||
self,
|
||||
) -> Union[str, Tuple[str, Dict[str, int]]]:
|
||||
number = self._get_param("phoneNumber")
|
||||
if self.OPT_OUT_PHONE_NUMBER_REGEX.match(number) is None:
|
||||
error_response = self._error(
|
||||
@ -707,11 +712,11 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(CHECK_IF_OPTED_OUT_TEMPLATE)
|
||||
return template.render(opt_out=str(number.endswith("99")).lower())
|
||||
|
||||
def list_phone_numbers_opted_out(self):
|
||||
def list_phone_numbers_opted_out(self) -> str:
|
||||
template = self.response_template(LIST_OPTOUT_TEMPLATE)
|
||||
return template.render(opt_outs=self.backend.opt_out_numbers)
|
||||
|
||||
def opt_in_phone_number(self):
|
||||
def opt_in_phone_number(self) -> str:
|
||||
number = self._get_param("phoneNumber")
|
||||
|
||||
try:
|
||||
@ -722,7 +727,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(OPT_IN_NUMBER_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def add_permission(self):
|
||||
def add_permission(self) -> str:
|
||||
topic_arn = self._get_param("TopicArn")
|
||||
label = self._get_param("Label")
|
||||
aws_account_ids = self._get_multi_param("AWSAccountId.member.")
|
||||
@ -733,7 +738,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(ADD_PERMISSION_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def remove_permission(self):
|
||||
def remove_permission(self) -> str:
|
||||
topic_arn = self._get_param("TopicArn")
|
||||
label = self._get_param("Label")
|
||||
|
||||
@ -742,7 +747,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(DEL_PERMISSION_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def confirm_subscription(self):
|
||||
def confirm_subscription(self) -> Union[str, Tuple[str, Dict[str, int]]]:
|
||||
arn = self._get_param("TopicArn")
|
||||
|
||||
if arn not in self.backend.topics:
|
||||
@ -767,7 +772,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(CONFIRM_SUBSCRIPTION_TEMPLATE)
|
||||
return template.render(sub_arn=f"{arn}:68762e72-e9b1-410a-8b3b-903da69ee1d5")
|
||||
|
||||
def list_tags_for_resource(self):
|
||||
def list_tags_for_resource(self) -> str:
|
||||
arn = self._get_param("ResourceArn")
|
||||
|
||||
result = self.backend.list_tags_for_resource(arn)
|
||||
@ -775,7 +780,7 @@ class SNSResponse(BaseResponse):
|
||||
template = self.response_template(LIST_TAGS_FOR_RESOURCE_TEMPLATE)
|
||||
return template.render(tags=result)
|
||||
|
||||
def tag_resource(self):
|
||||
def tag_resource(self) -> str:
|
||||
arn = self._get_param("ResourceArn")
|
||||
tags = self._get_tags()
|
||||
|
||||
@ -783,7 +788,7 @@ class SNSResponse(BaseResponse):
|
||||
|
||||
return self.response_template(TAG_RESOURCE_TEMPLATE).render()
|
||||
|
||||
def untag_resource(self):
|
||||
def untag_resource(self) -> str:
|
||||
arn = self._get_param("ResourceArn")
|
||||
tag_keys = self._get_multi_param("TagKeys.member")
|
||||
|
||||
|
@ -4,14 +4,14 @@ from moto.moto_api._internal import mock_random
|
||||
E164_REGEX = re.compile(r"^\+?[1-9]\d{1,14}$")
|
||||
|
||||
|
||||
def make_arn_for_topic(account_id, name, region_name):
|
||||
def make_arn_for_topic(account_id: str, name: str, region_name: str) -> str:
|
||||
return f"arn:aws:sns:{region_name}:{account_id}:{name}"
|
||||
|
||||
|
||||
def make_arn_for_subscription(topic_arn):
|
||||
def make_arn_for_subscription(topic_arn: str) -> str:
|
||||
subscription_id = mock_random.uuid4()
|
||||
return f"{topic_arn}:{subscription_id}"
|
||||
|
||||
|
||||
def is_e164(number):
|
||||
def is_e164(number: str) -> bool:
|
||||
return E164_REGEX.match(number) is not 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/ses,moto/sqs,moto/ssm,moto/scheduler,moto/swf
|
||||
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,moto/swf,moto/sns
|
||||
show_column_numbers=True
|
||||
show_error_codes = True
|
||||
disable_error_code=abstract
|
||||
|
Loading…
Reference in New Issue
Block a user