Skip to content

refactor(cfn-lang-ext): consolidate copy helpers and relocate prop-name helpers#9017

Open
bnusunny wants to merge 1 commit into
developfrom
fix/9005-followup-consolidate-copy-helpers
Open

refactor(cfn-lang-ext): consolidate copy helpers and relocate prop-name helpers#9017
bnusunny wants to merge 1 commit into
developfrom
fix/9005-followup-consolidate-copy-helpers

Conversation

@bnusunny

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #9009 (issue #9005). Closes a structural divergence the bot reviews on that PR repeatedly caught: two near-duplicate copy helpers — `_copy_artifact_uris_for_type` in `samcli/lib/package/language_extensions_packaging.py` and `_copy_artifact_paths` in `samcli/commands/build/build_context.py` — that differed only in their optional `dynamic_prop_keys` skip-set. The first round of bot review on #9009 caught the exact bug class this duplication enables: a flat-`.get()` slipping into one helper while the other had been jmespath-aware.

Changes

  • New module `samcli/lib/cfn_language_extensions/property_paths.py`. Promotes the four prop-name helpers (previously underscore-private in `language_extensions_packaging.py` but imported across packages by `build_context.py` and `sam_integration.py`) into a properly-named, public module with no leading underscores: `get_prop_value`, `set_prop_value`, `leaf_prop_name`, `resolve_property_paths`. Adds a fifth helper, `copy_artifact_properties`, that consolidates the two duplicated copy implementations into one.
  • New test file `tests/unit/lib/cfn_language_extensions/test_property_paths.py`. Includes a contract test that asserts every entry in `PACKAGEABLE_RESOURCE_ARTIFACT_PROPERTIES` round-trips through `set_prop_value` + `get_prop_value` and that every leaf is alphanumeric (a precondition for use in CloudFormation Mapping names and the third arg of `Fn::FindInMap`). Future additions to the canonical packageable-resource list are now caught at the contract layer rather than at user-template execution time.
  • Delete `_copy_artifact_uris_for_type` from `samcli/lib/package/language_extensions_packaging.py`. Both in-file callers (`_copy_artifact_uris` and `_update_foreach_with_s3_uris`) now call `copy_artifact_properties` directly.
  • Delete `BuildContext._copy_artifact_paths` from `samcli/commands/build/build_context.py`. The single call site now calls `copy_artifact_properties` directly.
  • Update inline imports in `build_context.py` and `sam_integration.py` to point at the new `property_paths` module (six inline-import blocks in total).
  • Rename `test_copy_artifact_uris_for_type_` → `test_copy_artifact_properties_` and update function-import sites across three test files.
  • Delete duplicate `get_prop_value` / `set_prop_value` tests from `test_package_context.py`; the helper-level tests now live in the new `test_property_paths.py` file.

What's NOT in this PR

  • No behavior change. The consolidated `copy_artifact_properties` has the same signature and behavior as the union of the two helpers it replaces (kwargs-only `foreach_key` / `dynamic_prop_keys` for the language-extensions merge path; both omitted for the simpler build-time call site).
  • No changes to the Mapping-emission pipelines. The build-side recursive walker (`_update_foreach_artifact_paths`) and the package-side detect-then-emit pipeline (`detect_dynamic_artifact_properties` → `_generate_artifact_mappings` → `_replace_dynamic_artifact_with_findmap`) are unchanged. Their consolidation is tracked as a separate piece of follow-up work.

Test plan

  • Baseline (clean develop): `9191 passed, 25 skipped, 28 subtests passed`
  • After this PR: `9203 passed, 25 skipped, 56 subtests passed` — net +12 tests, +28 subtests, 0 regressions
  • `ruff check samcli schema` clean
  • `mypy --no-incremental setup.py samcli tests schema` clean
  • `black --check` clean on all touched files
  • End-to-end Sam deploy fails on missing /tmp folder files #9005 repro: `samdev package --resolve-s3` against `language-extensions/tc-026-nested-application` (parent uses `AWS::LanguageExtensions` + nested `AWS::Serverless::Application` with local `Location: ./PublisherApi/template.yaml`). Output template still rewrites `Properties.Location` to an `https://s3.us-west-2.amazonaws.com/.../...template\` URL; the uploaded nested template still has its `CodeUri` rewritten to an `s3://...` URI. Behavior unchanged.

Why now

PR #9009 went through three rounds of bot review, each catching a real silent-corruption bug:

  1. flat-`.get()` consumer in `build_context` (the duplication this PR closes)
  2. missing `SAM*` Mapping prefixes in `is_sam_generated_mapping` after the canonical dict gained new entries
  3. collision-keying bug in `_compute_mapping_name` after the leaf-vs-dotted introduction

Bugs 2 and 3 were structural: two separate hand-maintained lists that needed to track the canonical packageable-property registry. The fixes for those — deriving the prefix list from the canonical dict, keying collision detection on the leaf — landed in #9009 itself. Bug 1's fix (route `_copy_artifact_paths` through the new jmespath helpers) also landed in #9009, but the duplication between the two copy helpers remained, leaving the failure mode latent. This PR closes that.

…me helpers

PR #9009 (issue #9005) left two near-duplicate copy helpers in the language-
extensions code: _copy_artifact_uris_for_type in samcli/lib/package/
language_extensions_packaging.py and _copy_artifact_paths in samcli/commands/
build/build_context.py. They were 90% identical and differed only in the
optional dynamic_prop_keys skip-set. Bot review #1 on that PR caught the
exact bug class this duplication enables — a flat-key .get() slipping into
one helper while the other had been jmespath-aware.

Promote the four prop-name helpers (_get_prop_value, _set_prop_value,
_leaf_prop_name, _resolve_property_paths) — until now underscore-private
helpers in language_extensions_packaging.py imported across packages by
samcli/commands/build and samcli/lib/cfn_language_extensions/sam_integration —
into a properly-named module samcli/lib/cfn_language_extensions/property_paths
with no leading underscore. Add a fifth helper, copy_artifact_properties,
that consolidates the two duplicated copy implementations into one.

Add a contract test that asserts every entry in PACKAGEABLE_RESOURCE_
ARTIFACT_PROPERTIES round-trips through set_prop_value / get_prop_value and
that every leaf is alphanumeric (a precondition for use in CloudFormation
Mapping names and Fn::FindInMap third-arg keys). Future additions to the
canonical packageable-resource list are now caught at the contract layer
rather than at user-template execution time.

Mechanical changes:
- new samcli/lib/cfn_language_extensions/property_paths.py
- new tests/unit/lib/cfn_language_extensions/test_property_paths.py
- delete _copy_artifact_uris_for_type from
  samcli/lib/package/language_extensions_packaging.py; collapse its two
  in-file callers (_copy_artifact_uris and _update_foreach_with_s3_uris)
  to call copy_artifact_properties directly
- delete BuildContext._copy_artifact_paths from samcli/commands/build/
  build_context.py; inline the single call site
- update inline imports in build_context.py and sam_integration.py to
  point at the new module
- rename test_copy_artifact_uris_for_type_* to test_copy_artifact_properties_*
  in tests/unit/commands/package/test_package_context.py and update
  function-import sites in three test files
- delete duplicate get_prop_value / set_prop_value tests from
  test_package_context.py (now in test_property_paths.py)

Verification:
- baseline 9191 passed, 25 skipped → after 9203 passed, 25 skipped (+12)
- ruff check, mypy, black --check all clean
- end-to-end #9005 repro (sam package against
  language-extensions/tc-026-nested-application) still rewrites
  Properties.Location to an https://s3... URL
# Regular resource - copy updated paths from modified template
modified_resource = modified_resources.get(resource_key, {})
self._copy_artifact_paths(resource_value, modified_resource)
from samcli.lib.cfn_language_extensions.property_paths import copy_artifact_properties

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should import everything at the top level.

@reedham-aws reedham-aws May 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that's not the pattern that is already being used here, though. We can just keep it like this then, although I think it should be changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/build sam build command pr/internal

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants