Skip to content

Fix multiple file upload generation#553

Merged
koxudaxi merged 1 commit intomainfrom
fix-multiple-file-upload
Apr 28, 2026
Merged

Fix multiple file upload generation#553
koxudaxi merged 1 commit intomainfrom
fix-multiple-file-upload

Conversation

@koxudaxi
Copy link
Copy Markdown
Owner

@koxudaxi koxudaxi commented Apr 27, 2026

Summary by CodeRabbit

  • New Features

    • Added support for uploading multiple files in a single multipart/form-data request and a new multipart upload endpoint.
  • Improvements

    • Smarter detection of multipart parameter names and correct selection of single vs. list upload types.
    • Parser now skips irrelevant parent request-body fields for pure multipart/form-data inputs.
  • Chores

    • Removed unused type imports and streamlined import generation.
  • Tests

    • Added unit tests for upload-type detection and naming.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Request-body parsing now treats multipart/form-data specially: when the request body’s content types are exactly multipart/form-data, parent field extraction is skipped and {} is returned. Referenced schemas are instantiated using JsonSchemaObject.model_validate(...) (replacing parse_obj(...)), and helpers derive file parameter names and choose UploadFile vs List[UploadFile] for array-of-binary shapes. Import collection now scans parsed operations for identifier-like tokens to prune unused imports.

Changes

Cohort / File(s) Summary
Core Parser Logic
fastapi_code_generator/parser.py
Skip parent request-body field parsing when content is exactly multipart/form-data; replace parse_obj(...) with JsonSchemaObject.model_validate(...) for referenced schemas; derive file parameter names from schema properties and select UploadFile vs List[UploadFile] (add typing.List import when needed).
Import Visitor
fastapi_code_generator/visitors/imports.py
Add IDENTIFIER_PATTERN and _collect_used_names(parser) to gather identifier-like tokens from operations (arguments, return_type, responses, callbacks) and call imports.remove_unused(...) with the collected names to remove unused imports.
Generated/Expected Outputs
tests/data/expected/openapi/default_template/upload/main.py, tests/data/expected/openapi/coverage/callbacks/main.py, tests/data/expected/openapi/coverage/callbacks_with_operation_id/main.py
Update typing imports to include List and remove unused Union; add handler upload_pet_images_with_form_data(id: str, images: List[UploadFile] = ...) -> Optional[Error] for multipart-array upload case.
OpenAPI Test Fixture
tests/data/openapi/default_template/upload.yaml
Add POST /pets/{id}/images/form-data (operationId: uploadPetImagesWithFormData) with multipart/form-data requestBody where images is an array of binary strings; include id path parameter and responses (201 and default error).
Tests
tests/test_parser.py
Add unit tests for _get_upload_file_type resolving $ref (expecting parameter name and List[UploadFile]), _is_upload_file_array behavior with non-schema input, and _get_upload_file_name fallback behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nibble schemas, seek the file's true name,
If binaries come in arrays, I call List by name,
I skip the parent when form-data's alone,
Tidy imports I prune, leaving less to comb,
A little rabbit hop — the generator's fame.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix multiple file upload generation' directly and clearly describes the main change: enabling proper handling of multiple file uploads in the code generator.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-multiple-file-upload

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 27, 2026

Merging this PR will improve performance by 16.31%

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark

Performance Changes

Benchmark BASE HEAD Efficiency
test_generate_default_template_benchmark 53.1 ms 45.7 ms +16.31%

Comparing fix-multiple-file-upload (a00be0e) with main (88b72cd)

Open in CodSpeed

Comment thread tests/data/expected/openapi/default_template/upload/main.py Fixed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 27, 2026

📚 Docs Preview: https://pr-553.fastapi-code-generator.pages.dev

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@fastapi_code_generator/parser.py`:
- Around line 535-559: The helpers _get_upload_file_type_hint,
_has_upload_file_array and _is_upload_file_schema currently hardcode the form
parameter name to "file" and mix Pydantic v1/v2 APIs; update
_get_upload_file_type_hint to derive the parameter name from the schema when the
schema is an object with properties (e.g., use the property key such as "images"
when schema.properties exists, falling back to "file" only if no property name
is available), update any code that converts ReferenceObject to JsonSchemaObject
to consistently use JsonSchemaObject.model_validate(...) (replace parse_obj
usages to align with model_validate), and ensure _has_upload_file_array inspects
property keys so the generator uses the actual property name when producing
List[UploadFile] vs UploadFile; reference functions: _get_upload_file_type_hint,
_has_upload_file_array, _is_upload_file_schema, and the place where
ReferenceObject is converted to JsonSchemaObject.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67375519-9a2b-483a-ad41-444ea7970c7b

📥 Commits

Reviewing files that changed from the base of the PR and between 67bb774 and c15f7ed.

📒 Files selected for processing (3)
  • fastapi_code_generator/parser.py
  • tests/data/expected/openapi/default_template/upload/main.py
  • tests/data/openapi/default_template/upload.yaml

Comment thread fastapi_code_generator/parser.py Outdated
@koxudaxi koxudaxi force-pushed the fix-multiple-file-upload branch 3 times, most recently from 2da9d10 to d7053b6 Compare April 27, 2026 08:37
@koxudaxi koxudaxi marked this pull request as ready for review April 27, 2026 08:38
@koxudaxi koxudaxi force-pushed the fix-multiple-file-upload branch 4 times, most recently from 2044806 to 96662fd Compare April 27, 2026 08:50
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@fastapi_code_generator/parser.py`:
- Around line 577-584: _update _get_upload_file_name currently only returns a
property when _is_upload_file_array(property_schema) is true, which skips single
binary properties; modify _get_upload_file_name to also detect plain binary
properties (e.g., property_schema.type == "string" and property_schema.format ==
"binary" or via a small helper _is_binary_property) and return
stringcase.snakecase(self.model_resolver.get_valid_field_name(property_name))
for those as well; also update _get_upload_file_type so when the selected
property_schema represents a single binary (not an array) it yields UploadFile
(matching the array case) and ensure you pick a consistent strategy for multiple
file properties (e.g., return the first matching file property but avoid
silently dropping others by documenting or adding a separate handling path).
- Around line 497-501: The current check only skips parsing when the entire
content set equals {'multipart/form-data'}, so mixed content types still allow
generation of multipart fields; update the parse_request_body handling in
parser.parse_request_body so that if 'multipart/form-data' is present you strip
it out before delegating: if removing 'multipart/form-data' leaves no media
types return {} (same as pure-multipart case), otherwise call
super().parse_request_body with a temporary request_body that has
request_body.content filtered to exclude 'multipart/form-data'. Ensure you
reference the existing request_body_fields assignment and use
request_body.content and parse_request_body when implementing the change.

In `@fastapi_code_generator/visitors/imports.py`:
- Around line 25-38: The code calls a non-existent Imports.remove_unused which
will raise AttributeError; instead compute the used identifier set via
_collect_used_names (using IDENTIFIER_PATTERN against operation.arguments,
return_type, response, additional_responses, callbacks) and then prune the
Imports instance by iterating its tracked imports and calling Imports.remove for
any import name not in that used set (or use Imports.extract_future separately
for from __future__ entries); update the call site to use this explicit removal
loop on the Imports object rather than remove_unused.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 328c7d6e-7162-4166-8659-4f374127bf27

📥 Commits

Reviewing files that changed from the base of the PR and between 7b277df and 2044806.

📒 Files selected for processing (7)
  • fastapi_code_generator/parser.py
  • fastapi_code_generator/visitors/imports.py
  • tests/data/expected/openapi/coverage/callbacks/main.py
  • tests/data/expected/openapi/coverage/callbacks_with_operation_id/main.py
  • tests/data/expected/openapi/default_template/upload/main.py
  • tests/data/openapi/default_template/upload.yaml
  • tests/test_parser.py
✅ Files skipped from review due to trivial changes (2)
  • tests/data/expected/openapi/coverage/callbacks/main.py
  • tests/data/expected/openapi/coverage/callbacks_with_operation_id/main.py

Comment thread fastapi_code_generator/parser.py Outdated
Comment thread fastapi_code_generator/parser.py
Comment thread fastapi_code_generator/visitors/imports.py
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (88b72cd) to head (a00be0e).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #553   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           16        17    +1     
  Lines         1117      1199   +82     
  Branches       111       126   +15     
=========================================
+ Hits          1117      1199   +82     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/test_parser.py (2)

42-47: Consider broadening the non-schema rejection coverage.

The test only exercises the boolean True. Since _is_upload_file_array is supposed to return False for any non-JsonSchemaObject input, parametrizing across a representative set (e.g., None, "str", 123, {}, []) would more robustly guard the contract.

♻️ Optional parametrization
-def test_is_upload_file_array_rejects_non_schema() -> None:
-    parser = OpenAPIParser(
-        "openapi: 3.0.0\ninfo: {title: Test, version: '1.0'}\npaths: {}\n"
-    )
-
-    assert parser._is_upload_file_array(True) is False
+@pytest.mark.parametrize("value", [True, None, "string", 123, {}, []])
+def test_is_upload_file_array_rejects_non_schema(value) -> None:
+    parser = OpenAPIParser(
+        "openapi: 3.0.0\ninfo: {title: Test, version: '1.0'}\npaths: {}\n"
+    )
+
+    assert parser._is_upload_file_array(value) is False

(Requires import pytest.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_parser.py` around lines 42 - 47, Update the
test_is_upload_file_array_rejects_non_schema to cover a broader set of
non-JsonSchemaObject inputs by parametrizing it (e.g., values None, "str", 123,
{}, []); call parser._is_upload_file_array with each value and assert it returns
False. Use pytest.mark.parametrize to iterate the inputs so
OpenAPIParser._is_upload_file_array is validated against multiple non-schema
types rather than only True.

9-39: LGTM on the reference-resolution test.

The test cleanly exercises the new multipart/form-data path: a ReferenceObject is resolved through get_ref_model, the array-of-binary property is detected, and both the derived field name ("images") and List[UploadFile] type hint are asserted. Good coverage of the regression that motivated the PR.

One small optional addition: you could also assert that an Import(from_='typing', import_='List') was appended to parser.imports_for_fastapi, since that side effect is part of _get_upload_file_type's contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_parser.py` around lines 9 - 39, Add an assertion to the
test_get_upload_file_type_resolves_reference test to verify the side-effect that
_get_upload_file_type appends the typing import; after calling
parser._get_upload_file_type (and before final asserts), check that
parser.imports_for_fastapi contains an Import object for List (e.g.,
Import(from_='typing', import_='List')) so the test validates both the returned
type_hint and the required import being recorded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/test_parser.py`:
- Around line 42-47: Update the test_is_upload_file_array_rejects_non_schema to
cover a broader set of non-JsonSchemaObject inputs by parametrizing it (e.g.,
values None, "str", 123, {}, []); call parser._is_upload_file_array with each
value and assert it returns False. Use pytest.mark.parametrize to iterate the
inputs so OpenAPIParser._is_upload_file_array is validated against multiple
non-schema types rather than only True.
- Around line 9-39: Add an assertion to the
test_get_upload_file_type_resolves_reference test to verify the side-effect that
_get_upload_file_type appends the typing import; after calling
parser._get_upload_file_type (and before final asserts), check that
parser.imports_for_fastapi contains an Import object for List (e.g.,
Import(from_='typing', import_='List')) so the test validates both the returned
type_hint and the required import being recorded.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b3bc065f-fef2-415b-9e6f-aa37135f0029

📥 Commits

Reviewing files that changed from the base of the PR and between 2044806 and 96662fd.

📒 Files selected for processing (7)
  • fastapi_code_generator/parser.py
  • fastapi_code_generator/visitors/imports.py
  • tests/data/expected/openapi/coverage/callbacks/main.py
  • tests/data/expected/openapi/coverage/callbacks_with_operation_id/main.py
  • tests/data/expected/openapi/default_template/upload/main.py
  • tests/data/openapi/default_template/upload.yaml
  • tests/test_parser.py
✅ Files skipped from review due to trivial changes (2)
  • tests/data/expected/openapi/coverage/callbacks/main.py
  • tests/data/expected/openapi/coverage/callbacks_with_operation_id/main.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • fastapi_code_generator/visitors/imports.py
  • fastapi_code_generator/parser.py

@koxudaxi koxudaxi force-pushed the fix-multiple-file-upload branch 2 times, most recently from c5c914d to 09d0eb7 Compare April 27, 2026 09:09
@koxudaxi koxudaxi force-pushed the fix-multiple-file-upload branch from 09d0eb7 to a00be0e Compare April 27, 2026 09:12
@koxudaxi koxudaxi merged commit 327e52e into main Apr 28, 2026
44 checks passed
@koxudaxi koxudaxi deleted the fix-multiple-file-upload branch April 28, 2026 02:40
@github-actions github-actions Bot added the breaking-change-analyzed PR has been checked for release draft updates label Apr 28, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Breaking Change Analysis

Result: No breaking changes detected

Reasoning: The breaking-change label is absent, so this PR is treated as a non-breaking release update.


This analysis was performed by repository automation using PR labels and the ## Breaking Changes section.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change-analyzed PR has been checked for release draft updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants