fix(etl-uvicorn): do not require a body when every input field is optional - #73
Merged
Conversation
…ional
A pydantic body parameter with no default is mandatory even when every
field inside the model is optional. `wrap_in_fastapi` chose its `/invoke`
signature on parameter *presence*, so a plugin whose parameters are all
optional got a required body that no caller has a reason to populate --
and before it grew those parameters the same plugin accepted no body at
all. Adding an optional parameter therefore looked backward-compatible
while flipping the HTTP contract to 422 for every bodyless caller.
Observed in production: the playground indexer gained
`invocation_settings`/`invocation_context` (both defaulting to None) and
every ephemeral job began failing with
[{"type":"missing","loc":["body"],"msg":"Field required","input":null}]
The indexer is the first node in the DAG and the source of all documents,
so nothing was indexed, every downstream node idled, and the job still
reported COMPLETED -- with total_docs 0 and an empty failed-files list.
An absent body now resolves each field to its own default, which is what
the signature already promised. Plugins with at least one required field
keep a mandatory body, so a downloader invoked without `file_data` still
fails validation rather than receiving None. The two model-bearing
branches share one handler so they cannot drift.
Verified against the real playground indexer: a bodyless POST returns 200
and indexes from the settings-file fallback, while a populated body still
takes precedence, so the wire-settings migration keeps working.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 4 files
Shadow auto-approve: would not auto-approve because issues were found.
Re-trigger cubic
Review found that `run_job_with_body` converted `file_data` from its dict
form unconditionally, so a plugin declaring `file_data` optional raised
AttributeError: 'NoneType' object has no attribute 'model_dump'
before `wrap_fn` could run -- surfacing as a 500 rather than the normal
response the signature promises.
This predates the optional-body change: it was already reachable on main
via `POST {}`, since an omitted field is None whether the body is absent
or merely partial. Defaulting the body widens the same hole to bodyless
requests, so fix it here rather than leaving a 500 behind the contract
this branch is establishing.
Pass None through untouched and convert only a real value. A plugin with a
required `file_data` is unaffected: validation rejects the request before
this line, so the conversion still always runs when the field is declared
mandatory.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
0 issues found across 3 files (changes from recent commits).
Shadow auto-approve: would auto-approve. Fixes a bug where plugins with all-optional parameters incorrectly required a request body, and prevents a 500 when optional file_data is absent. The change is bounded, well-tested, and clearly beneficial.
Re-trigger cubic
jordan-homan
approved these changes
Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
wrap_in_fastapichooses its/invokesignature on parameter presence:A pydantic body parameter with no default is mandatory even when every field inside the model is optional. So a plugin whose parameters are all optional gets a required body that no caller has a reason to populate — and before it grew those parameters, that same plugin accepted no body at all. Adding an optional parameter therefore looks backward-compatible while silently flipping the HTTP contract.
Fix
Default the body when the generated input model has no required fields:
request: Optional[input_schema_model] = None; an absent body resolves each field to its own default, which is exactly what the function signature already promises.file_datastill fails validation rather than receivingNone.The handler body is extracted into
run_job_with_bodyso the two model-bearing branches cannot drift.Testing
make check-version,make checkclean; 75 tests pass (5 new).{}New tests are in
test/api/test_api.py. Verified they catch the regression by reverting the fix:test_all_optional_params_accept_absent_or_empty_body[None]fails. The other four are guards against the fix over-reaching — notablytest_required_param_still_rejects_an_absent_body, since silently accepting an absentfile_datawould be worse than the bug being fixed.Also verified against the real playground indexer served through the patched generator, rather than only synthetic functions:
invocation_settings→ 200, and takes precedence over the file fallbackThat second case is the point: this restores the bodyless contract without reverting the wire-settings capability. Both planes work.
🤖 Generated with Claude Code