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.

Copy link
Copy Markdown
Contributor

@reedham-aws reedham-aws May 15, 2026

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