sagemaker-core 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sagemaker-core might be problematic. Click here for more details.
- sagemaker_core/__init__.py +0 -0
- sagemaker_core/_version.py +11 -0
- sagemaker_core/code_injection/__init__.py +0 -0
- sagemaker_core/code_injection/base.py +42 -0
- sagemaker_core/code_injection/codec.py +241 -0
- sagemaker_core/code_injection/constants.py +18 -0
- sagemaker_core/code_injection/shape_dag.py +14527 -0
- sagemaker_core/generated/__init__.py +0 -0
- sagemaker_core/generated/config_schema.py +870 -0
- sagemaker_core/generated/exceptions.py +147 -0
- sagemaker_core/generated/intelligent_defaults_helper.py +198 -0
- sagemaker_core/generated/resources.py +26998 -0
- sagemaker_core/generated/shapes.py +11584 -0
- sagemaker_core/generated/utils.py +314 -0
- sagemaker_core/tools/__init__.py +1 -0
- sagemaker_core/tools/codegen.py +56 -0
- sagemaker_core/tools/constants.py +96 -0
- sagemaker_core/tools/data_extractor.py +49 -0
- sagemaker_core/tools/method.py +32 -0
- sagemaker_core/tools/resources_codegen.py +2122 -0
- sagemaker_core/tools/resources_extractor.py +373 -0
- sagemaker_core/tools/shapes_codegen.py +284 -0
- sagemaker_core/tools/shapes_extractor.py +259 -0
- sagemaker_core/tools/templates.py +747 -0
- sagemaker_core/util/__init__.py +0 -0
- sagemaker_core/util/util.py +81 -0
- sagemaker_core-0.1.3.dist-info/LICENSE +201 -0
- sagemaker_core-0.1.3.dist-info/METADATA +28 -0
- sagemaker_core-0.1.3.dist-info/RECORD +31 -0
- sagemaker_core-0.1.3.dist-info/WHEEL +5 -0
- sagemaker_core-0.1.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,2122 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
|
5
|
+
# the License is located at
|
|
6
|
+
#
|
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
|
8
|
+
#
|
|
9
|
+
# or in the "license" file accompanying this file. This file is
|
|
10
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
11
|
+
# ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
# language governing permissions and limitations under the License.
|
|
13
|
+
"""Generates the resource classes for the service model."""
|
|
14
|
+
from collections import OrderedDict
|
|
15
|
+
import logging
|
|
16
|
+
from functools import lru_cache
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import json
|
|
20
|
+
from sagemaker_core.code_injection.codec import pascal_to_snake
|
|
21
|
+
from sagemaker_core.generated.config_schema import SAGEMAKER_PYTHON_SDK_CONFIG_SCHEMA
|
|
22
|
+
from sagemaker_core.generated.exceptions import IntelligentDefaultsError
|
|
23
|
+
from sagemaker_core.tools.constants import (
|
|
24
|
+
BASIC_RETURN_TYPES,
|
|
25
|
+
GENERATED_CLASSES_LOCATION,
|
|
26
|
+
RESOURCES_CODEGEN_FILE_NAME,
|
|
27
|
+
LICENCES_STRING,
|
|
28
|
+
TERMINAL_STATES,
|
|
29
|
+
BASIC_IMPORTS_STRING,
|
|
30
|
+
LOGGER_STRING,
|
|
31
|
+
CONFIG_SCHEMA_FILE_NAME,
|
|
32
|
+
PYTHON_TYPES_TO_BASIC_JSON_TYPES,
|
|
33
|
+
CONFIGURABLE_ATTRIBUTE_SUBSTRINGS,
|
|
34
|
+
)
|
|
35
|
+
from sagemaker_core.tools.method import Method, MethodType
|
|
36
|
+
from sagemaker_core.util.util import (
|
|
37
|
+
add_indent,
|
|
38
|
+
convert_to_snake_case,
|
|
39
|
+
snake_to_pascal,
|
|
40
|
+
remove_html_tags,
|
|
41
|
+
)
|
|
42
|
+
from sagemaker_core.tools.resources_extractor import ResourcesExtractor
|
|
43
|
+
from sagemaker_core.tools.shapes_extractor import ShapesExtractor
|
|
44
|
+
from sagemaker_core.tools.templates import (
|
|
45
|
+
CALL_OPERATION_API_NO_ARG_TEMPLATE,
|
|
46
|
+
CALL_OPERATION_API_TEMPLATE,
|
|
47
|
+
CREATE_METHOD_TEMPLATE,
|
|
48
|
+
DELETE_FAILED_STATUS_CHECK,
|
|
49
|
+
DELETED_STATUS_CHECK,
|
|
50
|
+
DESERIALIZE_INPUT_AND_RESPONSE_TO_CLS_TEMPLATE,
|
|
51
|
+
DESERIALIZE_RESPONSE_TEMPLATE,
|
|
52
|
+
DESERIALIZE_RESPONSE_TO_BASIC_TYPE_TEMPLATE,
|
|
53
|
+
GENERIC_METHOD_TEMPLATE,
|
|
54
|
+
GET_METHOD_TEMPLATE,
|
|
55
|
+
INITIALIZE_CLIENT_TEMPLATE,
|
|
56
|
+
REFRESH_METHOD_TEMPLATE,
|
|
57
|
+
RESOURCE_BASE_CLASS_TEMPLATE,
|
|
58
|
+
RETURN_ITERATOR_TEMPLATE,
|
|
59
|
+
SERIALIZE_INPUT_TEMPLATE,
|
|
60
|
+
SERIALIZE_LIST_INPUT_TEMPLATE,
|
|
61
|
+
STOP_METHOD_TEMPLATE,
|
|
62
|
+
DELETE_METHOD_TEMPLATE,
|
|
63
|
+
WAIT_FOR_DELETE_METHOD_TEMPLATE,
|
|
64
|
+
WAIT_METHOD_TEMPLATE,
|
|
65
|
+
WAIT_FOR_STATUS_METHOD_TEMPLATE,
|
|
66
|
+
UPDATE_METHOD_TEMPLATE,
|
|
67
|
+
POPULATE_DEFAULTS_DECORATOR_TEMPLATE,
|
|
68
|
+
CREATE_METHOD_TEMPLATE_WITHOUT_DEFAULTS,
|
|
69
|
+
INVOKE_METHOD_TEMPLATE,
|
|
70
|
+
INVOKE_ASYNC_METHOD_TEMPLATE,
|
|
71
|
+
INVOKE_WITH_RESPONSE_STREAM_METHOD_TEMPLATE,
|
|
72
|
+
IMPORT_METHOD_TEMPLATE,
|
|
73
|
+
FAILED_STATUS_ERROR_TEMPLATE,
|
|
74
|
+
GET_NAME_METHOD_TEMPLATE,
|
|
75
|
+
GET_ALL_METHOD_NO_ARGS_TEMPLATE,
|
|
76
|
+
GET_ALL_METHOD_WITH_ARGS_TEMPLATE,
|
|
77
|
+
UPDATE_METHOD_TEMPLATE_WITHOUT_DECORATOR,
|
|
78
|
+
RESOURCE_METHOD_EXCEPTION_DOCSTRING,
|
|
79
|
+
)
|
|
80
|
+
from sagemaker_core.tools.data_extractor import (
|
|
81
|
+
load_combined_shapes_data,
|
|
82
|
+
load_combined_operations_data,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
logging.basicConfig(level=logging.INFO)
|
|
86
|
+
log = logging.getLogger(__name__)
|
|
87
|
+
|
|
88
|
+
TYPE = "type"
|
|
89
|
+
OBJECT = "object"
|
|
90
|
+
PROPERTIES = "properties"
|
|
91
|
+
SAGEMAKER = "SageMaker"
|
|
92
|
+
PYTHON_SDK = "PythonSDK"
|
|
93
|
+
SCHEMA_VERSION = "SchemaVersion"
|
|
94
|
+
RESOURCES = "Resources"
|
|
95
|
+
REQUIRED = "required"
|
|
96
|
+
GLOBAL_DEFAULTS = "GlobalDefaults"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ResourcesCodeGen:
|
|
100
|
+
"""
|
|
101
|
+
A class for generating resources based on a service JSON file.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
service_json (dict): The Botocore service.json containing the shape definitions.
|
|
105
|
+
|
|
106
|
+
Attributes:
|
|
107
|
+
service_json (dict): The Botocore service.json containing the shape definitions.
|
|
108
|
+
version (str): The API version of the service.
|
|
109
|
+
protocol (str): The protocol used by the service.
|
|
110
|
+
service (str): The full name of the service.
|
|
111
|
+
service_id (str): The ID of the service.
|
|
112
|
+
uid (str): The unique identifier of the service.
|
|
113
|
+
operations (dict): The operations supported by the service.
|
|
114
|
+
shapes (dict): The shapes used by the service.
|
|
115
|
+
resources_extractor (ResourcesExtractor): An instance of the ResourcesExtractor class.
|
|
116
|
+
resources_plan (DataFrame): The resource plan in dataframe format.
|
|
117
|
+
shapes_extractor (ShapesExtractor): An instance of the ShapesExtractor class.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
Exception: If the service ID is not supported or the protocol is not supported.
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(self, service_json: dict):
|
|
125
|
+
# Initialize the service_json dict
|
|
126
|
+
self.service_json = service_json
|
|
127
|
+
|
|
128
|
+
# Extract the metadata
|
|
129
|
+
metadata = self.service_json["metadata"]
|
|
130
|
+
self.version = metadata["apiVersion"]
|
|
131
|
+
self.protocol = metadata["protocol"]
|
|
132
|
+
self.service = metadata["serviceFullName"]
|
|
133
|
+
self.service_id = metadata["serviceId"]
|
|
134
|
+
self.uid = metadata["uid"]
|
|
135
|
+
|
|
136
|
+
# Check if the service ID and protocol are supported
|
|
137
|
+
if self.service_id != "SageMaker":
|
|
138
|
+
raise Exception(f"ServiceId {self.service_id} not supported in this resource generator")
|
|
139
|
+
if self.protocol != "json":
|
|
140
|
+
raise Exception(f"Protocol {self.protocol} not supported in this resource generator")
|
|
141
|
+
|
|
142
|
+
# Extract the operations and shapes
|
|
143
|
+
self.operations = load_combined_operations_data()
|
|
144
|
+
self.shapes = load_combined_shapes_data()
|
|
145
|
+
|
|
146
|
+
# Initialize the resources and shapes extractors
|
|
147
|
+
self.resources_extractor = ResourcesExtractor()
|
|
148
|
+
self.shapes_extractor = ShapesExtractor()
|
|
149
|
+
|
|
150
|
+
# Extract the resources plan and shapes DAG
|
|
151
|
+
self.resources_plan = self.resources_extractor.get_resource_plan()
|
|
152
|
+
self.resource_methods = self.resources_extractor.get_resource_methods()
|
|
153
|
+
self.shape_dag = self.shapes_extractor.get_shapes_dag()
|
|
154
|
+
|
|
155
|
+
# Create the Config Schema
|
|
156
|
+
self.generate_config_schema()
|
|
157
|
+
# Generate the resources
|
|
158
|
+
self.generate_resources()
|
|
159
|
+
|
|
160
|
+
def generate_license(self) -> str:
|
|
161
|
+
"""
|
|
162
|
+
Generate the license for the generated resources file.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
str: The license.
|
|
166
|
+
|
|
167
|
+
"""
|
|
168
|
+
return LICENCES_STRING
|
|
169
|
+
|
|
170
|
+
def generate_imports(self) -> str:
|
|
171
|
+
"""
|
|
172
|
+
Generate the import statements for the generated resources file.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
str: The import statements.
|
|
176
|
+
"""
|
|
177
|
+
# List of import statements
|
|
178
|
+
imports = [
|
|
179
|
+
BASIC_IMPORTS_STRING,
|
|
180
|
+
"import botocore",
|
|
181
|
+
"import datetime",
|
|
182
|
+
"import time",
|
|
183
|
+
"import functools",
|
|
184
|
+
"from pprint import pprint",
|
|
185
|
+
"from pydantic import validate_call",
|
|
186
|
+
"from typing import Dict, List, Literal, Optional, Union\n"
|
|
187
|
+
"from boto3.session import Session",
|
|
188
|
+
"from sagemaker_core.code_injection.codec import transform",
|
|
189
|
+
"from sagemaker_core.generated.utils import SageMakerClient, SageMakerRuntimeClient, ResourceIterator,"
|
|
190
|
+
" Unassigned, snake_to_pascal, pascal_to_snake, is_not_primitive, is_not_str_dict, is_snake_case, is_primitive_list",
|
|
191
|
+
"from sagemaker_core.generated.intelligent_defaults_helper import load_default_configs_for_resource_name, get_config_value",
|
|
192
|
+
"from sagemaker_core.generated.shapes import *",
|
|
193
|
+
"from sagemaker_core.generated.exceptions import *",
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
formated_imports = "\n".join(imports)
|
|
197
|
+
formated_imports += "\n\n"
|
|
198
|
+
|
|
199
|
+
# Join the import statements with a newline character and return
|
|
200
|
+
return formated_imports
|
|
201
|
+
|
|
202
|
+
def generate_base_class(self) -> str:
|
|
203
|
+
"""
|
|
204
|
+
Generate the base class for the resources.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
str: The base class.
|
|
208
|
+
|
|
209
|
+
"""
|
|
210
|
+
return RESOURCE_BASE_CLASS_TEMPLATE
|
|
211
|
+
|
|
212
|
+
def generate_logging(self) -> str:
|
|
213
|
+
"""
|
|
214
|
+
Generate the logging statements for the generated resources file.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
str: The logging statements.
|
|
218
|
+
|
|
219
|
+
"""
|
|
220
|
+
return LOGGER_STRING
|
|
221
|
+
|
|
222
|
+
@staticmethod
|
|
223
|
+
def generate_defaults_decorator(
|
|
224
|
+
config_schema_for_resource: dict, resource_name: str, class_attributes: dict
|
|
225
|
+
) -> str:
|
|
226
|
+
return POPULATE_DEFAULTS_DECORATOR_TEMPLATE.format(
|
|
227
|
+
config_schema_for_resource=add_indent(
|
|
228
|
+
json.dumps(config_schema_for_resource.get(PROPERTIES), indent=2), 4
|
|
229
|
+
),
|
|
230
|
+
resource_name=resource_name,
|
|
231
|
+
configurable_attributes=CONFIGURABLE_ATTRIBUTE_SUBSTRINGS,
|
|
232
|
+
class_attributes=class_attributes,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def generate_resources(
|
|
236
|
+
self,
|
|
237
|
+
output_folder: str = GENERATED_CLASSES_LOCATION,
|
|
238
|
+
file_name: str = RESOURCES_CODEGEN_FILE_NAME,
|
|
239
|
+
) -> None:
|
|
240
|
+
"""
|
|
241
|
+
Generate the resources file.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
output_folder (str, optional): The output folder path. Defaults to "GENERATED_CLASSES_LOCATION".
|
|
245
|
+
file_name (str, optional): The output file name. Defaults to "RESOURCES_CODEGEN_FILE_NAME".
|
|
246
|
+
"""
|
|
247
|
+
# Check if the output folder exists, if not, create it
|
|
248
|
+
os.makedirs(output_folder, exist_ok=True)
|
|
249
|
+
|
|
250
|
+
# Create the full path for the output file
|
|
251
|
+
output_file = os.path.join(output_folder, file_name)
|
|
252
|
+
|
|
253
|
+
# Open the output file
|
|
254
|
+
with open(output_file, "w") as file:
|
|
255
|
+
# Generate and write the license to the file
|
|
256
|
+
file.write(self.generate_license())
|
|
257
|
+
|
|
258
|
+
# Generate and write the imports to the file
|
|
259
|
+
file.write(self.generate_imports())
|
|
260
|
+
|
|
261
|
+
# Generate and write the logging statements to the file
|
|
262
|
+
file.write(self.generate_logging())
|
|
263
|
+
|
|
264
|
+
# Generate and write the base class to the file
|
|
265
|
+
file.write(self.generate_base_class())
|
|
266
|
+
|
|
267
|
+
self.resource_names = [
|
|
268
|
+
row["resource_name"] for _, row in self.resources_plan.iterrows()
|
|
269
|
+
]
|
|
270
|
+
# Iterate over the rows in the resources plan
|
|
271
|
+
for _, row in self.resources_plan.iterrows():
|
|
272
|
+
# Extract the necessary data from the row
|
|
273
|
+
resource_name = row["resource_name"]
|
|
274
|
+
class_methods = row["class_methods"]
|
|
275
|
+
object_methods = row["object_methods"]
|
|
276
|
+
additional_methods = row["additional_methods"]
|
|
277
|
+
raw_actions = row["raw_actions"]
|
|
278
|
+
resource_status_chain = row["resource_status_chain"]
|
|
279
|
+
resource_states = row["resource_states"]
|
|
280
|
+
|
|
281
|
+
# Generate the resource class
|
|
282
|
+
resource_class = self.generate_resource_class(
|
|
283
|
+
resource_name,
|
|
284
|
+
class_methods,
|
|
285
|
+
object_methods,
|
|
286
|
+
additional_methods,
|
|
287
|
+
raw_actions,
|
|
288
|
+
resource_status_chain,
|
|
289
|
+
resource_states,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# If the resource class was successfully generated, write it to the file
|
|
293
|
+
if resource_class:
|
|
294
|
+
file.write(f"{resource_class}\n\n")
|
|
295
|
+
|
|
296
|
+
def _evaluate_method(
|
|
297
|
+
self, resource_name: str, method_name: str, methods: list, **kwargs
|
|
298
|
+
) -> str:
|
|
299
|
+
"""Evaluate the specified method for a resource.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
resource_name (str): The name of the resource.
|
|
303
|
+
method_name (str): The name of the method to evaluate.
|
|
304
|
+
methods (list): The list of methods for the resource.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
str: Formatted method if needed for a resource, else returns an empty string.
|
|
308
|
+
"""
|
|
309
|
+
if method_name in methods:
|
|
310
|
+
return getattr(self, f"generate_{method_name}_method")(resource_name, **kwargs)
|
|
311
|
+
else:
|
|
312
|
+
# log.warning(f"Resource {resource_name} does not have a {method_name.upper()} method")
|
|
313
|
+
return ""
|
|
314
|
+
|
|
315
|
+
def generate_resource_class(
|
|
316
|
+
self,
|
|
317
|
+
resource_name: str,
|
|
318
|
+
class_methods: list,
|
|
319
|
+
object_methods: list,
|
|
320
|
+
additional_methods: list,
|
|
321
|
+
raw_actions: list,
|
|
322
|
+
resource_status_chain: list,
|
|
323
|
+
resource_states: list,
|
|
324
|
+
) -> str:
|
|
325
|
+
"""
|
|
326
|
+
Generate the resource class for a resource.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
resource_name (str): The name of the resource.
|
|
330
|
+
class_methods (list): The class methods.
|
|
331
|
+
object_methods (list): The object methods.
|
|
332
|
+
additional_methods (list): The additional methods.
|
|
333
|
+
raw_actions (list): The raw actions.
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
str: The formatted resource class.
|
|
337
|
+
|
|
338
|
+
"""
|
|
339
|
+
# Initialize an empty string for the resource class
|
|
340
|
+
resource_class = ""
|
|
341
|
+
|
|
342
|
+
# _get_class_attributes will return value only if the resource has get or get_all method
|
|
343
|
+
if class_attribute_info := self._get_class_attributes(resource_name, class_methods):
|
|
344
|
+
class_attributes, class_attributes_string, attributes_and_documentation = (
|
|
345
|
+
class_attribute_info
|
|
346
|
+
)
|
|
347
|
+
# Start defining the class
|
|
348
|
+
resource_class = f"class {resource_name}(Base):\n"
|
|
349
|
+
|
|
350
|
+
class_documentation_string = f"Class representing resource {resource_name}\n\n"
|
|
351
|
+
class_documentation_string += f"Attributes:\n"
|
|
352
|
+
class_documentation_string += self._get_shape_attr_documentation_string(
|
|
353
|
+
attributes_and_documentation
|
|
354
|
+
)
|
|
355
|
+
resource_attributes = list(class_attributes.keys())
|
|
356
|
+
|
|
357
|
+
defaults_decorator_method = ""
|
|
358
|
+
# Check if 'create' is in the class methods
|
|
359
|
+
if "create" in class_methods or "update" in class_methods:
|
|
360
|
+
if config_schema_for_resource := self._get_config_schema_for_resources().get(
|
|
361
|
+
resource_name
|
|
362
|
+
):
|
|
363
|
+
defaults_decorator_method = self.generate_defaults_decorator(
|
|
364
|
+
resource_name=resource_name,
|
|
365
|
+
class_attributes=class_attributes,
|
|
366
|
+
config_schema_for_resource=config_schema_for_resource,
|
|
367
|
+
)
|
|
368
|
+
needs_defaults_decorator = defaults_decorator_method != ""
|
|
369
|
+
|
|
370
|
+
# Add the class attributes and methods to the class definition
|
|
371
|
+
resource_class += add_indent(f'"""\n{class_documentation_string}\n"""\n', 4)
|
|
372
|
+
|
|
373
|
+
# Add the class attributes and methods to the class definition
|
|
374
|
+
resource_class += add_indent(class_attributes_string, 4)
|
|
375
|
+
|
|
376
|
+
resource_lower = convert_to_snake_case(resource_name)
|
|
377
|
+
get_name_method = self.generate_get_name_method(resource_lower=resource_lower)
|
|
378
|
+
resource_class += add_indent(get_name_method, 4)
|
|
379
|
+
|
|
380
|
+
if defaults_decorator_method:
|
|
381
|
+
resource_class += "\n"
|
|
382
|
+
resource_class += add_indent(defaults_decorator_method, 4)
|
|
383
|
+
|
|
384
|
+
if create_method := self._evaluate_method(
|
|
385
|
+
resource_name,
|
|
386
|
+
"create",
|
|
387
|
+
class_methods,
|
|
388
|
+
needs_defaults_decorator=needs_defaults_decorator,
|
|
389
|
+
):
|
|
390
|
+
resource_class += add_indent(create_method, 4)
|
|
391
|
+
|
|
392
|
+
if get_method := self._evaluate_method(
|
|
393
|
+
resource_name,
|
|
394
|
+
"get",
|
|
395
|
+
class_methods,
|
|
396
|
+
):
|
|
397
|
+
resource_class += add_indent(get_method, 4)
|
|
398
|
+
|
|
399
|
+
if refresh_method := self._evaluate_method(
|
|
400
|
+
resource_name, "refresh", object_methods, resource_attributes=resource_attributes
|
|
401
|
+
):
|
|
402
|
+
resource_class += add_indent(refresh_method, 4)
|
|
403
|
+
|
|
404
|
+
if update_method := self._evaluate_method(
|
|
405
|
+
resource_name,
|
|
406
|
+
"update",
|
|
407
|
+
object_methods,
|
|
408
|
+
resource_attributes=resource_attributes,
|
|
409
|
+
needs_defaults_decorator=needs_defaults_decorator,
|
|
410
|
+
):
|
|
411
|
+
resource_class += add_indent(update_method, 4)
|
|
412
|
+
|
|
413
|
+
if delete_method := self._evaluate_method(
|
|
414
|
+
resource_name, "delete", object_methods, resource_attributes=resource_attributes
|
|
415
|
+
):
|
|
416
|
+
resource_class += add_indent(delete_method, 4)
|
|
417
|
+
|
|
418
|
+
if stop_method := self._evaluate_method(resource_name, "stop", object_methods):
|
|
419
|
+
resource_class += add_indent(stop_method, 4)
|
|
420
|
+
|
|
421
|
+
if wait_method := self._evaluate_method(resource_name, "wait", object_methods):
|
|
422
|
+
resource_class += add_indent(wait_method, 4)
|
|
423
|
+
|
|
424
|
+
if wait_for_status_method := self._evaluate_method(
|
|
425
|
+
resource_name, "wait_for_status", object_methods
|
|
426
|
+
):
|
|
427
|
+
resource_class += add_indent(wait_for_status_method, 4)
|
|
428
|
+
|
|
429
|
+
if wait_for_delete_method := self._evaluate_method(
|
|
430
|
+
resource_name, "wait_for_delete", object_methods
|
|
431
|
+
):
|
|
432
|
+
resource_class += add_indent(wait_for_delete_method, 4)
|
|
433
|
+
|
|
434
|
+
if invoke_method := self._evaluate_method(
|
|
435
|
+
resource_name,
|
|
436
|
+
"invoke",
|
|
437
|
+
object_methods,
|
|
438
|
+
resource_attributes=resource_attributes,
|
|
439
|
+
):
|
|
440
|
+
resource_class += add_indent(invoke_method, 4)
|
|
441
|
+
|
|
442
|
+
if invoke_async_method := self._evaluate_method(
|
|
443
|
+
resource_name,
|
|
444
|
+
"invoke_async",
|
|
445
|
+
object_methods,
|
|
446
|
+
resource_attributes=resource_attributes,
|
|
447
|
+
):
|
|
448
|
+
resource_class += add_indent(invoke_async_method, 4)
|
|
449
|
+
|
|
450
|
+
if invoke_with_response_stream_method := self._evaluate_method(
|
|
451
|
+
resource_name,
|
|
452
|
+
"invoke_with_response_stream",
|
|
453
|
+
object_methods,
|
|
454
|
+
resource_attributes=resource_attributes,
|
|
455
|
+
):
|
|
456
|
+
resource_class += add_indent(invoke_with_response_stream_method, 4)
|
|
457
|
+
|
|
458
|
+
if import_method := self._evaluate_method(resource_name, "import", class_methods):
|
|
459
|
+
resource_class += add_indent(import_method, 4)
|
|
460
|
+
|
|
461
|
+
if list_method := self._evaluate_method(resource_name, "get_all", class_methods):
|
|
462
|
+
resource_class += add_indent(list_method, 4)
|
|
463
|
+
|
|
464
|
+
else:
|
|
465
|
+
# If there's no 'get' or 'list' or 'create' method, generate a class with no attributes
|
|
466
|
+
resource_attributes = []
|
|
467
|
+
resource_class = f"class {resource_name}(Base):\n"
|
|
468
|
+
class_documentation_string = f"Class representing resource {resource_name}\n"
|
|
469
|
+
resource_class += add_indent(f'"""\n{class_documentation_string}\n"""\n', 4)
|
|
470
|
+
|
|
471
|
+
if resource_name in self.resource_methods:
|
|
472
|
+
# TODO: use resource_methods for all methods
|
|
473
|
+
for method in self.resource_methods[resource_name].values():
|
|
474
|
+
formatted_method = self.generate_method(method, resource_attributes)
|
|
475
|
+
resource_class += add_indent(formatted_method, 4)
|
|
476
|
+
|
|
477
|
+
# Return the class definition
|
|
478
|
+
return resource_class
|
|
479
|
+
|
|
480
|
+
def _get_class_attributes(self, resource_name: str, class_methods: list) -> tuple:
|
|
481
|
+
"""Get the class attributes for a resource.
|
|
482
|
+
|
|
483
|
+
Args:
|
|
484
|
+
resource_name (str): The name of the resource.
|
|
485
|
+
class_methods (list): The class methods of the resource. Now it can only get the class
|
|
486
|
+
attributes if the resource has get or get_all method.
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
tuple:
|
|
490
|
+
class_attributes: The class attributes and the formatted class attributes string.
|
|
491
|
+
class_attributes_string: The code string of the class attributes
|
|
492
|
+
attributes_and_documentation: A dict of doc strings of the class attributes
|
|
493
|
+
"""
|
|
494
|
+
if "get" in class_methods:
|
|
495
|
+
# Get the operation and shape for the 'get' method
|
|
496
|
+
get_operation = self.operations["Describe" + resource_name]
|
|
497
|
+
get_operation_shape = get_operation["output"]["shape"]
|
|
498
|
+
|
|
499
|
+
# Use 'get' operation input as the required class attributes.
|
|
500
|
+
# These are the mimumum identifing attributes for a resource object (ie, required for refresh())
|
|
501
|
+
get_operation_input_shape = get_operation["input"]["shape"]
|
|
502
|
+
required_attributes = self.shapes[get_operation_input_shape].get("required", [])
|
|
503
|
+
|
|
504
|
+
# Generate the class attributes based on the shape
|
|
505
|
+
class_attributes, class_attributes_string = (
|
|
506
|
+
self.shapes_extractor.generate_data_shape_members_and_string_body(
|
|
507
|
+
shape=get_operation_shape, required_override=tuple(required_attributes)
|
|
508
|
+
)
|
|
509
|
+
)
|
|
510
|
+
attributes_and_documentation = (
|
|
511
|
+
self.shapes_extractor.fetch_shape_members_and_doc_strings(get_operation_shape)
|
|
512
|
+
)
|
|
513
|
+
# Some resources are configured in the service.json inconsistently.
|
|
514
|
+
# These resources take in the main identifier in the create and get methods , but is not present in the describe response output
|
|
515
|
+
# Hence for consistent behaviour of functions such as refresh and delete, the identifiers are hardcoded
|
|
516
|
+
if resource_name == "ImageVersion":
|
|
517
|
+
class_attributes["image_name"] = "str"
|
|
518
|
+
class_attributes_string = "image_name: str\n" + class_attributes_string
|
|
519
|
+
if resource_name == "Workteam":
|
|
520
|
+
class_attributes["workteam_name"] = "str"
|
|
521
|
+
class_attributes_string = "workteam_name: str\n" + class_attributes_string
|
|
522
|
+
if resource_name == "Workforce":
|
|
523
|
+
class_attributes["workforce_name"] = "str"
|
|
524
|
+
class_attributes_string = "workforce_name: str\n" + class_attributes_string
|
|
525
|
+
if resource_name == "SubscribedWorkteam":
|
|
526
|
+
class_attributes["workteam_arn"] = "str"
|
|
527
|
+
class_attributes_string = "workteam_arn: str\n" + class_attributes_string
|
|
528
|
+
|
|
529
|
+
if resource_name == "HubContent":
|
|
530
|
+
class_attributes["hub_name"] = "Optional[str] = Unassigned()"
|
|
531
|
+
class_attributes_string = class_attributes_string.replace("hub_name: str", "")
|
|
532
|
+
class_attributes_string = (
|
|
533
|
+
class_attributes_string + "hub_name: Optional[str] = Unassigned()"
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
return class_attributes, class_attributes_string, attributes_and_documentation
|
|
537
|
+
elif "get_all" in class_methods:
|
|
538
|
+
# Get the operation and shape for the 'get_all' method
|
|
539
|
+
list_operation = self.operations["List" + resource_name + "s"]
|
|
540
|
+
list_operation_output_shape = list_operation["output"]["shape"]
|
|
541
|
+
list_operation_output_members = self.shapes[list_operation_output_shape]["members"]
|
|
542
|
+
|
|
543
|
+
# Use the object shape of 'get_all' operation output as the required class attributes.
|
|
544
|
+
filtered_list_operation_output_members = next(
|
|
545
|
+
{key: value}
|
|
546
|
+
for key, value in list_operation_output_members.items()
|
|
547
|
+
if key != "NextToken"
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
summaries_key = next(iter(filtered_list_operation_output_members))
|
|
551
|
+
summaries_shape_name = filtered_list_operation_output_members[summaries_key]["shape"]
|
|
552
|
+
summary_name = self.shapes[summaries_shape_name]["member"]["shape"]
|
|
553
|
+
required_attributes = self.shapes[summary_name].get("required", [])
|
|
554
|
+
# Generate the class attributes based on the shape
|
|
555
|
+
class_attributes, class_attributes_string = (
|
|
556
|
+
self.shapes_extractor.generate_data_shape_members_and_string_body(
|
|
557
|
+
shape=summary_name, required_override=tuple(required_attributes)
|
|
558
|
+
)
|
|
559
|
+
)
|
|
560
|
+
attributes_and_documentation = (
|
|
561
|
+
self.shapes_extractor.fetch_shape_members_and_doc_strings(summary_name)
|
|
562
|
+
)
|
|
563
|
+
return class_attributes, class_attributes_string, attributes_and_documentation
|
|
564
|
+
elif "create" in class_methods:
|
|
565
|
+
# Get the operation and shape for the 'create' method
|
|
566
|
+
create_operation = self.operations["Create" + resource_name]
|
|
567
|
+
create_operation_input_shape = create_operation["input"]["shape"]
|
|
568
|
+
create_operation_output_shape = create_operation["output"]["shape"]
|
|
569
|
+
# Generate the class attributes based on the input and output shape
|
|
570
|
+
class_attributes, class_attributes_string = self._get_resource_members_and_string_body(
|
|
571
|
+
resource_name, create_operation_input_shape, create_operation_output_shape
|
|
572
|
+
)
|
|
573
|
+
attributes_and_documentation = self._get_resouce_attributes_and_documentation(
|
|
574
|
+
create_operation_input_shape, create_operation_output_shape
|
|
575
|
+
)
|
|
576
|
+
return class_attributes, class_attributes_string, attributes_and_documentation
|
|
577
|
+
else:
|
|
578
|
+
return None
|
|
579
|
+
|
|
580
|
+
def _get_resource_members_and_string_body(self, resource_name: str, input_shape, output_shape):
|
|
581
|
+
input_members = self.shapes_extractor.generate_shape_members(input_shape)
|
|
582
|
+
output_members = self.shapes_extractor.generate_shape_members(output_shape)
|
|
583
|
+
resource_members = {**input_members, **output_members}
|
|
584
|
+
# bring the required members in front
|
|
585
|
+
ordered_members = {
|
|
586
|
+
attr: value
|
|
587
|
+
for attr, value in resource_members.items()
|
|
588
|
+
if not value.startswith("Optional")
|
|
589
|
+
}
|
|
590
|
+
ordered_members.update(resource_members)
|
|
591
|
+
|
|
592
|
+
resource_name_snake_case = pascal_to_snake(resource_name)
|
|
593
|
+
resource_names = [row["resource_name"] for _, row in self.resources_plan.iterrows()]
|
|
594
|
+
init_data_body = ""
|
|
595
|
+
for attr, value in ordered_members.items():
|
|
596
|
+
if (
|
|
597
|
+
resource_names
|
|
598
|
+
and attr.endswith("name")
|
|
599
|
+
and attr[: -len("_name")] != resource_name_snake_case
|
|
600
|
+
and attr != "name"
|
|
601
|
+
and snake_to_pascal(attr[: -len("_name")]) in resource_names
|
|
602
|
+
):
|
|
603
|
+
if value.startswith("Optional"):
|
|
604
|
+
init_data_body += f"{attr}: Optional[Union[str, object]] = Unassigned()\n"
|
|
605
|
+
else:
|
|
606
|
+
init_data_body += f"{attr}: Union[str, object]\n"
|
|
607
|
+
elif attr == "lambda":
|
|
608
|
+
init_data_body += f"# {attr}: {value}\n"
|
|
609
|
+
else:
|
|
610
|
+
init_data_body += f"{attr}: {value}\n"
|
|
611
|
+
return ordered_members, init_data_body
|
|
612
|
+
|
|
613
|
+
def _get_resouce_attributes_and_documentation(self, input_shape, output_shape):
|
|
614
|
+
input_members = self.shapes[input_shape]["members"]
|
|
615
|
+
required_args = set(self.shapes[input_shape].get("required", []))
|
|
616
|
+
output_members = self.shapes[output_shape]["members"]
|
|
617
|
+
members = {**input_members, **output_members}
|
|
618
|
+
required_args.update(self.shapes[output_shape].get("required", []))
|
|
619
|
+
# bring the required members in front
|
|
620
|
+
ordered_members = {key: members[key] for key in members if key in required_args}
|
|
621
|
+
ordered_members.update(members)
|
|
622
|
+
shape_members_and_docstrings = {}
|
|
623
|
+
for member_name, member_attrs in ordered_members.items():
|
|
624
|
+
member_shape_documentation = member_attrs.get("documentation")
|
|
625
|
+
shape_members_and_docstrings[member_name] = member_shape_documentation
|
|
626
|
+
return shape_members_and_docstrings
|
|
627
|
+
|
|
628
|
+
def _get_shape_attr_documentation_string(
|
|
629
|
+
self, attributes_and_documentation, exclude_resource_attrs=None
|
|
630
|
+
) -> str:
|
|
631
|
+
documentation_string = ""
|
|
632
|
+
for attribute, documentation in attributes_and_documentation.items():
|
|
633
|
+
attribute_snake = pascal_to_snake(attribute)
|
|
634
|
+
if exclude_resource_attrs and attribute_snake in exclude_resource_attrs:
|
|
635
|
+
# exclude resource attributes from documentation
|
|
636
|
+
continue
|
|
637
|
+
else:
|
|
638
|
+
if documentation == None:
|
|
639
|
+
documentation_string += f"{attribute_snake}: \n"
|
|
640
|
+
else:
|
|
641
|
+
documentation_string += f"{attribute_snake}: {documentation}\n"
|
|
642
|
+
documentation_string = add_indent(documentation_string)
|
|
643
|
+
return remove_html_tags(documentation_string)
|
|
644
|
+
|
|
645
|
+
def _generate_create_method_args(
|
|
646
|
+
self, operation_input_shape_name: str, resource_name: str
|
|
647
|
+
) -> str:
|
|
648
|
+
"""Generates the arguments for a method.
|
|
649
|
+
Args:
|
|
650
|
+
operation_input_shape_name (str): The name of the input shape for the operation.
|
|
651
|
+
Returns:
|
|
652
|
+
str: The generated arguments string.
|
|
653
|
+
"""
|
|
654
|
+
typed_shape_members = self.shapes_extractor.generate_shape_members(
|
|
655
|
+
operation_input_shape_name
|
|
656
|
+
)
|
|
657
|
+
resource_name_in_snake_case = pascal_to_snake(resource_name)
|
|
658
|
+
method_args = ""
|
|
659
|
+
last_key = list(typed_shape_members.keys())[-1]
|
|
660
|
+
for attr, attr_type in typed_shape_members.items():
|
|
661
|
+
method_parameter_type = attr_type
|
|
662
|
+
if (
|
|
663
|
+
attr.endswith("name")
|
|
664
|
+
and attr[: -len("_name")] != resource_name_in_snake_case
|
|
665
|
+
and attr != "name"
|
|
666
|
+
and snake_to_pascal(attr[: -len("_name")]) in self.resource_names
|
|
667
|
+
):
|
|
668
|
+
if attr_type.startswith("Optional"):
|
|
669
|
+
method_args += f"{attr}: Optional[Union[str, object]] = Unassigned(),"
|
|
670
|
+
else:
|
|
671
|
+
method_args += f"{attr}: Union[str, object],"
|
|
672
|
+
else:
|
|
673
|
+
method_args += f"{attr}: {method_parameter_type},"
|
|
674
|
+
if attr != last_key:
|
|
675
|
+
method_args += "\n"
|
|
676
|
+
method_args = add_indent(method_args)
|
|
677
|
+
return method_args
|
|
678
|
+
|
|
679
|
+
# TODO: use this method to replace _generate_operation_input_args
|
|
680
|
+
def _generate_operation_input_args_updated(
|
|
681
|
+
self,
|
|
682
|
+
resource_operation: dict,
|
|
683
|
+
is_class_method: bool,
|
|
684
|
+
resource_attributes: list = [],
|
|
685
|
+
exclude_list: list = [],
|
|
686
|
+
) -> str:
|
|
687
|
+
"""Generate the operation input arguments string.
|
|
688
|
+
|
|
689
|
+
Args:
|
|
690
|
+
resource_operation (dict): The resource operation dictionary.
|
|
691
|
+
is_class_method (bool): Indicates method is class method, else object method.
|
|
692
|
+
|
|
693
|
+
Returns:
|
|
694
|
+
str: The formatted operation input arguments string.
|
|
695
|
+
"""
|
|
696
|
+
input_shape_name = resource_operation["input"]["shape"]
|
|
697
|
+
input_shape_members = list(self.shapes[input_shape_name]["members"].keys())
|
|
698
|
+
|
|
699
|
+
if is_class_method:
|
|
700
|
+
args = (
|
|
701
|
+
f"'{member}': {convert_to_snake_case(member)}"
|
|
702
|
+
for member in input_shape_members
|
|
703
|
+
if convert_to_snake_case(member) not in exclude_list
|
|
704
|
+
)
|
|
705
|
+
else:
|
|
706
|
+
args = []
|
|
707
|
+
for member in input_shape_members:
|
|
708
|
+
if convert_to_snake_case(member) not in exclude_list:
|
|
709
|
+
if convert_to_snake_case(member) in resource_attributes:
|
|
710
|
+
args.append(f"'{member}': self.{convert_to_snake_case(member)}")
|
|
711
|
+
else:
|
|
712
|
+
args.append(f"'{member}': {convert_to_snake_case(member)}")
|
|
713
|
+
|
|
714
|
+
operation_input_args = ",\n".join(args)
|
|
715
|
+
operation_input_args += ","
|
|
716
|
+
operation_input_args = add_indent(operation_input_args, 8)
|
|
717
|
+
|
|
718
|
+
return operation_input_args
|
|
719
|
+
|
|
720
|
+
def _generate_operation_input_args(
|
|
721
|
+
self, resource_operation: dict, is_class_method: bool, exclude_list: list = []
|
|
722
|
+
) -> str:
|
|
723
|
+
"""Generate the operation input arguments string.
|
|
724
|
+
|
|
725
|
+
Args:
|
|
726
|
+
resource_operation (dict): The resource operation dictionary.
|
|
727
|
+
is_class_method (bool): Indicates method is class method, else object method.
|
|
728
|
+
|
|
729
|
+
Returns:
|
|
730
|
+
str: The formatted operation input arguments string.
|
|
731
|
+
"""
|
|
732
|
+
input_shape_name = resource_operation["input"]["shape"]
|
|
733
|
+
input_shape_members = list(self.shapes[input_shape_name]["members"].keys())
|
|
734
|
+
|
|
735
|
+
if is_class_method:
|
|
736
|
+
args = (
|
|
737
|
+
f"'{member}': {convert_to_snake_case(member)}"
|
|
738
|
+
for member in input_shape_members
|
|
739
|
+
if convert_to_snake_case(member) not in exclude_list
|
|
740
|
+
)
|
|
741
|
+
else:
|
|
742
|
+
args = (
|
|
743
|
+
f"'{member}': self.{convert_to_snake_case(member)}"
|
|
744
|
+
for member in input_shape_members
|
|
745
|
+
if convert_to_snake_case(member) not in exclude_list
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
operation_input_args = ",\n".join(args)
|
|
749
|
+
operation_input_args += ","
|
|
750
|
+
operation_input_args = add_indent(operation_input_args, 8)
|
|
751
|
+
|
|
752
|
+
return operation_input_args
|
|
753
|
+
|
|
754
|
+
def _generate_operation_input_necessary_args(
|
|
755
|
+
self, resource_operation: dict, resource_attributes: list
|
|
756
|
+
) -> str:
|
|
757
|
+
"""
|
|
758
|
+
Generate the operation input arguments string.
|
|
759
|
+
This will try to re-use args from the object attributes if present and it not presebt will use te ones provided in the parameter.
|
|
760
|
+
Args:
|
|
761
|
+
resource_operation (dict): The resource operation dictionary.
|
|
762
|
+
is_class_method (bool): Indicates method is class method, else object method.
|
|
763
|
+
|
|
764
|
+
Returns:
|
|
765
|
+
str: The formatted operation input arguments string.
|
|
766
|
+
"""
|
|
767
|
+
input_shape_name = resource_operation["input"]["shape"]
|
|
768
|
+
input_shape_members = list(self.shapes[input_shape_name]["members"].keys())
|
|
769
|
+
|
|
770
|
+
args = list()
|
|
771
|
+
for member in input_shape_members:
|
|
772
|
+
if convert_to_snake_case(member) in resource_attributes:
|
|
773
|
+
args.append(f"'{member}': self.{convert_to_snake_case(member)}")
|
|
774
|
+
else:
|
|
775
|
+
args.append(f"'{member}': {convert_to_snake_case(member)}")
|
|
776
|
+
|
|
777
|
+
operation_input_args = ",\n".join(args)
|
|
778
|
+
operation_input_args += ","
|
|
779
|
+
operation_input_args = add_indent(operation_input_args, 8)
|
|
780
|
+
|
|
781
|
+
return operation_input_args
|
|
782
|
+
|
|
783
|
+
def _generate_method_args(
|
|
784
|
+
self, operation_input_shape_name: str, exclude_list: list = []
|
|
785
|
+
) -> str:
|
|
786
|
+
"""Generates the arguments for a method.
|
|
787
|
+
This will exclude attributes in the exclude_list from the arguments. For example, This is used for update() method
|
|
788
|
+
which does not require the resource identifier attributes to be passed as arguments.
|
|
789
|
+
|
|
790
|
+
Args:
|
|
791
|
+
operation_input_shape_name (str): The name of the input shape for the operation.
|
|
792
|
+
exclude_list (list): The list of attributes to exclude from the arguments.
|
|
793
|
+
|
|
794
|
+
Returns:
|
|
795
|
+
str: The generated arguments string.
|
|
796
|
+
"""
|
|
797
|
+
typed_shape_members = self.shapes_extractor.generate_shape_members(
|
|
798
|
+
operation_input_shape_name
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
args = (
|
|
802
|
+
f"{attr}: {attr_type}"
|
|
803
|
+
for attr, attr_type in typed_shape_members.items()
|
|
804
|
+
if attr not in exclude_list
|
|
805
|
+
)
|
|
806
|
+
method_args = ",\n".join(args)
|
|
807
|
+
if not method_args:
|
|
808
|
+
return ""
|
|
809
|
+
method_args += ","
|
|
810
|
+
method_args = add_indent(method_args)
|
|
811
|
+
return method_args
|
|
812
|
+
|
|
813
|
+
def _generate_get_args(self, resource_name: str, operation_input_shape_name: str) -> str:
|
|
814
|
+
"""
|
|
815
|
+
Generates a resource identifier based on the required members for the Describe and Create operations.
|
|
816
|
+
|
|
817
|
+
Args:
|
|
818
|
+
resource_name (str): The name of the resource.
|
|
819
|
+
operation_input_shape_name (str): The name of the input shape for the operation.
|
|
820
|
+
|
|
821
|
+
Returns:
|
|
822
|
+
str: The generated resource identifier.
|
|
823
|
+
"""
|
|
824
|
+
describe_operation = self.operations["Describe" + resource_name]
|
|
825
|
+
describe_operation_input_shape_name = describe_operation["input"]["shape"]
|
|
826
|
+
|
|
827
|
+
required_members = self.shapes_extractor.get_required_members(
|
|
828
|
+
describe_operation_input_shape_name
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
operation_required_members = self.shapes_extractor.get_required_members(
|
|
832
|
+
operation_input_shape_name
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
identifiers = []
|
|
836
|
+
for member in required_members:
|
|
837
|
+
if member not in operation_required_members:
|
|
838
|
+
identifiers.append(f"{member}=response['{snake_to_pascal(member)}']")
|
|
839
|
+
else:
|
|
840
|
+
identifiers.append(f"{member}={member}")
|
|
841
|
+
|
|
842
|
+
get_args = ", ".join(identifiers)
|
|
843
|
+
return get_args
|
|
844
|
+
|
|
845
|
+
def generate_create_method(self, resource_name: str, **kwargs) -> str:
|
|
846
|
+
"""
|
|
847
|
+
Auto-generate the CREATE method for a resource.
|
|
848
|
+
|
|
849
|
+
Args:
|
|
850
|
+
resource_name (str): The resource name.
|
|
851
|
+
|
|
852
|
+
Returns:
|
|
853
|
+
str: The formatted Create Method template.
|
|
854
|
+
|
|
855
|
+
"""
|
|
856
|
+
# Get the operation and shape for the 'create' method
|
|
857
|
+
operation_name = "Create" + resource_name
|
|
858
|
+
operation_metadata = self.operations[operation_name]
|
|
859
|
+
operation_input_shape_name = operation_metadata["input"]["shape"]
|
|
860
|
+
|
|
861
|
+
# Generate the arguments for the 'create' method
|
|
862
|
+
create_args = self._generate_create_method_args(operation_input_shape_name, resource_name)
|
|
863
|
+
|
|
864
|
+
operation_input_args = self._generate_operation_input_args(
|
|
865
|
+
operation_metadata, is_class_method=True
|
|
866
|
+
)
|
|
867
|
+
|
|
868
|
+
# Convert the resource name to snake case
|
|
869
|
+
resource_lower = convert_to_snake_case(resource_name)
|
|
870
|
+
|
|
871
|
+
# Convert the operation name to snake case
|
|
872
|
+
operation = convert_to_snake_case(operation_name)
|
|
873
|
+
|
|
874
|
+
docstring = self._generate_docstring(
|
|
875
|
+
title=f"Create a {resource_name} resource",
|
|
876
|
+
operation_name=operation_name,
|
|
877
|
+
resource_name=resource_name,
|
|
878
|
+
operation_input_shape_name=operation_input_shape_name,
|
|
879
|
+
include_session_region=True,
|
|
880
|
+
include_return_resource_docstring=True,
|
|
881
|
+
include_intelligent_defaults_errors=True,
|
|
882
|
+
)
|
|
883
|
+
|
|
884
|
+
if "Describe" + resource_name in self.operations:
|
|
885
|
+
# If the resource has Describe method, call Describe API and return its value
|
|
886
|
+
get_args = self._generate_get_args(resource_name, operation_input_shape_name)
|
|
887
|
+
|
|
888
|
+
# Format the method using the CREATE_METHOD_TEMPLATE
|
|
889
|
+
if kwargs["needs_defaults_decorator"]:
|
|
890
|
+
formatted_method = CREATE_METHOD_TEMPLATE.format(
|
|
891
|
+
docstring=docstring,
|
|
892
|
+
resource_name=resource_name,
|
|
893
|
+
create_args=create_args,
|
|
894
|
+
resource_lower=resource_lower,
|
|
895
|
+
service_name="sagemaker", # TODO: change service name based on the service - runtime, sagemaker, etc.
|
|
896
|
+
operation_input_args=operation_input_args,
|
|
897
|
+
operation=operation,
|
|
898
|
+
get_args=get_args,
|
|
899
|
+
)
|
|
900
|
+
else:
|
|
901
|
+
formatted_method = CREATE_METHOD_TEMPLATE_WITHOUT_DEFAULTS.format(
|
|
902
|
+
docstring=docstring,
|
|
903
|
+
resource_name=resource_name,
|
|
904
|
+
create_args=create_args,
|
|
905
|
+
resource_lower=resource_lower,
|
|
906
|
+
service_name="sagemaker", # TODO: change service name based on the service - runtime, sagemaker, etc.
|
|
907
|
+
operation_input_args=operation_input_args,
|
|
908
|
+
operation=operation,
|
|
909
|
+
get_args=get_args,
|
|
910
|
+
)
|
|
911
|
+
# Return the formatted method
|
|
912
|
+
return formatted_method
|
|
913
|
+
else:
|
|
914
|
+
# If the resource does not have Describe method, return a instance with
|
|
915
|
+
# the input and output of Create method
|
|
916
|
+
decorator = "@classmethod"
|
|
917
|
+
serialize_operation_input = SERIALIZE_INPUT_TEMPLATE.format(
|
|
918
|
+
operation_input_args=operation_input_args
|
|
919
|
+
)
|
|
920
|
+
initialize_client = INITIALIZE_CLIENT_TEMPLATE.format(service_name="sagemaker")
|
|
921
|
+
call_operation_api = CALL_OPERATION_API_TEMPLATE.format(
|
|
922
|
+
operation=convert_to_snake_case(operation_name)
|
|
923
|
+
)
|
|
924
|
+
operation_output_shape_name = operation_metadata["output"]["shape"]
|
|
925
|
+
deserialize_response = DESERIALIZE_INPUT_AND_RESPONSE_TO_CLS_TEMPLATE.format(
|
|
926
|
+
operation_output_shape=operation_output_shape_name
|
|
927
|
+
)
|
|
928
|
+
formatted_method = GENERIC_METHOD_TEMPLATE.format(
|
|
929
|
+
docstring=docstring,
|
|
930
|
+
decorator=decorator,
|
|
931
|
+
method_name="create",
|
|
932
|
+
method_args=add_indent("cls,\n", 4) + create_args,
|
|
933
|
+
return_type='Optional["resource_name"]',
|
|
934
|
+
serialize_operation_input=serialize_operation_input,
|
|
935
|
+
initialize_client=initialize_client,
|
|
936
|
+
call_operation_api=call_operation_api,
|
|
937
|
+
deserialize_response=deserialize_response,
|
|
938
|
+
)
|
|
939
|
+
# Return the formatted method
|
|
940
|
+
return formatted_method
|
|
941
|
+
|
|
942
|
+
@lru_cache
|
|
943
|
+
def _fetch_shape_errors_and_doc_strings(self, operation):
|
|
944
|
+
operation_dict = self.operations[operation]
|
|
945
|
+
errors = operation_dict.get("errors", [])
|
|
946
|
+
shape_errors_and_docstrings = {}
|
|
947
|
+
if errors:
|
|
948
|
+
for e in errors:
|
|
949
|
+
error_shape = e["shape"]
|
|
950
|
+
error_shape_dict = self.shapes[error_shape]
|
|
951
|
+
error_shape_documentation = error_shape_dict.get("documentation").strip()
|
|
952
|
+
shape_errors_and_docstrings[error_shape] = error_shape_documentation
|
|
953
|
+
sorted_keys = sorted(shape_errors_and_docstrings.keys())
|
|
954
|
+
return {key: shape_errors_and_docstrings[key] for key in sorted_keys}
|
|
955
|
+
|
|
956
|
+
def _exception_docstring(self, operation: str) -> str:
|
|
957
|
+
_docstring = RESOURCE_METHOD_EXCEPTION_DOCSTRING
|
|
958
|
+
for error, documentaion in self._fetch_shape_errors_and_doc_strings(operation).items():
|
|
959
|
+
_docstring += f"\n {error}: {remove_html_tags(documentaion).strip()}"
|
|
960
|
+
return _docstring
|
|
961
|
+
|
|
962
|
+
def _generate_docstring(
|
|
963
|
+
self,
|
|
964
|
+
title: str,
|
|
965
|
+
operation_name: str,
|
|
966
|
+
resource_name: str,
|
|
967
|
+
operation_input_shape_name: str = None,
|
|
968
|
+
include_session_region: bool = False,
|
|
969
|
+
include_return_resource_docstring: bool = False,
|
|
970
|
+
return_string: str = None,
|
|
971
|
+
include_intelligent_defaults_errors: bool = False,
|
|
972
|
+
exclude_resource_attrs: list = None,
|
|
973
|
+
) -> str:
|
|
974
|
+
"""
|
|
975
|
+
Generate the docstring for a method of a resource.
|
|
976
|
+
|
|
977
|
+
Args:
|
|
978
|
+
title (str): The title of the docstring.
|
|
979
|
+
operation_name (str): The name of the operation.
|
|
980
|
+
resource_name (str): The name of the resource.
|
|
981
|
+
operation_input_shape_name (str): The name of the operation input shape.
|
|
982
|
+
include_session_region (bool): Whether to include session and region documentation.
|
|
983
|
+
include_return_resource_docstring (bool): Whether to include resource-specific documentation.
|
|
984
|
+
return_string (str): The return string.
|
|
985
|
+
include_intelligent_defaults_errors (bool): Whether to include intelligent defaults errors.
|
|
986
|
+
exclude_resource_attrs (list): A list of attributes to exclude from the docstring.
|
|
987
|
+
|
|
988
|
+
Returns:
|
|
989
|
+
str: The generated docstring for the IMPORT method.
|
|
990
|
+
"""
|
|
991
|
+
docstring = f"{title}\n"
|
|
992
|
+
if operation_input_shape_name:
|
|
993
|
+
_shape_attr_documentation_string = self._get_shape_attr_documentation_string(
|
|
994
|
+
self.shapes_extractor.fetch_shape_members_and_doc_strings(
|
|
995
|
+
operation_input_shape_name
|
|
996
|
+
),
|
|
997
|
+
exclude_resource_attrs=exclude_resource_attrs,
|
|
998
|
+
)
|
|
999
|
+
if _shape_attr_documentation_string:
|
|
1000
|
+
docstring += f"\nParameters:\n"
|
|
1001
|
+
docstring += _shape_attr_documentation_string
|
|
1002
|
+
|
|
1003
|
+
if include_session_region:
|
|
1004
|
+
if not _shape_attr_documentation_string:
|
|
1005
|
+
docstring += f"\nParameters:\n"
|
|
1006
|
+
docstring += add_indent(f"session: Boto3 session.\nregion: Region name.\n")
|
|
1007
|
+
|
|
1008
|
+
if include_return_resource_docstring:
|
|
1009
|
+
docstring += f"\nReturns:\n" f" The {resource_name} resource.\n"
|
|
1010
|
+
elif return_string:
|
|
1011
|
+
docstring += "\n" + return_string
|
|
1012
|
+
|
|
1013
|
+
docstring += self._exception_docstring(operation_name)
|
|
1014
|
+
|
|
1015
|
+
if include_intelligent_defaults_errors:
|
|
1016
|
+
subclasses = set(IntelligentDefaultsError.__subclasses__())
|
|
1017
|
+
_id_exception_docstrings = [
|
|
1018
|
+
f"\n {subclass.__name__}: {subclass.__doc__}" for subclass in subclasses
|
|
1019
|
+
]
|
|
1020
|
+
sorted_id_exception_docstrings = sorted(_id_exception_docstrings)
|
|
1021
|
+
docstring += "".join(sorted_id_exception_docstrings)
|
|
1022
|
+
docstring = add_indent(f'"""\n{docstring}\n"""\n', 4)
|
|
1023
|
+
|
|
1024
|
+
return docstring
|
|
1025
|
+
|
|
1026
|
+
def generate_import_method(self, resource_name: str) -> str:
|
|
1027
|
+
"""
|
|
1028
|
+
Auto-generate the IMPORT method for a resource.
|
|
1029
|
+
|
|
1030
|
+
Args:
|
|
1031
|
+
resource_name (str): The resource name.
|
|
1032
|
+
|
|
1033
|
+
Returns:
|
|
1034
|
+
str: The formatted Import Method template.
|
|
1035
|
+
|
|
1036
|
+
"""
|
|
1037
|
+
# Get the operation and shape for the 'import' method
|
|
1038
|
+
operation_name = "Import" + resource_name
|
|
1039
|
+
operation_metadata = self.operations[operation_name]
|
|
1040
|
+
operation_input_shape_name = operation_metadata["input"]["shape"]
|
|
1041
|
+
|
|
1042
|
+
# Generate the arguments for the 'import' method
|
|
1043
|
+
import_args = self._generate_method_args(operation_input_shape_name)
|
|
1044
|
+
|
|
1045
|
+
operation_input_args = self._generate_operation_input_args(
|
|
1046
|
+
operation_metadata, is_class_method=True
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
# Convert the resource name to snake case
|
|
1050
|
+
resource_lower = convert_to_snake_case(resource_name)
|
|
1051
|
+
|
|
1052
|
+
# Convert the operation name to snake case
|
|
1053
|
+
operation = convert_to_snake_case(operation_name)
|
|
1054
|
+
|
|
1055
|
+
get_args = self._generate_get_args(resource_name, operation_input_shape_name)
|
|
1056
|
+
|
|
1057
|
+
docstring = self._generate_docstring(
|
|
1058
|
+
title=f"Import a {resource_name} resource",
|
|
1059
|
+
operation_name=operation_name,
|
|
1060
|
+
resource_name=resource_name,
|
|
1061
|
+
operation_input_shape_name=operation_input_shape_name,
|
|
1062
|
+
include_session_region=True,
|
|
1063
|
+
include_return_resource_docstring=True,
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
# Format the method using the IMPORT_METHOD_TEMPLATE
|
|
1067
|
+
formatted_method = IMPORT_METHOD_TEMPLATE.format(
|
|
1068
|
+
docstring=docstring,
|
|
1069
|
+
resource_name=resource_name,
|
|
1070
|
+
import_args=import_args,
|
|
1071
|
+
resource_lower=resource_lower,
|
|
1072
|
+
service_name="sagemaker", # TODO: change service name based on the service - runtime, sagemaker, etc.
|
|
1073
|
+
operation_input_args=operation_input_args,
|
|
1074
|
+
operation=operation,
|
|
1075
|
+
get_args=get_args,
|
|
1076
|
+
)
|
|
1077
|
+
|
|
1078
|
+
# Return the formatted method
|
|
1079
|
+
return formatted_method
|
|
1080
|
+
|
|
1081
|
+
def generate_get_name_method(self, resource_lower: str) -> str:
|
|
1082
|
+
"""
|
|
1083
|
+
Autogenerate the method that would return the identifier of the object
|
|
1084
|
+
Args:
|
|
1085
|
+
resource_name: Name of Resource
|
|
1086
|
+
Returns:
|
|
1087
|
+
str: Formatted Get Name Method
|
|
1088
|
+
"""
|
|
1089
|
+
return GET_NAME_METHOD_TEMPLATE.format(resource_lower=resource_lower)
|
|
1090
|
+
|
|
1091
|
+
def generate_update_method(self, resource_name: str, **kwargs) -> str:
|
|
1092
|
+
"""
|
|
1093
|
+
Auto-generate the UPDATE method for a resource.
|
|
1094
|
+
|
|
1095
|
+
Args:
|
|
1096
|
+
resource_name (str): The resource name.
|
|
1097
|
+
|
|
1098
|
+
Returns:
|
|
1099
|
+
str: The formatted Update Method template.
|
|
1100
|
+
|
|
1101
|
+
"""
|
|
1102
|
+
# Get the operation and shape for the 'update' method
|
|
1103
|
+
operation_name = "Update" + resource_name
|
|
1104
|
+
operation_metadata = self.operations[operation_name]
|
|
1105
|
+
operation_input_shape_name = operation_metadata["input"]["shape"]
|
|
1106
|
+
|
|
1107
|
+
required_members = self.shapes[operation_input_shape_name]["required"]
|
|
1108
|
+
|
|
1109
|
+
# Exclude any required attributes that are already present as resource attributes and are also identifiers
|
|
1110
|
+
exclude_required_attributes = []
|
|
1111
|
+
for member in required_members:
|
|
1112
|
+
snake_member = convert_to_snake_case(member)
|
|
1113
|
+
if snake_member in kwargs["resource_attributes"] and any(
|
|
1114
|
+
id in snake_member for id in ["name", "arn", "id"]
|
|
1115
|
+
):
|
|
1116
|
+
exclude_required_attributes.append(snake_member)
|
|
1117
|
+
|
|
1118
|
+
# Generate the arguments for the 'update' method
|
|
1119
|
+
update_args = self._generate_method_args(
|
|
1120
|
+
operation_input_shape_name, exclude_required_attributes
|
|
1121
|
+
)
|
|
1122
|
+
|
|
1123
|
+
operation_input_args = self._generate_operation_input_necessary_args(
|
|
1124
|
+
operation_metadata, exclude_required_attributes
|
|
1125
|
+
)
|
|
1126
|
+
|
|
1127
|
+
# Convert the resource name to snake case
|
|
1128
|
+
resource_lower = convert_to_snake_case(resource_name)
|
|
1129
|
+
|
|
1130
|
+
# Convert the operation name to snake case
|
|
1131
|
+
operation = convert_to_snake_case(operation_name)
|
|
1132
|
+
|
|
1133
|
+
docstring = self._generate_docstring(
|
|
1134
|
+
title=f"Update a {resource_name} resource",
|
|
1135
|
+
operation_name=operation_name,
|
|
1136
|
+
resource_name=resource_name,
|
|
1137
|
+
operation_input_shape_name=operation_input_shape_name,
|
|
1138
|
+
include_session_region=False,
|
|
1139
|
+
include_return_resource_docstring=True,
|
|
1140
|
+
exclude_resource_attrs=kwargs["resource_attributes"],
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
# Format the method using the CREATE_METHOD_TEMPLATE
|
|
1144
|
+
if kwargs["needs_defaults_decorator"]:
|
|
1145
|
+
formatted_method = UPDATE_METHOD_TEMPLATE.format(
|
|
1146
|
+
docstring=docstring,
|
|
1147
|
+
service_name="sagemaker",
|
|
1148
|
+
resource_name=resource_name,
|
|
1149
|
+
resource_lower=resource_lower,
|
|
1150
|
+
update_args=update_args,
|
|
1151
|
+
operation_input_args=operation_input_args,
|
|
1152
|
+
operation=operation,
|
|
1153
|
+
)
|
|
1154
|
+
else:
|
|
1155
|
+
formatted_method = UPDATE_METHOD_TEMPLATE_WITHOUT_DECORATOR.format(
|
|
1156
|
+
docstring=docstring,
|
|
1157
|
+
service_name="sagemaker",
|
|
1158
|
+
resource_name=resource_name,
|
|
1159
|
+
resource_lower=resource_lower,
|
|
1160
|
+
update_args=update_args,
|
|
1161
|
+
operation_input_args=operation_input_args,
|
|
1162
|
+
operation=operation,
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
# Return the formatted method
|
|
1166
|
+
return formatted_method
|
|
1167
|
+
|
|
1168
|
+
def generate_invoke_method(self, resource_name: str, **kwargs) -> str:
|
|
1169
|
+
"""
|
|
1170
|
+
Auto-generate the INVOKE ASYNC method for a resource.
|
|
1171
|
+
|
|
1172
|
+
Args:
|
|
1173
|
+
resource_name (str): The resource name.
|
|
1174
|
+
|
|
1175
|
+
Returns:
|
|
1176
|
+
str: The formatted Update Method template.
|
|
1177
|
+
|
|
1178
|
+
"""
|
|
1179
|
+
# Get the operation and shape for the 'create' method
|
|
1180
|
+
operation_name = "Invoke" + resource_name
|
|
1181
|
+
operation_metadata = self.operations[operation_name]
|
|
1182
|
+
operation_input_shape_name = operation_metadata["input"]["shape"]
|
|
1183
|
+
|
|
1184
|
+
# Generate the arguments for the 'create' method
|
|
1185
|
+
invoke_args = self._generate_method_args(
|
|
1186
|
+
operation_input_shape_name, kwargs["resource_attributes"]
|
|
1187
|
+
)
|
|
1188
|
+
|
|
1189
|
+
operation_input_args = self._generate_operation_input_necessary_args(
|
|
1190
|
+
operation_metadata, kwargs["resource_attributes"]
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
# Convert the resource name to snake case
|
|
1194
|
+
resource_lower = convert_to_snake_case(resource_name)
|
|
1195
|
+
|
|
1196
|
+
# Convert the operation name to snake case
|
|
1197
|
+
operation = convert_to_snake_case(operation_name)
|
|
1198
|
+
|
|
1199
|
+
# generate docstring
|
|
1200
|
+
docstring = self._generate_docstring(
|
|
1201
|
+
title=f"Invoke a {resource_name} resource",
|
|
1202
|
+
operation_name=operation_name,
|
|
1203
|
+
resource_name=resource_name,
|
|
1204
|
+
operation_input_shape_name=operation_input_shape_name,
|
|
1205
|
+
include_session_region=False,
|
|
1206
|
+
include_return_resource_docstring=False,
|
|
1207
|
+
return_string=f"Returns:\n" f" The Invoke response.\n",
|
|
1208
|
+
exclude_resource_attrs=kwargs["resource_attributes"],
|
|
1209
|
+
)
|
|
1210
|
+
# Format the method using the CREATE_METHOD_TEMPLATE
|
|
1211
|
+
formatted_method = INVOKE_METHOD_TEMPLATE.format(
|
|
1212
|
+
docstring=docstring,
|
|
1213
|
+
service_name="sagemaker-runtime",
|
|
1214
|
+
invoke_args=invoke_args,
|
|
1215
|
+
resource_name=resource_name,
|
|
1216
|
+
resource_lower=resource_lower,
|
|
1217
|
+
operation_input_args=operation_input_args,
|
|
1218
|
+
operation=operation,
|
|
1219
|
+
)
|
|
1220
|
+
|
|
1221
|
+
# Return the formatted method
|
|
1222
|
+
return formatted_method
|
|
1223
|
+
|
|
1224
|
+
def generate_invoke_async_method(self, resource_name: str, **kwargs) -> str:
|
|
1225
|
+
"""
|
|
1226
|
+
Auto-generate the INVOKE method for a resource.
|
|
1227
|
+
|
|
1228
|
+
Args:
|
|
1229
|
+
resource_name (str): The resource name.
|
|
1230
|
+
|
|
1231
|
+
Returns:
|
|
1232
|
+
str: The formatted Update Method template.
|
|
1233
|
+
|
|
1234
|
+
"""
|
|
1235
|
+
# Get the operation and shape for the 'create' method
|
|
1236
|
+
operation_name = "Invoke" + resource_name + "Async"
|
|
1237
|
+
operation_metadata = self.operations[operation_name]
|
|
1238
|
+
operation_input_shape_name = operation_metadata["input"]["shape"]
|
|
1239
|
+
|
|
1240
|
+
# Generate the arguments for the 'create' method
|
|
1241
|
+
invoke_args = self._generate_method_args(
|
|
1242
|
+
operation_input_shape_name, kwargs["resource_attributes"]
|
|
1243
|
+
)
|
|
1244
|
+
|
|
1245
|
+
operation_input_args = self._generate_operation_input_necessary_args(
|
|
1246
|
+
operation_metadata, kwargs["resource_attributes"]
|
|
1247
|
+
)
|
|
1248
|
+
|
|
1249
|
+
# Convert the resource name to snake case
|
|
1250
|
+
resource_lower = convert_to_snake_case(resource_name)
|
|
1251
|
+
|
|
1252
|
+
# Convert the operation name to snake case
|
|
1253
|
+
operation = convert_to_snake_case(operation_name)
|
|
1254
|
+
|
|
1255
|
+
# generate docstring
|
|
1256
|
+
docstring = self._generate_docstring(
|
|
1257
|
+
title=f"Invoke Async a {resource_name} resource",
|
|
1258
|
+
operation_name=operation_name,
|
|
1259
|
+
resource_name=resource_name,
|
|
1260
|
+
operation_input_shape_name=operation_input_shape_name,
|
|
1261
|
+
include_session_region=False,
|
|
1262
|
+
include_return_resource_docstring=False,
|
|
1263
|
+
return_string=f"Returns:\n" f" The Invoke response.\n",
|
|
1264
|
+
exclude_resource_attrs=kwargs["resource_attributes"],
|
|
1265
|
+
)
|
|
1266
|
+
# Format the method using the CREATE_METHOD_TEMPLATE
|
|
1267
|
+
formatted_method = INVOKE_ASYNC_METHOD_TEMPLATE.format(
|
|
1268
|
+
docstring=docstring,
|
|
1269
|
+
service_name="sagemaker-runtime",
|
|
1270
|
+
create_args=invoke_args,
|
|
1271
|
+
resource_name=resource_name,
|
|
1272
|
+
resource_lower=resource_lower,
|
|
1273
|
+
operation_input_args=operation_input_args,
|
|
1274
|
+
operation=operation,
|
|
1275
|
+
)
|
|
1276
|
+
|
|
1277
|
+
# Return the formatted method
|
|
1278
|
+
return formatted_method
|
|
1279
|
+
|
|
1280
|
+
def generate_invoke_with_response_stream_method(self, resource_name: str, **kwargs) -> str:
|
|
1281
|
+
"""
|
|
1282
|
+
Auto-generate the INVOKE with response stream method for a resource.
|
|
1283
|
+
|
|
1284
|
+
Args:
|
|
1285
|
+
resource_name (str): The resource name.
|
|
1286
|
+
|
|
1287
|
+
Returns:
|
|
1288
|
+
str: The formatted Update Method template.
|
|
1289
|
+
|
|
1290
|
+
"""
|
|
1291
|
+
# Get the operation and shape for the 'create' method
|
|
1292
|
+
operation_name = "Invoke" + resource_name + "WithResponseStream"
|
|
1293
|
+
operation_metadata = self.operations[operation_name]
|
|
1294
|
+
operation_input_shape_name = operation_metadata["input"]["shape"]
|
|
1295
|
+
|
|
1296
|
+
# Generate the arguments for the 'create' method
|
|
1297
|
+
invoke_args = self._generate_method_args(
|
|
1298
|
+
operation_input_shape_name, kwargs["resource_attributes"]
|
|
1299
|
+
)
|
|
1300
|
+
|
|
1301
|
+
operation_input_args = self._generate_operation_input_necessary_args(
|
|
1302
|
+
operation_metadata, kwargs["resource_attributes"]
|
|
1303
|
+
)
|
|
1304
|
+
|
|
1305
|
+
# Convert the resource name to snake case
|
|
1306
|
+
resource_lower = convert_to_snake_case(resource_name)
|
|
1307
|
+
|
|
1308
|
+
# Convert the operation name to snake case
|
|
1309
|
+
operation = convert_to_snake_case(operation_name)
|
|
1310
|
+
|
|
1311
|
+
# generate docstring
|
|
1312
|
+
docstring = self._generate_docstring(
|
|
1313
|
+
title=f"Invoke with response stream a {resource_name} resource",
|
|
1314
|
+
operation_name=operation_name,
|
|
1315
|
+
resource_name=resource_name,
|
|
1316
|
+
operation_input_shape_name=operation_input_shape_name,
|
|
1317
|
+
include_session_region=False,
|
|
1318
|
+
include_return_resource_docstring=False,
|
|
1319
|
+
return_string=f"Returns:\n" f" The Invoke response.\n",
|
|
1320
|
+
exclude_resource_attrs=kwargs["resource_attributes"],
|
|
1321
|
+
)
|
|
1322
|
+
# Format the method using the CREATE_METHOD_TEMPLATE
|
|
1323
|
+
formatted_method = INVOKE_WITH_RESPONSE_STREAM_METHOD_TEMPLATE.format(
|
|
1324
|
+
docstring=docstring,
|
|
1325
|
+
service_name="sagemaker-runtime",
|
|
1326
|
+
create_args=invoke_args,
|
|
1327
|
+
resource_name=resource_name,
|
|
1328
|
+
resource_lower=resource_lower,
|
|
1329
|
+
operation_input_args=operation_input_args,
|
|
1330
|
+
operation=operation,
|
|
1331
|
+
)
|
|
1332
|
+
|
|
1333
|
+
# Return the formatted method
|
|
1334
|
+
return formatted_method
|
|
1335
|
+
|
|
1336
|
+
def generate_get_method(self, resource_name: str) -> str:
|
|
1337
|
+
"""
|
|
1338
|
+
Auto-generate the GET method (describe API) for a resource.
|
|
1339
|
+
|
|
1340
|
+
Args:
|
|
1341
|
+
resource_name (str): The resource name.
|
|
1342
|
+
|
|
1343
|
+
Returns:
|
|
1344
|
+
str: The formatted Get Method template.
|
|
1345
|
+
|
|
1346
|
+
"""
|
|
1347
|
+
operation_name = "Describe" + resource_name
|
|
1348
|
+
operation_metadata = self.operations[operation_name]
|
|
1349
|
+
resource_operation_input_shape_name = operation_metadata["input"]["shape"]
|
|
1350
|
+
resource_operation_output_shape_name = operation_metadata["output"]["shape"]
|
|
1351
|
+
|
|
1352
|
+
operation_input_args = self._generate_operation_input_args(
|
|
1353
|
+
operation_metadata, is_class_method=True
|
|
1354
|
+
)
|
|
1355
|
+
|
|
1356
|
+
# Generate the arguments for the 'update' method
|
|
1357
|
+
describe_args = self._generate_method_args(resource_operation_input_shape_name)
|
|
1358
|
+
|
|
1359
|
+
resource_lower = convert_to_snake_case(resource_name)
|
|
1360
|
+
|
|
1361
|
+
operation = convert_to_snake_case(operation_name)
|
|
1362
|
+
|
|
1363
|
+
# generate docstring
|
|
1364
|
+
docstring = self._generate_docstring(
|
|
1365
|
+
title=f"Get a {resource_name} resource",
|
|
1366
|
+
operation_name=operation_name,
|
|
1367
|
+
resource_name=resource_name,
|
|
1368
|
+
operation_input_shape_name=resource_operation_input_shape_name,
|
|
1369
|
+
include_session_region=True,
|
|
1370
|
+
include_return_resource_docstring=True,
|
|
1371
|
+
)
|
|
1372
|
+
|
|
1373
|
+
formatted_method = GET_METHOD_TEMPLATE.format(
|
|
1374
|
+
docstring=docstring,
|
|
1375
|
+
resource_name=resource_name,
|
|
1376
|
+
service_name="sagemaker", # TODO: change service name based on the service - runtime, sagemaker, etc.
|
|
1377
|
+
describe_args=describe_args,
|
|
1378
|
+
resource_lower=resource_lower,
|
|
1379
|
+
operation_input_args=operation_input_args,
|
|
1380
|
+
operation=operation,
|
|
1381
|
+
describe_operation_output_shape=resource_operation_output_shape_name,
|
|
1382
|
+
)
|
|
1383
|
+
return formatted_method
|
|
1384
|
+
|
|
1385
|
+
def generate_refresh_method(self, resource_name: str, **kwargs) -> str:
|
|
1386
|
+
"""Auto-Generate 'refresh' object Method [describe API] for a resource.
|
|
1387
|
+
|
|
1388
|
+
Args:
|
|
1389
|
+
resource_name (str): The resource name.
|
|
1390
|
+
|
|
1391
|
+
Returns:
|
|
1392
|
+
str: The formatted refresh Method template.
|
|
1393
|
+
"""
|
|
1394
|
+
operation_name = "Describe" + resource_name
|
|
1395
|
+
operation_metadata = self.operations[operation_name]
|
|
1396
|
+
resource_operation_input_shape_name = operation_metadata["input"]["shape"]
|
|
1397
|
+
resource_operation_output_shape_name = operation_metadata["output"]["shape"]
|
|
1398
|
+
|
|
1399
|
+
# Generate the arguments for the 'refresh' method
|
|
1400
|
+
refresh_args = self._generate_method_args(
|
|
1401
|
+
resource_operation_input_shape_name, kwargs["resource_attributes"]
|
|
1402
|
+
)
|
|
1403
|
+
|
|
1404
|
+
operation_input_args = self._generate_operation_input_necessary_args(
|
|
1405
|
+
operation_metadata, kwargs["resource_attributes"]
|
|
1406
|
+
)
|
|
1407
|
+
|
|
1408
|
+
operation = convert_to_snake_case(operation_name)
|
|
1409
|
+
|
|
1410
|
+
# generate docstring
|
|
1411
|
+
docstring = self._generate_docstring(
|
|
1412
|
+
title=f"Refresh a {resource_name} resource",
|
|
1413
|
+
operation_name=operation_name,
|
|
1414
|
+
resource_name=resource_name,
|
|
1415
|
+
include_session_region=False,
|
|
1416
|
+
include_return_resource_docstring=True,
|
|
1417
|
+
)
|
|
1418
|
+
|
|
1419
|
+
formatted_method = REFRESH_METHOD_TEMPLATE.format(
|
|
1420
|
+
docstring=docstring,
|
|
1421
|
+
resource_name=resource_name,
|
|
1422
|
+
operation_input_args=operation_input_args,
|
|
1423
|
+
refresh_args=refresh_args,
|
|
1424
|
+
operation=operation,
|
|
1425
|
+
describe_operation_output_shape=resource_operation_output_shape_name,
|
|
1426
|
+
)
|
|
1427
|
+
return formatted_method
|
|
1428
|
+
|
|
1429
|
+
def generate_delete_method(self, resource_name: str, **kwargs) -> str:
|
|
1430
|
+
"""Auto-Generate 'delete' object Method [delete API] for a resource.
|
|
1431
|
+
|
|
1432
|
+
Args:
|
|
1433
|
+
resource_name (str): The resource name.
|
|
1434
|
+
|
|
1435
|
+
Returns:
|
|
1436
|
+
str: The formatted delete Method template.
|
|
1437
|
+
"""
|
|
1438
|
+
operation_name = "Delete" + resource_name
|
|
1439
|
+
operation_metadata = self.operations[operation_name]
|
|
1440
|
+
resource_operation_input_shape_name = operation_metadata["input"]["shape"]
|
|
1441
|
+
|
|
1442
|
+
# Generate the arguments for the 'update' method
|
|
1443
|
+
delete_args = self._generate_method_args(
|
|
1444
|
+
resource_operation_input_shape_name, kwargs["resource_attributes"]
|
|
1445
|
+
)
|
|
1446
|
+
operation_input_args = self._generate_operation_input_necessary_args(
|
|
1447
|
+
operation_metadata, kwargs["resource_attributes"]
|
|
1448
|
+
)
|
|
1449
|
+
|
|
1450
|
+
operation = convert_to_snake_case(operation_name)
|
|
1451
|
+
|
|
1452
|
+
# generate docstring
|
|
1453
|
+
docstring = self._generate_docstring(
|
|
1454
|
+
title=f"Delete a {resource_name} resource",
|
|
1455
|
+
operation_name=operation_name,
|
|
1456
|
+
resource_name=resource_name,
|
|
1457
|
+
include_session_region=False,
|
|
1458
|
+
include_return_resource_docstring=False,
|
|
1459
|
+
)
|
|
1460
|
+
|
|
1461
|
+
formatted_method = DELETE_METHOD_TEMPLATE.format(
|
|
1462
|
+
docstring=docstring,
|
|
1463
|
+
resource_name=resource_name,
|
|
1464
|
+
delete_args=delete_args,
|
|
1465
|
+
operation_input_args=operation_input_args,
|
|
1466
|
+
operation=operation,
|
|
1467
|
+
)
|
|
1468
|
+
return formatted_method
|
|
1469
|
+
|
|
1470
|
+
def generate_stop_method(self, resource_name: str) -> str:
|
|
1471
|
+
"""Auto-Generate 'stop' object Method [delete API] for a resource.
|
|
1472
|
+
|
|
1473
|
+
Args:
|
|
1474
|
+
resource_name (str): The resource name.
|
|
1475
|
+
|
|
1476
|
+
Returns:
|
|
1477
|
+
str: The formatted stop Method template.
|
|
1478
|
+
"""
|
|
1479
|
+
operation_name = "Stop" + resource_name
|
|
1480
|
+
operation_metadata = self.operations[operation_name]
|
|
1481
|
+
|
|
1482
|
+
operation_input_args = self._generate_operation_input_args(
|
|
1483
|
+
operation_metadata, is_class_method=False
|
|
1484
|
+
)
|
|
1485
|
+
|
|
1486
|
+
operation = convert_to_snake_case(operation_name)
|
|
1487
|
+
|
|
1488
|
+
# generate docstring
|
|
1489
|
+
docstring = self._generate_docstring(
|
|
1490
|
+
title=f"Stop a {resource_name} resource",
|
|
1491
|
+
operation_name=operation_name,
|
|
1492
|
+
resource_name=resource_name,
|
|
1493
|
+
include_session_region=False,
|
|
1494
|
+
include_return_resource_docstring=False,
|
|
1495
|
+
)
|
|
1496
|
+
|
|
1497
|
+
formatted_method = STOP_METHOD_TEMPLATE.format(
|
|
1498
|
+
docstring=docstring,
|
|
1499
|
+
resource_name=resource_name,
|
|
1500
|
+
operation_input_args=operation_input_args,
|
|
1501
|
+
operation=operation,
|
|
1502
|
+
)
|
|
1503
|
+
return formatted_method
|
|
1504
|
+
|
|
1505
|
+
def generate_method(self, method: Method, resource_attributes: list):
|
|
1506
|
+
# TODO: Use special templates for some methods with different formats like list and wait
|
|
1507
|
+
if method.method_name.startswith("get_all"):
|
|
1508
|
+
return self.generate_additional_get_all_method(method, resource_attributes)
|
|
1509
|
+
operation_metadata = self.operations[method.operation_name]
|
|
1510
|
+
operation_input_shape_name = operation_metadata["input"]["shape"]
|
|
1511
|
+
if method.method_type == MethodType.CLASS.value:
|
|
1512
|
+
decorator = "@classmethod"
|
|
1513
|
+
method_args = add_indent("cls,\n", 4)
|
|
1514
|
+
method_args += self._generate_method_args(operation_input_shape_name)
|
|
1515
|
+
operation_input_args = self._generate_operation_input_args_updated(
|
|
1516
|
+
operation_metadata, True, resource_attributes
|
|
1517
|
+
)
|
|
1518
|
+
exclude_resource_attrs = None
|
|
1519
|
+
elif method.method_type == MethodType.STATIC.value:
|
|
1520
|
+
decorator = "@staticmethod"
|
|
1521
|
+
method_args = self._generate_method_args(operation_input_shape_name)
|
|
1522
|
+
operation_input_args = self._generate_operation_input_args_updated(
|
|
1523
|
+
operation_metadata, True
|
|
1524
|
+
)
|
|
1525
|
+
exclude_resource_attrs = None
|
|
1526
|
+
else:
|
|
1527
|
+
decorator = ""
|
|
1528
|
+
method_args = add_indent("self,\n", 4)
|
|
1529
|
+
method_args += (
|
|
1530
|
+
self._generate_method_args(operation_input_shape_name, resource_attributes) + "\n"
|
|
1531
|
+
)
|
|
1532
|
+
operation_input_args = self._generate_operation_input_args_updated(
|
|
1533
|
+
operation_metadata, False, resource_attributes
|
|
1534
|
+
)
|
|
1535
|
+
exclude_resource_attrs = resource_attributes
|
|
1536
|
+
method_args += add_indent("session: Optional[Session] = None,\n", 4)
|
|
1537
|
+
method_args += add_indent("region: Optional[str] = None,", 4)
|
|
1538
|
+
|
|
1539
|
+
initialize_client = INITIALIZE_CLIENT_TEMPLATE.format(service_name=method.service_name)
|
|
1540
|
+
if len(self.shapes[operation_input_shape_name]["members"]) != 0:
|
|
1541
|
+
# the method has input arguments
|
|
1542
|
+
serialize_operation_input = SERIALIZE_INPUT_TEMPLATE.format(
|
|
1543
|
+
operation_input_args=operation_input_args
|
|
1544
|
+
)
|
|
1545
|
+
call_operation_api = CALL_OPERATION_API_TEMPLATE.format(
|
|
1546
|
+
operation=convert_to_snake_case(method.operation_name)
|
|
1547
|
+
)
|
|
1548
|
+
else:
|
|
1549
|
+
# the method has no input arguments
|
|
1550
|
+
serialize_operation_input = ""
|
|
1551
|
+
call_operation_api = CALL_OPERATION_API_NO_ARG_TEMPLATE.format(
|
|
1552
|
+
operation=convert_to_snake_case(method.operation_name)
|
|
1553
|
+
)
|
|
1554
|
+
|
|
1555
|
+
if method.return_type == "None":
|
|
1556
|
+
return_type = "None"
|
|
1557
|
+
deserialize_response = ""
|
|
1558
|
+
return_string = None
|
|
1559
|
+
elif method.return_type in BASIC_RETURN_TYPES:
|
|
1560
|
+
return_type = f"Optional[{method.return_type}]"
|
|
1561
|
+
deserialize_response = DESERIALIZE_RESPONSE_TO_BASIC_TYPE_TEMPLATE
|
|
1562
|
+
return_string = f"Returns:\n" f" {method.return_type}\n"
|
|
1563
|
+
else:
|
|
1564
|
+
if method.return_type == "cls":
|
|
1565
|
+
return_type = f'Optional["{method.resource_name}"]'
|
|
1566
|
+
return_type_conversion = "cls"
|
|
1567
|
+
return_string = f"Returns:\n" f" {method.resource_name}\n"
|
|
1568
|
+
else:
|
|
1569
|
+
return_type = f"Optional[{method.return_type}]"
|
|
1570
|
+
return_type_conversion = method.return_type
|
|
1571
|
+
return_string = f"Returns:\n" f" {method.return_type}\n"
|
|
1572
|
+
operation_output_shape = operation_metadata["output"]["shape"]
|
|
1573
|
+
deserialize_response = DESERIALIZE_RESPONSE_TEMPLATE.format(
|
|
1574
|
+
operation_output_shape=operation_output_shape,
|
|
1575
|
+
return_type_conversion=return_type_conversion,
|
|
1576
|
+
)
|
|
1577
|
+
|
|
1578
|
+
initialize_client = INITIALIZE_CLIENT_TEMPLATE.format(service_name=method.service_name)
|
|
1579
|
+
if len(self.shapes[operation_input_shape_name]["members"]) != 0:
|
|
1580
|
+
# the method has input arguments
|
|
1581
|
+
serialize_operation_input = SERIALIZE_INPUT_TEMPLATE.format(
|
|
1582
|
+
operation_input_args=operation_input_args
|
|
1583
|
+
)
|
|
1584
|
+
call_operation_api = CALL_OPERATION_API_TEMPLATE.format(
|
|
1585
|
+
operation=convert_to_snake_case(method.operation_name)
|
|
1586
|
+
)
|
|
1587
|
+
else:
|
|
1588
|
+
# the method has no input arguments
|
|
1589
|
+
serialize_operation_input = ""
|
|
1590
|
+
call_operation_api = CALL_OPERATION_API_NO_ARG_TEMPLATE.format(
|
|
1591
|
+
operation=convert_to_snake_case(method.operation_name)
|
|
1592
|
+
)
|
|
1593
|
+
|
|
1594
|
+
# generate docstring
|
|
1595
|
+
docstring = self._generate_docstring(
|
|
1596
|
+
title=method.docstring_title,
|
|
1597
|
+
operation_name=method.operation_name,
|
|
1598
|
+
resource_name=method.resource_name,
|
|
1599
|
+
operation_input_shape_name=operation_input_shape_name,
|
|
1600
|
+
include_session_region=True,
|
|
1601
|
+
return_string=return_string,
|
|
1602
|
+
exclude_resource_attrs=exclude_resource_attrs,
|
|
1603
|
+
)
|
|
1604
|
+
|
|
1605
|
+
formatted_method = GENERIC_METHOD_TEMPLATE.format(
|
|
1606
|
+
docstring=docstring,
|
|
1607
|
+
decorator=decorator,
|
|
1608
|
+
method_name=method.method_name,
|
|
1609
|
+
method_args=method_args,
|
|
1610
|
+
return_type=return_type,
|
|
1611
|
+
serialize_operation_input=serialize_operation_input,
|
|
1612
|
+
initialize_client=initialize_client,
|
|
1613
|
+
call_operation_api=call_operation_api,
|
|
1614
|
+
deserialize_response=deserialize_response,
|
|
1615
|
+
)
|
|
1616
|
+
return formatted_method
|
|
1617
|
+
|
|
1618
|
+
def generate_additional_get_all_method(self, method: Method, resource_attributes: list):
|
|
1619
|
+
"""Auto-Generate methods that return a list of objects.
|
|
1620
|
+
|
|
1621
|
+
Args:
|
|
1622
|
+
resource_name (str): The resource name.
|
|
1623
|
+
|
|
1624
|
+
Returns:
|
|
1625
|
+
str: The formatted method code.
|
|
1626
|
+
"""
|
|
1627
|
+
# TODO: merge this with generate_get_all_method
|
|
1628
|
+
operation_metadata = self.operations[method.operation_name]
|
|
1629
|
+
operation_input_shape_name = operation_metadata["input"]["shape"]
|
|
1630
|
+
exclude_list = ["next_token", "max_results"]
|
|
1631
|
+
if method.method_type == MethodType.CLASS.value:
|
|
1632
|
+
decorator = "@classmethod"
|
|
1633
|
+
method_args = add_indent("cls,\n", 4)
|
|
1634
|
+
method_args += self._generate_method_args(operation_input_shape_name, exclude_list)
|
|
1635
|
+
operation_input_args = self._generate_operation_input_args_updated(
|
|
1636
|
+
operation_metadata, True, resource_attributes, exclude_list
|
|
1637
|
+
)
|
|
1638
|
+
exclude_resource_attrs = None
|
|
1639
|
+
else:
|
|
1640
|
+
decorator = ""
|
|
1641
|
+
method_args = add_indent("self,\n", 4)
|
|
1642
|
+
method_args += self._generate_method_args(
|
|
1643
|
+
operation_input_shape_name, exclude_list + resource_attributes
|
|
1644
|
+
)
|
|
1645
|
+
operation_input_args = self._generate_operation_input_args_updated(
|
|
1646
|
+
operation_metadata, False, resource_attributes, exclude_list
|
|
1647
|
+
)
|
|
1648
|
+
exclude_resource_attrs = resource_attributes
|
|
1649
|
+
method_args += add_indent("session: Optional[Session] = None,\n", 4)
|
|
1650
|
+
method_args += add_indent("region: Optional[str] = None,", 4)
|
|
1651
|
+
|
|
1652
|
+
if method.return_type == method.resource_name:
|
|
1653
|
+
return_type = f'ResourceIterator["{method.resource_name}"]'
|
|
1654
|
+
else:
|
|
1655
|
+
return_type = f"ResourceIterator[{method.return_type}]"
|
|
1656
|
+
return_string = f"Returns:\n" f" Iterator for listed {method.return_type}.\n"
|
|
1657
|
+
|
|
1658
|
+
get_list_operation_output_shape = operation_metadata["output"]["shape"]
|
|
1659
|
+
list_operation_output_members = self.shapes[get_list_operation_output_shape]["members"]
|
|
1660
|
+
|
|
1661
|
+
filtered_list_operation_output_members = next(
|
|
1662
|
+
{key: value}
|
|
1663
|
+
for key, value in list_operation_output_members.items()
|
|
1664
|
+
if key != "NextToken"
|
|
1665
|
+
)
|
|
1666
|
+
summaries_key = next(iter(filtered_list_operation_output_members))
|
|
1667
|
+
summaries_shape_name = filtered_list_operation_output_members[summaries_key]["shape"]
|
|
1668
|
+
summary_name = self.shapes[summaries_shape_name]["member"]["shape"]
|
|
1669
|
+
|
|
1670
|
+
list_method = convert_to_snake_case(method.operation_name)
|
|
1671
|
+
|
|
1672
|
+
# TODO: add rules for custom key mapping and list methods with no args
|
|
1673
|
+
resource_iterator_args_list = [
|
|
1674
|
+
"client=client",
|
|
1675
|
+
f"list_method='{list_method}'",
|
|
1676
|
+
f"summaries_key='{summaries_key}'",
|
|
1677
|
+
f"summary_name='{summary_name}'",
|
|
1678
|
+
f"resource_cls={method.return_type}",
|
|
1679
|
+
"list_method_kwargs=operation_input_args",
|
|
1680
|
+
]
|
|
1681
|
+
|
|
1682
|
+
resource_iterator_args = ",\n".join(resource_iterator_args_list)
|
|
1683
|
+
resource_iterator_args = add_indent(resource_iterator_args, 8)
|
|
1684
|
+
serialize_operation_input = SERIALIZE_LIST_INPUT_TEMPLATE.format(
|
|
1685
|
+
operation_input_args=operation_input_args
|
|
1686
|
+
)
|
|
1687
|
+
initialize_client = INITIALIZE_CLIENT_TEMPLATE.format(service_name=method.service_name)
|
|
1688
|
+
deserialize_response = RETURN_ITERATOR_TEMPLATE.format(
|
|
1689
|
+
resource_iterator_args=resource_iterator_args
|
|
1690
|
+
)
|
|
1691
|
+
|
|
1692
|
+
# generate docstring
|
|
1693
|
+
docstring = self._generate_docstring(
|
|
1694
|
+
title=method.docstring_title,
|
|
1695
|
+
operation_name=method.operation_name,
|
|
1696
|
+
resource_name=method.resource_name,
|
|
1697
|
+
operation_input_shape_name=operation_input_shape_name,
|
|
1698
|
+
include_session_region=True,
|
|
1699
|
+
return_string=return_string,
|
|
1700
|
+
exclude_resource_attrs=exclude_resource_attrs,
|
|
1701
|
+
)
|
|
1702
|
+
|
|
1703
|
+
return GENERIC_METHOD_TEMPLATE.format(
|
|
1704
|
+
docstring=docstring,
|
|
1705
|
+
decorator=decorator,
|
|
1706
|
+
method_name=method.method_name,
|
|
1707
|
+
method_args=method_args,
|
|
1708
|
+
return_type=return_type,
|
|
1709
|
+
serialize_operation_input=serialize_operation_input,
|
|
1710
|
+
initialize_client=initialize_client,
|
|
1711
|
+
call_operation_api="",
|
|
1712
|
+
deserialize_response=deserialize_response,
|
|
1713
|
+
)
|
|
1714
|
+
|
|
1715
|
+
def _get_failure_reason_ref(self, resource_name: str) -> str:
|
|
1716
|
+
"""Get the failure reason reference for a resource object.
|
|
1717
|
+
Args:
|
|
1718
|
+
resource_name (str): The resource name.
|
|
1719
|
+
Returns:
|
|
1720
|
+
str: The failure reason reference for resource object
|
|
1721
|
+
"""
|
|
1722
|
+
describe_output = self.operations["Describe" + resource_name]["output"]["shape"]
|
|
1723
|
+
shape_members = self.shapes[describe_output]
|
|
1724
|
+
|
|
1725
|
+
for member in shape_members["members"]:
|
|
1726
|
+
if "FailureReason" in member or "StatusMessage" in member:
|
|
1727
|
+
return f"self.{convert_to_snake_case(member)}"
|
|
1728
|
+
|
|
1729
|
+
return "'(Unknown)'"
|
|
1730
|
+
|
|
1731
|
+
def generate_wait_method(self, resource_name: str) -> str:
|
|
1732
|
+
"""Auto-Generate WAIT Method for a waitable resource.
|
|
1733
|
+
|
|
1734
|
+
Args:
|
|
1735
|
+
resource_name (str): The resource name.
|
|
1736
|
+
|
|
1737
|
+
Returns:
|
|
1738
|
+
str: The formatted Wait Method template.
|
|
1739
|
+
"""
|
|
1740
|
+
resource_status_chain, resource_states = (
|
|
1741
|
+
self.resources_extractor.get_status_chain_and_states(resource_name)
|
|
1742
|
+
)
|
|
1743
|
+
|
|
1744
|
+
# Get terminal states for resource
|
|
1745
|
+
terminal_resource_states = []
|
|
1746
|
+
for state in resource_states:
|
|
1747
|
+
# Handles when a resource has terminal states like UpdateCompleted, CreateFailed, etc.
|
|
1748
|
+
# Checking lower because case is not consistent accross resources (ie, COMPLETED vs Completed)
|
|
1749
|
+
if any(terminal_state.lower() in state.lower() for terminal_state in TERMINAL_STATES):
|
|
1750
|
+
terminal_resource_states.append(state)
|
|
1751
|
+
|
|
1752
|
+
# Get resource status key path
|
|
1753
|
+
status_key_path = ""
|
|
1754
|
+
for member in resource_status_chain:
|
|
1755
|
+
status_key_path += f'.{convert_to_snake_case(member["name"])}'
|
|
1756
|
+
|
|
1757
|
+
failure_reason = self._get_failure_reason_ref(resource_name)
|
|
1758
|
+
formatted_failed_block = FAILED_STATUS_ERROR_TEMPLATE.format(
|
|
1759
|
+
resource_name=resource_name, reason=failure_reason
|
|
1760
|
+
)
|
|
1761
|
+
formatted_failed_block = add_indent(formatted_failed_block, 12)
|
|
1762
|
+
|
|
1763
|
+
formatted_method = WAIT_METHOD_TEMPLATE.format(
|
|
1764
|
+
terminal_resource_states=terminal_resource_states,
|
|
1765
|
+
status_key_path=status_key_path,
|
|
1766
|
+
failed_error_block=formatted_failed_block,
|
|
1767
|
+
resource_name=resource_name,
|
|
1768
|
+
)
|
|
1769
|
+
return formatted_method
|
|
1770
|
+
|
|
1771
|
+
def generate_wait_for_status_method(self, resource_name: str) -> str:
|
|
1772
|
+
"""Auto-Generate WAIT_FOR_STATUS Method for a waitable resource.
|
|
1773
|
+
|
|
1774
|
+
Args:
|
|
1775
|
+
resource_name (str): The resource name.
|
|
1776
|
+
|
|
1777
|
+
Returns:
|
|
1778
|
+
str: The formatted wait_for_status Method template.
|
|
1779
|
+
"""
|
|
1780
|
+
resource_status_chain, resource_states = (
|
|
1781
|
+
self.resources_extractor.get_status_chain_and_states(resource_name)
|
|
1782
|
+
)
|
|
1783
|
+
|
|
1784
|
+
# Get resource status key path
|
|
1785
|
+
status_key_path = ""
|
|
1786
|
+
for member in resource_status_chain:
|
|
1787
|
+
status_key_path += f'.{convert_to_snake_case(member["name"])}'
|
|
1788
|
+
|
|
1789
|
+
formatted_failed_block = ""
|
|
1790
|
+
if any("failed" in state.lower() for state in resource_states):
|
|
1791
|
+
failure_reason = self._get_failure_reason_ref(resource_name)
|
|
1792
|
+
formatted_failed_block = FAILED_STATUS_ERROR_TEMPLATE.format(
|
|
1793
|
+
resource_name=resource_name, reason=failure_reason
|
|
1794
|
+
)
|
|
1795
|
+
formatted_failed_block = add_indent(formatted_failed_block, 8)
|
|
1796
|
+
|
|
1797
|
+
formatted_method = WAIT_FOR_STATUS_METHOD_TEMPLATE.format(
|
|
1798
|
+
resource_states=resource_states,
|
|
1799
|
+
status_key_path=status_key_path,
|
|
1800
|
+
failed_error_block=formatted_failed_block,
|
|
1801
|
+
resource_name=resource_name,
|
|
1802
|
+
)
|
|
1803
|
+
return formatted_method
|
|
1804
|
+
|
|
1805
|
+
def generate_wait_for_delete_method(self, resource_name: str) -> str:
|
|
1806
|
+
"""Auto-Generate WAIT_FOR_DELETE Method for a resource with deleting status.
|
|
1807
|
+
|
|
1808
|
+
Args:
|
|
1809
|
+
resource_name (str): The resource name.
|
|
1810
|
+
|
|
1811
|
+
Returns:
|
|
1812
|
+
str: The formatted wait_for_delete Method template.
|
|
1813
|
+
"""
|
|
1814
|
+
resource_status_chain, resource_states = (
|
|
1815
|
+
self.resources_extractor.get_status_chain_and_states(resource_name)
|
|
1816
|
+
)
|
|
1817
|
+
|
|
1818
|
+
# Get resource status key path
|
|
1819
|
+
status_key_path = ""
|
|
1820
|
+
for member in resource_status_chain:
|
|
1821
|
+
status_key_path += f'.{convert_to_snake_case(member["name"])}'
|
|
1822
|
+
|
|
1823
|
+
formatted_failed_block = ""
|
|
1824
|
+
if any("delete_failed" in state.lower() for state in resource_states):
|
|
1825
|
+
failure_reason = self._get_failure_reason_ref(resource_name)
|
|
1826
|
+
formatted_failed_block = DELETE_FAILED_STATUS_CHECK.format(
|
|
1827
|
+
resource_name=resource_name, reason=failure_reason
|
|
1828
|
+
)
|
|
1829
|
+
formatted_failed_block = add_indent(formatted_failed_block, 12)
|
|
1830
|
+
|
|
1831
|
+
if any(state.lower() == "deleted" for state in resource_states):
|
|
1832
|
+
deleted_status_check = add_indent(DELETED_STATUS_CHECK, 12)
|
|
1833
|
+
else:
|
|
1834
|
+
deleted_status_check = ""
|
|
1835
|
+
|
|
1836
|
+
formatted_method = WAIT_FOR_DELETE_METHOD_TEMPLATE.format(
|
|
1837
|
+
resource_states=resource_states,
|
|
1838
|
+
status_key_path=status_key_path,
|
|
1839
|
+
delete_failed_error_block=formatted_failed_block,
|
|
1840
|
+
deleted_status_check=deleted_status_check,
|
|
1841
|
+
resource_name=resource_name,
|
|
1842
|
+
)
|
|
1843
|
+
return formatted_method
|
|
1844
|
+
|
|
1845
|
+
def generate_get_all_method(self, resource_name: str) -> str:
|
|
1846
|
+
"""Auto-Generate 'get_all' class Method [list API] for a resource.
|
|
1847
|
+
|
|
1848
|
+
Args:
|
|
1849
|
+
resource_name (str): The resource name.
|
|
1850
|
+
|
|
1851
|
+
Returns:
|
|
1852
|
+
str: The formatted get_all Method template.
|
|
1853
|
+
"""
|
|
1854
|
+
operation_name = "List" + resource_name + "s"
|
|
1855
|
+
operation_metadata = self.operations[operation_name]
|
|
1856
|
+
operation_input_shape_name = operation_metadata["input"]["shape"]
|
|
1857
|
+
|
|
1858
|
+
operation = convert_to_snake_case(operation_name)
|
|
1859
|
+
|
|
1860
|
+
get_list_operation_output_shape = self.operations[operation_name]["output"]["shape"]
|
|
1861
|
+
list_operation_output_members = self.shapes[get_list_operation_output_shape]["members"]
|
|
1862
|
+
|
|
1863
|
+
filtered_list_operation_output_members = next(
|
|
1864
|
+
{key: value}
|
|
1865
|
+
for key, value in list_operation_output_members.items()
|
|
1866
|
+
if key != "NextToken"
|
|
1867
|
+
)
|
|
1868
|
+
|
|
1869
|
+
summaries_key = next(iter(filtered_list_operation_output_members))
|
|
1870
|
+
summaries_shape_name = filtered_list_operation_output_members[summaries_key]["shape"]
|
|
1871
|
+
|
|
1872
|
+
summary_name = self.shapes[summaries_shape_name]["member"]["shape"]
|
|
1873
|
+
summary_members = self.shapes[summary_name]["members"].keys()
|
|
1874
|
+
|
|
1875
|
+
if "Describe" + resource_name in self.operations:
|
|
1876
|
+
get_operation = self.operations["Describe" + resource_name]
|
|
1877
|
+
get_operation_input_shape = get_operation["input"]["shape"]
|
|
1878
|
+
get_operation_required_input = self.shapes[get_operation_input_shape].get(
|
|
1879
|
+
"required", []
|
|
1880
|
+
)
|
|
1881
|
+
else:
|
|
1882
|
+
get_operation_required_input = []
|
|
1883
|
+
|
|
1884
|
+
custom_key_mapping_str = ""
|
|
1885
|
+
if any(member not in summary_members for member in get_operation_required_input):
|
|
1886
|
+
if "MonitoringJobDefinitionSummary" == summary_name:
|
|
1887
|
+
custom_key_mapping = {
|
|
1888
|
+
"monitoring_job_definition_name": "job_definition_name",
|
|
1889
|
+
"monitoring_job_definition_arn": "job_definition_arn",
|
|
1890
|
+
}
|
|
1891
|
+
custom_key_mapping_str = f"custom_key_mapping = {json.dumps(custom_key_mapping)}"
|
|
1892
|
+
custom_key_mapping_str = add_indent(custom_key_mapping_str, 4)
|
|
1893
|
+
else:
|
|
1894
|
+
log.warning(
|
|
1895
|
+
f"Resource {resource_name} summaries do not have required members to create object instance. Resource may require custom key mapping for get_all().\n"
|
|
1896
|
+
f"List {summary_name} Members: {summary_members}, Object Required Members: {get_operation_required_input}"
|
|
1897
|
+
)
|
|
1898
|
+
return ""
|
|
1899
|
+
|
|
1900
|
+
resource_iterator_args_list = [
|
|
1901
|
+
"client=client",
|
|
1902
|
+
f"list_method='{operation}'",
|
|
1903
|
+
f"summaries_key='{summaries_key}'",
|
|
1904
|
+
f"summary_name='{summary_name}'",
|
|
1905
|
+
f"resource_cls={resource_name}",
|
|
1906
|
+
]
|
|
1907
|
+
|
|
1908
|
+
if custom_key_mapping_str:
|
|
1909
|
+
resource_iterator_args_list.append(f"custom_key_mapping=custom_key_mapping")
|
|
1910
|
+
|
|
1911
|
+
exclude_list = ["next_token", "max_results"]
|
|
1912
|
+
get_all_args = self._generate_method_args(operation_input_shape_name, exclude_list)
|
|
1913
|
+
|
|
1914
|
+
if not get_all_args.strip().strip(","):
|
|
1915
|
+
resource_iterator_args = ",\n".join(resource_iterator_args_list)
|
|
1916
|
+
resource_iterator_args = add_indent(resource_iterator_args, 8)
|
|
1917
|
+
|
|
1918
|
+
formatted_method = GET_ALL_METHOD_NO_ARGS_TEMPLATE.format(
|
|
1919
|
+
service_name="sagemaker",
|
|
1920
|
+
resource=resource_name,
|
|
1921
|
+
operation=operation,
|
|
1922
|
+
custom_key_mapping=custom_key_mapping_str,
|
|
1923
|
+
resource_iterator_args=resource_iterator_args,
|
|
1924
|
+
)
|
|
1925
|
+
return formatted_method
|
|
1926
|
+
|
|
1927
|
+
operation_input_args = self._generate_operation_input_args(
|
|
1928
|
+
operation_metadata, is_class_method=True, exclude_list=exclude_list
|
|
1929
|
+
)
|
|
1930
|
+
|
|
1931
|
+
resource_iterator_args_list.append("list_method_kwargs=operation_input_args")
|
|
1932
|
+
resource_iterator_args = ",\n".join(resource_iterator_args_list)
|
|
1933
|
+
resource_iterator_args = add_indent(resource_iterator_args, 8)
|
|
1934
|
+
|
|
1935
|
+
# generate docstring
|
|
1936
|
+
docstring = self._generate_docstring(
|
|
1937
|
+
title=f"Get all {resource_name} resources",
|
|
1938
|
+
operation_name=operation_name,
|
|
1939
|
+
resource_name=resource_name,
|
|
1940
|
+
operation_input_shape_name=operation_input_shape_name,
|
|
1941
|
+
include_session_region=True,
|
|
1942
|
+
include_return_resource_docstring=False,
|
|
1943
|
+
return_string=f"Returns:\n" f" Iterator for listed {resource_name} resources.\n",
|
|
1944
|
+
)
|
|
1945
|
+
|
|
1946
|
+
formatted_method = GET_ALL_METHOD_WITH_ARGS_TEMPLATE.format(
|
|
1947
|
+
docstring=docstring,
|
|
1948
|
+
service_name="sagemaker",
|
|
1949
|
+
resource=resource_name,
|
|
1950
|
+
get_all_args=get_all_args,
|
|
1951
|
+
operation_input_args=operation_input_args,
|
|
1952
|
+
custom_key_mapping=custom_key_mapping_str,
|
|
1953
|
+
resource_iterator_args=resource_iterator_args,
|
|
1954
|
+
)
|
|
1955
|
+
return formatted_method
|
|
1956
|
+
|
|
1957
|
+
def generate_config_schema(self):
|
|
1958
|
+
"""
|
|
1959
|
+
Generates the Config Schema that is used by json Schema to validate config jsons .
|
|
1960
|
+
This function creates a python file with a variable that is consumed in the scripts to further fetch configs.
|
|
1961
|
+
|
|
1962
|
+
Input for generating the Schema is the service JSON that is already loaded in the class
|
|
1963
|
+
|
|
1964
|
+
"""
|
|
1965
|
+
self.resources_extractor = ResourcesExtractor()
|
|
1966
|
+
self.resources_plan = self.resources_extractor.get_resource_plan()
|
|
1967
|
+
|
|
1968
|
+
resource_properties = {}
|
|
1969
|
+
|
|
1970
|
+
for _, row in self.resources_plan.iterrows():
|
|
1971
|
+
resource_name = row["resource_name"]
|
|
1972
|
+
# Get the operation and shape for the 'get' method
|
|
1973
|
+
if self._is_get_in_class_methods(row["class_methods"]):
|
|
1974
|
+
get_operation = self.operations["Describe" + resource_name]
|
|
1975
|
+
get_operation_shape = get_operation["output"]["shape"]
|
|
1976
|
+
|
|
1977
|
+
# Generate the class attributes based on the shape
|
|
1978
|
+
class_attributes = self.shapes_extractor.generate_shape_members(get_operation_shape)
|
|
1979
|
+
cleaned_class_attributes = self._cleanup_class_attributes_types(class_attributes)
|
|
1980
|
+
resource_name = row["resource_name"]
|
|
1981
|
+
|
|
1982
|
+
if default_attributes := self._get_dict_with_default_configurable_attributes(
|
|
1983
|
+
cleaned_class_attributes
|
|
1984
|
+
):
|
|
1985
|
+
resource_properties[resource_name] = {
|
|
1986
|
+
TYPE: OBJECT,
|
|
1987
|
+
PROPERTIES: default_attributes,
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
combined_config_schema = {
|
|
1991
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
1992
|
+
TYPE: OBJECT,
|
|
1993
|
+
PROPERTIES: {
|
|
1994
|
+
SCHEMA_VERSION: {
|
|
1995
|
+
TYPE: "string",
|
|
1996
|
+
"enum": ["1.0"],
|
|
1997
|
+
"description": "The schema version of the document.",
|
|
1998
|
+
},
|
|
1999
|
+
SAGEMAKER: {
|
|
2000
|
+
TYPE: OBJECT,
|
|
2001
|
+
PROPERTIES: {
|
|
2002
|
+
PYTHON_SDK: {
|
|
2003
|
+
TYPE: OBJECT,
|
|
2004
|
+
PROPERTIES: {
|
|
2005
|
+
RESOURCES: {
|
|
2006
|
+
TYPE: OBJECT,
|
|
2007
|
+
PROPERTIES: resource_properties,
|
|
2008
|
+
}
|
|
2009
|
+
},
|
|
2010
|
+
"required": [RESOURCES],
|
|
2011
|
+
}
|
|
2012
|
+
},
|
|
2013
|
+
"required": [PYTHON_SDK],
|
|
2014
|
+
},
|
|
2015
|
+
},
|
|
2016
|
+
"required": [SAGEMAKER],
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
output = f"{GENERATED_CLASSES_LOCATION}/{CONFIG_SCHEMA_FILE_NAME}"
|
|
2020
|
+
# Open the output file
|
|
2021
|
+
with open(output, "w") as file:
|
|
2022
|
+
# Generate and write the license to the file
|
|
2023
|
+
file.write(
|
|
2024
|
+
f"SAGEMAKER_PYTHON_SDK_CONFIG_SCHEMA = {json.dumps(combined_config_schema, indent=4)}"
|
|
2025
|
+
)
|
|
2026
|
+
|
|
2027
|
+
def _cleanup_class_attributes_types(self, class_attributes: dict) -> dict:
|
|
2028
|
+
"""
|
|
2029
|
+
Helper function that creates a direct mapping of attribute to type without default parameters assigned and without Optionals
|
|
2030
|
+
Args:
|
|
2031
|
+
class_attributes: attributes of the class in raw form
|
|
2032
|
+
|
|
2033
|
+
Returns:
|
|
2034
|
+
class attributes that have a direct mapping and can be used for processing
|
|
2035
|
+
|
|
2036
|
+
"""
|
|
2037
|
+
cleaned_class_attributes = {}
|
|
2038
|
+
for key, value in class_attributes.items():
|
|
2039
|
+
new_val = value.split("=")[0].strip()
|
|
2040
|
+
if new_val.startswith("Optional"):
|
|
2041
|
+
new_val = new_val.replace("Optional[", "")[:-1]
|
|
2042
|
+
cleaned_class_attributes[key] = new_val
|
|
2043
|
+
return cleaned_class_attributes
|
|
2044
|
+
|
|
2045
|
+
def _get_dict_with_default_configurable_attributes(self, class_attributes: dict) -> dict:
|
|
2046
|
+
"""
|
|
2047
|
+
Creates default attributes dict for a particular resource.
|
|
2048
|
+
Iterates through all class attributes and filters by attributes that have particular substrings in their name
|
|
2049
|
+
Args:
|
|
2050
|
+
class_attributes: Dict that has all the attributes of a class
|
|
2051
|
+
|
|
2052
|
+
Returns:
|
|
2053
|
+
Dict with attributes that can be configurable
|
|
2054
|
+
|
|
2055
|
+
"""
|
|
2056
|
+
PYTHON_TYPES = ["str", "datetime.datetime", "bool", "int", "float"]
|
|
2057
|
+
default_attributes = {}
|
|
2058
|
+
for key, value in class_attributes.items():
|
|
2059
|
+
if value in PYTHON_TYPES or value.startswith("List"):
|
|
2060
|
+
for config_attribute_substring in CONFIGURABLE_ATTRIBUTE_SUBSTRINGS:
|
|
2061
|
+
if config_attribute_substring in key:
|
|
2062
|
+
if value.startswith("List"):
|
|
2063
|
+
element = value.replace("List[", "")[:-1]
|
|
2064
|
+
if element in PYTHON_TYPES:
|
|
2065
|
+
default_attributes[key] = {
|
|
2066
|
+
TYPE: "array",
|
|
2067
|
+
"items": {
|
|
2068
|
+
TYPE: self._get_json_schema_type_from_python_type(element)
|
|
2069
|
+
},
|
|
2070
|
+
}
|
|
2071
|
+
else:
|
|
2072
|
+
default_attributes[key] = {
|
|
2073
|
+
TYPE: self._get_json_schema_type_from_python_type(value) or value
|
|
2074
|
+
}
|
|
2075
|
+
elif value.startswith("List") or value.startswith("Dict"):
|
|
2076
|
+
log.info("Script does not currently support list of objects as configurable")
|
|
2077
|
+
continue
|
|
2078
|
+
else:
|
|
2079
|
+
class_attributes = self.shapes_extractor.generate_shape_members(value)
|
|
2080
|
+
cleaned_class_attributes = self._cleanup_class_attributes_types(class_attributes)
|
|
2081
|
+
if nested_default_attributes := self._get_dict_with_default_configurable_attributes(
|
|
2082
|
+
cleaned_class_attributes
|
|
2083
|
+
):
|
|
2084
|
+
default_attributes[key] = nested_default_attributes
|
|
2085
|
+
|
|
2086
|
+
return default_attributes
|
|
2087
|
+
|
|
2088
|
+
def _get_json_schema_type_from_python_type(self, python_type) -> str:
|
|
2089
|
+
"""
|
|
2090
|
+
Helper for generating Schema
|
|
2091
|
+
Converts Python Types to JSON Schema compliant string
|
|
2092
|
+
Args:
|
|
2093
|
+
python_type: Type as a string
|
|
2094
|
+
|
|
2095
|
+
Returns:
|
|
2096
|
+
JSON Schema compliant type
|
|
2097
|
+
"""
|
|
2098
|
+
if python_type.startswith("List"):
|
|
2099
|
+
return "array"
|
|
2100
|
+
return PYTHON_TYPES_TO_BASIC_JSON_TYPES.get(python_type, None)
|
|
2101
|
+
|
|
2102
|
+
@staticmethod
|
|
2103
|
+
def _is_get_in_class_methods(class_methods) -> bool:
|
|
2104
|
+
"""
|
|
2105
|
+
Helper to check if class methods contain Get
|
|
2106
|
+
Args:
|
|
2107
|
+
class_methods: list of methods
|
|
2108
|
+
|
|
2109
|
+
Returns:
|
|
2110
|
+
True if 'get' in list , else False
|
|
2111
|
+
"""
|
|
2112
|
+
return "get" in class_methods
|
|
2113
|
+
|
|
2114
|
+
@staticmethod
|
|
2115
|
+
@lru_cache(maxsize=None)
|
|
2116
|
+
def _get_config_schema_for_resources():
|
|
2117
|
+
"""
|
|
2118
|
+
Fetches Schema JSON for all resources from generated file
|
|
2119
|
+
"""
|
|
2120
|
+
return SAGEMAKER_PYTHON_SDK_CONFIG_SCHEMA[PROPERTIES][SAGEMAKER][PROPERTIES][PYTHON_SDK][
|
|
2121
|
+
PROPERTIES
|
|
2122
|
+
][RESOURCES][PROPERTIES]
|