| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  | #!/usr/bin/env python | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  | """Generates template code and response body for specified boto3's operation.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  | To execute: | 
					
						
							|  |  |  |     cd moto  # top-level directory; script will not work from scripts dir | 
					
						
							|  |  |  |     ./scripts/scaffold.py | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  | When prompted, select the service and operation that you want to add. | 
					
						
							|  |  |  | This script will look at the botocore's definition file for the selected | 
					
						
							|  |  |  | service and operation, then auto-generate the code and responses. | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  | Almost all services are supported, as long as the service's protocol is | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  | `query`, `json`, `rest-xml` or `rest-json`.  Even if aws adds new services, this script | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  | will work if the protocol is known. | 
					
						
							| 
									
										
										
										
											2017-09-23 17:03:42 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | TODO: | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |   - This script doesn't generate functions in `responses.py` for | 
					
						
							|  |  |  |     `rest-json`.  That logic needs to be added. | 
					
						
							|  |  |  |   - Some services's operations might cause this script to crash. If that | 
					
						
							|  |  |  |     should happen, please create an issue for the problem. | 
					
						
							| 
									
										
										
										
											2017-09-23 17:03:42 +09:00
										 |  |  | """
 | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  | import os | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  | import random | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | import re | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  | import inspect | 
					
						
							|  |  |  | import importlib | 
					
						
							| 
									
										
										
										
											2024-01-17 10:05:50 +00:00
										 |  |  | import subprocess | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | from lxml import etree | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | import click | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  | import jinja2 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  | from prompt_toolkit import prompt | 
					
						
							| 
									
										
										
										
											2020-01-07 10:12:50 -05:00
										 |  |  | from prompt_toolkit.completion import WordCompleter | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | from botocore import xform_name | 
					
						
							|  |  |  | from botocore.session import Session | 
					
						
							|  |  |  | import boto3 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  | from moto.core.responses import BaseResponse | 
					
						
							| 
									
										
										
										
											2024-02-01 21:08:30 +00:00
										 |  |  | from moto.core.base_backend import BaseBackend | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | from inflection import singularize | 
					
						
							| 
									
										
										
										
											2021-10-21 15:13:43 +00:00
										 |  |  | from implementation_coverage import get_moto_implementation | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  | PRIMITIVE_SHAPES = ["string", "timestamp", "integer", "boolean", "sensitiveStringType", "long"] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  | TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), "./template") | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  | INPUT_IGNORED_IN_BACKEND = ["Marker", "PageSize"] | 
					
						
							|  |  |  | OUTPUT_IGNORED_IN_BACKEND = ["NextMarker"] | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 10:05:50 +00:00
										 |  |  | root_dir = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode().strip() | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | def print_progress(title, body, color): | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     """Prints a color-code message describing current state of progress.""" | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |     click.secho(f"\t{title}\t", fg=color, nl=False) | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  |     click.echo(body) | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  | def select_service(): | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     """Prompt user to select service and operation.""" | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  |     service_name = None | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  |     service_names = Session().get_available_services() | 
					
						
							|  |  |  |     service_completer = WordCompleter(service_names) | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  |     while service_name not in service_names: | 
					
						
							|  |  |  |         service_name = prompt("Select service: ", completer=service_completer) | 
					
						
							|  |  |  |         if service_name not in service_names: | 
					
						
							|  |  |  |             click.secho(f"{service_name} is not valid service", fg="red") | 
					
						
							|  |  |  |     return service_name | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  | def print_service_status(service_name): | 
					
						
							|  |  |  |     implemented, operation_names = get_operations(service_name) | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     click.echo("==Current Implementation Status==") | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  |     for operation_name in operation_names: | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         check = "X" if operation_name in implemented else " " | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |         click.secho(f"[{check}] {operation_name}") | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     click.echo("=================================") | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  | def select_operation(service_name): | 
					
						
							|  |  |  |     implemented, operation_names = get_operations(service_name) | 
					
						
							|  |  |  |     operation_completer = WordCompleter(operation_names) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     operation_available = False | 
					
						
							|  |  |  |     while not operation_available: | 
					
						
							|  |  |  |         operation_name = prompt("Select Operation: ", completer=operation_completer) | 
					
						
							|  |  |  |         if operation_name not in operation_names: | 
					
						
							|  |  |  |             click.secho(f"{operation_name} is not valid operation", fg="red") | 
					
						
							|  |  |  |         elif operation_name in implemented: | 
					
						
							|  |  |  |             click.secho(f"{operation_name} is already implemented", fg="red") | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             operation_available = True | 
					
						
							|  |  |  |     return operation_name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_operations(service_name): | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         moto_client, _name = get_moto_implementation(service_name) | 
					
						
							|  |  |  |     except ModuleNotFoundError: | 
					
						
							|  |  |  |         moto_client = None | 
					
						
							|  |  |  |     real_client = boto3.client(service_name, region_name="us-east-1") | 
					
						
							|  |  |  |     implemented = [] | 
					
						
							|  |  |  |     operation_names = [ | 
					
						
							|  |  |  |         xform_name(op) for op in real_client.meta.service_model.operation_names | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  |     for operation in operation_names: | 
					
						
							|  |  |  |         if moto_client and operation in dir(moto_client): | 
					
						
							|  |  |  |             implemented.append(operation) | 
					
						
							|  |  |  |     return implemented, operation_names | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  | def get_escaped_service(service): | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     """Remove dashes from the service name.""" | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     return service.replace("-", "") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  | def get_lib_dir(service): | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     """Return moto path for the location of the code supporting the service.""" | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     return os.path.join("moto", get_escaped_service(service)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | def get_test_dir(service): | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     """Return moto path for the test directory for the service.""" | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |     return os.path.join("tests", f"test_{get_escaped_service(service)}") | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-22 14:03:12 +09:00
										 |  |  | def render_template(tmpl_dir, tmpl_filename, context, service, alt_filename=None): | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     """Create specified files from Jinja templates for specified service.""" | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |     is_test = "test" in tmpl_dir | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     rendered = ( | 
					
						
							|  |  |  |         jinja2.Environment(loader=jinja2.FileSystemLoader(tmpl_dir)) | 
					
						
							|  |  |  |         .get_template(tmpl_filename) | 
					
						
							|  |  |  |         .render(context) | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     dirname = get_test_dir(service) if is_test else get_lib_dir(service) | 
					
						
							|  |  |  |     filename = alt_filename or os.path.splitext(tmpl_filename)[0] | 
					
						
							|  |  |  |     filepath = os.path.join(dirname, filename) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if os.path.exists(filepath): | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         print_progress("skip creating", filepath, "yellow") | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         print_progress("creating", filepath, "green") | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |         with open(filepath, "w", encoding="utf-8") as fhandle: | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |             fhandle.write(rendered) | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-30 13:00:02 -04:00
										 |  |  | def initialize_service(service, api_protocol): | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     """Create lib and test dirs if they don't exist.""" | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  |     lib_dir = get_lib_dir(service) | 
					
						
							|  |  |  |     test_dir = get_test_dir(service) | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     print_progress("Initializing service", service, "green") | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-02 07:17:02 +09:00
										 |  |  |     client = boto3.client(service) | 
					
						
							|  |  |  |     service_class = client.__class__.__name__ | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     endpoint_prefix = ( | 
					
						
							|  |  |  |         # pylint: disable=protected-access | 
					
						
							|  |  |  |         client._service_model.endpoint_prefix | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  |     tmpl_context = { | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         "service": service, | 
					
						
							|  |  |  |         "service_class": service_class, | 
					
						
							|  |  |  |         "endpoint_prefix": endpoint_prefix, | 
					
						
							|  |  |  |         "api_protocol": api_protocol, | 
					
						
							|  |  |  |         "escaped_service": get_escaped_service(service), | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  |     # initialize service directory | 
					
						
							|  |  |  |     if os.path.exists(lib_dir): | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         print_progress("skip creating", lib_dir, "yellow") | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         print_progress("creating", lib_dir, "green") | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  |         os.makedirs(lib_dir) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     tmpl_dir = os.path.join(TEMPLATE_DIR, "lib") | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  |     for tmpl_filename in os.listdir(tmpl_dir): | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         render_template(tmpl_dir, tmpl_filename, tmpl_context, service) | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # initialize test directory | 
					
						
							|  |  |  |     if os.path.exists(test_dir): | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         print_progress("skip creating", test_dir, "yellow") | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         print_progress("creating", test_dir, "green") | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  |         os.makedirs(test_dir) | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     tmpl_dir = os.path.join(TEMPLATE_DIR, "test") | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  |     for tmpl_filename in os.listdir(tmpl_dir): | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         alt_filename = ( | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |             f"test_{get_escaped_service(service)}.py" | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |             if tmpl_filename == "test_service.py.j2" | 
					
						
							|  |  |  |             else None | 
					
						
							| 
									
										
										
										
											2017-09-20 03:14:14 +09:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         render_template(tmpl_dir, tmpl_filename, tmpl_context, service, alt_filename) | 
					
						
							| 
									
										
										
										
											2017-10-02 07:17:02 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  | def to_upper_camel_case(string): | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     """Convert snake case to camel case.""" | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |     return "".join([_.title() for _ in string.split("_")]) | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  | def to_lower_camel_case(string): | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     """Convert snake to camel case, but start string with lowercase letter.""" | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |     words = string.split("_") | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     return "".join(words[:1] + [_.title() for _ in words[1:]]) | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  | def to_snake_case(string): | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     """Convert camel case to snake case.""" | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |     new_string = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", string) | 
					
						
							|  |  |  |     return re.sub("([a-z0-9])([A-Z])", r"\1_\2", new_string).lower() | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-01 19:44:13 +09:00
										 |  |  | def get_operation_name_in_keys(operation_name, operation_keys): | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     """Return AWS operation name (service) found in list of client services.""" | 
					
						
							| 
									
										
										
										
											2020-09-01 19:44:13 +09:00
										 |  |  |     index = [_.lower() for _ in operation_keys].index(operation_name.lower()) | 
					
						
							|  |  |  |     return operation_keys[index] | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  | def get_function_in_responses( | 
					
						
							|  |  |  |     service, operation, protocol | 
					
						
							|  |  |  | ):  # pylint: disable=too-many-locals | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     """refers to definition of API in botocore, and autogenerates function
 | 
					
						
							|  |  |  |     You can see example of elbv2 from link below. | 
					
						
							|  |  |  |       https://github.com/boto/botocore/blob/develop/botocore/data/elbv2/2015-12-01/service-2.json | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |     escaped_service = get_escaped_service(service) | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     client = boto3.client(service) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     # pylint: disable=protected-access | 
					
						
							| 
									
										
										
										
											2020-09-01 19:44:13 +09:00
										 |  |  |     aws_operation_name = get_operation_name_in_keys( | 
					
						
							|  |  |  |         to_upper_camel_case(operation), | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         list(client._service_model._service_description["operations"].keys()), | 
					
						
							| 
									
										
										
										
											2020-09-01 19:44:13 +09:00
										 |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     op_model = client._service_model.operation_model(aws_operation_name) | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     if not hasattr(op_model.output_shape, "members"): | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  |         outputs = {} | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         outputs = op_model.output_shape.members | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     inputs = op_model.input_shape.members | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     input_names = [ | 
					
						
							|  |  |  |         to_snake_case(_) for _ in inputs.keys() if _ not in INPUT_IGNORED_IN_BACKEND | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  |     output_names = [ | 
					
						
							|  |  |  |         to_snake_case(_) for _ in outputs.keys() if _ not in OUTPUT_IGNORED_IN_BACKEND | 
					
						
							|  |  |  |     ] | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     body = "" | 
					
						
							|  |  |  |     if protocol in ["rest-xml"]: | 
					
						
							|  |  |  |         body += f"\ndef {operation}(self, request, full_url, headers):\n" | 
					
						
							|  |  |  |         body += "    self.setup_class(request, full_url, headers)\n" | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         body = f"\ndef {operation}(self):\n" | 
					
						
							|  |  |  |     body += "    params = self._get_params()\n" | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     for input_name, input_type in inputs.items(): | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  |         body += f"    {to_snake_case(input_name)} = params.get(\"{input_name}\")\n" | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     if output_names: | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |         body += f"    {', '.join(output_names)} = self.{escaped_service}_backend.{operation}(\n" | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |         body += f"    self.{escaped_service}_backend.{operation}(\n" | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     for input_name in input_names: | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |         body += f"        {input_name}={input_name},\n" | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     body += "    )\n" | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  |     if protocol in ["query", "rest-xml"]: | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |         body += f"    template = self.response_template({operation.upper()}_TEMPLATE)\n" | 
					
						
							|  |  |  |         names = ", ".join([f"{n}={n}" for n in output_names]) | 
					
						
							|  |  |  |         body += f"    return template.render({names})\n" | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     elif protocol in ["json", "rest-json"]: | 
					
						
							|  |  |  |         body += "    # TODO: adjust response\n" | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |         names = ", ".join([f"{to_lower_camel_case(_)}={_}" for _ in output_names]) | 
					
						
							|  |  |  |         body += f"    return json.dumps(dict({names}))\n" | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     return body | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_function_in_models(service, operation): | 
					
						
							|  |  |  |     """refers to definition of API in botocore, and autogenerates function
 | 
					
						
							|  |  |  |     You can see example of elbv2 from link below. | 
					
						
							|  |  |  |       https://github.com/boto/botocore/blob/develop/botocore/data/elbv2/2015-12-01/service-2.json | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     client = boto3.client(service) | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # pylint: disable=protected-access | 
					
						
							| 
									
										
										
										
											2020-09-01 19:44:13 +09:00
										 |  |  |     aws_operation_name = get_operation_name_in_keys( | 
					
						
							|  |  |  |         to_upper_camel_case(operation), | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         list(client._service_model._service_description["operations"].keys()), | 
					
						
							| 
									
										
										
										
											2020-09-01 19:44:13 +09:00
										 |  |  |     ) | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     op_model = client._service_model.operation_model(aws_operation_name) | 
					
						
							|  |  |  |     inputs = op_model.input_shape.members | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     if not hasattr(op_model.output_shape, "members"): | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  |         outputs = {} | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         outputs = op_model.output_shape.members | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     input_names = [ | 
					
						
							|  |  |  |         to_snake_case(_) for _ in inputs.keys() if _ not in INPUT_IGNORED_IN_BACKEND | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  |     output_names = [ | 
					
						
							|  |  |  |         to_snake_case(_) for _ in outputs.keys() if _ not in OUTPUT_IGNORED_IN_BACKEND | 
					
						
							|  |  |  |     ] | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     if input_names: | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |         body = f"def {operation}(self, {', '.join(input_names)}):\n" | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         body = "def {}(self)\n" | 
					
						
							|  |  |  |     body += "    # implement here\n" | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |     body += f"    return {', '.join(output_names)}\n\n" | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return body | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  | def get_func_in_tests(service, operation): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Autogenerates an example unit test | 
					
						
							|  |  |  |     Throws an exception by default, to remind the user to implement this | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     escaped_service = get_escaped_service(service) | 
					
						
							|  |  |  |     random_region = random.choice(["us-east-2", "eu-west-1", "ap-southeast-1"]) | 
					
						
							|  |  |  |     body = "\n\n" | 
					
						
							| 
									
										
										
										
											2024-01-17 10:05:50 +00:00
										 |  |  |     body += f"@mock_aws\n" | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  |     body += f"def test_{operation}():\n" | 
					
						
							|  |  |  |     body += f"    client = boto3.client(\"{service}\", region_name=\"{random_region}\")\n" | 
					
						
							|  |  |  |     body += f"    resp = client.{operation}()\n" | 
					
						
							|  |  |  |     body += f"\n" | 
					
						
							|  |  |  |     body += f"    raise Exception(\"NotYetImplemented\")" | 
					
						
							|  |  |  |     body += "\n" | 
					
						
							|  |  |  |     return body | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  | def _get_subtree(name, shape, replace_list, name_prefix=None): | 
					
						
							|  |  |  |     if not name_prefix: | 
					
						
							|  |  |  |         name_prefix = [] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     class_name = shape.__class__.__name__ | 
					
						
							| 
									
										
										
										
											2021-12-07 11:09:13 -01:00
										 |  |  |     shape_type = shape.type_name | 
					
						
							|  |  |  |     if class_name in ("StringShape", "Shape") or shape_type == "structure": | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |         tree = etree.Element(name)  # pylint: disable=c-extension-no-member | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |         if name_prefix: | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |             tree.text = f"{{{{ {name_prefix[-1]}.{to_snake_case(name)} }}}}" | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |             tree.text = f"{{{{ {to_snake_case(name)} }}}}" | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |         return tree | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-07 11:09:13 -01:00
										 |  |  |     if class_name in ("ListShape",) or shape_type == "list": | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |         # pylint: disable=c-extension-no-member | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |         replace_list.append((name, name_prefix)) | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |         tree = etree.Element(name) | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         t_member = etree.Element("member") | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |         tree.append(t_member) | 
					
						
							| 
									
										
										
										
											2021-12-07 11:09:13 -01:00
										 |  |  |         if hasattr(shape.member, "members"): | 
					
						
							|  |  |  |             for nested_name, nested_shape in shape.member.members.items(): | 
					
						
							|  |  |  |                 t_member.append( | 
					
						
							|  |  |  |                     _get_subtree( | 
					
						
							|  |  |  |                         nested_name, | 
					
						
							|  |  |  |                         nested_shape, | 
					
						
							|  |  |  |                         replace_list, | 
					
						
							|  |  |  |                         name_prefix + [singularize(name.lower())], | 
					
						
							|  |  |  |                     ) | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |         return tree | 
					
						
							| 
									
										
										
										
											2021-12-07 11:09:13 -01:00
										 |  |  |     raise ValueError(f"Not supported Shape: {shape}") | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  | def get_response_query_template(service, operation):  # pylint: disable=too-many-locals | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     """refers to definition of API in botocore, and autogenerates template
 | 
					
						
							| 
									
										
										
										
											2017-09-21 21:23:13 +09:00
										 |  |  |     Assume that response format is xml when protocol is query | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     You can see example of elbv2 from link below. | 
					
						
							|  |  |  |       https://github.com/boto/botocore/blob/develop/botocore/data/elbv2/2015-12-01/service-2.json | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     client = boto3.client(service) | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # pylint: disable=protected-access | 
					
						
							| 
									
										
										
										
											2020-09-01 19:44:13 +09:00
										 |  |  |     aws_operation_name = get_operation_name_in_keys( | 
					
						
							|  |  |  |         to_upper_camel_case(operation), | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         list(client._service_model._service_description["operations"].keys()), | 
					
						
							| 
									
										
										
										
											2020-09-01 19:44:13 +09:00
										 |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     op_model = client._service_model.operation_model(aws_operation_name) | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     result_wrapper = op_model.output_shape.serialization["resultWrapper"] | 
					
						
							|  |  |  |     response_wrapper = result_wrapper.replace("Result", "Response") | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     metadata = op_model.metadata | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     xml_namespace = metadata["xmlNamespace"] | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # build xml tree | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |     # pylint: disable=c-extension-no-member | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     t_root = etree.Element(response_wrapper, xmlns=xml_namespace) | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # build metadata | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     t_metadata = etree.Element("ResponseMetadata") | 
					
						
							|  |  |  |     t_request_id = etree.Element("RequestId") | 
					
						
							|  |  |  |     t_request_id.text = "1549581b-12b7-11e3-895e-1334aEXAMPLE" | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     t_metadata.append(t_request_id) | 
					
						
							|  |  |  |     t_root.append(t_metadata) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # build result | 
					
						
							|  |  |  |     t_result = etree.Element(result_wrapper) | 
					
						
							|  |  |  |     outputs = op_model.output_shape.members | 
					
						
							|  |  |  |     replace_list = [] | 
					
						
							|  |  |  |     for output_name, output_shape in outputs.items(): | 
					
						
							|  |  |  |         t_result.append(_get_subtree(output_name, output_shape, replace_list)) | 
					
						
							|  |  |  |     t_root.append(t_result) | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     xml_body = etree.tostring(t_root, pretty_print=True).decode("utf-8") | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  |     xml_body_lines = xml_body.splitlines() | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  |     for replace in replace_list: | 
					
						
							|  |  |  |         name = replace[0] | 
					
						
							|  |  |  |         prefix = replace[1] | 
					
						
							|  |  |  |         singular_name = singularize(name) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |         start_tag = f"<{name}>" | 
					
						
							|  |  |  |         iter_name = f"{prefix[-1]}.{name.lower()}" if prefix else name.lower() | 
					
						
							| 
									
										
										
										
											2021-11-24 22:07:44 -01:00
										 |  |  |         loop_start = f"{{% for {singular_name.lower()} in {iter_name} %}}" | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |         end_tag = f"</{name}>" | 
					
						
							| 
									
										
										
										
											2021-11-24 22:07:44 -01:00
										 |  |  |         loop_end = "{% endfor %}" | 
					
						
							| 
									
										
										
										
											2017-09-21 21:23:13 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  |         start_tag_indexes = [i for i, l in enumerate(xml_body_lines) if start_tag in l] | 
					
						
							| 
									
										
										
										
											2017-09-21 21:23:13 +09:00
										 |  |  |         if len(start_tag_indexes) != 1: | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |             raise Exception(f"tag {start_tag} not found in response body") | 
					
						
							| 
									
										
										
										
											2017-09-21 21:23:13 +09:00
										 |  |  |         start_tag_index = start_tag_indexes[0] | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  |         xml_body_lines.insert(start_tag_index + 1, loop_start) | 
					
						
							| 
									
										
										
										
											2017-09-21 21:23:13 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  |         end_tag_indexes = [i for i, l in enumerate(xml_body_lines) if end_tag in l] | 
					
						
							| 
									
										
										
										
											2017-09-21 21:23:13 +09:00
										 |  |  |         if len(end_tag_indexes) != 1: | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |             raise Exception(f"tag {end_tag} not found in response body") | 
					
						
							| 
									
										
										
										
											2017-09-21 21:23:13 +09:00
										 |  |  |         end_tag_index = end_tag_indexes[0] | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  |         xml_body_lines.insert(end_tag_index, loop_end) | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     xml_body = "\n".join(xml_body_lines) | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |     body = f'\n{operation.upper()}_TEMPLATE = """{xml_body}"""' | 
					
						
							| 
									
										
										
										
											2017-09-21 21:23:13 +09:00
										 |  |  |     return body | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  | def get_response_restxml_template(service, operation): | 
					
						
							|  |  |  |     """refers to definition of API in botocore, and autogenerates template
 | 
					
						
							|  |  |  |         Assume that protocol is rest-xml. Shares some familiarity with protocol=query | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     client = boto3.client(service) | 
					
						
							|  |  |  |     aws_operation_name = get_operation_name_in_keys( | 
					
						
							|  |  |  |         to_upper_camel_case(operation), | 
					
						
							|  |  |  |         list(client._service_model._service_description["operations"].keys()), | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     op_model = client._service_model.operation_model(aws_operation_name) | 
					
						
							|  |  |  |     result_wrapper = op_model._operation_model["output"]["shape"] | 
					
						
							|  |  |  |     response_wrapper = result_wrapper.replace("Result", "Response") | 
					
						
							|  |  |  |     metadata = op_model.metadata | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     shapes = client._service_model._shape_resolver._shape_map | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # build xml tree | 
					
						
							|  |  |  |     t_root = etree.Element(response_wrapper) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # build metadata | 
					
						
							|  |  |  |     t_metadata = etree.Element("ResponseMetadata") | 
					
						
							|  |  |  |     t_request_id = etree.Element("RequestId") | 
					
						
							|  |  |  |     t_request_id.text = "1549581b-12b7-11e3-895e-1334aEXAMPLE" | 
					
						
							|  |  |  |     t_metadata.append(t_request_id) | 
					
						
							|  |  |  |     t_root.append(t_metadata) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _find_member(tree, shape_name, name_prefix): | 
					
						
							|  |  |  |         shape = shapes[shape_name] | 
					
						
							|  |  |  |         if shape["type"] == "list": | 
					
						
							|  |  |  |             t_for = etree.Element("REPLACE_FOR") | 
					
						
							|  |  |  |             t_for.set("for", to_snake_case(shape_name)) | 
					
						
							|  |  |  |             t_for.set("in", ".".join(name_prefix[:-1]) + "." + shape_name) | 
					
						
							|  |  |  |             member_shape = shape["member"]["shape"] | 
					
						
							|  |  |  |             member_name = shape["member"].get("locationName") | 
					
						
							|  |  |  |             if member_shape in PRIMITIVE_SHAPES: | 
					
						
							|  |  |  |                 t_member = etree.Element(member_name) | 
					
						
							|  |  |  |                 t_member.text = f"{{{{ {to_snake_case(shape_name)}.{to_snake_case(member_name)} }}}}" | 
					
						
							|  |  |  |                 t_for.append(t_member) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 _find_member(t_for, member_shape, [to_snake_case(shape_name)]) | 
					
						
							|  |  |  |             tree.append(t_for) | 
					
						
							|  |  |  |         elif shape["type"] in PRIMITIVE_SHAPES: | 
					
						
							|  |  |  |             tree.text = f"{{{{ {shape_name} }}}}" | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             for child, details in shape["members"].items(): | 
					
						
							|  |  |  |                 child = details.get("locationName", child) | 
					
						
							|  |  |  |                 if details["shape"] in PRIMITIVE_SHAPES: | 
					
						
							|  |  |  |                     t_member = etree.Element(child) | 
					
						
							|  |  |  |                     t_member.text = "{{ " + ".".join(name_prefix) + f".{to_snake_case(child)} }}}}" | 
					
						
							|  |  |  |                     tree.append(t_member) | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     t = etree.Element(child) | 
					
						
							|  |  |  |                     _find_member(t, details["shape"], name_prefix + [to_snake_case(child)]) | 
					
						
							|  |  |  |                     tree.append(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # build result | 
					
						
							|  |  |  |     t_result = etree.Element(result_wrapper) | 
					
						
							|  |  |  |     for name, details in shapes[result_wrapper]["members"].items(): | 
					
						
							|  |  |  |         shape = details["shape"] | 
					
						
							|  |  |  |         name = details.get("locationName", name) | 
					
						
							|  |  |  |         if shape in PRIMITIVE_SHAPES: | 
					
						
							|  |  |  |             t_member = etree.Element(name) | 
					
						
							|  |  |  |             t_member.text = f"{{{{ {to_snake_case(name)} }}}}" | 
					
						
							|  |  |  |             t_result.append(t_member) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             _find_member(t_result, name, name_prefix=["root"]) | 
					
						
							|  |  |  |     t_root.append(t_result) | 
					
						
							|  |  |  |     xml_body = etree.tostring(t_root, pretty_print=True).decode("utf-8") | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  |     # Still need to add FOR-loops in this template | 
					
						
							|  |  |  |     #     <REPLACE_FOR for="x" in="y"> | 
					
						
							|  |  |  |     # becomes | 
					
						
							|  |  |  |     #     {% for x in y %} | 
					
						
							|  |  |  |     def conv(m): | 
					
						
							|  |  |  |         return m.group().replace("REPLACE_FOR", "").replace("=", "").replace('"', " ").replace("<", "{%").replace(">", "%}").strip() | 
					
						
							|  |  |  |     xml_body = re.sub(r"<REPLACE_FOR[\sa-zA-Z\"=_.]+>", conv, xml_body) | 
					
						
							|  |  |  |     xml_body = xml_body.replace("</REPLACE_FOR>", "{% endfor %}") | 
					
						
							|  |  |  |     body = f'\n{operation.upper()}_TEMPLATE = """{xml_body}"""' | 
					
						
							|  |  |  |     return body | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  | def insert_code_to_class(path, base_class, new_code): | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     """Add code for class handling service's response or backend.""" | 
					
						
							|  |  |  |     with open(path, encoding="utf-8") as fhandle: | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |         lines = [_.replace("\n", "") for _ in fhandle.readlines()] | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     mod_path = os.path.splitext(path)[0].replace("/", ".") | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  |     mod = importlib.import_module(mod_path) | 
					
						
							|  |  |  |     clsmembers = inspect.getmembers(mod, inspect.isclass) | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     _response_cls = [ | 
					
						
							|  |  |  |         _[1] for _ in clsmembers if issubclass(_[1], base_class) and _[1] != base_class | 
					
						
							|  |  |  |     ] | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  |     if len(_response_cls) != 1: | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         raise Exception("unknown error, number of clsmembers is not 1") | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  |     response_cls = _response_cls[0] | 
					
						
							|  |  |  |     code_lines, line_no = inspect.getsourcelines(response_cls) | 
					
						
							|  |  |  |     end_line_no = line_no + len(code_lines) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     func_lines = [" " * 4 + _ for _ in new_code.splitlines()] | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     lines = lines[:end_line_no] + func_lines + lines[end_line_no:] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     body = "\n".join(lines) + "\n" | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     with open(path, "w", encoding="utf-8") as fhandle: | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |         fhandle.write(body) | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  | def insert_url(service, operation, api_protocol):  # pylint: disable=too-many-locals | 
					
						
							|  |  |  |     """Create urls.py with appropriate URL bases and paths.""" | 
					
						
							| 
									
										
										
										
											2017-10-02 07:17:02 +09:00
										 |  |  |     client = boto3.client(service) | 
					
						
							|  |  |  |     service_class = client.__class__.__name__ | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # pylint: disable=protected-access | 
					
						
							| 
									
										
										
										
											2020-09-01 19:44:13 +09:00
										 |  |  |     aws_operation_name = get_operation_name_in_keys( | 
					
						
							|  |  |  |         to_upper_camel_case(operation), | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         list(client._service_model._service_description["operations"].keys()), | 
					
						
							| 
									
										
										
										
											2020-09-01 19:44:13 +09:00
										 |  |  |     ) | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     uri = client._service_model.operation_model(aws_operation_name).http["requestUri"] | 
					
						
							| 
									
										
										
										
											2017-10-02 07:17:02 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     path = os.path.join( | 
					
						
							|  |  |  |         os.path.dirname(__file__), "..", "moto", get_escaped_service(service), "urls.py" | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     with open(path, encoding="utf-8") as fhandle: | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |         lines = [_.replace("\n", "") for _ in fhandle.readlines()] | 
					
						
							| 
									
										
										
										
											2017-10-02 07:17:02 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if any(_ for _ in lines if re.match(uri, _)): | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     url_paths_found = False | 
					
						
							|  |  |  |     last_elem_line_index = -1 | 
					
						
							|  |  |  |     for i, line in enumerate(lines): | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         if line.startswith("url_paths"): | 
					
						
							| 
									
										
										
										
											2017-10-02 07:17:02 +09:00
										 |  |  |             url_paths_found = True | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |         if url_paths_found and line.startswith("}"): | 
					
						
							| 
									
										
										
										
											2017-10-02 07:17:02 +09:00
										 |  |  |             last_elem_line_index = i - 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     prev_line = lines[last_elem_line_index] | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     if not prev_line.endswith("{") and not prev_line.endswith(","): | 
					
						
							|  |  |  |         lines[last_elem_line_index] += "," | 
					
						
							| 
									
										
										
										
											2017-10-02 07:17:02 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  |     # generate url pattern | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     if api_protocol == "rest-json": | 
					
						
							| 
									
										
										
										
											2024-01-17 10:05:50 +00:00
										 |  |  |         new_line = f'    "{0}/.*$": {service_class}Response.dispatch,' | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  |     elif api_protocol == "rest-xml": | 
					
						
							|  |  |  |         new_line = f'    "{{0}}{uri}$": {service_class}Response.{operation},' | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |         new_line = f'    "{{0}}{uri}$": {service_class}Response.dispatch,' | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  |     if new_line in lines: | 
					
						
							|  |  |  |         return | 
					
						
							| 
									
										
										
										
											2017-10-02 07:17:02 +09:00
										 |  |  |     lines.insert(last_elem_line_index + 1, new_line) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     body = "\n".join(lines) + "\n" | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     with open(path, "w", encoding="utf-8") as fhandle: | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |         fhandle.write(body) | 
					
						
							| 
									
										
										
										
											2017-10-02 07:17:02 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  | def insert_codes(service, operation, api_protocol): | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     """Create the responses.py and models.py for the service and operation.""" | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |     escaped_service = get_escaped_service(service) | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  |     func_in_responses = get_function_in_responses(service, operation, api_protocol) | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  |     func_in_models = get_function_in_models(service, operation) | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  |     func_in_tests = get_func_in_tests(service, operation) | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  |     # edit responses.py | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |     responses_path = f"moto/{escaped_service}/responses.py" | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     print_progress("inserting code", responses_path, "green") | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  |     insert_code_to_class(responses_path, BaseResponse, func_in_responses) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # insert template | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  |     if api_protocol in ["query", "rest-xml"]: | 
					
						
							|  |  |  |         if api_protocol == "query": | 
					
						
							|  |  |  |             template = get_response_query_template(service, operation) | 
					
						
							|  |  |  |         elif api_protocol == "rest-xml": | 
					
						
							|  |  |  |             template = get_response_restxml_template(service, operation) | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |         with open(responses_path, encoding="utf-8") as fhandle: | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |             lines = [_[:-1] for _ in fhandle.readlines()] | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  |         lines += template.splitlines() | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |         with open(responses_path, "w", encoding="utf-8") as fhandle: | 
					
						
							| 
									
										
										
										
											2021-08-24 11:49:45 -04:00
										 |  |  |             fhandle.write("\n".join(lines)) | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # edit models.py | 
					
						
							| 
									
										
										
										
											2021-10-30 06:09:44 -04:00
										 |  |  |     models_path = f"moto/{escaped_service}/models.py" | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  |     print_progress("inserting code", models_path, "green") | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  |     insert_code_to_class(models_path, BaseBackend, func_in_models) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-02 07:17:02 +09:00
										 |  |  |     # edit urls.py | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  |     insert_url(service, operation, api_protocol) | 
					
						
							| 
									
										
										
										
											2017-10-02 07:17:02 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  |     # Edit tests | 
					
						
							|  |  |  |     tests_path= f"tests/test_{escaped_service}/test_{escaped_service}.py" | 
					
						
							|  |  |  |     print_progress("inserting code", tests_path, "green") | 
					
						
							|  |  |  |     with open(tests_path, "a", encoding="utf-8") as fhandle: | 
					
						
							|  |  |  |         fhandle.write(func_in_tests) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-02 07:17:02 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-20 02:10:10 +09:00
										 |  |  | @click.command() | 
					
						
							|  |  |  | def main(): | 
					
						
							| 
									
										
										
										
											2021-11-08 22:04:44 -01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     click.echo("This script uses the click-module.\n") | 
					
						
							|  |  |  |     click.echo(" - Start typing the name of the service you want to extend\n" | 
					
						
							|  |  |  |                " - Use Tab to auto-complete the first suggest service\n" | 
					
						
							|  |  |  |                " - Use the up and down-arrows on the keyboard to select something from the dropdown\n" | 
					
						
							|  |  |  |                " - Press enter to continue\n") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  |     """Create basic files needed for the user's choice of service and op.""" | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  |     service = select_service() | 
					
						
							|  |  |  |     print_service_status(service) | 
					
						
							| 
									
										
										
										
											2021-10-30 18:18:44 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  |     while True: | 
					
						
							|  |  |  |         operation = select_operation(service) | 
					
						
							| 
									
										
										
										
											2017-10-25 03:45:39 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  |         # pylint: disable=protected-access | 
					
						
							|  |  |  |         api_protocol = boto3.client(service)._service_model.metadata["protocol"] | 
					
						
							|  |  |  |         initialize_service(service, api_protocol) | 
					
						
							| 
									
										
										
										
											2017-09-20 04:36:11 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  |         if api_protocol in ["query", "json", "rest-json", "rest-xml"]: | 
					
						
							|  |  |  |             insert_codes(service, operation, api_protocol) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             print_progress( | 
					
						
							|  |  |  |                 "skip inserting code", | 
					
						
							|  |  |  |                 f'api protocol "{api_protocol}" is not supported', | 
					
						
							|  |  |  |                 "yellow", | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 10:05:50 +00:00
										 |  |  |         click.echo("Updating backend index...") | 
					
						
							|  |  |  |         subprocess.check_output([f"{root_dir}/scripts/update_backend_index.py"]).decode().strip() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-29 14:36:24 -01:00
										 |  |  |         click.echo( | 
					
						
							|  |  |  |             "\n" | 
					
						
							|  |  |  |             "Please select another operation, or Ctrl-X/Ctrl-C to cancel." | 
					
						
							|  |  |  |             "\n\n" | 
					
						
							|  |  |  |             "Remaining steps after development is complete:\n" | 
					
						
							|  |  |  |             '- Run scripts/implementation_coverage.py,\n' | 
					
						
							|  |  |  |             "\n" | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2017-09-26 17:33:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 08:46:05 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							| 
									
										
										
										
											2017-09-21 21:54:14 +09:00
										 |  |  |     main() |