Skip to content

Fix 201 response generation - #610

Merged
koxudaxi merged 1 commit into
mainfrom
fix-201-response-generation
Jun 26, 2026
Merged

Fix 201 response generation#610
koxudaxi merged 1 commit into
mainfrom
fix-201-response-generation

Conversation

@koxudaxi

@koxudaxi koxudaxi commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Fixes: #607

Summary by CodeRabbit

  • Bug Fixes
    • Improved success-response handling to select the most appropriate HTTP status and align the generated response type and operation metadata accordingly.
    • Generated endpoints now more consistently declare explicit success status_code values (including 201 and 202) and match handler return annotations to the declared response models, especially for non-200 scenarios.
  • Tests
    • Updated OpenAPI fixtures and generated-route expectations to reflect the revised status-code selection behavior and corresponding typing/metadata changes.

@github-actions

Copy link
Copy Markdown
Contributor

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

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@koxudaxi, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 36 minutes and 16 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c55021c1-a8b6-41e7-a9f1-99354f7be7d7

📥 Commits

Reviewing files that changed from the base of the PR and between 3ea19b7 and 3a218b5.

📒 Files selected for processing (20)
  • .github/workflows/test.yml
  • fastapi_code_generator/parser.py
  • tests/data/expected/openapi/coverage/callbacks/main.py
  • tests/data/expected/openapi/coverage/callbacks_with_operation_id/main.py
  • tests/data/expected/openapi/coverage/model_options/main.py
  • tests/data/expected/openapi/coverage/non_200_responses/main.py
  • tests/data/expected/openapi/coverage/non_200_status_code/main.py
  • tests/data/expected/openapi/default_template/body_and_parameters/main.py
  • tests/data/expected/openapi/default_template/duplicate_request_param/main.py
  • tests/data/expected/openapi/default_template/same_response_model_for_different_status_codes/main.py
  • tests/data/expected/openapi/default_template/simple/main.py
  • tests/data/expected/openapi/default_template/upload/main.py
  • tests/data/expected/openapi/disable_timestamp/simple/main.py
  • tests/data/expected/openapi/modify_specific_routers/expected/using_routers_example/routers/fat_cats.py
  • tests/data/expected/openapi/modify_specific_routers/expected/using_routers_example/routers/wild_boars.py
  • tests/data/expected/openapi/remote_ref/body_and_parameters/main.py
  • tests/data/expected/openapi/using_routers/using_routers_example/routers/fat_cats.py
  • tests/data/expected/openapi/using_routers/using_routers_example/routers/slim_dogs.py
  • tests/data/expected/openapi/using_routers/using_routers_example/routers/wild_boars.py
  • tests/data/openapi/coverage/non_200_status_code.yaml

Walkthrough

The parser now picks a primary successful response from 2xx status codes, sets the route status and response model from that choice, and updates generated fixtures to emit explicit 201 and 202 status codes with matching return annotations.

Changes

OpenAPI response selection

Layer / File(s) Summary
Success status helpers
fastapi_code_generator/parser.py
Helpers define response mapping shapes, collect 2xx status codes, normalize status lookups, and resolve the primary response data type.
Response assembly
fastapi_code_generator/parser.py
parse_responses now sets the primary response, route status code, and additional responses from parsed data types.
Generated OpenAPI fixtures
tests/data/expected/openapi/coverage/callbacks/main.py, tests/data/expected/openapi/coverage/callbacks_with_operation_id/main.py, tests/data/expected/openapi/coverage/model_options/main.py, tests/data/expected/openapi/coverage/non_200_responses/main.py, tests/data/expected/openapi/coverage/non_200_status_code/main.py, tests/data/expected/openapi/default_template/body_and_parameters/main.py, tests/data/expected/openapi/default_template/duplicate_request_param/main.py, tests/data/expected/openapi/default_template/same_response_model_for_different_status_codes/main.py, tests/data/expected/openapi/default_template/simple/main.py, tests/data/expected/openapi/default_template/upload/main.py, tests/data/expected/openapi/disable_timestamp/simple/main.py, tests/data/expected/openapi/modify_specific_routers/expected/using_routers_example/routers/fat_cats.py, tests/data/expected/openapi/modify_specific_routers/expected/using_routers_example/routers/wild_boars.py, tests/data/expected/openapi/using_routers/using_routers_example/routers/fat_cats.py, tests/data/expected/openapi/using_routers/using_routers_example/routers/slim_dogs.py, tests/data/expected/openapi/using_routers/using_routers_example/routers/wild_boars.py, tests/data/expected/openapi/remote_ref/body_and_parameters/main.py, tests/data/openapi/coverage/non_200_status_code.yaml
Expected endpoint outputs and OpenAPI YAML fixtures now include explicit 201 and 202 status codes, updated return annotations, and revised response modeling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

A rabbit hops through 2xx land,
Picks one response, neatly planned.
With 201 bright and 202 seen,
The code feels tidy, fast, and keen.
🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.51% 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 is concise and matches the main change: generating 201 responses as the primary endpoint response.
Linked Issues check ✅ Passed The parser and fixtures now promote 201 JSON responses to the primary response_model, status_code, and return type as requested in #607.
Out of Scope Changes check ✅ Passed The edits stay focused on OpenAPI response selection and generated 201 status/return-type fixtures, with no unrelated feature work.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-201-response-generation

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.

@codspeed-hq

codspeed-hq Bot commented Jun 26, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️ 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.

✅ 1 untouched benchmark


Comparing fix-201-response-generation (3a218b5) with main (29a4368)

Open in CodSpeed

Comment thread fastapi_code_generator/parser.py Fixed
@koxudaxi
koxudaxi force-pushed the fix-201-response-generation branch from dfc01ca to 371f3bc Compare June 26, 2026 04:36
Comment thread fastapi_code_generator/parser.py Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@fastapi_code_generator/parser.py`:
- Around line 771-783: The _select_route_status_code helper is dropping sole
non-200 success codes when there is no response body, causing the route to fall
back to FastAPI’s default 200. Update this method in parser.py so it preserves a
single unambiguous success code from success_status_codes even when data_types
has no schema for it, while still keeping the existing primary_status_code and
204 handling in place.
🪄 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: 986364f5-93e7-4fa1-844c-49612c54665c

📥 Commits

Reviewing files that changed from the base of the PR and between 29a4368 and dfc01ca.

📒 Files selected for processing (4)
  • fastapi_code_generator/parser.py
  • tests/data/expected/openapi/coverage/callbacks/main.py
  • tests/data/expected/openapi/coverage/callbacks_with_operation_id/main.py
  • tests/data/expected/openapi/coverage/non_200_responses/main.py

Comment thread fastapi_code_generator/parser.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
fastapi_code_generator/parser.py (1)

768-768: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer next(iter(...)) over single-element slicing.

Ruff (RUF015) flags list(...)[0] at both locations; it materializes the full list just to take the first element.

♻️ Proposed change
-        data_type = list(response_data_types.values())[0]
+        data_type = next(iter(response_data_types.values()))
-            data_type = list(additional_responses.values())[0]
+            data_type = next(iter(additional_responses.values()))

Also applies to: 814-814

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastapi_code_generator/parser.py` at line 768, Replace the eager list
indexing in the parser logic with an iterator-based first-element lookup. In the
code paths using response_data_types, update the single-element access in the
parser function(s) so they use next(iter(...)) instead of materializing
list(...)[0], and apply the same change to both flagged locations to satisfy
Ruff RUF015 while preserving the existing behavior.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@fastapi_code_generator/parser.py`:
- Around line 749-767: The primary response selection logic in
_select_primary_response_status_code and _get_primary_response_data_type is
using string lookups against data_types even though
parse_responses/_get_success_status_codes work with integer status codes, which
can make primary_status_code resolve to None and fall back to
DataType(type='None') incorrectly. Make the response-status key handling
consistent across these methods by using the same key type everywhere (prefer
integers if that is what parse_responses produces), including the
additional-responses loop, and update the lookups in
_select_primary_response_status_code and _get_primary_response_data_type to
match.

---

Nitpick comments:
In `@fastapi_code_generator/parser.py`:
- Line 768: Replace the eager list indexing in the parser logic with an
iterator-based first-element lookup. In the code paths using
response_data_types, update the single-element access in the parser function(s)
so they use next(iter(...)) instead of materializing list(...)[0], and apply the
same change to both flagged locations to satisfy Ruff RUF015 while preserving
the existing behavior.
🪄 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: 439d32dd-9565-436d-ae89-2f35466b7ae1

📥 Commits

Reviewing files that changed from the base of the PR and between dfc01ca and 371f3bc.

📒 Files selected for processing (4)
  • fastapi_code_generator/parser.py
  • tests/data/expected/openapi/coverage/callbacks/main.py
  • tests/data/expected/openapi/coverage/callbacks_with_operation_id/main.py
  • tests/data/expected/openapi/coverage/non_200_responses/main.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/data/expected/openapi/coverage/non_200_responses/main.py
  • tests/data/expected/openapi/coverage/callbacks_with_operation_id/main.py
  • tests/data/expected/openapi/coverage/callbacks/main.py

Comment thread fastapi_code_generator/parser.py Outdated
@koxudaxi
koxudaxi force-pushed the fix-201-response-generation branch 2 times, most recently from 42c8ca6 to 3ea19b7 Compare June 26, 2026 04:47
Comment thread fastapi_code_generator/parser.py
@koxudaxi
koxudaxi force-pushed the fix-201-response-generation branch from 3ea19b7 to 221e6bf Compare June 26, 2026 04:51

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
fastapi_code_generator/parser.py (1)

773-790: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Normalize the primary data lookup for the 200 branch.

Line 773 can return '200' from responses, but data_types is typed to allow either '200' or 200. The direct data_types.get(status_code) on Line 789 can therefore miss the parsed schema and generate None instead of the primary response_model.

Proposed fix
     def _get_primary_response_data_type(
         self,
         status_code: ResponseStatusCode | None,
         data_types: ParsedResponseDataTypes,
     ) -> DataType:
-        if status_code is None or not (
-            response_data_types := data_types.get(status_code)
-        ):
+        parsed_status_code = (
+            self._parse_success_status_code(status_code)
+            if status_code is not None
+            else None
+        )
+        if parsed_status_code is None or not (
+            response_data_types := self._get_response_data_types(
+                data_types, parsed_status_code
+            )
+        ):
             return DataType(type='None')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastapi_code_generator/parser.py` around lines 773 - 790, The primary
response data lookup is inconsistent between the status-code selection and
schema lookup paths: `_get_primary_response_status_code_key` can return the
string form for 200, while `_get_primary_response_data_type` currently does a
direct `data_types.get(status_code)` and may miss the parsed entry. Update the
lookup in `_get_primary_response_data_type` (and any related helper logic in
`_get_primary_response_status_code_key` / `_find_response_status_code_key`) to
normalize the status code key before accessing `data_types`, so both `200` and
`'200'` resolve to the same primary response schema and the correct
`response_model` is returned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@fastapi_code_generator/parser.py`:
- Around line 773-790: The primary response data lookup is inconsistent between
the status-code selection and schema lookup paths:
`_get_primary_response_status_code_key` can return the string form for 200,
while `_get_primary_response_data_type` currently does a direct
`data_types.get(status_code)` and may miss the parsed entry. Update the lookup
in `_get_primary_response_data_type` (and any related helper logic in
`_get_primary_response_status_code_key` / `_find_response_status_code_key`) to
normalize the status code key before accessing `data_types`, so both `200` and
`'200'` resolve to the same primary response schema and the correct
`response_model` is returned.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d479ff5c-7d76-4b9d-aa57-ccaeaea8f734

📥 Commits

Reviewing files that changed from the base of the PR and between 42c8ca6 and 3ea19b7.

📒 Files selected for processing (19)
  • fastapi_code_generator/parser.py
  • tests/data/expected/openapi/coverage/callbacks/main.py
  • tests/data/expected/openapi/coverage/callbacks_with_operation_id/main.py
  • tests/data/expected/openapi/coverage/model_options/main.py
  • tests/data/expected/openapi/coverage/non_200_responses/main.py
  • tests/data/expected/openapi/coverage/non_200_status_code/main.py
  • tests/data/expected/openapi/default_template/body_and_parameters/main.py
  • tests/data/expected/openapi/default_template/duplicate_request_param/main.py
  • tests/data/expected/openapi/default_template/same_response_model_for_different_status_codes/main.py
  • tests/data/expected/openapi/default_template/simple/main.py
  • tests/data/expected/openapi/default_template/upload/main.py
  • tests/data/expected/openapi/disable_timestamp/simple/main.py
  • tests/data/expected/openapi/modify_specific_routers/expected/using_routers_example/routers/fat_cats.py
  • tests/data/expected/openapi/modify_specific_routers/expected/using_routers_example/routers/wild_boars.py
  • tests/data/expected/openapi/remote_ref/body_and_parameters/main.py
  • tests/data/expected/openapi/using_routers/using_routers_example/routers/fat_cats.py
  • tests/data/expected/openapi/using_routers/using_routers_example/routers/slim_dogs.py
  • tests/data/expected/openapi/using_routers/using_routers_example/routers/wild_boars.py
  • tests/data/openapi/coverage/non_200_status_code.yaml
✅ Files skipped from review due to trivial changes (6)
  • tests/data/expected/openapi/modify_specific_routers/expected/using_routers_example/routers/fat_cats.py
  • tests/data/expected/openapi/default_template/same_response_model_for_different_status_codes/main.py
  • tests/data/expected/openapi/modify_specific_routers/expected/using_routers_example/routers/wild_boars.py
  • tests/data/expected/openapi/default_template/upload/main.py
  • tests/data/expected/openapi/default_template/simple/main.py
  • tests/data/openapi/coverage/non_200_status_code.yaml
🚧 Files skipped from review as they are similar to previous changes (8)
  • tests/data/expected/openapi/using_routers/using_routers_example/routers/wild_boars.py
  • tests/data/expected/openapi/using_routers/using_routers_example/routers/slim_dogs.py
  • tests/data/expected/openapi/default_template/duplicate_request_param/main.py
  • tests/data/expected/openapi/using_routers/using_routers_example/routers/fat_cats.py
  • tests/data/expected/openapi/coverage/callbacks/main.py
  • tests/data/expected/openapi/coverage/callbacks_with_operation_id/main.py
  • tests/data/expected/openapi/coverage/non_200_status_code/main.py
  • tests/data/expected/openapi/coverage/non_200_responses/main.py

@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (29a4368) to head (3a218b5).

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #610   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           17        17           
  Lines         1414      1461   +47     
  Branches       143       153   +10     
=========================================
+ Hits          1414      1461   +47     
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 Harness.
📢 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.

@koxudaxi
koxudaxi force-pushed the fix-201-response-generation branch from 221e6bf to 3a218b5 Compare June 26, 2026 04:57
@koxudaxi
koxudaxi merged commit a253d2a into main Jun 26, 2026
47 checks passed
@koxudaxi
koxudaxi deleted the fix-201-response-generation branch June 26, 2026 14:24
@github-actions github-actions Bot added breaking-change-analyzed PR has been checked for release draft updates breaking-change PR contains breaking changes labels Jun 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Breaking Change Analysis

Result: Breaking changes detected

Reasoning: This PR fundamentally changes how the code generator handles non-200 success responses (201, 202, etc.). Previously, only 200 responses were treated as the "primary" response (setting response_model), and only 204 got an explicit status_code. Now, any non-200 success response can become the primary response with a proper response_model and status_code. This changes the generated FastAPI code output in several ways: (1) new status_code parameters appear in route decorators, (2) response_model changes from None to actual model types for 201 responses, (3) return type annotations change from Optional to non-Optional, and (4) entries move from additional_responses to the primary response_model. These are breaking changes because anyone regenerating their code from the same OpenAPI spec will get different output, and custom templates may behave differently with the changed operation metadata.

Content for Release Notes

Code Generation Changes

  • Non-200 success status codes now generate explicit status_code parameter - Endpoints whose OpenAPI spec defines only non-200 success responses (e.g., 201, 202) now include status_code=201 (or 202, etc.) in the generated route decorator. Previously, only 204 no-content responses received an explicit status_code. This changes the generated output for any spec with 201, 202, or other 2xx-only responses, which will cause diffs when regenerating code. (Fix 201 response generation #610)

  • Primary non-200 success response now uses response_model instead of responses dict - When an endpoint's only success response is a non-200 code (e.g., 201) with a response body schema, the generated code now uses response_model=<Model> and status_code=201 instead of the previous response_model=None, responses={'201': {'model': Model}}. For example:

    # Before
    @app.post('/subscriptions', response_model=None, responses={'201': {'model': Subscription}})
    def create_subscription(body: SubscriptionRequest) -> Optional[Subscription]:
    
    # After
    @app.post('/subscriptions', response_model=Subscription, status_code=201)
    def create_subscription(body: SubscriptionRequest) -> Subscription:

    This also changes the return type annotation from Optional[Model] or Optional[Union[...]] to Model or Union[...] (removing the Optional wrapper). Any code depending on the previously generated signatures, response handling, or Optional return types will need updating. (Fix 201 response generation #610)

Custom Template Update Required

  • operation.status_code is now populated for more endpoints - Custom Jinja2 templates that reference operation.status_code will now receive non-None values for endpoints with non-200 success status codes (e.g., 201, 202), not just 204. Templates that already handle status_code (like the built-in templates) will work correctly, but custom templates that assumed status_code was only set for 204 may need review. (Fix 201 response generation #610)
  • operation.response and operation.additional_responses may differ - For specs where the primary success response is non-200, operation.response now contains the actual model type (e.g., Subscription) instead of None, and the primary status code entry is removed from operation.additional_responses. Custom templates referencing these fields may produce different output. (Fix 201 response generation #610)

This analysis was performed by Claude Code Action

@github-actions

Copy link
Copy Markdown
Contributor

🎉 Released in 0.8.0

This PR is now available in the latest release. See the release notes for details.

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

201 response body is generated as additional response and endpoint return type becomes None

2 participants