| 
									
										
										
										
											2015-11-27 14:14:40 -05:00
										 |  |  | import datetime | 
					
						
							| 
									
										
										
										
											2013-02-23 22:26:46 -05:00
										 |  |  | import inspect | 
					
						
							| 
									
										
										
										
											2013-03-05 08:14:43 -05:00
										 |  |  | import re | 
					
						
							| 
									
										
										
										
											2019-11-20 08:27:46 +00:00
										 |  |  | from botocore.exceptions import ClientError | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | from typing import Any, Optional, List, Callable, Dict | 
					
						
							| 
									
										
										
										
											2021-07-26 07:40:39 +01:00
										 |  |  | from urllib.parse import urlparse | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | from .common_types import TYPE_RESPONSE | 
					
						
							| 
									
										
										
										
											2017-10-17 01:06:22 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def camelcase_to_underscores(argument: str) -> str: | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     """Converts a camelcase param like theNewAttribute to the equivalent
 | 
					
						
							| 
									
										
										
										
											2019-10-31 08:44:26 -07:00
										 |  |  |     python underscore variable like the_new_attribute"""
 | 
					
						
							|  |  |  |     result = "" | 
					
						
							| 
									
										
										
										
											2013-02-23 22:26:46 -05:00
										 |  |  |     prev_char_title = True | 
					
						
							| 
									
										
										
										
											2017-12-27 22:58:24 -05:00
										 |  |  |     if not argument: | 
					
						
							|  |  |  |         return argument | 
					
						
							| 
									
										
										
										
											2017-03-15 23:39:36 -04:00
										 |  |  |     for index, char in enumerate(argument): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             next_char_title = argument[index + 1].istitle() | 
					
						
							|  |  |  |         except IndexError: | 
					
						
							|  |  |  |             next_char_title = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         upper_to_lower = char.istitle() and not next_char_title | 
					
						
							|  |  |  |         lower_to_upper = char.istitle() and not prev_char_title | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if index and (upper_to_lower or lower_to_upper): | 
					
						
							|  |  |  |             # Only add underscore if char is capital, not first letter, and next | 
					
						
							|  |  |  |             # char is not capital | 
					
						
							| 
									
										
										
										
											2013-02-23 22:26:46 -05:00
										 |  |  |             result += "_" | 
					
						
							|  |  |  |         prev_char_title = char.istitle() | 
					
						
							|  |  |  |         if not char.isspace():  # Only add non-whitespace | 
					
						
							|  |  |  |             result += char.lower() | 
					
						
							|  |  |  |     return result | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def underscores_to_camelcase(argument: str) -> str: | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     """Converts a camelcase param like the_new_attribute to the equivalent
 | 
					
						
							| 
									
										
										
										
											2015-11-23 14:09:31 +01:00
										 |  |  |     camelcase version like theNewAttribute. Note that the first letter is | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     NOT capitalized by this function"""
 | 
					
						
							| 
									
										
										
										
											2019-10-31 08:44:26 -07:00
										 |  |  |     result = "" | 
					
						
							| 
									
										
										
										
											2015-11-23 14:09:31 +01:00
										 |  |  |     previous_was_underscore = False | 
					
						
							|  |  |  |     for char in argument: | 
					
						
							| 
									
										
										
										
											2019-10-31 08:44:26 -07:00
										 |  |  |         if char != "_": | 
					
						
							| 
									
										
										
										
											2015-11-23 14:09:31 +01:00
										 |  |  |             if previous_was_underscore: | 
					
						
							|  |  |  |                 result += char.upper() | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 result += char | 
					
						
							| 
									
										
										
										
											2019-10-31 08:44:26 -07:00
										 |  |  |         previous_was_underscore = char == "_" | 
					
						
							| 
									
										
										
										
											2015-11-23 14:09:31 +01:00
										 |  |  |     return result | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def pascal_to_camelcase(argument: str) -> str: | 
					
						
							| 
									
										
										
										
											2020-10-12 12:53:30 -07:00
										 |  |  |     """Converts a PascalCase param to the camelCase equivalent""" | 
					
						
							|  |  |  |     return argument[0].lower() + argument[1:] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def camelcase_to_pascal(argument: str) -> str: | 
					
						
							| 
									
										
										
										
											2021-03-05 11:42:07 +01:00
										 |  |  |     """Converts a camelCase param to the PascalCase equivalent""" | 
					
						
							|  |  |  |     return argument[0].upper() + argument[1:] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def method_names_from_class(clazz: object) -> List[str]: | 
					
						
							| 
									
										
										
										
											2021-07-26 07:40:39 +01:00
										 |  |  |     predicate = inspect.isfunction | 
					
						
							| 
									
										
										
										
											2014-08-26 13:25:50 -04:00
										 |  |  |     return [x[0] for x in inspect.getmembers(clazz, predicate=predicate)] | 
					
						
							| 
									
										
										
										
											2013-02-24 11:06:42 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 08:43:20 -01:00
										 |  |  | def convert_regex_to_flask_path(url_path: str) -> str: | 
					
						
							| 
									
										
										
										
											2013-03-05 08:14:43 -05:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Converts a regex matching url to one that can be used with flask | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     for token in ["$"]: | 
					
						
							|  |  |  |         url_path = url_path.replace(token, "") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  |     def caller(reg: Any) -> str: | 
					
						
							| 
									
										
										
										
											2013-03-05 08:14:43 -05:00
										 |  |  |         match_name, match_pattern = reg.groups() | 
					
						
							|  |  |  |         return '<regex("{0}"):{1}>'.format(match_pattern, match_name) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-12 09:34:25 -07:00
										 |  |  |     url_path = re.sub(r"\(\?P<(.*?)>(.*?)\)", caller, url_path) | 
					
						
							| 
									
										
										
										
											2016-01-24 17:13:32 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if url_path.endswith("/?"): | 
					
						
							|  |  |  |         # Flask does own handling of trailing slashes | 
					
						
							|  |  |  |         url_path = url_path.rstrip("/?") | 
					
						
							| 
									
										
										
										
											2013-03-05 08:14:43 -05:00
										 |  |  |     return url_path | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-18 14:18:57 -01:00
										 |  |  | class convert_to_flask_response(object): | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  |     def __init__(self, callback: Callable[..., Any]): | 
					
						
							| 
									
										
										
										
											2013-03-05 08:14:43 -05:00
										 |  |  |         self.callback = callback | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  |     def __name__(self) -> str: | 
					
						
							| 
									
										
										
										
											2013-03-05 08:14:43 -05:00
										 |  |  |         # For instance methods, use class and method names. Otherwise | 
					
						
							|  |  |  |         # use module and method name | 
					
						
							|  |  |  |         if inspect.ismethod(self.callback): | 
					
						
							| 
									
										
										
										
											2014-08-26 13:25:50 -04:00
										 |  |  |             outer = self.callback.__self__.__class__.__name__ | 
					
						
							| 
									
										
										
										
											2013-03-05 08:14:43 -05:00
										 |  |  |         else: | 
					
						
							|  |  |  |             outer = self.callback.__module__ | 
					
						
							| 
									
										
										
										
											2013-10-03 20:34:13 -04:00
										 |  |  |         return "{0}.{1}".format(outer, self.callback.__name__) | 
					
						
							| 
									
										
										
										
											2013-03-05 08:14:43 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  |     def __call__(self, args: Any = None, **kwargs: Any) -> Any: | 
					
						
							| 
									
										
										
										
											2017-02-23 19:43:48 -05:00
										 |  |  |         from flask import request, Response | 
					
						
							| 
									
										
										
										
											2022-09-19 15:04:29 +00:00
										 |  |  |         from moto.moto_api import recorder | 
					
						
							| 
									
										
										
										
											2016-11-07 14:54:22 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-20 08:27:46 +00:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2022-09-19 15:04:29 +00:00
										 |  |  |             recorder._record_request(request) | 
					
						
							| 
									
										
										
										
											2021-11-29 13:35:43 -01:00
										 |  |  |             result = self.callback(request, request.url, dict(request.headers)) | 
					
						
							| 
									
										
										
										
											2019-11-20 08:27:46 +00:00
										 |  |  |         except ClientError as exc: | 
					
						
							|  |  |  |             result = 400, {}, exc.response["Error"]["Message"] | 
					
						
							| 
									
										
										
										
											2013-05-03 20:14:33 -04:00
										 |  |  |         # result is a status, headers, response tuple | 
					
						
							| 
									
										
										
										
											2017-03-11 22:45:42 -05:00
										 |  |  |         if len(result) == 3: | 
					
						
							|  |  |  |             status, headers, content = result | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             status, headers, content = 200, {}, result | 
					
						
							| 
									
										
										
										
											2017-02-23 19:43:48 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |         response = Response(response=content, status=status, headers=headers) | 
					
						
							| 
									
										
										
										
											2019-10-31 08:44:26 -07:00
										 |  |  |         if request.method == "HEAD" and "content-length" in headers: | 
					
						
							|  |  |  |             response.headers["Content-Length"] = headers["content-length"] | 
					
						
							| 
									
										
										
										
											2017-02-23 19:43:48 -05:00
										 |  |  |         return response | 
					
						
							| 
									
										
										
										
											2013-05-24 17:22:34 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-15 22:35:45 -05:00
										 |  |  | class convert_flask_to_responses_response(object): | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  |     def __init__(self, callback: Callable[..., Any]): | 
					
						
							| 
									
										
										
										
											2017-02-15 22:35:45 -05:00
										 |  |  |         self.callback = callback | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  |     def __name__(self) -> str: | 
					
						
							| 
									
										
										
										
											2017-02-15 22:35:45 -05:00
										 |  |  |         # For instance methods, use class and method names. Otherwise | 
					
						
							|  |  |  |         # use module and method name | 
					
						
							|  |  |  |         if inspect.ismethod(self.callback): | 
					
						
							|  |  |  |             outer = self.callback.__self__.__class__.__name__ | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             outer = self.callback.__module__ | 
					
						
							|  |  |  |         return "{0}.{1}".format(outer, self.callback.__name__) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  |     def __call__(self, request: Any, *args: Any, **kwargs: Any) -> TYPE_RESPONSE: | 
					
						
							| 
									
										
										
										
											2017-02-16 22:51:04 -05:00
										 |  |  |         for key, val in request.headers.items(): | 
					
						
							| 
									
										
										
										
											2021-07-26 07:40:39 +01:00
										 |  |  |             if isinstance(val, bytes): | 
					
						
							| 
									
										
										
										
											2017-02-16 22:51:04 -05:00
										 |  |  |                 request.headers[key] = val.decode("utf-8") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-15 22:35:45 -05:00
										 |  |  |         result = self.callback(request, request.url, request.headers) | 
					
						
							|  |  |  |         status, headers, response = result | 
					
						
							|  |  |  |         return status, headers, response | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def iso_8601_datetime_with_milliseconds(value: datetime.datetime) -> str: | 
					
						
							| 
									
										
										
										
											2022-10-04 16:28:30 +00:00
										 |  |  |     return value.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z" | 
					
						
							| 
									
										
										
										
											2013-05-24 17:22:34 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-16 16:52:19 +09:00
										 |  |  | # Even Python does not support nanoseconds, other languages like Go do (needed for Terraform) | 
					
						
							| 
									
										
										
										
											2022-10-26 21:36:02 +00:00
										 |  |  | def iso_8601_datetime_with_nanoseconds(value: datetime.datetime) -> str: | 
					
						
							| 
									
										
										
										
											2022-10-04 16:28:30 +00:00
										 |  |  |     return value.strftime("%Y-%m-%dT%H:%M:%S.%f000Z") | 
					
						
							| 
									
										
										
										
											2021-08-16 16:52:19 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-26 21:36:02 +00:00
										 |  |  | def iso_8601_datetime_without_milliseconds(value: datetime.datetime) -> Optional[str]: | 
					
						
							|  |  |  |     return value.strftime("%Y-%m-%dT%H:%M:%SZ") if value else None | 
					
						
							| 
									
										
										
										
											2017-04-13 21:39:00 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-26 21:36:02 +00:00
										 |  |  | def iso_8601_datetime_without_milliseconds_s3( | 
					
						
							|  |  |  |     value: datetime.datetime, | 
					
						
							|  |  |  | ) -> Optional[str]: | 
					
						
							|  |  |  |     return value.strftime("%Y-%m-%dT%H:%M:%S.000Z") if value else None | 
					
						
							| 
									
										
										
										
											2020-07-26 15:00:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-31 08:44:26 -07:00
										 |  |  | RFC1123 = "%a, %d %b %Y %H:%M:%S GMT" | 
					
						
							| 
									
										
										
										
											2017-05-19 15:59:25 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def rfc_1123_datetime(src: datetime.datetime) -> str: | 
					
						
							| 
									
										
										
										
											2022-10-04 16:28:30 +00:00
										 |  |  |     return src.strftime(RFC1123) | 
					
						
							| 
									
										
										
										
											2015-11-27 14:14:40 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def str_to_rfc_1123_datetime(value: str) -> datetime.datetime: | 
					
						
							| 
									
										
										
										
											2021-12-01 22:06:58 -01:00
										 |  |  |     return datetime.datetime.strptime(value, RFC1123) | 
					
						
							| 
									
										
										
										
											2017-05-19 15:59:25 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def unix_time(dt: Optional[datetime.datetime] = None) -> float: | 
					
						
							| 
									
										
										
										
											2015-11-27 14:14:40 -05:00
										 |  |  |     dt = dt or datetime.datetime.utcnow() | 
					
						
							|  |  |  |     epoch = datetime.datetime.utcfromtimestamp(0) | 
					
						
							|  |  |  |     delta = dt - epoch | 
					
						
							|  |  |  |     return (delta.days * 86400) + (delta.seconds + (delta.microseconds / 1e6)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def unix_time_millis(dt: Optional[datetime.datetime] = None) -> float: | 
					
						
							| 
									
										
										
										
											2015-11-27 14:14:40 -05:00
										 |  |  |     return unix_time(dt) * 1000.0 | 
					
						
							| 
									
										
										
										
											2017-10-17 01:06:22 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-11 13:16:27 +00:00
										 |  |  | def path_url(url: str) -> str: | 
					
						
							| 
									
										
										
										
											2018-09-06 15:15:27 -07:00
										 |  |  |     parsed_url = urlparse(url) | 
					
						
							|  |  |  |     path = parsed_url.path | 
					
						
							|  |  |  |     if not path: | 
					
						
							| 
									
										
										
										
											2019-10-31 08:44:26 -07:00
										 |  |  |         path = "/" | 
					
						
							| 
									
										
										
										
											2018-09-06 15:15:27 -07:00
										 |  |  |     if parsed_url.query: | 
					
						
							| 
									
										
										
										
											2019-10-31 08:44:26 -07:00
										 |  |  |         path = path + "?" + parsed_url.query | 
					
						
							| 
									
										
										
										
											2018-09-06 15:15:27 -07:00
										 |  |  |     return path | 
					
						
							| 
									
										
										
										
											2019-12-09 17:38:26 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-04 16:55:34 +02:00
										 |  |  | def tags_from_query_string( | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  |     querystring_dict: Dict[str, Any], | 
					
						
							|  |  |  |     prefix: str = "Tag", | 
					
						
							|  |  |  |     key_suffix: str = "Key", | 
					
						
							|  |  |  |     value_suffix: str = "Value", | 
					
						
							|  |  |  | ) -> Dict[str, str]: | 
					
						
							| 
									
										
										
										
											2019-09-04 16:25:43 +02:00
										 |  |  |     response_values = {} | 
					
						
							| 
									
										
										
										
											2021-12-01 22:06:58 -01:00
										 |  |  |     for key in querystring_dict.keys(): | 
					
						
							| 
									
										
										
										
											2019-09-04 16:55:34 +02:00
										 |  |  |         if key.startswith(prefix) and key.endswith(key_suffix): | 
					
						
							|  |  |  |             tag_index = key.replace(prefix + ".", "").replace("." + key_suffix, "") | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  |             tag_key = querystring_dict[ | 
					
						
							| 
									
										
										
										
											2019-09-04 16:55:34 +02:00
										 |  |  |                 "{prefix}.{index}.{key_suffix}".format( | 
					
						
							| 
									
										
										
										
											2022-03-10 13:39:59 -01:00
										 |  |  |                     prefix=prefix, index=tag_index, key_suffix=key_suffix | 
					
						
							| 
									
										
										
										
											2020-03-30 13:42:00 +01:00
										 |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  |             ][0] | 
					
						
							| 
									
										
										
										
											2019-09-04 16:55:34 +02:00
										 |  |  |             tag_value_key = "{prefix}.{index}.{value_suffix}".format( | 
					
						
							| 
									
										
										
										
											2022-03-10 13:39:59 -01:00
										 |  |  |                 prefix=prefix, index=tag_index, value_suffix=value_suffix | 
					
						
							| 
									
										
										
										
											2019-09-04 16:55:34 +02:00
										 |  |  |             ) | 
					
						
							| 
									
										
										
										
											2019-09-04 16:25:43 +02:00
										 |  |  |             if tag_value_key in querystring_dict: | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  |                 response_values[tag_key] = querystring_dict[tag_value_key][0] | 
					
						
							| 
									
										
										
										
											2019-09-04 16:25:43 +02:00
										 |  |  |             else: | 
					
						
							|  |  |  |                 response_values[tag_key] = None | 
					
						
							|  |  |  |     return response_values | 
					
						
							| 
									
										
										
										
											2020-07-29 12:44:02 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def tags_from_cloudformation_tags_list( | 
					
						
							|  |  |  |     tags_list: List[Dict[str, str]] | 
					
						
							|  |  |  | ) -> Dict[str, str]: | 
					
						
							| 
									
										
										
										
											2020-07-29 12:44:02 +02:00
										 |  |  |     """Return tags in dict form from cloudformation resource tags form (list of dicts)""" | 
					
						
							|  |  |  |     tags = {} | 
					
						
							|  |  |  |     for entry in tags_list: | 
					
						
							|  |  |  |         key = entry["Key"] | 
					
						
							|  |  |  |         value = entry["Value"] | 
					
						
							|  |  |  |         tags[key] = value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return tags | 
					
						
							| 
									
										
										
										
											2020-10-12 12:53:30 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def remap_nested_keys(root: Any, key_transform: Callable[[str], str]) -> Any: | 
					
						
							| 
									
										
										
										
											2020-10-12 12:53:30 -07:00
										 |  |  |     """This remap ("recursive map") function is used to traverse and
 | 
					
						
							|  |  |  |     transform the dictionary keys of arbitrarily nested structures. | 
					
						
							|  |  |  |     List comprehensions do not recurse, making it tedious to apply | 
					
						
							|  |  |  |     transforms to all keys in a tree-like structure. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     A common issue for `moto` is changing the casing of dict keys: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> remap_nested_keys({'KeyName': 'Value'}, camelcase_to_underscores) | 
					
						
							|  |  |  |     {'key_name': 'Value'} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Args: | 
					
						
							|  |  |  |         root: The target data to traverse. Supports iterables like | 
					
						
							|  |  |  |             :class:`list`, :class:`tuple`, and :class:`dict`. | 
					
						
							|  |  |  |         key_transform (callable): This function is called on every | 
					
						
							|  |  |  |             dictionary key found in *root*. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if isinstance(root, (list, tuple)): | 
					
						
							|  |  |  |         return [remap_nested_keys(item, key_transform) for item in root] | 
					
						
							|  |  |  |     if isinstance(root, dict): | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             key_transform(k): remap_nested_keys(v, key_transform) | 
					
						
							| 
									
										
										
										
											2021-07-26 07:40:39 +01:00
										 |  |  |             for k, v in root.items() | 
					
						
							| 
									
										
										
										
											2020-10-12 12:53:30 -07:00
										 |  |  |         } | 
					
						
							|  |  |  |     return root | 
					
						
							| 
									
										
										
										
											2021-06-30 00:15:45 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def merge_dicts( | 
					
						
							|  |  |  |     dict1: Dict[str, Any], dict2: Dict[str, Any], remove_nulls: bool = False | 
					
						
							|  |  |  | ) -> None: | 
					
						
							| 
									
										
										
										
											2021-06-30 00:15:45 -07:00
										 |  |  |     """Given two arbitrarily nested dictionaries, merge the second dict into the first.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :param dict dict1: the dictionary to be updated. | 
					
						
							|  |  |  |     :param dict dict2: a dictionary of keys/values to be merged into dict1. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     :param bool remove_nulls: If true, updated values equal to None or an empty dictionary | 
					
						
							|  |  |  |         will be removed from dict1. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     for key in dict2: | 
					
						
							|  |  |  |         if isinstance(dict2[key], dict): | 
					
						
							|  |  |  |             if key in dict1 and key in dict2: | 
					
						
							|  |  |  |                 merge_dicts(dict1[key], dict2[key], remove_nulls) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 dict1[key] = dict2[key] | 
					
						
							|  |  |  |             if dict1[key] == {} and remove_nulls: | 
					
						
							|  |  |  |                 dict1.pop(key) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             dict1[key] = dict2[key] | 
					
						
							|  |  |  |             if dict1[key] is None and remove_nulls: | 
					
						
							|  |  |  |                 dict1.pop(key) | 
					
						
							| 
									
										
										
										
											2021-09-30 11:28:13 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def aws_api_matches(pattern: str, string: str) -> bool: | 
					
						
							| 
									
										
										
										
											2021-11-16 07:24:14 -05:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2022-03-10 13:39:59 -01:00
										 |  |  |     AWS API can match a value based on a glob, or an exact match | 
					
						
							| 
									
										
										
										
											2021-11-16 07:24:14 -05:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     # use a negative lookback regex to match stars that are not prefixed with a backslash | 
					
						
							|  |  |  |     # and replace all stars not prefixed w/ a backslash with '.*' to take this from "glob" to PCRE syntax | 
					
						
							| 
									
										
										
										
											2021-12-01 22:06:58 -01:00
										 |  |  |     pattern, _ = re.subn(r"(?<!\\)\*", r".*", pattern) | 
					
						
							| 
									
										
										
										
											2021-11-16 07:24:14 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # ? in the AWS glob form becomes .? in regex | 
					
						
							|  |  |  |     # also, don't substitute it if it is prefixed w/ a backslash | 
					
						
							| 
									
										
										
										
											2021-12-01 22:06:58 -01:00
										 |  |  |     pattern, _ = re.subn(r"(?<!\\)\?", r".?", pattern) | 
					
						
							| 
									
										
										
										
											2021-09-30 11:28:13 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-16 07:24:14 -05:00
										 |  |  |     # aws api seems to anchor | 
					
						
							|  |  |  |     anchored_pattern = f"^{pattern}$" | 
					
						
							| 
									
										
										
										
											2021-09-30 11:28:13 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-16 07:24:14 -05:00
										 |  |  |     if re.match(anchored_pattern, str(string)): | 
					
						
							| 
									
										
										
										
											2021-09-30 11:28:13 -04:00
										 |  |  |         return True | 
					
						
							| 
									
										
										
										
											2021-11-16 07:24:14 -05:00
										 |  |  |     else: | 
					
						
							|  |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-12-24 20:02:45 -01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 18:54:38 -01:00
										 |  |  | def extract_region_from_aws_authorization(string: str) -> Optional[str]: | 
					
						
							| 
									
										
										
										
											2022-03-31 05:47:29 -05:00
										 |  |  |     auth = string or "" | 
					
						
							|  |  |  |     region = re.sub(r".*Credential=[^/]+/[^/]+/([^/]+)/.*", r"\1", auth) | 
					
						
							|  |  |  |     if region == auth: | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  |     return region |