Commit 6a88783
fix(etl-uvicorn): do not require a body when every input field is optional (#73)
## Problem
`wrap_in_fastapi` chooses its `/invoke` signature on parameter
**presence**:
```python
if input_schema_model.model_fields:
async def run_job(request: input_schema_model) # no default => body REQUIRED
else:
async def run_job() # no body accepted
```
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:
```python
body_is_optional = input_schema_model.model_fields and not any(
field.is_required() for field in input_schema_model.model_fields.values()
)
```
- **all fields optional** → `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.
- **any field required** → unchanged, body mandatory. A downloader
invoked without `file_data` still fails validation rather than receiving
`None`.
- **no parameters** → unchanged, no body accepted.
The handler body is extracted into `run_job_with_body` so the two
model-bearing branches cannot drift.
## Testing
`make check-version`, `make check` clean; **75 tests pass** (5 new).
| plugin shape | bodyless | `{}` | populated |
|---|---|---|---|
| no params | 200 | 200 | — |
| all params optional | **200** (was 422) | 200 | 200 |
| any param required | 422 | 422 | 200 |
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 — notably
`test_required_param_still_rejects_an_absent_body`, since silently
accepting an absent `file_data` would be worse than the bug being fixed.
Also verified against the **real** playground indexer served through the
patched generator, rather than only synthetic functions:
- bodyless POST → 200, indexes from the settings-file fallback
- populated wire `invocation_settings` → 200, and **takes precedence**
over the file fallback
That second case is the point: this restores the bodyless contract
without reverting the wire-settings capability. Both planes work.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/Unstructured-IO/unstructured-platform-plugins/pull/73?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent 4aea423 commit 6a88783
4 files changed
Lines changed: 167 additions & 14 deletions
File tree
- test/api
- unstructured_platform_plugins
- etl_uvicorn
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
1 | 17 | | |
2 | 18 | | |
3 | 19 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
470 | 470 | | |
471 | 471 | | |
472 | 472 | | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
Lines changed: 39 additions & 13 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
254 | 254 | | |
255 | 255 | | |
256 | 256 | | |
257 | | - | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
258 | 295 | | |
259 | 296 | | |
260 | 297 | | |
261 | | - | |
262 | | - | |
263 | | - | |
264 | | - | |
265 | | - | |
266 | | - | |
267 | | - | |
268 | | - | |
269 | | - | |
270 | | - | |
271 | | - | |
272 | | - | |
| 298 | + | |
273 | 299 | | |
274 | 300 | | |
275 | 301 | | |
| |||
0 commit comments