Techdebt: MyPy S (#6261)
This commit is contained in:
		
							parent
							
								
									37f1456747
								
							
						
					
					
						commit
						f38babb026
					
				| @ -1,4 +1,5 @@ | ||||
| """Exceptions raised by the sdb service.""" | ||||
| from typing import Any | ||||
| from moto.core.exceptions import RESTError | ||||
| 
 | ||||
| 
 | ||||
| @ -18,7 +19,7 @@ SDB_ERROR = """<?xml version="1.0"?> | ||||
| class InvalidParameterError(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, **kwargs): | ||||
|     def __init__(self, **kwargs: Any): | ||||
|         kwargs.setdefault("template", "sdb_error") | ||||
|         self.templates["sdb_error"] = SDB_ERROR | ||||
|         kwargs["error_type"] = "InvalidParameterValue" | ||||
| @ -28,7 +29,7 @@ class InvalidParameterError(RESTError): | ||||
| class InvalidDomainName(InvalidParameterError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, domain_name): | ||||
|     def __init__(self, domain_name: str): | ||||
|         super().__init__( | ||||
|             message=f"Value ({domain_name}) for parameter DomainName is invalid. " | ||||
|         ) | ||||
| @ -37,7 +38,7 @@ class InvalidDomainName(InvalidParameterError): | ||||
| class UnknownDomainName(RESTError): | ||||
|     code = 400 | ||||
| 
 | ||||
|     def __init__(self, **kwargs): | ||||
|     def __init__(self, **kwargs: Any): | ||||
|         kwargs.setdefault("template", "sdb_error") | ||||
|         self.templates["sdb_error"] = SDB_ERROR | ||||
|         kwargs["error_type"] = "NoSuchDomain" | ||||
|  | ||||
| @ -1,23 +1,24 @@ | ||||
| """SimpleDBBackend class with methods for supported APIs.""" | ||||
| import re | ||||
| from collections import defaultdict | ||||
| from moto.core import BaseBackend, BackendDict, BaseModel | ||||
| from threading import Lock | ||||
| from typing import Any, Dict, List, Iterable, Optional | ||||
| 
 | ||||
| from moto.core import BaseBackend, BackendDict, BaseModel | ||||
| from .exceptions import InvalidDomainName, UnknownDomainName | ||||
| 
 | ||||
| 
 | ||||
| class FakeItem(BaseModel): | ||||
|     def __init__(self): | ||||
|         self.attributes = [] | ||||
|     def __init__(self) -> None: | ||||
|         self.attributes: List[Dict[str, Any]] = [] | ||||
|         self.lock = Lock() | ||||
| 
 | ||||
|     def get_attributes(self, names): | ||||
|     def get_attributes(self, names: Optional[List[str]]) -> List[Dict[str, Any]]: | ||||
|         if not names: | ||||
|             return self.attributes | ||||
|         return [attr for attr in self.attributes if attr["name"] in names] | ||||
| 
 | ||||
|     def put_attributes(self, attributes): | ||||
|     def put_attributes(self, attributes: List[Dict[str, Any]]) -> None: | ||||
|         # Replacing attributes involves quite a few loops | ||||
|         # Lock this, so we know noone else touches this list while we're operating on it | ||||
|         with self.lock: | ||||
| @ -26,56 +27,58 @@ class FakeItem(BaseModel): | ||||
|                     self._remove_attributes(attr["name"]) | ||||
|                 self.attributes.append(attr) | ||||
| 
 | ||||
|     def _remove_attributes(self, name): | ||||
|     def _remove_attributes(self, name: str) -> None: | ||||
|         self.attributes = [attr for attr in self.attributes if attr["name"] != name] | ||||
| 
 | ||||
| 
 | ||||
| class FakeDomain(BaseModel): | ||||
|     def __init__(self, name): | ||||
|     def __init__(self, name: str): | ||||
|         self.name = name | ||||
|         self.items = defaultdict(FakeItem) | ||||
|         self.items: Dict[str, FakeItem] = defaultdict(FakeItem) | ||||
| 
 | ||||
|     def get(self, item_name, attribute_names): | ||||
|     def get(self, item_name: str, attribute_names: List[str]) -> List[Dict[str, Any]]: | ||||
|         item = self.items[item_name] | ||||
|         return item.get_attributes(attribute_names) | ||||
| 
 | ||||
|     def put(self, item_name, attributes): | ||||
|     def put(self, item_name: str, attributes: List[Dict[str, Any]]) -> None: | ||||
|         item = self.items[item_name] | ||||
|         item.put_attributes(attributes) | ||||
| 
 | ||||
| 
 | ||||
| class SimpleDBBackend(BaseBackend): | ||||
|     def __init__(self, region_name, account_id): | ||||
|     def __init__(self, region_name: str, account_id: str): | ||||
|         super().__init__(region_name, account_id) | ||||
|         self.domains = dict() | ||||
|         self.domains: Dict[str, FakeDomain] = dict() | ||||
| 
 | ||||
|     def create_domain(self, domain_name): | ||||
|     def create_domain(self, domain_name: str) -> None: | ||||
|         self._validate_domain_name(domain_name) | ||||
|         self.domains[domain_name] = FakeDomain(name=domain_name) | ||||
| 
 | ||||
|     def list_domains(self): | ||||
|     def list_domains(self) -> Iterable[str]: | ||||
|         """ | ||||
|         The `max_number_of_domains` and `next_token` parameter have not been implemented yet - we simply return all domains. | ||||
|         """ | ||||
|         return self.domains.keys() | ||||
| 
 | ||||
|     def delete_domain(self, domain_name): | ||||
|     def delete_domain(self, domain_name: str) -> None: | ||||
|         self._validate_domain_name(domain_name) | ||||
|         # Ignore unknown domains - AWS does the same | ||||
|         self.domains.pop(domain_name, None) | ||||
| 
 | ||||
|     def _validate_domain_name(self, domain_name): | ||||
|     def _validate_domain_name(self, domain_name: str) -> None: | ||||
|         # Domain Name needs to have at least 3 chars | ||||
|         # Can only contain characters: a-z, A-Z, 0-9, '_', '-', and '.' | ||||
|         if not re.match("^[a-zA-Z0-9-_.]{3,}$", domain_name): | ||||
|             raise InvalidDomainName(domain_name) | ||||
| 
 | ||||
|     def _get_domain(self, domain_name): | ||||
|     def _get_domain(self, domain_name: str) -> FakeDomain: | ||||
|         if domain_name not in self.domains: | ||||
|             raise UnknownDomainName() | ||||
|         return self.domains[domain_name] | ||||
| 
 | ||||
|     def get_attributes(self, domain_name, item_name, attribute_names): | ||||
|     def get_attributes( | ||||
|         self, domain_name: str, item_name: str, attribute_names: List[str] | ||||
|     ) -> List[Dict[str, Any]]: | ||||
|         """ | ||||
|         Behaviour for the consistent_read-attribute is not yet implemented | ||||
|         """ | ||||
| @ -83,7 +86,9 @@ class SimpleDBBackend(BaseBackend): | ||||
|         domain = self._get_domain(domain_name) | ||||
|         return domain.get(item_name, attribute_names) | ||||
| 
 | ||||
|     def put_attributes(self, domain_name, item_name, attributes): | ||||
|     def put_attributes( | ||||
|         self, domain_name: str, item_name: str, attributes: List[Dict[str, Any]] | ||||
|     ) -> None: | ||||
|         """ | ||||
|         Behaviour for the expected-attribute is not yet implemented. | ||||
|         """ | ||||
|  | ||||
| @ -1,33 +1,33 @@ | ||||
| from moto.core.responses import BaseResponse | ||||
| from .models import sdb_backends | ||||
| from .models import sdb_backends, SimpleDBBackend | ||||
| 
 | ||||
| 
 | ||||
| class SimpleDBResponse(BaseResponse): | ||||
|     def __init__(self): | ||||
|     def __init__(self) -> None: | ||||
|         super().__init__(service_name="sdb") | ||||
| 
 | ||||
|     @property | ||||
|     def sdb_backend(self): | ||||
|     def sdb_backend(self) -> SimpleDBBackend: | ||||
|         return sdb_backends[self.current_account][self.region] | ||||
| 
 | ||||
|     def create_domain(self): | ||||
|     def create_domain(self) -> str: | ||||
|         domain_name = self._get_param("DomainName") | ||||
|         self.sdb_backend.create_domain(domain_name=domain_name) | ||||
|         template = self.response_template(CREATE_DOMAIN_TEMPLATE) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def delete_domain(self): | ||||
|     def delete_domain(self) -> str: | ||||
|         domain_name = self._get_param("DomainName") | ||||
|         self.sdb_backend.delete_domain(domain_name=domain_name) | ||||
|         template = self.response_template(DELETE_DOMAIN_TEMPLATE) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def list_domains(self): | ||||
|     def list_domains(self) -> str: | ||||
|         domain_names = self.sdb_backend.list_domains() | ||||
|         template = self.response_template(LIST_DOMAINS_TEMPLATE) | ||||
|         return template.render(domain_names=domain_names, next_token=None) | ||||
| 
 | ||||
|     def get_attributes(self): | ||||
|     def get_attributes(self) -> str: | ||||
|         domain_name = self._get_param("DomainName") | ||||
|         item_name = self._get_param("ItemName") | ||||
|         attribute_names = self._get_multi_param("AttributeName.") | ||||
| @ -39,7 +39,7 @@ class SimpleDBResponse(BaseResponse): | ||||
|         template = self.response_template(GET_ATTRIBUTES_TEMPLATE) | ||||
|         return template.render(attributes=attributes) | ||||
| 
 | ||||
|     def put_attributes(self): | ||||
|     def put_attributes(self) -> str: | ||||
|         domain_name = self._get_param("DomainName") | ||||
|         item_name = self._get_param("ItemName") | ||||
|         attributes = self._get_list_prefix("Attribute") | ||||
|  | ||||
| @ -3,6 +3,7 @@ import os | ||||
| import signal | ||||
| import sys | ||||
| import warnings | ||||
| from typing import Any, List, Optional | ||||
| 
 | ||||
| from werkzeug.serving import run_simple | ||||
| 
 | ||||
| @ -15,11 +16,11 @@ from moto.moto_server.threaded_moto_server import (  # noqa # pylint: disable=un | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| def signal_handler(signum, frame):  # pylint: disable=unused-argument | ||||
| def signal_handler(signum: Any, frame: Any) -> None:  # pylint: disable=unused-argument | ||||
|     sys.exit(0) | ||||
| 
 | ||||
| 
 | ||||
| def main(argv=None): | ||||
| def main(argv: Optional[List[str]] = None) -> None: | ||||
|     argv = argv or sys.argv[1:] | ||||
|     parser = argparse.ArgumentParser() | ||||
| 
 | ||||
| @ -79,9 +80,9 @@ def main(argv=None): | ||||
| 
 | ||||
|     # Wrap the main application | ||||
|     main_app = DomainDispatcherApplication(create_backend_app, service=args.service) | ||||
|     main_app.debug = True | ||||
|     main_app.debug = True  # type: ignore | ||||
| 
 | ||||
|     ssl_context = None | ||||
|     ssl_context: Any = None | ||||
|     if args.ssl_key and args.ssl_cert: | ||||
|         ssl_context = (args.ssl_cert, args.ssl_key) | ||||
|     elif args.ssl: | ||||
|  | ||||
| @ -3,20 +3,20 @@ from moto.core.exceptions import JsonRESTError | ||||
| 
 | ||||
| 
 | ||||
| class OperationNotFound(JsonRESTError): | ||||
|     def __init__(self): | ||||
|     def __init__(self) -> None: | ||||
|         super().__init__("OperationNotFound", "") | ||||
| 
 | ||||
| 
 | ||||
| class NamespaceNotFound(JsonRESTError): | ||||
|     def __init__(self, ns_id): | ||||
|     def __init__(self, ns_id: str): | ||||
|         super().__init__("NamespaceNotFound", f"{ns_id}") | ||||
| 
 | ||||
| 
 | ||||
| class ServiceNotFound(JsonRESTError): | ||||
|     def __init__(self, ns_id): | ||||
|     def __init__(self, ns_id: str): | ||||
|         super().__init__("ServiceNotFound", f"{ns_id}") | ||||
| 
 | ||||
| 
 | ||||
| class ConflictingDomainExists(JsonRESTError): | ||||
|     def __init__(self, vpc_id): | ||||
|     def __init__(self, vpc_id: str): | ||||
|         super().__init__("ConflictingDomainExists", f"{vpc_id}") | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| import string | ||||
| from typing import Any, Dict, Iterable, List, Optional | ||||
| 
 | ||||
| from moto.core import BaseBackend, BackendDict, BaseModel | ||||
| from moto.core.utils import unix_time | ||||
| @ -13,7 +14,7 @@ from .exceptions import ( | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| def random_id(size): | ||||
| def random_id(size: int) -> str: | ||||
|     return "".join( | ||||
|         [random.choice(string.ascii_lowercase + string.digits) for _ in range(size)] | ||||
|     ) | ||||
| @ -22,17 +23,16 @@ def random_id(size): | ||||
| class Namespace(BaseModel): | ||||
|     def __init__( | ||||
|         self, | ||||
|         account_id, | ||||
|         region, | ||||
|         name, | ||||
|         ns_type, | ||||
|         creator_request_id, | ||||
|         description, | ||||
|         dns_properties, | ||||
|         http_properties, | ||||
|         vpc=None, | ||||
|         account_id: str, | ||||
|         region: str, | ||||
|         name: str, | ||||
|         ns_type: str, | ||||
|         creator_request_id: str, | ||||
|         description: str, | ||||
|         dns_properties: Dict[str, Any], | ||||
|         http_properties: Dict[str, Any], | ||||
|         vpc: Optional[str] = None, | ||||
|     ): | ||||
|         super().__init__() | ||||
|         self.id = f"ns-{random_id(20)}" | ||||
|         self.arn = f"arn:aws:servicediscovery:{region}:{account_id}:namespace/{self.id}" | ||||
|         self.name = name | ||||
| @ -45,7 +45,7 @@ class Namespace(BaseModel): | ||||
|         self.created = unix_time() | ||||
|         self.updated = unix_time() | ||||
| 
 | ||||
|     def to_json(self): | ||||
|     def to_json(self) -> Dict[str, Any]: | ||||
|         return { | ||||
|             "Arn": self.arn, | ||||
|             "Id": self.id, | ||||
| @ -65,31 +65,30 @@ class Namespace(BaseModel): | ||||
| class Service(BaseModel): | ||||
|     def __init__( | ||||
|         self, | ||||
|         account_id, | ||||
|         region, | ||||
|         name, | ||||
|         namespace_id, | ||||
|         description, | ||||
|         creator_request_id, | ||||
|         dns_config, | ||||
|         health_check_config, | ||||
|         health_check_custom_config, | ||||
|         service_type, | ||||
|         account_id: str, | ||||
|         region: str, | ||||
|         name: str, | ||||
|         namespace_id: str, | ||||
|         description: str, | ||||
|         creator_request_id: str, | ||||
|         dns_config: Dict[str, Any], | ||||
|         health_check_config: Dict[str, Any], | ||||
|         health_check_custom_config: Dict[str, int], | ||||
|         service_type: str, | ||||
|     ): | ||||
|         super().__init__() | ||||
|         self.id = f"srv-{random_id(8)}" | ||||
|         self.arn = f"arn:aws:servicediscovery:{region}:{account_id}:service/{self.id}" | ||||
|         self.name = name | ||||
|         self.namespace_id = namespace_id | ||||
|         self.description = description | ||||
|         self.creator_request_id = creator_request_id | ||||
|         self.dns_config = dns_config | ||||
|         self.dns_config: Optional[Dict[str, Any]] = dns_config | ||||
|         self.health_check_config = health_check_config | ||||
|         self.health_check_custom_config = health_check_custom_config | ||||
|         self.service_type = service_type | ||||
|         self.created = unix_time() | ||||
| 
 | ||||
|     def update(self, details): | ||||
|     def update(self, details: Dict[str, Any]) -> None: | ||||
|         if "Description" in details: | ||||
|             self.description = details["Description"] | ||||
|         if "DnsConfig" in details: | ||||
| @ -104,7 +103,7 @@ class Service(BaseModel): | ||||
|         if "HealthCheckConfig" in details: | ||||
|             self.health_check_config = details["HealthCheckConfig"] | ||||
| 
 | ||||
|     def to_json(self): | ||||
|     def to_json(self) -> Dict[str, Any]: | ||||
|         return { | ||||
|             "Arn": self.arn, | ||||
|             "Id": self.id, | ||||
| @ -121,7 +120,7 @@ class Service(BaseModel): | ||||
| 
 | ||||
| 
 | ||||
| class Operation(BaseModel): | ||||
|     def __init__(self, operation_type, targets): | ||||
|     def __init__(self, operation_type: str, targets: Dict[str, str]): | ||||
|         super().__init__() | ||||
|         self.id = f"{random_id(32)}-{random_id(8)}" | ||||
|         self.status = "SUCCESS" | ||||
| @ -130,7 +129,7 @@ class Operation(BaseModel): | ||||
|         self.updated = unix_time() | ||||
|         self.targets = targets | ||||
| 
 | ||||
|     def to_json(self, short=False): | ||||
|     def to_json(self, short: bool = False) -> Dict[str, Any]: | ||||
|         if short: | ||||
|             return {"Id": self.id, "Status": self.status} | ||||
|         else: | ||||
| @ -147,20 +146,26 @@ class Operation(BaseModel): | ||||
| class ServiceDiscoveryBackend(BaseBackend): | ||||
|     """Implementation of ServiceDiscovery APIs.""" | ||||
| 
 | ||||
|     def __init__(self, region_name, account_id): | ||||
|     def __init__(self, region_name: str, account_id: str): | ||||
|         super().__init__(region_name, account_id) | ||||
|         self.operations = dict() | ||||
|         self.namespaces = dict() | ||||
|         self.services = dict() | ||||
|         self.operations: Dict[str, Operation] = dict() | ||||
|         self.namespaces: Dict[str, Namespace] = dict() | ||||
|         self.services: Dict[str, Service] = dict() | ||||
|         self.tagger = TaggingService() | ||||
| 
 | ||||
|     def list_namespaces(self): | ||||
|     def list_namespaces(self) -> Iterable[Namespace]: | ||||
|         """ | ||||
|         Pagination or the Filters-parameter is not yet implemented | ||||
|         """ | ||||
|         return self.namespaces.values() | ||||
| 
 | ||||
|     def create_http_namespace(self, name, creator_request_id, description, tags): | ||||
|     def create_http_namespace( | ||||
|         self, | ||||
|         name: str, | ||||
|         creator_request_id: str, | ||||
|         description: str, | ||||
|         tags: List[Dict[str, str]], | ||||
|     ) -> str: | ||||
|         namespace = Namespace( | ||||
|             account_id=self.account_id, | ||||
|             region=self.region_name, | ||||
| @ -179,13 +184,12 @@ class ServiceDiscoveryBackend(BaseBackend): | ||||
|         ) | ||||
|         return operation_id | ||||
| 
 | ||||
|     def _create_operation(self, op_type, targets): | ||||
|     def _create_operation(self, op_type: str, targets: Dict[str, str]) -> str: | ||||
|         operation = Operation(operation_type=op_type, targets=targets) | ||||
|         self.operations[operation.id] = operation | ||||
|         operation_id = operation.id | ||||
|         return operation_id | ||||
|         return operation.id | ||||
| 
 | ||||
|     def delete_namespace(self, namespace_id): | ||||
|     def delete_namespace(self, namespace_id: str) -> str: | ||||
|         if namespace_id not in self.namespaces: | ||||
|             raise NamespaceNotFound(namespace_id) | ||||
|         del self.namespaces[namespace_id] | ||||
| @ -194,12 +198,12 @@ class ServiceDiscoveryBackend(BaseBackend): | ||||
|         ) | ||||
|         return operation_id | ||||
| 
 | ||||
|     def get_namespace(self, namespace_id): | ||||
|     def get_namespace(self, namespace_id: str) -> Namespace: | ||||
|         if namespace_id not in self.namespaces: | ||||
|             raise NamespaceNotFound(namespace_id) | ||||
|         return self.namespaces[namespace_id] | ||||
| 
 | ||||
|     def list_operations(self): | ||||
|     def list_operations(self) -> Iterable[Operation]: | ||||
|         """ | ||||
|         Pagination or the Filters-argument is not yet implemented | ||||
|         """ | ||||
| @ -211,23 +215,31 @@ class ServiceDiscoveryBackend(BaseBackend): | ||||
|         } | ||||
|         return self.operations.values() | ||||
| 
 | ||||
|     def get_operation(self, operation_id): | ||||
|     def get_operation(self, operation_id: str) -> Operation: | ||||
|         if operation_id not in self.operations: | ||||
|             raise OperationNotFound() | ||||
|         return self.operations[operation_id] | ||||
| 
 | ||||
|     def tag_resource(self, resource_arn, tags): | ||||
|     def tag_resource(self, resource_arn: str, tags: List[Dict[str, str]]) -> None: | ||||
|         self.tagger.tag_resource(resource_arn, tags) | ||||
| 
 | ||||
|     def untag_resource(self, resource_arn, tag_keys): | ||||
|     def untag_resource(self, resource_arn: str, tag_keys: List[str]) -> None: | ||||
|         self.tagger.untag_resource_using_names(resource_arn, tag_keys) | ||||
| 
 | ||||
|     def list_tags_for_resource(self, resource_arn): | ||||
|     def list_tags_for_resource( | ||||
|         self, resource_arn: str | ||||
|     ) -> Dict[str, List[Dict[str, str]]]: | ||||
|         return self.tagger.list_tags_for_resource(resource_arn) | ||||
| 
 | ||||
|     def create_private_dns_namespace( | ||||
|         self, name, creator_request_id, description, vpc, tags, properties | ||||
|     ): | ||||
|         self, | ||||
|         name: str, | ||||
|         creator_request_id: str, | ||||
|         description: str, | ||||
|         vpc: str, | ||||
|         tags: List[Dict[str, str]], | ||||
|         properties: Dict[str, Any], | ||||
|     ) -> str: | ||||
|         for namespace in self.namespaces.values(): | ||||
|             if namespace.vpc == vpc: | ||||
|                 raise ConflictingDomainExists(vpc) | ||||
| @ -253,8 +265,13 @@ class ServiceDiscoveryBackend(BaseBackend): | ||||
|         return operation_id | ||||
| 
 | ||||
|     def create_public_dns_namespace( | ||||
|         self, name, creator_request_id, description, tags, properties | ||||
|     ): | ||||
|         self, | ||||
|         name: str, | ||||
|         creator_request_id: str, | ||||
|         description: str, | ||||
|         tags: List[Dict[str, str]], | ||||
|         properties: Dict[str, Any], | ||||
|     ) -> str: | ||||
|         dns_properties = (properties or {}).get("DnsProperties", {}) | ||||
|         dns_properties["HostedZoneId"] = "hzi" | ||||
|         namespace = Namespace( | ||||
| @ -277,16 +294,16 @@ class ServiceDiscoveryBackend(BaseBackend): | ||||
| 
 | ||||
|     def create_service( | ||||
|         self, | ||||
|         name, | ||||
|         namespace_id, | ||||
|         creator_request_id, | ||||
|         description, | ||||
|         dns_config, | ||||
|         health_check_config, | ||||
|         health_check_custom_config, | ||||
|         tags, | ||||
|         service_type, | ||||
|     ): | ||||
|         name: str, | ||||
|         namespace_id: str, | ||||
|         creator_request_id: str, | ||||
|         description: str, | ||||
|         dns_config: Dict[str, Any], | ||||
|         health_check_config: Dict[str, Any], | ||||
|         health_check_custom_config: Dict[str, Any], | ||||
|         tags: List[Dict[str, str]], | ||||
|         service_type: str, | ||||
|     ) -> Service: | ||||
|         service = Service( | ||||
|             account_id=self.account_id, | ||||
|             region=self.region_name, | ||||
| @ -304,21 +321,21 @@ class ServiceDiscoveryBackend(BaseBackend): | ||||
|             self.tagger.tag_resource(service.arn, tags) | ||||
|         return service | ||||
| 
 | ||||
|     def get_service(self, service_id): | ||||
|     def get_service(self, service_id: str) -> Service: | ||||
|         if service_id not in self.services: | ||||
|             raise ServiceNotFound(service_id) | ||||
|         return self.services[service_id] | ||||
| 
 | ||||
|     def delete_service(self, service_id): | ||||
|     def delete_service(self, service_id: str) -> None: | ||||
|         self.services.pop(service_id, None) | ||||
| 
 | ||||
|     def list_services(self): | ||||
|     def list_services(self) -> Iterable[Service]: | ||||
|         """ | ||||
|         Pagination or the Filters-argument is not yet implemented | ||||
|         """ | ||||
|         return self.services.values() | ||||
| 
 | ||||
|     def update_service(self, service_id, details): | ||||
|     def update_service(self, service_id: str, details: Dict[str, Any]) -> str: | ||||
|         service = self.get_service(service_id) | ||||
|         service.update(details=details) | ||||
|         operation_id = self._create_operation( | ||||
|  | ||||
| @ -1,24 +1,25 @@ | ||||
| """Handles incoming servicediscovery requests, invokes methods, returns responses.""" | ||||
| import json | ||||
| 
 | ||||
| from moto.core.common_types import TYPE_RESPONSE | ||||
| from moto.core.responses import BaseResponse | ||||
| from .models import servicediscovery_backends | ||||
| from .models import servicediscovery_backends, ServiceDiscoveryBackend | ||||
| 
 | ||||
| 
 | ||||
| class ServiceDiscoveryResponse(BaseResponse): | ||||
|     def __init__(self): | ||||
|     def __init__(self) -> None: | ||||
|         super().__init__(service_name="servicediscovery") | ||||
| 
 | ||||
|     @property | ||||
|     def servicediscovery_backend(self): | ||||
|     def servicediscovery_backend(self) -> ServiceDiscoveryBackend: | ||||
|         """Return backend instance specific for this region.""" | ||||
|         return servicediscovery_backends[self.current_account][self.region] | ||||
| 
 | ||||
|     def list_namespaces(self): | ||||
|     def list_namespaces(self) -> TYPE_RESPONSE: | ||||
|         namespaces = self.servicediscovery_backend.list_namespaces() | ||||
|         return 200, {}, json.dumps({"Namespaces": [ns.to_json() for ns in namespaces]}) | ||||
| 
 | ||||
|     def create_http_namespace(self): | ||||
|     def create_http_namespace(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         name = params.get("Name") | ||||
|         creator_request_id = params.get("CreatorRequestId") | ||||
| @ -32,7 +33,7 @@ class ServiceDiscoveryResponse(BaseResponse): | ||||
|         ) | ||||
|         return json.dumps(dict(OperationId=operation_id)) | ||||
| 
 | ||||
|     def delete_namespace(self): | ||||
|     def delete_namespace(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         namespace_id = params.get("Id") | ||||
|         operation_id = self.servicediscovery_backend.delete_namespace( | ||||
| @ -40,7 +41,7 @@ class ServiceDiscoveryResponse(BaseResponse): | ||||
|         ) | ||||
|         return json.dumps(dict(OperationId=operation_id)) | ||||
| 
 | ||||
|     def list_operations(self): | ||||
|     def list_operations(self) -> TYPE_RESPONSE: | ||||
|         operations = self.servicediscovery_backend.list_operations() | ||||
|         return ( | ||||
|             200, | ||||
| @ -48,7 +49,7 @@ class ServiceDiscoveryResponse(BaseResponse): | ||||
|             json.dumps({"Operations": [o.to_json(short=True) for o in operations]}), | ||||
|         ) | ||||
| 
 | ||||
|     def get_operation(self): | ||||
|     def get_operation(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         operation_id = params.get("OperationId") | ||||
|         operation = self.servicediscovery_backend.get_operation( | ||||
| @ -56,7 +57,7 @@ class ServiceDiscoveryResponse(BaseResponse): | ||||
|         ) | ||||
|         return json.dumps(dict(Operation=operation.to_json())) | ||||
| 
 | ||||
|     def get_namespace(self): | ||||
|     def get_namespace(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         namespace_id = params.get("Id") | ||||
|         namespace = self.servicediscovery_backend.get_namespace( | ||||
| @ -64,23 +65,23 @@ class ServiceDiscoveryResponse(BaseResponse): | ||||
|         ) | ||||
|         return json.dumps(dict(Namespace=namespace.to_json())) | ||||
| 
 | ||||
|     def tag_resource(self): | ||||
|     def tag_resource(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         resource_arn = params.get("ResourceARN") | ||||
|         tags = params.get("Tags") | ||||
|         self.servicediscovery_backend.tag_resource(resource_arn=resource_arn, tags=tags) | ||||
|         return json.dumps(dict()) | ||||
|         return "{}" | ||||
| 
 | ||||
|     def untag_resource(self): | ||||
|     def untag_resource(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         resource_arn = params.get("ResourceARN") | ||||
|         tag_keys = params.get("TagKeys") | ||||
|         self.servicediscovery_backend.untag_resource( | ||||
|             resource_arn=resource_arn, tag_keys=tag_keys | ||||
|         ) | ||||
|         return json.dumps(dict()) | ||||
|         return "{}" | ||||
| 
 | ||||
|     def list_tags_for_resource(self): | ||||
|     def list_tags_for_resource(self) -> TYPE_RESPONSE: | ||||
|         params = json.loads(self.body) | ||||
|         resource_arn = params.get("ResourceARN") | ||||
|         tags = self.servicediscovery_backend.list_tags_for_resource( | ||||
| @ -88,7 +89,7 @@ class ServiceDiscoveryResponse(BaseResponse): | ||||
|         ) | ||||
|         return 200, {}, json.dumps(tags) | ||||
| 
 | ||||
|     def create_private_dns_namespace(self): | ||||
|     def create_private_dns_namespace(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         name = params.get("Name") | ||||
|         creator_request_id = params.get("CreatorRequestId") | ||||
| @ -106,7 +107,7 @@ class ServiceDiscoveryResponse(BaseResponse): | ||||
|         ) | ||||
|         return json.dumps(dict(OperationId=operation_id)) | ||||
| 
 | ||||
|     def create_public_dns_namespace(self): | ||||
|     def create_public_dns_namespace(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         name = params.get("Name") | ||||
|         creator_request_id = params.get("CreatorRequestId") | ||||
| @ -122,7 +123,7 @@ class ServiceDiscoveryResponse(BaseResponse): | ||||
|         ) | ||||
|         return json.dumps(dict(OperationId=operation_id)) | ||||
| 
 | ||||
|     def create_service(self): | ||||
|     def create_service(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         name = params.get("Name") | ||||
|         namespace_id = params.get("NamespaceId") | ||||
| @ -146,23 +147,23 @@ class ServiceDiscoveryResponse(BaseResponse): | ||||
|         ) | ||||
|         return json.dumps(dict(Service=service.to_json())) | ||||
| 
 | ||||
|     def get_service(self): | ||||
|     def get_service(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         service_id = params.get("Id") | ||||
|         service = self.servicediscovery_backend.get_service(service_id=service_id) | ||||
|         return json.dumps(dict(Service=service.to_json())) | ||||
| 
 | ||||
|     def delete_service(self): | ||||
|     def delete_service(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         service_id = params.get("Id") | ||||
|         self.servicediscovery_backend.delete_service(service_id=service_id) | ||||
|         return json.dumps(dict()) | ||||
|         return "{}" | ||||
| 
 | ||||
|     def list_services(self): | ||||
|     def list_services(self) -> str: | ||||
|         services = self.servicediscovery_backend.list_services() | ||||
|         return json.dumps(dict(Services=[s.to_json() for s in services])) | ||||
| 
 | ||||
|     def update_service(self): | ||||
|     def update_service(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         service_id = params.get("Id") | ||||
|         details = params.get("Service") | ||||
|  | ||||
| @ -38,7 +38,7 @@ SKIP_REQUIRES_DOCKER = bool(os.environ.get("TESTS_SKIP_REQUIRES_DOCKER", False)) | ||||
| LAMBDA_DATA_DIR = os.environ.get("MOTO_LAMBDA_DATA_DIR", "/tmp/data") | ||||
| 
 | ||||
| 
 | ||||
| def get_sf_execution_history_type(): | ||||
| def get_sf_execution_history_type() -> str: | ||||
|     """ | ||||
|     Determines which execution history events `get_execution_history` returns | ||||
|     :returns: str representing the type of Step Function Execution Type events should be | ||||
| @ -97,11 +97,11 @@ def moto_lambda_image() -> str: | ||||
|     return os.environ.get("MOTO_DOCKER_LAMBDA_IMAGE", "mlupin/docker-lambda") | ||||
| 
 | ||||
| 
 | ||||
| def moto_network_name() -> str: | ||||
| def moto_network_name() -> Optional[str]: | ||||
|     return os.environ.get("MOTO_DOCKER_NETWORK_NAME") | ||||
| 
 | ||||
| 
 | ||||
| def moto_network_mode() -> str: | ||||
| def moto_network_mode() -> Optional[str]: | ||||
|     return os.environ.get("MOTO_DOCKER_NETWORK_MODE") | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -1,10 +1,18 @@ | ||||
| from typing import Any, Dict, List, Optional | ||||
| 
 | ||||
| from moto.core import BaseBackend, BackendDict, BaseModel | ||||
| from moto.moto_api._internal import mock_random | ||||
| 
 | ||||
| 
 | ||||
| class SigningProfile(BaseModel): | ||||
|     def __init__( | ||||
|         self, account_id, region, name, platform_id, signature_validity_period, tags | ||||
|         self, | ||||
|         account_id: str, | ||||
|         region: str, | ||||
|         name: str, | ||||
|         platform_id: str, | ||||
|         signature_validity_period: Optional[Dict[str, Any]], | ||||
|         tags: Dict[str, str], | ||||
|     ): | ||||
|         self.name = name | ||||
|         self.platform_id = platform_id | ||||
| @ -19,11 +27,11 @@ class SigningProfile(BaseModel): | ||||
|         self.profile_version = mock_random.get_random_hex(10) | ||||
|         self.profile_version_arn = f"{self.arn}/{self.profile_version}" | ||||
| 
 | ||||
|     def cancel(self): | ||||
|     def cancel(self) -> None: | ||||
|         self.status = "Canceled" | ||||
| 
 | ||||
|     def to_dict(self, full=True): | ||||
|         small = { | ||||
|     def to_dict(self, full: bool = True) -> Dict[str, Any]: | ||||
|         small: Dict[str, Any] = { | ||||
|             "arn": self.arn, | ||||
|             "profileVersion": self.profile_version, | ||||
|             "profileVersionArn": self.profile_version_arn, | ||||
| @ -149,22 +157,22 @@ class SignerBackend(BaseBackend): | ||||
|         }, | ||||
|     ] | ||||
| 
 | ||||
|     def __init__(self, region_name, account_id): | ||||
|     def __init__(self, region_name: str, account_id: str): | ||||
|         super().__init__(region_name, account_id) | ||||
|         self.signing_profiles: [str, SigningProfile] = dict() | ||||
|         self.signing_profiles: Dict[str, SigningProfile] = dict() | ||||
| 
 | ||||
|     def cancel_signing_profile(self, profile_name) -> None: | ||||
|     def cancel_signing_profile(self, profile_name: str) -> None: | ||||
|         self.signing_profiles[profile_name].cancel() | ||||
| 
 | ||||
|     def get_signing_profile(self, profile_name) -> SigningProfile: | ||||
|     def get_signing_profile(self, profile_name: str) -> SigningProfile: | ||||
|         return self.signing_profiles[profile_name] | ||||
| 
 | ||||
|     def put_signing_profile( | ||||
|         self, | ||||
|         profile_name, | ||||
|         signature_validity_period, | ||||
|         platform_id, | ||||
|         tags, | ||||
|         profile_name: str, | ||||
|         signature_validity_period: Optional[Dict[str, Any]], | ||||
|         platform_id: str, | ||||
|         tags: Dict[str, str], | ||||
|     ) -> SigningProfile: | ||||
|         """ | ||||
|         The following parameters are not yet implemented: SigningMaterial, Overrides, SigningParamaters | ||||
| @ -180,7 +188,7 @@ class SignerBackend(BaseBackend): | ||||
|         self.signing_profiles[profile_name] = profile | ||||
|         return profile | ||||
| 
 | ||||
|     def list_signing_platforms(self): | ||||
|     def list_signing_platforms(self) -> List[Dict[str, Any]]: | ||||
|         """ | ||||
|         Pagination is not yet implemented. The parameters category, partner, target are not yet implemented | ||||
|         """ | ||||
| @ -189,4 +197,4 @@ class SignerBackend(BaseBackend): | ||||
| 
 | ||||
| # Using the lambda-regions | ||||
| # boto3.Session().get_available_regions("signer") still returns an empty list | ||||
| signer_backends: [str, [str, SignerBackend]] = BackendDict(SignerBackend, "lambda") | ||||
| signer_backends = BackendDict(SignerBackend, "lambda") | ||||
|  | ||||
| @ -6,7 +6,7 @@ from .models import signer_backends, SignerBackend | ||||
| 
 | ||||
| 
 | ||||
| class signerResponse(BaseResponse): | ||||
|     def __init__(self): | ||||
|     def __init__(self) -> None: | ||||
|         super().__init__(service_name="signer") | ||||
| 
 | ||||
|     @property | ||||
| @ -14,17 +14,17 @@ class signerResponse(BaseResponse): | ||||
|         """Return backend instance specific for this region.""" | ||||
|         return signer_backends[self.current_account][self.region] | ||||
| 
 | ||||
|     def cancel_signing_profile(self): | ||||
|     def cancel_signing_profile(self) -> str: | ||||
|         profile_name = self.path.split("/")[-1] | ||||
|         self.signer_backend.cancel_signing_profile(profile_name=profile_name) | ||||
|         return "{}" | ||||
| 
 | ||||
|     def get_signing_profile(self): | ||||
|     def get_signing_profile(self) -> str: | ||||
|         profile_name = self.path.split("/")[-1] | ||||
|         profile = self.signer_backend.get_signing_profile(profile_name=profile_name) | ||||
|         return json.dumps(profile.to_dict()) | ||||
| 
 | ||||
|     def put_signing_profile(self): | ||||
|     def put_signing_profile(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         profile_name = self.path.split("/")[-1] | ||||
|         signature_validity_period = params.get("signatureValidityPeriod") | ||||
| @ -38,6 +38,6 @@ class signerResponse(BaseResponse): | ||||
|         ) | ||||
|         return json.dumps(profile.to_dict(full=False)) | ||||
| 
 | ||||
|     def list_signing_platforms(self): | ||||
|     def list_signing_platforms(self) -> str: | ||||
|         platforms = self.signer_backend.list_signing_platforms() | ||||
|         return json.dumps(dict(platforms=platforms)) | ||||
|  | ||||
| @ -3,5 +3,5 @@ from moto.core.exceptions import JsonRESTError | ||||
| 
 | ||||
| 
 | ||||
| class ResourceNotFound(JsonRESTError): | ||||
|     def __init__(self): | ||||
|     def __init__(self) -> None: | ||||
|         super().__init__("ResourceNotFound", "Account not found") | ||||
|  | ||||
| @ -1,21 +1,22 @@ | ||||
| from .exceptions import ResourceNotFound | ||||
| from typing import Any, Dict, List | ||||
| 
 | ||||
| from moto.core import BaseBackend, BackendDict, BaseModel | ||||
| from moto.core.utils import unix_time | ||||
| from moto.moto_api._internal import mock_random as random | ||||
| from moto.utilities.paginator import paginate | ||||
| from .exceptions import ResourceNotFound | ||||
| from .utils import PAGINATION_MODEL | ||||
| 
 | ||||
| 
 | ||||
| class AccountAssignment(BaseModel): | ||||
|     def __init__( | ||||
|         self, | ||||
|         instance_arn, | ||||
|         target_id, | ||||
|         target_type, | ||||
|         permission_set_arn, | ||||
|         principal_type, | ||||
|         principal_id, | ||||
|         instance_arn: str, | ||||
|         target_id: str, | ||||
|         target_type: str, | ||||
|         permission_set_arn: str, | ||||
|         principal_type: str, | ||||
|         principal_id: str, | ||||
|     ): | ||||
|         self.request_id = str(random.uuid4()) | ||||
|         self.instance_arn = instance_arn | ||||
| @ -26,8 +27,8 @@ class AccountAssignment(BaseModel): | ||||
|         self.principal_id = principal_id | ||||
|         self.created_date = unix_time() | ||||
| 
 | ||||
|     def to_json(self, include_creation_date=False): | ||||
|         summary = { | ||||
|     def to_json(self, include_creation_date: bool = False) -> Dict[str, Any]: | ||||
|         summary: Dict[str, Any] = { | ||||
|             "TargetId": self.target_id, | ||||
|             "TargetType": self.target_type, | ||||
|             "PermissionSetArn": self.permission_set_arn, | ||||
| @ -42,12 +43,12 @@ class AccountAssignment(BaseModel): | ||||
| class PermissionSet(BaseModel): | ||||
|     def __init__( | ||||
|         self, | ||||
|         name, | ||||
|         description, | ||||
|         instance_arn, | ||||
|         session_duration, | ||||
|         relay_state, | ||||
|         tags, | ||||
|         name: str, | ||||
|         description: str, | ||||
|         instance_arn: str, | ||||
|         session_duration: str, | ||||
|         relay_state: str, | ||||
|         tags: List[Dict[str, str]], | ||||
|     ): | ||||
|         self.name = name | ||||
|         self.description = description | ||||
| @ -58,8 +59,8 @@ class PermissionSet(BaseModel): | ||||
|         self.tags = tags | ||||
|         self.created_date = unix_time() | ||||
| 
 | ||||
|     def to_json(self, include_creation_date=False): | ||||
|         summary = { | ||||
|     def to_json(self, include_creation_date: bool = False) -> Dict[str, Any]: | ||||
|         summary: Dict[str, Any] = { | ||||
|             "Name": self.name, | ||||
|             "Description": self.description, | ||||
|             "PermissionSetArn": self.permission_set_arn, | ||||
| @ -71,7 +72,7 @@ class PermissionSet(BaseModel): | ||||
|         return summary | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def generate_id(instance_arn): | ||||
|     def generate_id(instance_arn: str) -> str: | ||||
|         chars = list(range(10)) + ["a", "b", "c", "d", "e", "f"] | ||||
|         return ( | ||||
|             instance_arn | ||||
| @ -83,20 +84,20 @@ class PermissionSet(BaseModel): | ||||
| class SSOAdminBackend(BaseBackend): | ||||
|     """Implementation of SSOAdmin APIs.""" | ||||
| 
 | ||||
|     def __init__(self, region_name, account_id): | ||||
|     def __init__(self, region_name: str, account_id: str): | ||||
|         super().__init__(region_name, account_id) | ||||
|         self.account_assignments = list() | ||||
|         self.permission_sets = list() | ||||
|         self.account_assignments: List[AccountAssignment] = list() | ||||
|         self.permission_sets: List[PermissionSet] = list() | ||||
| 
 | ||||
|     def create_account_assignment( | ||||
|         self, | ||||
|         instance_arn, | ||||
|         target_id, | ||||
|         target_type, | ||||
|         permission_set_arn, | ||||
|         principal_type, | ||||
|         principal_id, | ||||
|     ): | ||||
|         instance_arn: str, | ||||
|         target_id: str, | ||||
|         target_type: str, | ||||
|         permission_set_arn: str, | ||||
|         principal_type: str, | ||||
|         principal_id: str, | ||||
|     ) -> Dict[str, Any]: | ||||
|         assignment = AccountAssignment( | ||||
|             instance_arn, | ||||
|             target_id, | ||||
| @ -110,13 +111,13 @@ class SSOAdminBackend(BaseBackend): | ||||
| 
 | ||||
|     def delete_account_assignment( | ||||
|         self, | ||||
|         instance_arn, | ||||
|         target_id, | ||||
|         target_type, | ||||
|         permission_set_arn, | ||||
|         principal_type, | ||||
|         principal_id, | ||||
|     ): | ||||
|         instance_arn: str, | ||||
|         target_id: str, | ||||
|         target_type: str, | ||||
|         permission_set_arn: str, | ||||
|         principal_type: str, | ||||
|         principal_id: str, | ||||
|     ) -> Dict[str, Any]: | ||||
|         account = self._find_account( | ||||
|             instance_arn, | ||||
|             target_id, | ||||
| @ -130,13 +131,13 @@ class SSOAdminBackend(BaseBackend): | ||||
| 
 | ||||
|     def _find_account( | ||||
|         self, | ||||
|         instance_arn, | ||||
|         target_id, | ||||
|         target_type, | ||||
|         permission_set_arn, | ||||
|         principal_type, | ||||
|         principal_id, | ||||
|     ): | ||||
|         instance_arn: str, | ||||
|         target_id: str, | ||||
|         target_type: str, | ||||
|         permission_set_arn: str, | ||||
|         principal_type: str, | ||||
|         principal_id: str, | ||||
|     ) -> AccountAssignment: | ||||
|         for account in self.account_assignments: | ||||
|             instance_arn_match = account.instance_arn == instance_arn | ||||
|             target_id_match = account.target_id == target_id | ||||
| @ -155,7 +156,9 @@ class SSOAdminBackend(BaseBackend): | ||||
|                 return account | ||||
|         raise ResourceNotFound | ||||
| 
 | ||||
|     def list_account_assignments(self, instance_arn, account_id, permission_set_arn): | ||||
|     def list_account_assignments( | ||||
|         self, instance_arn: str, account_id: str, permission_set_arn: str | ||||
|     ) -> List[Dict[str, Any]]: | ||||
|         """ | ||||
|         Pagination has not yet been implemented | ||||
|         """ | ||||
| @ -178,13 +181,13 @@ class SSOAdminBackend(BaseBackend): | ||||
| 
 | ||||
|     def create_permission_set( | ||||
|         self, | ||||
|         name, | ||||
|         description, | ||||
|         instance_arn, | ||||
|         session_duration, | ||||
|         relay_state, | ||||
|         tags, | ||||
|     ): | ||||
|         name: str, | ||||
|         description: str, | ||||
|         instance_arn: str, | ||||
|         session_duration: str, | ||||
|         relay_state: str, | ||||
|         tags: List[Dict[str, str]], | ||||
|     ) -> Dict[str, Any]: | ||||
|         permission_set = PermissionSet( | ||||
|             name, | ||||
|             description, | ||||
| @ -198,12 +201,12 @@ class SSOAdminBackend(BaseBackend): | ||||
| 
 | ||||
|     def update_permission_set( | ||||
|         self, | ||||
|         instance_arn, | ||||
|         permission_set_arn, | ||||
|         description, | ||||
|         session_duration, | ||||
|         relay_state, | ||||
|     ): | ||||
|         instance_arn: str, | ||||
|         permission_set_arn: str, | ||||
|         description: str, | ||||
|         session_duration: str, | ||||
|         relay_state: str, | ||||
|     ) -> Dict[str, Any]: | ||||
|         permission_set = self._find_permission_set( | ||||
|             instance_arn, | ||||
|             permission_set_arn, | ||||
| @ -216,10 +219,8 @@ class SSOAdminBackend(BaseBackend): | ||||
|         return permission_set.to_json(True) | ||||
| 
 | ||||
|     def describe_permission_set( | ||||
|         self, | ||||
|         instance_arn, | ||||
|         permission_set_arn, | ||||
|     ): | ||||
|         self, instance_arn: str, permission_set_arn: str | ||||
|     ) -> Dict[str, Any]: | ||||
|         permission_set = self._find_permission_set( | ||||
|             instance_arn, | ||||
|             permission_set_arn, | ||||
| @ -227,10 +228,8 @@ class SSOAdminBackend(BaseBackend): | ||||
|         return permission_set.to_json(True) | ||||
| 
 | ||||
|     def delete_permission_set( | ||||
|         self, | ||||
|         instance_arn, | ||||
|         permission_set_arn, | ||||
|     ): | ||||
|         self, instance_arn: str, permission_set_arn: str | ||||
|     ) -> Dict[str, Any]: | ||||
|         permission_set = self._find_permission_set( | ||||
|             instance_arn, | ||||
|             permission_set_arn, | ||||
| @ -239,10 +238,8 @@ class SSOAdminBackend(BaseBackend): | ||||
|         return permission_set.to_json(include_creation_date=True) | ||||
| 
 | ||||
|     def _find_permission_set( | ||||
|         self, | ||||
|         instance_arn, | ||||
|         permission_set_arn, | ||||
|     ): | ||||
|         self, instance_arn: str, permission_set_arn: str | ||||
|     ) -> PermissionSet: | ||||
|         for permission_set in self.permission_sets: | ||||
|             instance_arn_match = permission_set.instance_arn == instance_arn | ||||
|             permission_set_match = ( | ||||
| @ -253,7 +250,7 @@ class SSOAdminBackend(BaseBackend): | ||||
|         raise ResourceNotFound | ||||
| 
 | ||||
|     @paginate(pagination_model=PAGINATION_MODEL) | ||||
|     def list_permission_sets(self, instance_arn): | ||||
|     def list_permission_sets(self, instance_arn: str) -> List[PermissionSet]:  # type: ignore[misc] | ||||
|         permission_sets = [] | ||||
|         for permission_set in self.permission_sets: | ||||
|             if permission_set.instance_arn == instance_arn: | ||||
|  | ||||
| @ -3,21 +3,21 @@ import json | ||||
| from moto.core.responses import BaseResponse | ||||
| from moto.moto_api._internal import mock_random | ||||
| 
 | ||||
| from .models import ssoadmin_backends | ||||
| from .models import ssoadmin_backends, SSOAdminBackend | ||||
| 
 | ||||
| 
 | ||||
| class SSOAdminResponse(BaseResponse): | ||||
|     """Handler for SSOAdmin requests and responses.""" | ||||
| 
 | ||||
|     def __init__(self): | ||||
|     def __init__(self) -> None: | ||||
|         super().__init__(service_name="sso-admin") | ||||
| 
 | ||||
|     @property | ||||
|     def ssoadmin_backend(self): | ||||
|     def ssoadmin_backend(self) -> SSOAdminBackend: | ||||
|         """Return backend instance specific for this region.""" | ||||
|         return ssoadmin_backends[self.current_account][self.region] | ||||
| 
 | ||||
|     def create_account_assignment(self): | ||||
|     def create_account_assignment(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         instance_arn = params.get("InstanceArn") | ||||
|         target_id = params.get("TargetId") | ||||
| @ -37,7 +37,7 @@ class SSOAdminResponse(BaseResponse): | ||||
|         summary["RequestId"] = str(mock_random.uuid4()) | ||||
|         return json.dumps({"AccountAssignmentCreationStatus": summary}) | ||||
| 
 | ||||
|     def delete_account_assignment(self): | ||||
|     def delete_account_assignment(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         instance_arn = params.get("InstanceArn") | ||||
|         target_id = params.get("TargetId") | ||||
| @ -57,7 +57,7 @@ class SSOAdminResponse(BaseResponse): | ||||
|         summary["RequestId"] = str(mock_random.uuid4()) | ||||
|         return json.dumps({"AccountAssignmentDeletionStatus": summary}) | ||||
| 
 | ||||
|     def list_account_assignments(self): | ||||
|     def list_account_assignments(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         instance_arn = params.get("InstanceArn") | ||||
|         account_id = params.get("AccountId") | ||||
| @ -69,7 +69,7 @@ class SSOAdminResponse(BaseResponse): | ||||
|         ) | ||||
|         return json.dumps({"AccountAssignments": assignments}) | ||||
| 
 | ||||
|     def create_permission_set(self): | ||||
|     def create_permission_set(self) -> str: | ||||
|         name = self._get_param("Name") | ||||
|         description = self._get_param("Description") | ||||
|         instance_arn = self._get_param("InstanceArn") | ||||
| @ -88,7 +88,7 @@ class SSOAdminResponse(BaseResponse): | ||||
| 
 | ||||
|         return json.dumps({"PermissionSet": permission_set}) | ||||
| 
 | ||||
|     def delete_permission_set(self): | ||||
|     def delete_permission_set(self) -> str: | ||||
|         params = json.loads(self.body) | ||||
|         instance_arn = params.get("InstanceArn") | ||||
|         permission_set_arn = params.get("PermissionSetArn") | ||||
| @ -96,8 +96,9 @@ class SSOAdminResponse(BaseResponse): | ||||
|             instance_arn=instance_arn, | ||||
|             permission_set_arn=permission_set_arn, | ||||
|         ) | ||||
|         return "{}" | ||||
| 
 | ||||
|     def update_permission_set(self): | ||||
|     def update_permission_set(self) -> str: | ||||
|         instance_arn = self._get_param("InstanceArn") | ||||
|         permission_set_arn = self._get_param("PermissionSetArn") | ||||
|         description = self._get_param("Description") | ||||
| @ -111,8 +112,9 @@ class SSOAdminResponse(BaseResponse): | ||||
|             session_duration=session_duration, | ||||
|             relay_state=relay_state, | ||||
|         ) | ||||
|         return "{}" | ||||
| 
 | ||||
|     def describe_permission_set(self): | ||||
|     def describe_permission_set(self) -> str: | ||||
|         instance_arn = self._get_param("InstanceArn") | ||||
|         permission_set_arn = self._get_param("PermissionSetArn") | ||||
| 
 | ||||
| @ -122,7 +124,7 @@ class SSOAdminResponse(BaseResponse): | ||||
|         ) | ||||
|         return json.dumps({"PermissionSet": permission_set}) | ||||
| 
 | ||||
|     def list_permission_sets(self): | ||||
|     def list_permission_sets(self) -> str: | ||||
|         instance_arn = self._get_param("InstanceArn") | ||||
|         max_results = self._get_int_param("MaxResults") | ||||
|         next_token = self._get_param("NextToken") | ||||
|  | ||||
| @ -35,7 +35,7 @@ class InvalidToken(AWSError): | ||||
|     TYPE = "InvalidToken" | ||||
|     STATUS = 400 | ||||
| 
 | ||||
|     def __init__(self, message="Invalid token"): | ||||
|     def __init__(self, message: str = "Invalid token"): | ||||
|         super().__init__(f"Invalid Token: {message}") | ||||
| 
 | ||||
| 
 | ||||
| @ -43,5 +43,5 @@ class ResourceNotFound(AWSError): | ||||
|     TYPE = "ResourceNotFound" | ||||
|     STATUS = 400 | ||||
| 
 | ||||
|     def __init__(self, arn): | ||||
|     def __init__(self, arn: str): | ||||
|         super().__init__(f"Resource not found: '{arn}'") | ||||
|  | ||||
| @ -2,6 +2,7 @@ import json | ||||
| import re | ||||
| from datetime import datetime | ||||
| from dateutil.tz import tzlocal | ||||
| from typing import Any, Dict, List, Iterable, Optional, Pattern | ||||
| 
 | ||||
| from moto.core import BaseBackend, BackendDict, CloudFormationModel | ||||
| from moto.core.utils import iso_8601_datetime_with_milliseconds | ||||
| @ -21,19 +22,32 @@ from moto.utilities.paginator import paginate | ||||
| 
 | ||||
| 
 | ||||
| class StateMachine(CloudFormationModel): | ||||
|     def __init__(self, arn, name, definition, roleArn, tags=None): | ||||
|     def __init__( | ||||
|         self, | ||||
|         arn: str, | ||||
|         name: str, | ||||
|         definition: str, | ||||
|         roleArn: str, | ||||
|         tags: Optional[List[Dict[str, str]]] = None, | ||||
|     ): | ||||
|         self.creation_date = iso_8601_datetime_with_milliseconds(datetime.now()) | ||||
|         self.update_date = self.creation_date | ||||
|         self.arn = arn | ||||
|         self.name = name | ||||
|         self.definition = definition | ||||
|         self.roleArn = roleArn | ||||
|         self.executions = [] | ||||
|         self.tags = [] | ||||
|         self.executions: List[Execution] = [] | ||||
|         self.tags: List[Dict[str, str]] = [] | ||||
|         if tags: | ||||
|             self.add_tags(tags) | ||||
| 
 | ||||
|     def start_execution(self, region_name, account_id, execution_name, execution_input): | ||||
|     def start_execution( | ||||
|         self, | ||||
|         region_name: str, | ||||
|         account_id: str, | ||||
|         execution_name: str, | ||||
|         execution_input: str, | ||||
|     ) -> "Execution": | ||||
|         self._ensure_execution_name_doesnt_exist(execution_name) | ||||
|         self._validate_execution_input(execution_input) | ||||
|         execution = Execution( | ||||
| @ -47,7 +61,7 @@ class StateMachine(CloudFormationModel): | ||||
|         self.executions.append(execution) | ||||
|         return execution | ||||
| 
 | ||||
|     def stop_execution(self, execution_arn): | ||||
|     def stop_execution(self, execution_arn: str) -> "Execution": | ||||
|         execution = next( | ||||
|             (x for x in self.executions if x.execution_arn == execution_arn), None | ||||
|         ) | ||||
| @ -58,14 +72,14 @@ class StateMachine(CloudFormationModel): | ||||
|         execution.stop() | ||||
|         return execution | ||||
| 
 | ||||
|     def _ensure_execution_name_doesnt_exist(self, name): | ||||
|     def _ensure_execution_name_doesnt_exist(self, name: str) -> None: | ||||
|         for execution in self.executions: | ||||
|             if execution.name == name: | ||||
|                 raise ExecutionAlreadyExists( | ||||
|                     "Execution Already Exists: '" + execution.execution_arn + "'" | ||||
|                 ) | ||||
| 
 | ||||
|     def _validate_execution_input(self, execution_input): | ||||
|     def _validate_execution_input(self, execution_input: str) -> None: | ||||
|         try: | ||||
|             json.loads(execution_input) | ||||
|         except Exception as ex: | ||||
| @ -73,13 +87,13 @@ class StateMachine(CloudFormationModel): | ||||
|                 "Invalid State Machine Execution Input: '" + str(ex) + "'" | ||||
|             ) | ||||
| 
 | ||||
|     def update(self, **kwargs): | ||||
|     def update(self, **kwargs: Any) -> None: | ||||
|         for key, value in kwargs.items(): | ||||
|             if value is not None: | ||||
|                 setattr(self, key, value) | ||||
|         self.update_date = iso_8601_datetime_with_milliseconds(datetime.now()) | ||||
| 
 | ||||
|     def add_tags(self, tags): | ||||
|     def add_tags(self, tags: List[Dict[str, str]]) -> List[Dict[str, str]]: | ||||
|         merged_tags = [] | ||||
|         for tag in self.tags: | ||||
|             replacement_index = next( | ||||
| @ -96,15 +110,15 @@ class StateMachine(CloudFormationModel): | ||||
|         self.tags = merged_tags | ||||
|         return self.tags | ||||
| 
 | ||||
|     def remove_tags(self, tag_keys): | ||||
|     def remove_tags(self, tag_keys: List[str]) -> List[Dict[str, str]]: | ||||
|         self.tags = [tag_set for tag_set in self.tags if tag_set["key"] not in tag_keys] | ||||
|         return self.tags | ||||
| 
 | ||||
|     @property | ||||
|     def physical_resource_id(self): | ||||
|     def physical_resource_id(self) -> str: | ||||
|         return self.arn | ||||
| 
 | ||||
|     def get_cfn_properties(self, prop_overrides): | ||||
|     def get_cfn_properties(self, prop_overrides: Dict[str, Any]) -> Dict[str, Any]: | ||||
|         property_names = [ | ||||
|             "DefinitionString", | ||||
|             "RoleArn", | ||||
| @ -124,7 +138,7 @@ class StateMachine(CloudFormationModel): | ||||
|         return properties | ||||
| 
 | ||||
|     @classmethod | ||||
|     def has_cfn_attr(cls, attr): | ||||
|     def has_cfn_attr(cls, attr: str) -> bool: | ||||
|         return attr in [ | ||||
|             "Name", | ||||
|             "DefinitionString", | ||||
| @ -133,7 +147,7 @@ class StateMachine(CloudFormationModel): | ||||
|             "Tags", | ||||
|         ] | ||||
| 
 | ||||
|     def get_cfn_attribute(self, attribute_name): | ||||
|     def get_cfn_attribute(self, attribute_name: str) -> Any: | ||||
|         from moto.cloudformation.exceptions import UnformattedGetAttTemplateException | ||||
| 
 | ||||
|         if attribute_name == "Name": | ||||
| @ -150,17 +164,22 @@ class StateMachine(CloudFormationModel): | ||||
|         raise UnformattedGetAttTemplateException() | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def cloudformation_name_type(): | ||||
|     def cloudformation_name_type() -> str: | ||||
|         return "StateMachine" | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def cloudformation_type(): | ||||
|     def cloudformation_type() -> str: | ||||
|         return "AWS::StepFunctions::StateMachine" | ||||
| 
 | ||||
|     @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, | ||||
|     ) -> "StateMachine": | ||||
|         properties = cloudformation_json["Properties"] | ||||
|         name = properties.get("StateMachineName", resource_name) | ||||
|         definition = properties.get("DefinitionString", "") | ||||
| @ -170,19 +189,25 @@ class StateMachine(CloudFormationModel): | ||||
|         return sf_backend.create_state_machine(name, definition, role_arn, tags=tags) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def delete_from_cloudformation_json(cls, resource_name, _, 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: | ||||
|         sf_backend = stepfunction_backends[account_id][region_name] | ||||
|         sf_backend.delete_state_machine(resource_name) | ||||
| 
 | ||||
|     @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, | ||||
|     ) -> "StateMachine": | ||||
|         properties = cloudformation_json.get("Properties", {}) | ||||
|         name = properties.get("StateMachineName", original_resource.name) | ||||
| 
 | ||||
| @ -214,12 +239,12 @@ class StateMachine(CloudFormationModel): | ||||
| class Execution: | ||||
|     def __init__( | ||||
|         self, | ||||
|         region_name, | ||||
|         account_id, | ||||
|         state_machine_name, | ||||
|         execution_name, | ||||
|         state_machine_arn, | ||||
|         execution_input, | ||||
|         region_name: str, | ||||
|         account_id: str, | ||||
|         state_machine_name: str, | ||||
|         execution_name: str, | ||||
|         state_machine_arn: str, | ||||
|         execution_input: str, | ||||
|     ): | ||||
|         execution_arn = "arn:aws:states:{}:{}:execution:{}:{}" | ||||
|         execution_arn = execution_arn.format( | ||||
| @ -235,9 +260,9 @@ class Execution: | ||||
|             if settings.get_sf_execution_history_type() == "SUCCESS" | ||||
|             else "FAILED" | ||||
|         ) | ||||
|         self.stop_date = None | ||||
|         self.stop_date: Optional[str] = None | ||||
| 
 | ||||
|     def get_execution_history(self, roleArn): | ||||
|     def get_execution_history(self, roleArn: str) -> List[Dict[str, Any]]: | ||||
|         sf_execution_history_type = settings.get_sf_execution_history_type() | ||||
|         if sf_execution_history_type == "SUCCESS": | ||||
|             return [ | ||||
| @ -334,8 +359,9 @@ class Execution: | ||||
|                     }, | ||||
|                 }, | ||||
|             ] | ||||
|         return [] | ||||
| 
 | ||||
|     def stop(self): | ||||
|     def stop(self) -> None: | ||||
|         self.status = "ABORTED" | ||||
|         self.stop_date = iso_8601_datetime_with_milliseconds(datetime.now()) | ||||
| 
 | ||||
| @ -451,13 +477,19 @@ class StepFunctionBackend(BaseBackend): | ||||
|         "arn:aws:states:[-0-9a-zA-Z]+:(?P<account_id>[0-9]{12}):execution:.+" | ||||
|     ) | ||||
| 
 | ||||
|     def __init__(self, region_name, account_id): | ||||
|     def __init__(self, region_name: str, account_id: str): | ||||
|         super().__init__(region_name, account_id) | ||||
|         self.state_machines = [] | ||||
|         self.executions = [] | ||||
|         self.state_machines: List[StateMachine] = [] | ||||
|         self.executions: List[Execution] = [] | ||||
|         self._account_id = None | ||||
| 
 | ||||
|     def create_state_machine(self, name, definition, roleArn, tags=None): | ||||
|     def create_state_machine( | ||||
|         self, | ||||
|         name: str, | ||||
|         definition: str, | ||||
|         roleArn: str, | ||||
|         tags: Optional[List[Dict[str, str]]] = None, | ||||
|     ) -> StateMachine: | ||||
|         self._validate_name(name) | ||||
|         self._validate_role_arn(roleArn) | ||||
|         arn = f"arn:aws:states:{self.region_name}:{self.account_id}:stateMachine:{name}" | ||||
| @ -469,11 +501,10 @@ class StepFunctionBackend(BaseBackend): | ||||
|             return state_machine | ||||
| 
 | ||||
|     @paginate(pagination_model=PAGINATION_MODEL) | ||||
|     def list_state_machines(self): | ||||
|         state_machines = sorted(self.state_machines, key=lambda x: x.creation_date) | ||||
|         return state_machines | ||||
|     def list_state_machines(self) -> Iterable[StateMachine]:  # type: ignore[misc] | ||||
|         return sorted(self.state_machines, key=lambda x: x.creation_date) | ||||
| 
 | ||||
|     def describe_state_machine(self, arn): | ||||
|     def describe_state_machine(self, arn: str) -> StateMachine: | ||||
|         self._validate_machine_arn(arn) | ||||
|         sm = next((x for x in self.state_machines if x.arn == arn), None) | ||||
|         if not sm: | ||||
| @ -482,13 +513,15 @@ class StepFunctionBackend(BaseBackend): | ||||
|             ) | ||||
|         return sm | ||||
| 
 | ||||
|     def delete_state_machine(self, arn): | ||||
|     def delete_state_machine(self, arn: str) -> None: | ||||
|         self._validate_machine_arn(arn) | ||||
|         sm = next((x for x in self.state_machines if x.arn == arn), None) | ||||
|         if sm: | ||||
|             self.state_machines.remove(sm) | ||||
| 
 | ||||
|     def update_state_machine(self, arn, definition=None, role_arn=None): | ||||
|     def update_state_machine( | ||||
|         self, arn: str, definition: Optional[str] = None, role_arn: Optional[str] = None | ||||
|     ) -> StateMachine: | ||||
|         sm = self.describe_state_machine(arn) | ||||
|         updates = { | ||||
|             "definition": definition, | ||||
| @ -497,23 +530,24 @@ class StepFunctionBackend(BaseBackend): | ||||
|         sm.update(**updates) | ||||
|         return sm | ||||
| 
 | ||||
|     def start_execution(self, state_machine_arn, name=None, execution_input=None): | ||||
|     def start_execution( | ||||
|         self, state_machine_arn: str, name: str, execution_input: str | ||||
|     ) -> Execution: | ||||
|         state_machine = self.describe_state_machine(state_machine_arn) | ||||
|         execution = state_machine.start_execution( | ||||
|         return state_machine.start_execution( | ||||
|             region_name=self.region_name, | ||||
|             account_id=self.account_id, | ||||
|             execution_name=name or str(mock_random.uuid4()), | ||||
|             execution_input=execution_input, | ||||
|         ) | ||||
|         return execution | ||||
| 
 | ||||
|     def stop_execution(self, execution_arn): | ||||
|     def stop_execution(self, execution_arn: str) -> Execution: | ||||
|         self._validate_execution_arn(execution_arn) | ||||
|         state_machine = self._get_state_machine_for_execution(execution_arn) | ||||
|         return state_machine.stop_execution(execution_arn) | ||||
| 
 | ||||
|     @paginate(pagination_model=PAGINATION_MODEL) | ||||
|     def list_executions(self, state_machine_arn, status_filter=None): | ||||
|     def list_executions(self, state_machine_arn: str, status_filter: Optional[str] = None) -> Iterable[Execution]:  # type: ignore[misc] | ||||
|         """ | ||||
|         The status of every execution is set to 'RUNNING' by default. | ||||
|         Set the following environment variable if you want to get a FAILED status back: | ||||
| @ -530,7 +564,7 @@ class StepFunctionBackend(BaseBackend): | ||||
|         executions = sorted(executions, key=lambda x: x.start_date, reverse=True) | ||||
|         return executions | ||||
| 
 | ||||
|     def describe_execution(self, execution_arn): | ||||
|     def describe_execution(self, execution_arn: str) -> Execution: | ||||
|         """ | ||||
|         The status of every execution is set to 'RUNNING' by default. | ||||
|         Set the following environment variable if you want to get a FAILED status back: | ||||
| @ -551,7 +585,7 @@ class StepFunctionBackend(BaseBackend): | ||||
|             ) | ||||
|         return exctn | ||||
| 
 | ||||
|     def get_execution_history(self, execution_arn): | ||||
|     def get_execution_history(self, execution_arn: str) -> List[Dict[str, Any]]: | ||||
|         """ | ||||
|         A static list of successful events is returned by default. | ||||
|         Set the following environment variable if you want to get a static list of events for a failed execution: | ||||
| @ -572,61 +606,61 @@ class StepFunctionBackend(BaseBackend): | ||||
|             ) | ||||
|         return execution.get_execution_history(state_machine.roleArn) | ||||
| 
 | ||||
|     def list_tags_for_resource(self, arn): | ||||
|     def list_tags_for_resource(self, arn: str) -> List[Dict[str, str]]: | ||||
|         try: | ||||
|             state_machine = self.describe_state_machine(arn) | ||||
|             return state_machine.tags or [] | ||||
|         except StateMachineDoesNotExist: | ||||
|             return [] | ||||
| 
 | ||||
|     def tag_resource(self, resource_arn, tags): | ||||
|     def tag_resource(self, resource_arn: str, tags: List[Dict[str, str]]) -> None: | ||||
|         try: | ||||
|             state_machine = self.describe_state_machine(resource_arn) | ||||
|             state_machine.add_tags(tags) | ||||
|         except StateMachineDoesNotExist: | ||||
|             raise ResourceNotFound(resource_arn) | ||||
| 
 | ||||
|     def untag_resource(self, resource_arn, tag_keys): | ||||
|     def untag_resource(self, resource_arn: str, tag_keys: List[str]) -> None: | ||||
|         try: | ||||
|             state_machine = self.describe_state_machine(resource_arn) | ||||
|             state_machine.remove_tags(tag_keys) | ||||
|         except StateMachineDoesNotExist: | ||||
|             raise ResourceNotFound(resource_arn) | ||||
| 
 | ||||
|     def _validate_name(self, name): | ||||
|     def _validate_name(self, name: str) -> None: | ||||
|         if any(invalid_char in name for invalid_char in self.invalid_chars_for_name): | ||||
|             raise InvalidName("Invalid Name: '" + name + "'") | ||||
| 
 | ||||
|         if any(name.find(char) >= 0 for char in self.invalid_unicodes_for_name): | ||||
|             raise InvalidName("Invalid Name: '" + name + "'") | ||||
| 
 | ||||
|     def _validate_role_arn(self, role_arn): | ||||
|     def _validate_role_arn(self, role_arn: str) -> None: | ||||
|         self._validate_arn( | ||||
|             arn=role_arn, | ||||
|             regex=self.accepted_role_arn_format, | ||||
|             invalid_msg="Invalid Role Arn: '" + role_arn + "'", | ||||
|         ) | ||||
| 
 | ||||
|     def _validate_machine_arn(self, machine_arn): | ||||
|     def _validate_machine_arn(self, machine_arn: str) -> None: | ||||
|         self._validate_arn( | ||||
|             arn=machine_arn, | ||||
|             regex=self.accepted_mchn_arn_format, | ||||
|             invalid_msg="Invalid State Machine Arn: '" + machine_arn + "'", | ||||
|         ) | ||||
| 
 | ||||
|     def _validate_execution_arn(self, execution_arn): | ||||
|     def _validate_execution_arn(self, execution_arn: str) -> None: | ||||
|         self._validate_arn( | ||||
|             arn=execution_arn, | ||||
|             regex=self.accepted_exec_arn_format, | ||||
|             invalid_msg="Execution Does Not Exist: '" + execution_arn + "'", | ||||
|         ) | ||||
| 
 | ||||
|     def _validate_arn(self, arn, regex, invalid_msg): | ||||
|     def _validate_arn(self, arn: str, regex: Pattern[str], invalid_msg: str) -> None: | ||||
|         match = regex.match(arn) | ||||
|         if not arn or not match: | ||||
|             raise InvalidArn(invalid_msg) | ||||
| 
 | ||||
|     def _get_state_machine_for_execution(self, execution_arn): | ||||
|     def _get_state_machine_for_execution(self, execution_arn: str) -> StateMachine: | ||||
|         state_machine_name = execution_arn.split(":")[6] | ||||
|         state_machine_arn = next( | ||||
|             (x.arn for x in self.state_machines if x.name == state_machine_name), None | ||||
|  | ||||
| @ -1,20 +1,21 @@ | ||||
| import json | ||||
| 
 | ||||
| from moto.core.common_types import TYPE_RESPONSE | ||||
| from moto.core.responses import BaseResponse | ||||
| from moto.utilities.aws_headers import amzn_request_id | ||||
| from .models import stepfunction_backends | ||||
| from .models import stepfunction_backends, StepFunctionBackend | ||||
| 
 | ||||
| 
 | ||||
| class StepFunctionResponse(BaseResponse): | ||||
|     def __init__(self): | ||||
|     def __init__(self) -> None: | ||||
|         super().__init__(service_name="stepfunctions") | ||||
| 
 | ||||
|     @property | ||||
|     def stepfunction_backend(self): | ||||
|     def stepfunction_backend(self) -> StepFunctionBackend: | ||||
|         return stepfunction_backends[self.current_account][self.region] | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def create_state_machine(self): | ||||
|     def create_state_machine(self) -> TYPE_RESPONSE: | ||||
|         name = self._get_param("name") | ||||
|         definition = self._get_param("definition") | ||||
|         roleArn = self._get_param("roleArn") | ||||
| @ -29,7 +30,7 @@ class StepFunctionResponse(BaseResponse): | ||||
|         return 200, {}, json.dumps(response) | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def list_state_machines(self): | ||||
|     def list_state_machines(self) -> TYPE_RESPONSE: | ||||
|         max_results = self._get_int_param("maxResults") | ||||
|         next_token = self._get_param("nextToken") | ||||
|         results, next_token = self.stepfunction_backend.list_state_machines( | ||||
| @ -49,12 +50,12 @@ class StepFunctionResponse(BaseResponse): | ||||
|         return 200, {}, json.dumps(response) | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def describe_state_machine(self): | ||||
|     def describe_state_machine(self) -> TYPE_RESPONSE: | ||||
|         arn = self._get_param("stateMachineArn") | ||||
|         return self._describe_state_machine(arn) | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def _describe_state_machine(self, state_machine_arn): | ||||
|     def _describe_state_machine(self, state_machine_arn: str) -> TYPE_RESPONSE: | ||||
|         state_machine = self.stepfunction_backend.describe_state_machine( | ||||
|             state_machine_arn | ||||
|         ) | ||||
| @ -69,13 +70,13 @@ class StepFunctionResponse(BaseResponse): | ||||
|         return 200, {}, json.dumps(response) | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def delete_state_machine(self): | ||||
|     def delete_state_machine(self) -> TYPE_RESPONSE: | ||||
|         arn = self._get_param("stateMachineArn") | ||||
|         self.stepfunction_backend.delete_state_machine(arn) | ||||
|         return 200, {}, json.dumps("{}") | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def update_state_machine(self): | ||||
|     def update_state_machine(self) -> TYPE_RESPONSE: | ||||
|         arn = self._get_param("stateMachineArn") | ||||
|         definition = self._get_param("definition") | ||||
|         role_arn = self._get_param("roleArn") | ||||
| @ -88,28 +89,28 @@ class StepFunctionResponse(BaseResponse): | ||||
|         return 200, {}, json.dumps(response) | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def list_tags_for_resource(self): | ||||
|     def list_tags_for_resource(self) -> TYPE_RESPONSE: | ||||
|         arn = self._get_param("resourceArn") | ||||
|         tags = self.stepfunction_backend.list_tags_for_resource(arn) | ||||
|         response = {"tags": tags} | ||||
|         return 200, {}, json.dumps(response) | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def tag_resource(self): | ||||
|     def tag_resource(self) -> TYPE_RESPONSE: | ||||
|         arn = self._get_param("resourceArn") | ||||
|         tags = self._get_param("tags", []) | ||||
|         self.stepfunction_backend.tag_resource(arn, tags) | ||||
|         return 200, {}, json.dumps({}) | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def untag_resource(self): | ||||
|     def untag_resource(self) -> TYPE_RESPONSE: | ||||
|         arn = self._get_param("resourceArn") | ||||
|         tag_keys = self._get_param("tagKeys", []) | ||||
|         self.stepfunction_backend.untag_resource(arn, tag_keys) | ||||
|         return 200, {}, json.dumps({}) | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def start_execution(self): | ||||
|     def start_execution(self) -> TYPE_RESPONSE: | ||||
|         arn = self._get_param("stateMachineArn") | ||||
|         name = self._get_param("name") | ||||
|         execution_input = self._get_param("input", if_none="{}") | ||||
| @ -123,7 +124,7 @@ class StepFunctionResponse(BaseResponse): | ||||
|         return 200, {}, json.dumps(response) | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def list_executions(self): | ||||
|     def list_executions(self) -> TYPE_RESPONSE: | ||||
|         max_results = self._get_int_param("maxResults") | ||||
|         next_token = self._get_param("nextToken") | ||||
|         arn = self._get_param("stateMachineArn") | ||||
| @ -151,7 +152,7 @@ class StepFunctionResponse(BaseResponse): | ||||
|         return 200, {}, json.dumps(response) | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def describe_execution(self): | ||||
|     def describe_execution(self) -> TYPE_RESPONSE: | ||||
|         arn = self._get_param("executionArn") | ||||
|         execution = self.stepfunction_backend.describe_execution(arn) | ||||
|         response = { | ||||
| @ -166,20 +167,20 @@ class StepFunctionResponse(BaseResponse): | ||||
|         return 200, {}, json.dumps(response) | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def describe_state_machine_for_execution(self): | ||||
|     def describe_state_machine_for_execution(self) -> TYPE_RESPONSE: | ||||
|         arn = self._get_param("executionArn") | ||||
|         execution = self.stepfunction_backend.describe_execution(arn) | ||||
|         return self._describe_state_machine(execution.state_machine_arn) | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def stop_execution(self): | ||||
|     def stop_execution(self) -> TYPE_RESPONSE: | ||||
|         arn = self._get_param("executionArn") | ||||
|         execution = self.stepfunction_backend.stop_execution(arn) | ||||
|         response = {"stopDate": execution.stop_date} | ||||
|         return 200, {}, json.dumps(response) | ||||
| 
 | ||||
|     @amzn_request_id | ||||
|     def get_execution_history(self): | ||||
|     def get_execution_history(self) -> TYPE_RESPONSE: | ||||
|         execution_arn = self._get_param("executionArn") | ||||
|         execution_history = self.stepfunction_backend.get_execution_history( | ||||
|             execution_arn | ||||
|  | ||||
| @ -1,3 +1,6 @@ | ||||
| from typing import Dict, List | ||||
| 
 | ||||
| 
 | ||||
| PAGINATION_MODEL = { | ||||
|     "list_executions": { | ||||
|         "input_token": "next_token", | ||||
| @ -14,11 +17,9 @@ PAGINATION_MODEL = { | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| def cfn_to_api_tags(cfn_tags_entry): | ||||
|     api_tags = [{k.lower(): v for k, v in d.items()} for d in cfn_tags_entry] | ||||
|     return api_tags | ||||
| def cfn_to_api_tags(cfn_tags_entry: List[Dict[str, str]]) -> List[Dict[str, str]]: | ||||
|     return [{k.lower(): v for k, v in d.items()} for d in cfn_tags_entry] | ||||
| 
 | ||||
| 
 | ||||
| def api_to_cfn_tags(api_tags): | ||||
|     cfn_tags_entry = [{k.capitalize(): v for k, v in d.items()} for d in api_tags] | ||||
|     return cfn_tags_entry | ||||
| def api_to_cfn_tags(api_tags: List[Dict[str, str]]) -> List[Dict[str, str]]: | ||||
|     return [{k.capitalize(): v for k, v in d.items()} for d in api_tags] | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| from typing import Any | ||||
| from moto.core.exceptions import RESTError | ||||
| 
 | ||||
| 
 | ||||
| @ -6,5 +7,5 @@ class STSClientError(RESTError): | ||||
| 
 | ||||
| 
 | ||||
| class STSValidationError(STSClientError): | ||||
|     def __init__(self, *args, **kwargs): | ||||
|     def __init__(self, *args: Any, **kwargs: Any): | ||||
|         super().__init__("ValidationError", *args, **kwargs) | ||||
|  | ||||
| @ -1,10 +1,12 @@ | ||||
| from base64 import b64decode | ||||
| from typing import Any, Dict, List, Optional, Tuple | ||||
| import datetime | ||||
| import re | ||||
| import xmltodict | ||||
| 
 | ||||
| from moto.core import BaseBackend, BaseModel, BackendDict | ||||
| from moto.core.utils import iso_8601_datetime_with_milliseconds | ||||
| from moto.iam import iam_backends | ||||
| from moto.iam.models import iam_backends, AccessKey | ||||
| from moto.sts.utils import ( | ||||
|     random_session_token, | ||||
|     DEFAULT_STS_SESSION_DURATION, | ||||
| @ -13,27 +15,27 @@ from moto.sts.utils import ( | ||||
| 
 | ||||
| 
 | ||||
| class Token(BaseModel): | ||||
|     def __init__(self, duration, name=None): | ||||
|     def __init__(self, duration: int, name: Optional[str] = None): | ||||
|         now = datetime.datetime.utcnow() | ||||
|         self.expiration = now + datetime.timedelta(seconds=duration) | ||||
|         self.name = name | ||||
|         self.policy = None | ||||
| 
 | ||||
|     @property | ||||
|     def expiration_ISO8601(self): | ||||
|     def expiration_ISO8601(self) -> str: | ||||
|         return iso_8601_datetime_with_milliseconds(self.expiration) | ||||
| 
 | ||||
| 
 | ||||
| class AssumedRole(BaseModel): | ||||
|     def __init__( | ||||
|         self, | ||||
|         account_id, | ||||
|         access_key, | ||||
|         role_session_name, | ||||
|         role_arn, | ||||
|         policy, | ||||
|         duration, | ||||
|         external_id, | ||||
|         account_id: str, | ||||
|         access_key: AccessKey, | ||||
|         role_session_name: str, | ||||
|         role_arn: str, | ||||
|         policy: str, | ||||
|         duration: int, | ||||
|         external_id: str, | ||||
|     ): | ||||
|         self.account_id = account_id | ||||
|         self.session_name = role_session_name | ||||
| @ -48,11 +50,11 @@ class AssumedRole(BaseModel): | ||||
|         self.session_token = random_session_token() | ||||
| 
 | ||||
|     @property | ||||
|     def expiration_ISO8601(self): | ||||
|     def expiration_ISO8601(self) -> str: | ||||
|         return iso_8601_datetime_with_milliseconds(self.expiration) | ||||
| 
 | ||||
|     @property | ||||
|     def user_id(self): | ||||
|     def user_id(self) -> str: | ||||
|         iam_backend = iam_backends[self.account_id]["global"] | ||||
|         try: | ||||
|             role_id = iam_backend.get_role_by_arn(arn=self.role_arn).id | ||||
| @ -61,31 +63,38 @@ class AssumedRole(BaseModel): | ||||
|         return role_id + ":" + self.session_name | ||||
| 
 | ||||
|     @property | ||||
|     def arn(self): | ||||
|     def arn(self) -> str: | ||||
|         return f"arn:aws:sts::{self.account_id}:assumed-role/{self.role_arn.split('/')[-1]}/{self.session_name}" | ||||
| 
 | ||||
| 
 | ||||
| class STSBackend(BaseBackend): | ||||
|     def __init__(self, region_name, account_id): | ||||
|     def __init__(self, region_name: str, account_id: str): | ||||
|         super().__init__(region_name, account_id) | ||||
|         self.assumed_roles = [] | ||||
|         self.assumed_roles: List[AssumedRole] = [] | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def default_vpc_endpoint_service(service_region, zones): | ||||
|     def default_vpc_endpoint_service( | ||||
|         service_region: str, zones: List[str] | ||||
|     ) -> List[Dict[str, str]]: | ||||
|         """Default VPC endpoint service.""" | ||||
|         return BaseBackend.default_vpc_endpoint_service_factory( | ||||
|             service_region, zones, "sts" | ||||
|         ) | ||||
| 
 | ||||
|     def get_session_token(self, duration): | ||||
|         token = Token(duration=duration) | ||||
|         return token | ||||
|     def get_session_token(self, duration: int) -> Token: | ||||
|         return Token(duration=duration) | ||||
| 
 | ||||
|     def get_federation_token(self, name, duration): | ||||
|         token = Token(duration=duration, name=name) | ||||
|         return token | ||||
|     def get_federation_token(self, name: Optional[str], duration: int) -> Token: | ||||
|         return Token(duration=duration, name=name) | ||||
| 
 | ||||
|     def assume_role(self, role_session_name, role_arn, policy, duration, external_id): | ||||
|     def assume_role( | ||||
|         self, | ||||
|         role_session_name: str, | ||||
|         role_arn: str, | ||||
|         policy: str, | ||||
|         duration: int, | ||||
|         external_id: str, | ||||
|     ) -> AssumedRole: | ||||
|         """ | ||||
|         Assume an IAM Role. Note that the role does not need to exist. The ARN can point to another account, providing an opportunity to switch accounts. | ||||
|         """ | ||||
| @ -103,16 +112,18 @@ class STSBackend(BaseBackend): | ||||
|         account_backend.assumed_roles.append(role) | ||||
|         return role | ||||
| 
 | ||||
|     def get_assumed_role_from_access_key(self, access_key_id): | ||||
|     def get_assumed_role_from_access_key( | ||||
|         self, access_key_id: str | ||||
|     ) -> Optional[AssumedRole]: | ||||
|         for assumed_role in self.assumed_roles: | ||||
|             if assumed_role.access_key_id == access_key_id: | ||||
|                 return assumed_role | ||||
|         return None | ||||
| 
 | ||||
|     def assume_role_with_web_identity(self, **kwargs): | ||||
|     def assume_role_with_web_identity(self, **kwargs: Any) -> AssumedRole: | ||||
|         return self.assume_role(**kwargs) | ||||
| 
 | ||||
|     def assume_role_with_saml(self, **kwargs): | ||||
|     def assume_role_with_saml(self, **kwargs: Any) -> AssumedRole: | ||||
|         del kwargs["principal_arn"] | ||||
|         saml_assertion_encoded = kwargs.pop("saml_assertion") | ||||
|         saml_assertion_decoded = b64decode(saml_assertion_encoded) | ||||
| @ -150,7 +161,7 @@ class STSBackend(BaseBackend): | ||||
|         if "duration" not in kwargs: | ||||
|             kwargs["duration"] = DEFAULT_STS_SESSION_DURATION | ||||
| 
 | ||||
|         account_id, access_key = self._create_access_key(role=target_role) | ||||
|         account_id, access_key = self._create_access_key(role=target_role)  # type: ignore | ||||
|         kwargs["account_id"] = account_id | ||||
|         kwargs["access_key"] = access_key | ||||
| 
 | ||||
| @ -160,7 +171,7 @@ class STSBackend(BaseBackend): | ||||
|         self.assumed_roles.append(role) | ||||
|         return role | ||||
| 
 | ||||
|     def get_caller_identity(self, access_key_id): | ||||
|     def get_caller_identity(self, access_key_id: str) -> Tuple[str, str, str]: | ||||
|         assumed_role = self.get_assumed_role_from_access_key(access_key_id) | ||||
|         if assumed_role: | ||||
|             return assumed_role.user_id, assumed_role.arn, assumed_role.account_id | ||||
| @ -175,7 +186,7 @@ class STSBackend(BaseBackend): | ||||
|         arn = f"arn:aws:sts::{self.account_id}:user/moto" | ||||
|         return user_id, arn, self.account_id | ||||
| 
 | ||||
|     def _create_access_key(self, role): | ||||
|     def _create_access_key(self, role: str) -> Tuple[str, AccessKey]: | ||||
|         account_id_match = re.search(r"arn:aws:iam::([0-9]+).+", role) | ||||
|         if account_id_match: | ||||
|             account_id = account_id_match.group(1) | ||||
|  | ||||
| @ -1,25 +1,25 @@ | ||||
| from moto.core.responses import BaseResponse | ||||
| from .exceptions import STSValidationError | ||||
| from .models import sts_backends | ||||
| from .models import sts_backends, STSBackend | ||||
| 
 | ||||
| MAX_FEDERATION_TOKEN_POLICY_LENGTH = 2048 | ||||
| 
 | ||||
| 
 | ||||
| class TokenResponse(BaseResponse): | ||||
|     def __init__(self): | ||||
|     def __init__(self) -> None: | ||||
|         super().__init__(service_name="sts") | ||||
| 
 | ||||
|     @property | ||||
|     def backend(self): | ||||
|     def backend(self) -> STSBackend: | ||||
|         return sts_backends[self.current_account]["global"] | ||||
| 
 | ||||
|     def get_session_token(self): | ||||
|     def get_session_token(self) -> str: | ||||
|         duration = int(self.querystring.get("DurationSeconds", [43200])[0]) | ||||
|         token = self.backend.get_session_token(duration=duration) | ||||
|         template = self.response_template(GET_SESSION_TOKEN_RESPONSE) | ||||
|         return template.render(token=token) | ||||
| 
 | ||||
|     def get_federation_token(self): | ||||
|     def get_federation_token(self) -> str: | ||||
|         duration = int(self.querystring.get("DurationSeconds", [43200])[0]) | ||||
|         policy = self.querystring.get("Policy", [None])[0] | ||||
| 
 | ||||
| @ -31,14 +31,14 @@ class TokenResponse(BaseResponse): | ||||
|                 f" equal to {MAX_FEDERATION_TOKEN_POLICY_LENGTH}" | ||||
|             ) | ||||
| 
 | ||||
|         name = self.querystring.get("Name")[0] | ||||
|         name = self.querystring.get("Name")[0]  # type: ignore | ||||
|         token = self.backend.get_federation_token(duration=duration, name=name) | ||||
|         template = self.response_template(GET_FEDERATION_TOKEN_RESPONSE) | ||||
|         return template.render(token=token, account_id=self.current_account) | ||||
| 
 | ||||
|     def assume_role(self): | ||||
|         role_session_name = self.querystring.get("RoleSessionName")[0] | ||||
|         role_arn = self.querystring.get("RoleArn")[0] | ||||
|     def assume_role(self) -> str: | ||||
|         role_session_name = self.querystring.get("RoleSessionName")[0]  # type: ignore | ||||
|         role_arn = self.querystring.get("RoleArn")[0]  # type: ignore | ||||
| 
 | ||||
|         policy = self.querystring.get("Policy", [None])[0] | ||||
|         duration = int(self.querystring.get("DurationSeconds", [3600])[0]) | ||||
| @ -54,9 +54,9 @@ class TokenResponse(BaseResponse): | ||||
|         template = self.response_template(ASSUME_ROLE_RESPONSE) | ||||
|         return template.render(role=role) | ||||
| 
 | ||||
|     def assume_role_with_web_identity(self): | ||||
|         role_session_name = self.querystring.get("RoleSessionName")[0] | ||||
|         role_arn = self.querystring.get("RoleArn")[0] | ||||
|     def assume_role_with_web_identity(self) -> str: | ||||
|         role_session_name = self.querystring.get("RoleSessionName")[0]  # type: ignore | ||||
|         role_arn = self.querystring.get("RoleArn")[0]  # type: ignore | ||||
| 
 | ||||
|         policy = self.querystring.get("Policy", [None])[0] | ||||
|         duration = int(self.querystring.get("DurationSeconds", [3600])[0]) | ||||
| @ -72,10 +72,10 @@ class TokenResponse(BaseResponse): | ||||
|         template = self.response_template(ASSUME_ROLE_WITH_WEB_IDENTITY_RESPONSE) | ||||
|         return template.render(role=role) | ||||
| 
 | ||||
|     def assume_role_with_saml(self): | ||||
|         role_arn = self.querystring.get("RoleArn")[0] | ||||
|         principal_arn = self.querystring.get("PrincipalArn")[0] | ||||
|         saml_assertion = self.querystring.get("SAMLAssertion")[0] | ||||
|     def assume_role_with_saml(self) -> str: | ||||
|         role_arn = self.querystring.get("RoleArn")[0]  # type: ignore | ||||
|         principal_arn = self.querystring.get("PrincipalArn")[0]  # type: ignore | ||||
|         saml_assertion = self.querystring.get("SAMLAssertion")[0]  # type: ignore | ||||
| 
 | ||||
|         role = self.backend.assume_role_with_saml( | ||||
|             role_arn=role_arn, | ||||
| @ -85,7 +85,7 @@ class TokenResponse(BaseResponse): | ||||
|         template = self.response_template(ASSUME_ROLE_WITH_SAML_RESPONSE) | ||||
|         return template.render(role=role) | ||||
| 
 | ||||
|     def get_caller_identity(self): | ||||
|     def get_caller_identity(self) -> str: | ||||
|         template = self.response_template(GET_CALLER_IDENTITY_RESPONSE) | ||||
| 
 | ||||
|         access_key_id = self.get_access_key() | ||||
|  | ||||
| @ -16,13 +16,13 @@ def random_session_token() -> str: | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def random_assumed_role_id(): | ||||
| def random_assumed_role_id() -> str: | ||||
|     return ( | ||||
|         ACCOUNT_SPECIFIC_ASSUMED_ROLE_ID_PREFIX + _random_uppercase_or_digit_sequence(9) | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def _random_uppercase_or_digit_sequence(length): | ||||
| def _random_uppercase_or_digit_sequence(length: int) -> str: | ||||
|     return "".join( | ||||
|         str(random.choice(string.ascii_uppercase + string.digits)) | ||||
|         for _ in range(length) | ||||
|  | ||||
| @ -4,6 +4,7 @@ from moto.moto_api._internal.managed_state_model import ManagedState | ||||
| from moto.moto_api._internal import mock_random as random | ||||
| from moto.utilities.utils import load_resource | ||||
| import datetime | ||||
| from typing import Any, Dict, List, Optional | ||||
| 
 | ||||
| 
 | ||||
| checks_json = "resources/describe_trusted_advisor_checks.json" | ||||
| @ -11,7 +12,7 @@ ADVISOR_CHECKS = load_resource(__name__, checks_json) | ||||
| 
 | ||||
| 
 | ||||
| class SupportCase(ManagedState): | ||||
|     def __init__(self, **kwargs): | ||||
|     def __init__(self, **kwargs: Any): | ||||
|         # Configure ManagedState | ||||
|         super().__init__( | ||||
|             "support::case", | ||||
| @ -56,21 +57,21 @@ class SupportCase(ManagedState): | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|     def get_datetime(self): | ||||
|     def get_datetime(self) -> str: | ||||
|         return str(datetime.datetime.now().isoformat()) | ||||
| 
 | ||||
| 
 | ||||
| class SupportBackend(BaseBackend): | ||||
|     def __init__(self, region_name, account_id): | ||||
|     def __init__(self, region_name: str, account_id: str): | ||||
|         super().__init__(region_name, account_id) | ||||
|         self.check_status = {} | ||||
|         self.cases = {} | ||||
|         self.check_status: Dict[str, str] = {} | ||||
|         self.cases: Dict[str, SupportCase] = {} | ||||
| 
 | ||||
|         state_manager.register_default_transition( | ||||
|             model_name="support::case", transition={"progression": "manual", "times": 1} | ||||
|         ) | ||||
| 
 | ||||
|     def describe_trusted_advisor_checks(self): | ||||
|     def describe_trusted_advisor_checks(self) -> List[Dict[str, Any]]: | ||||
|         """ | ||||
|         The Language-parameter is not yet implemented | ||||
|         """ | ||||
| @ -78,18 +79,17 @@ class SupportBackend(BaseBackend): | ||||
|         checks = ADVISOR_CHECKS["checks"] | ||||
|         return checks | ||||
| 
 | ||||
|     def refresh_trusted_advisor_check(self, check_id): | ||||
|     def refresh_trusted_advisor_check(self, check_id: str) -> Dict[str, Any]: | ||||
|         self.advance_check_status(check_id) | ||||
|         status = { | ||||
|         return { | ||||
|             "status": { | ||||
|                 "checkId": check_id, | ||||
|                 "status": self.check_status[check_id], | ||||
|                 "millisUntilNextRefreshable": 123, | ||||
|             } | ||||
|         } | ||||
|         return status | ||||
| 
 | ||||
|     def advance_check_status(self, check_id): | ||||
|     def advance_check_status(self, check_id: str) -> None: | ||||
|         """ | ||||
|         Fake an advancement through statuses on refreshing TA checks | ||||
|         """ | ||||
| @ -111,14 +111,13 @@ class SupportBackend(BaseBackend): | ||||
|         elif self.check_status[check_id] == "abandoned": | ||||
|             self.check_status[check_id] = "none" | ||||
| 
 | ||||
|     def advance_case_status(self, case_id): | ||||
|     def advance_case_status(self, case_id: str) -> None: | ||||
|         """ | ||||
|         Fake an advancement through case statuses | ||||
|         """ | ||||
| 
 | ||||
|         self.cases[case_id].advance() | ||||
| 
 | ||||
|     def advance_case_severity_codes(self, case_id): | ||||
|     def advance_case_severity_codes(self, case_id: str) -> None: | ||||
|         """ | ||||
|         Fake an advancement through case status severities | ||||
|         """ | ||||
| @ -137,28 +136,26 @@ class SupportBackend(BaseBackend): | ||||
|         elif self.cases[case_id].severity_code == "critical": | ||||
|             self.cases[case_id].severity_code = "low" | ||||
| 
 | ||||
|     def resolve_case(self, case_id): | ||||
|     def resolve_case(self, case_id: str) -> Dict[str, Optional[str]]: | ||||
|         self.advance_case_status(case_id) | ||||
| 
 | ||||
|         resolved_case = { | ||||
|         return { | ||||
|             "initialCaseStatus": self.cases[case_id].status, | ||||
|             "finalCaseStatus": "resolved", | ||||
|         } | ||||
| 
 | ||||
|         return resolved_case | ||||
| 
 | ||||
|     # persist case details to self.cases | ||||
|     def create_case( | ||||
|         self, | ||||
|         subject, | ||||
|         service_code, | ||||
|         severity_code, | ||||
|         category_code, | ||||
|         communication_body, | ||||
|         cc_email_addresses, | ||||
|         language, | ||||
|         attachment_set_id, | ||||
|     ): | ||||
|         subject: str, | ||||
|         service_code: str, | ||||
|         severity_code: str, | ||||
|         category_code: str, | ||||
|         communication_body: str, | ||||
|         cc_email_addresses: List[str], | ||||
|         language: str, | ||||
|         attachment_set_id: str, | ||||
|     ) -> Dict[str, str]: | ||||
|         """ | ||||
|         The IssueType-parameter is not yet implemented | ||||
|         """ | ||||
| @ -184,11 +181,11 @@ class SupportBackend(BaseBackend): | ||||
| 
 | ||||
|     def describe_cases( | ||||
|         self, | ||||
|         case_id_list, | ||||
|         include_resolved_cases, | ||||
|         next_token, | ||||
|         include_communications, | ||||
|     ): | ||||
|         case_id_list: List[str], | ||||
|         include_resolved_cases: bool, | ||||
|         next_token: Optional[str], | ||||
|         include_communications: bool, | ||||
|     ) -> Dict[str, Any]: | ||||
|         """ | ||||
|         The following parameters have not yet been implemented: | ||||
|         DisplayID, AfterTime, BeforeTime, MaxResults, Language | ||||
| @ -223,10 +220,7 @@ class SupportBackend(BaseBackend): | ||||
|                 continue | ||||
| 
 | ||||
|             cases.append(formatted_case) | ||||
|         case_values = {"cases": cases} | ||||
|         case_values.update({"nextToken": next_token}) | ||||
| 
 | ||||
|         return case_values | ||||
|         return {"cases": cases, "nextToken": next_token} | ||||
| 
 | ||||
| 
 | ||||
| support_backends = BackendDict( | ||||
|  | ||||
| @ -1,33 +1,33 @@ | ||||
| from moto.core.responses import BaseResponse | ||||
| from .models import support_backends | ||||
| from .models import support_backends, SupportBackend | ||||
| import json | ||||
| 
 | ||||
| 
 | ||||
| class SupportResponse(BaseResponse): | ||||
|     def __init__(self): | ||||
|     def __init__(self) -> None: | ||||
|         super().__init__(service_name="support") | ||||
| 
 | ||||
|     @property | ||||
|     def support_backend(self): | ||||
|     def support_backend(self) -> SupportBackend: | ||||
|         return support_backends[self.current_account][self.region] | ||||
| 
 | ||||
|     def describe_trusted_advisor_checks(self): | ||||
|     def describe_trusted_advisor_checks(self) -> str: | ||||
|         checks = self.support_backend.describe_trusted_advisor_checks() | ||||
| 
 | ||||
|         return json.dumps({"checks": checks}) | ||||
| 
 | ||||
|     def refresh_trusted_advisor_check(self): | ||||
|     def refresh_trusted_advisor_check(self) -> str: | ||||
|         check_id = self._get_param("checkId") | ||||
|         status = self.support_backend.refresh_trusted_advisor_check(check_id=check_id) | ||||
| 
 | ||||
|         return json.dumps(status) | ||||
| 
 | ||||
|     def resolve_case(self): | ||||
|     def resolve_case(self) -> str: | ||||
|         case_id = self._get_param("caseId") | ||||
|         resolve_case_response = self.support_backend.resolve_case(case_id=case_id) | ||||
|         return json.dumps(resolve_case_response) | ||||
| 
 | ||||
|     def create_case(self): | ||||
|     def create_case(self) -> str: | ||||
|         subject = self._get_param("subject") | ||||
|         service_code = self._get_param("serviceCode") | ||||
|         severity_code = self._get_param("severityCode") | ||||
| @ -49,7 +49,7 @@ class SupportResponse(BaseResponse): | ||||
| 
 | ||||
|         return json.dumps(create_case_response) | ||||
| 
 | ||||
|     def describe_cases(self): | ||||
|     def describe_cases(self) -> str: | ||||
|         case_id_list = self._get_param("caseIdList") | ||||
|         include_resolved_cases = self._get_param("includeResolvedCases", False) | ||||
|         next_token = self._get_param("nextToken") | ||||
|  | ||||
| @ -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,moto/sns | ||||
| 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/s* | ||||
| show_column_numbers=True | ||||
| show_error_codes = True | ||||
| disable_error_code=abstract | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user