Skip to content

Commit 7970932

Browse files
committed
perf: pass template dict directly to Template, skip yaml round-trip
Template.__init__ now accepts an optional template_dict parameter. When provided, it skips both file reading and yaml_parse, avoiding the deepcopy → yaml_dump → yaml_parse round-trip that package_context and artifact_exporter were doing. For large templates with many Fn::ForEach expansions, this eliminates two O(n) serialization passes (yaml_dump + yaml_parse) per export.
1 parent 3a97220 commit 7970932

3 files changed

Lines changed: 13 additions & 7 deletions

File tree

samcli/commands/package/package_context.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,17 +193,17 @@ def _export(self, template_path, use_json):
193193

194194
uses_language_extensions = result.had_language_extensions
195195
dynamic_properties = result.dynamic_artifact_properties
196-
template_dict_for_export = copy.deepcopy(result.expanded_template)
197196

198-
# Create Template with the (possibly expanded) template
197+
# Create Template with the (possibly expanded) template dict directly,
198+
# avoiding a yaml_dump → yaml_parse round-trip.
199199
template = Template(
200200
template_path,
201201
os.getcwd(),
202202
self.uploaders,
203203
self.code_signer,
204204
normalize_template=True,
205205
normalize_parameters=True,
206-
template_str=yaml_dump(template_dict_for_export),
206+
template_dict=copy.deepcopy(result.expanded_template),
207207
parameter_values=parameter_values,
208208
)
209209
# Set template_dir since we're using template_str

samcli/lib/package/artifact_exporter.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ def do_export(self, resource_id, resource_dict, parent_dir):
223223
normalize_template=True,
224224
normalize_parameters=True,
225225
parent_stack_id=resource_id,
226-
template_str=yaml_dump(copy.deepcopy(result.expanded_template)),
226+
template_dict=copy.deepcopy(result.expanded_template),
227227
parameter_values=parameter_values,
228228
)
229229
template.template_dir = child_template_dir
@@ -351,11 +351,17 @@ def __init__(
351351
normalize_parameters: bool = False,
352352
parent_stack_id: str = "",
353353
parameter_values: Optional[Dict] = None,
354+
template_dict: Optional[Dict] = None,
354355
):
355356
"""
356357
Reads the template and makes it ready for export
357358
"""
358-
if not template_str:
359+
if template_dict is not None:
360+
# Pre-parsed dict provided — skip file reading and YAML parsing
361+
self.template_dict = template_dict
362+
elif template_str:
363+
self.template_dict = yaml_parse(template_str)
364+
else:
359365
if not (is_local_folder(parent_dir) and os.path.isabs(parent_dir)):
360366
raise ValueError("parent_dir parameter must be an absolute path to a folder {0}".format(parent_dir))
361367

@@ -367,7 +373,7 @@ def __init__(
367373

368374
self.template_dir = template_dir
369375
self.code_signer = code_signer
370-
self.template_dict = yaml_parse(template_str)
376+
self.template_dict = yaml_parse(template_str)
371377
if normalize_template:
372378
ResourceMetadataNormalizer.normalize(self.template_dict, normalize_parameters)
373379
self.resources_to_export = resources_to_export

tests/unit/lib/package/test_artifact_exporter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2424,7 +2424,7 @@ def test_export_cloudformation_stack_with_language_extensions(self, TemplateMock
24242424
# Template should have been called with the expanded template_str
24252425
TemplateMock.assert_called_once()
24262426
call_kwargs = TemplateMock.call_args
2427-
self.assertIn("template_str", call_kwargs.kwargs)
2427+
self.assertIn("template_dict", call_kwargs.kwargs)
24282428

24292429
template_instance_mock.export.assert_called_once_with()
24302430
self.s3_uploader_mock.upload.assert_called_once_with(mock.ANY, mock.ANY)

0 commit comments

Comments
 (0)