Parent PRD
#15
What to build
Refactor build_composite_config in pyfm/core/builder.py so all three container types (SIMPLE, LIST, DICT) follow a uniform _preprocessor slicing pattern, clean up dead code, update the default preprocessor, and add docstrings.
Specific changes:
Builder (pyfm/core/builder.py):
- DICT branch: Add
_preprocessor routing — each child receives processed_params | {"_preprocessor": _preprocessor[subconfig_label][key]} for its per-key slice
- LIST branch: Remove
processed_params[subconfig_label] merging. Iteration structure comes entirely from _preprocessor[subconfig_label] (a list). Remove implicit single-item fallback and dict-to-list coercion (user story 9)
- Remove
removesuffix("_config") dead code in LIST branch
- Add docstrings to
build_composite_config and inner builder functions describing the _preprocessor contract per container type (user story 7)
Default preprocessor (pyfm/tasks/register.py):
- Change
params.get("_preprocessor", {}) → params.pop("_preprocessor", {}) so preprocessor data is consumed (user story 5)
Tests (test/core/test_builder.py):
- Add a DICT container test fixture (currently missing)
- DICT: builder iterates
_preprocessor[field_name] as a dict, builds one child per key
- DICT: empty dict produces empty child dict
- DICT: each child receives its own per-key preprocessor slice
- LIST: empty list produces empty child list
- LIST: verify no implicit coercion (list input required)
- Verify
removesuffix removal doesn't break anything
Acceptance criteria
Blocked by
None - can start immediately
User stories addressed
- User story 1 (clear preprocessor contract)
- User story 2 (uniform slicing pattern)
- User story 3 (single source of truth from
_preprocessor)
- User story 4 (key by exact field name, no
removesuffix)
- User story 5 (default preprocessor
pop)
- User story 7 (docstrings)
- User story 9 (no implicit LIST coercion)
- User story 10 (DICT per-key routing)
Parent PRD
#15
What to build
Refactor
build_composite_configinpyfm/core/builder.pyso all three container types (SIMPLE, LIST, DICT) follow a uniform_preprocessorslicing pattern, clean up dead code, update the default preprocessor, and add docstrings.Specific changes:
Builder (
pyfm/core/builder.py):_preprocessorrouting — each child receivesprocessed_params | {"_preprocessor": _preprocessor[subconfig_label][key]}for its per-key sliceprocessed_params[subconfig_label]merging. Iteration structure comes entirely from_preprocessor[subconfig_label](a list). Remove implicit single-item fallback and dict-to-list coercion (user story 9)removesuffix("_config")dead code in LIST branchbuild_composite_configand inner builder functions describing the_preprocessorcontract per container type (user story 7)Default preprocessor (
pyfm/tasks/register.py):params.get("_preprocessor", {})→params.pop("_preprocessor", {})so preprocessor data is consumed (user story 5)Tests (
test/core/test_builder.py):_preprocessor[field_name]as a dict, builds one child per keyremovesuffixremoval doesn't break anythingAcceptance criteria
_preprocessor[subconfig_label]uniformlyremovesuffix("_config")removed from builderprocessed_params[subconfig_label]or coerces non-list input_preprocessorslices to childrenpopinstead ofget_preprocessorcontractBlocked by
None - can start immediately
User stories addressed
_preprocessor)removesuffix)pop)