fix(adapter): isolate missing-body error locations between requests
Maintainers usually reply within 1 day
A pull request for this has already been merged.
- #557 by @yeongseon — merged
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 75/100
Research direction
Read src/azure_functions_validation/adapter.py, especially _missing_body_validation_error() and the shared missing-body template around the referenced lines. First run the reproduction in the issue and find existing isolation tests for missing-body errors; add coverage for nested loc identity, formatter mutation, empty and whitespace-only bodies, and repeated or concurrent calls. Done means every invocation has independent locations while preserving the import-time model construction optimization and stable 422 details.
Written by the indexing model from the issue text.
Description
Context
Missing-body exceptions share the cached error's mutable loc list. A custom error formatter that edits its exception details changes later responses from other handlers, even when those handlers use separate default adapter instances. Priority is medium: this is persistent cross-request error-state corruption with a small fix, triggered by an otherwise ordinary custom formatting operation.
Reviewed commit: b38d702adb1039ed1eda2ef3799d873af2506f41 (azure-functions-validation 0.16.0). Locally reproduced on Python 3.12.14, Pydantic 2.14.1, and azure-functions 1.25.0, with the reviewed checkout's src on PYTHONPATH.
Run this complete snippet with those dependencies and the reviewed package installed:
import json
import azure.functions as func
from pydantic import BaseModel
from azure_functions_validation import validate_http
class Body(BaseModel):
value: int
def prefix_locations(exc, status):
for detail in exc.errors:
detail["loc"].insert(0, "request")
return {"detail": exc.errors}
@validate_http(body=Body, error_formatter=prefix_locations)
def custom_handler(req: func.HttpRequest, body: Body):
return body.model_dump()
@validate_http(body=Body)
def default_handler(req: func.HttpRequest, body: Body):
return body.model_dump()
req = func.HttpRequest(
method="POST", url="http://localhost/api/probe", headers={}, params={}, body=b"",
)
for handler in (default_handler, custom_handler, default_handler, custom_handler, default_handler):
response = handler(req)
details = json.loads(response.get_body())["detail"]
print(handler.__name__, response.status_code, details[0]["loc"])
Observed: locations are ['body'], then ['request', 'body'] for both custom and default handlers, then ['request', 'request', 'body'] for both. A direct adapter probe also confirms that successive exceptions' errors[0]['loc'] values are the exact same object.
Expected: each invocation starts with its own ['body'] location. The custom handler can format its response as ['request', 'body'], while every default-handler response remains ['body']. No mutation should accumulate across requests or handler instances.
Root cause / fix direction: _missing_body_validation_error() creates a fresh exception, list, and dictionaries using [dict(entry) for entry in detail], but this is a shallow copy: every dictionary still references the module-level cached loc list. Copy nested mutable error fields, or cache an immutable template and materialize a fresh normalized error structure for each call. Preserve the import-time model/error construction optimization from #255.
Acceptance Checklist
- Consecutive missing-body errors have distinct nested
loclist identities. - Editing a custom formatter's error location cannot alter later default responses or another adapter instance.
- Both empty bytes and whitespace-only bodies retain stable source-prefixed 422 details.
- Repeated and concurrent invocations cannot accumulate location prefixes.
- The missing-body model and template remain initialized once, without per-request model-class construction.
Out of scope
- Making arbitrary caller-owned custom adapter state thread-safe.
- Changing the missing-body status code or public error envelope.
- Broad changes to error aggregation semantics.
References
- Fresh-state promise and shallow copy.
- Shared missing-body template.
- Closed #255: cached missing-body error optimization.
- Duplicate check: no open issues at review time; missing-body and error-state mutation searches found no equivalent report. Existing isolation coverage checks outer error-list mutation, not nested
locmutation.
- Dominant language
- Python
- Stars
- 13
- Forks
- 4
- Avg merge
- 14h 46m
- Merged PRs (30d)
- 89
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
enhancement good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
elviskahoro/sdk-python-attio#144 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
RenyiELBO raises TypeError when model has no sample sitesPossibly taken @alibb007 claimed this today. Open
Difficulty 1/5 Under an hour Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
[Bug]: Four evaluation scripts unpack init_memmachine_params's five return values into three namesOpen
Difficulty 1/5 Under an hour Newbie friendliness 88/100
MemMachine/MemMachine#1911 ·
Maintainers usually reply within 3 days