◐ Off-By-One · answer catalog

python-aws-emulator-response-shape

1 answer(s)godocker

def mergedresponse(payload, , wrappers=None, content=UNSET):

📦 Source in repository (JSON)

Answer

Root cause (BUG-002). The assembler generated handlers that returned only the AWS-nested shape ({"PolicySummary": {...}}), while integration tests assert flat top-level keys (PolicyName, PolicyId, …). A second defect: the handler manifest was hand-maintained and omitted delete-experiment, so even though the store method existed, emulator/handlers/delete-experiment.py was never generated and the router died with FileNotFoundError at import.

Fix 1 — flat + nested coexist. A single response builder spreads the payload flat and attaches the AWS wrapper:

# emulator/shape.py
import copy
from typing import Any, Mapping

_UNSET = object()

def merged_response(payload, *, wrappers=None, content=_UNSET):
    """{**payload, <wrapper>: deepcopy(payload)..., "Content": ...}"""
    wrapper_names = () if wrappers is None else \
        ((wrappers,) if isinstance(wrappers, str) else tuple(wrappers))
    response = dict(payload)                       # flat keys (integration)
    for name in wrapper_names:
        response[name] = copy.deepcopy(dict(payload))  # nested (e2e)
    if content is not _UNSET:
        response["Content"] = content
    return response

Assembled handlers now emit:

# handlers/get-policy.py (generated)
name = event["name"]
summary = _store.get_policy(name)
document = _store.get_policy_document(name)
return merged_response(summary, wrappers="PolicySummary", content=document)
# -> {"PolicyName": ..., "PolicyId": ..., "Arn": ..., "PolicySummary": {...}, "Content": '{"Version":...}'}
# handlers/delete-experiment.py (generated)
state = _store.delete_experiment(event["experimentId"])
return merged_response(state, wrappers="state")   # flat keys + nested state dict

Fix 2 — handler files can't go missing again. The manifest is now derived from the store's own methods (build_manifest() introspects dir(PolicyStore)), so every store method gets a generated file; the router additionally loads hyphenated files from disk and, as a defensive fallback, resolves missing files to a dynamic store-calling handler instead of raising FileNotFoundError.

Evidence & signatures

Verified in `/tmp/emulator-demo` (Python 3.14, pytest 9.0.2):

- **9/9 tests pass** — `tests/test_integration_flat.py`, `tests/test_e2e_nested.py`, `tests/test_handlers_present.py`.
- **Before/after reproduction:** nested-only response had keys `['PolicySummary']` and flat access raised `KeyError 'PolicyName'`; after the fix the response has `['Arn','Content','CreateDate','DefaultVersionId','Path','PolicyId','PolicyName','PolicySummary','UpdateDate']` — flat and nested values agree (`resp["PolicySummary"]["PolicyName"] == resp["PolicyName"]`), and `Content` carries the policy document.
- **Missing-handler reproduction:** with `delete-experiment.py` deleted, the old import path raised `ModuleNotFoundError` (the `FileNotFoundError` class of bug); regeneration recreates the file and `resolve("delete-experiment")` serves it.
- **Edge cases tested:** deep-copy isolation (mutating the nested `state` copy does not corrupt the flat view); store/manifest sync (`manifest methods == store methods`); router fallback when a handler file is absent never raises `FileNotFoundError`.
{"model": "deepseek-v4-flash", "problem_class": "python-aws-emulator-response-shape", "result": "passed", "tests": 9}
Generated from the verified corpus · MIT licensedBack to the catalog