Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ Options:
--specify-tags TEXT
-c, --custom-visitor PATH
--disable-timestamp
--strict-nullable Respect explicit OpenAPI nullable flags when
generating models.
--include-request-argument Auto-inject a FastAPI Request parameter into
operations when not present.
-d, --output-model-type [pydantic_v2.BaseModel|pydantic_v2.dataclass|dataclasses.dataclass|typing.TypedDict|msgspec.Struct]
Expand Down
10 changes: 10 additions & 0 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Options:
--specify-tags TEXT
-c, --custom-visitor PATH
--disable-timestamp
--strict-nullable Respect explicit OpenAPI nullable flags when
generating models.
--include-request-argument Auto-inject a FastAPI Request parameter into
operations when not present.
-d, --output-model-type [pydantic_v2.BaseModel|pydantic_v2.dataclass|dataclasses.dataclass|typing.TypedDict|msgspec.Struct]
Expand Down Expand Up @@ -99,6 +101,14 @@ Omit the generated timestamp header from output files.

Input schema: `openapi/disable_timestamp/simple.yaml`

### --strict-nullable

Respect explicit OpenAPI nullable flags when generating models.

`fastapi-codegen --input openapi/default_template/nullable_test.yaml --output app --strict-nullable`

Input schema: `openapi/default_template/nullable_test.yaml`

### --generate-routers

Generate modular router files from tagged OpenAPI operations.
Expand Down
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ Options:
--specify-tags TEXT
-c, --custom-visitor PATH
--disable-timestamp
--strict-nullable Respect explicit OpenAPI nullable flags when
generating models.
--include-request-argument Auto-inject a FastAPI Request parameter into
operations when not present.
-d, --output-model-type [pydantic_v2.BaseModel|pydantic_v2.dataclass|dataclasses.dataclass|typing.TypedDict|msgspec.Struct]
Expand Down
16 changes: 14 additions & 2 deletions docs/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ Options:
--specify-tags TEXT
-c, --custom-visitor PATH
--disable-timestamp
--strict-nullable Respect explicit OpenAPI nullable flags when
generating models.
--include-request-argument Auto-inject a FastAPI Request parameter into
operations when not present.
-d, --output-model-type [pydantic_v2.BaseModel|pydantic_v2.dataclass|dataclasses.dataclass|typing.TypedDict|msgspec.Struct]
Expand Down Expand Up @@ -342,6 +344,8 @@ Options:
--specify-tags TEXT
-c, --custom-visitor PATH
--disable-timestamp
--strict-nullable Respect explicit OpenAPI nullable flags when
generating models.
--include-request-argument Auto-inject a FastAPI Request parameter into
operations when not present.
-d, --output-model-type [pydantic_v2.BaseModel|pydantic_v2.dataclass|dataclasses.dataclass|typing.TypedDict|msgspec.Struct]
Expand Down Expand Up @@ -423,6 +427,14 @@ Omit the generated timestamp header from output files.

Input schema: `openapi/disable_timestamp/simple.yaml`

### --strict-nullable

Respect explicit OpenAPI nullable flags when generating models.

`fastapi-codegen --input openapi/default_template/nullable_test.yaml --output app --strict-nullable`

Input schema: `openapi/default_template/nullable_test.yaml`

### --generate-routers

Generate modular router files from tagged OpenAPI operations.
Expand Down Expand Up @@ -515,15 +527,15 @@ Run `tox run -e schema-docs` after changing supported inputs or model backends.

| Format | Status | Evidence | Notes |
|--------|--------|----------|-------|
| OpenAPI YAML | tested | `tests/data/openapi/**/*.yaml` (23 fixtures) | Primary fixture format exercised under `tests/data/openapi/**/*.yaml`. |
| OpenAPI YAML | tested | `tests/data/openapi/**/*.yaml` (24 fixtures) | Primary fixture format exercised under `tests/data/openapi/**/*.yaml`. |
| OpenAPI JSON | tested | `tests/main/test_main.py::test_generate_from_json_input` | Covered by the JSON conversion CLI test in `tests/main/test_main.py`. |
| Remote HTTP `$ref` targets | tested | `tests/main/test_main.py::test_generate_remote_ref` | Covered by the remote `$ref` generation test against a live HTTP server. |

## Fixture Suites

| Suite | Fixtures | Example files | Notes |
|-------|----------|---------------|-------|
| Default template | 16 | `body_and_parameters.yaml`, `content_in_parameters.yaml`, `content_in_parameters_inline.yaml` | Core single-file generation scenarios exercised by the main CLI tests. |
| Default template | 17 | `body_and_parameters.yaml`, `content_in_parameters.yaml`, `content_in_parameters_inline.yaml` | Core single-file generation scenarios exercised by the main CLI tests. |
| Coverage fixtures | 3 | `callbacks.yaml`, `callbacks_with_operation_id.yaml`, `non_200_responses.yaml` | Focused fixtures for callbacks, non-200 responses, and other regression edges. |
| Custom template overrides | 1 | `custom_security.yaml` | Template override coverage for `--template-dir`. |
| Timestamp suppression | 1 | `simple.yaml` | Fixtures that exercise `--disable-timestamp`. |
Expand Down
4 changes: 2 additions & 2 deletions docs/supported_formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ Run `tox run -e schema-docs` after changing supported inputs or model backends.

| Format | Status | Evidence | Notes |
|--------|--------|----------|-------|
| OpenAPI YAML | tested | `tests/data/openapi/**/*.yaml` (23 fixtures) | Primary fixture format exercised under `tests/data/openapi/**/*.yaml`. |
| OpenAPI YAML | tested | `tests/data/openapi/**/*.yaml` (24 fixtures) | Primary fixture format exercised under `tests/data/openapi/**/*.yaml`. |
| OpenAPI JSON | tested | `tests/main/test_main.py::test_generate_from_json_input` | Covered by the JSON conversion CLI test in `tests/main/test_main.py`. |
| Remote HTTP `$ref` targets | tested | `tests/main/test_main.py::test_generate_remote_ref` | Covered by the remote `$ref` generation test against a live HTTP server. |

## Fixture Suites

| Suite | Fixtures | Example files | Notes |
|-------|----------|---------------|-------|
| Default template | 16 | `body_and_parameters.yaml`, `content_in_parameters.yaml`, `content_in_parameters_inline.yaml` | Core single-file generation scenarios exercised by the main CLI tests. |
| Default template | 17 | `body_and_parameters.yaml`, `content_in_parameters.yaml`, `content_in_parameters_inline.yaml` | Core single-file generation scenarios exercised by the main CLI tests. |
| Coverage fixtures | 3 | `callbacks.yaml`, `callbacks_with_operation_id.yaml`, `non_200_responses.yaml` | Focused fixtures for callbacks, non-200 responses, and other regression edges. |
| Custom template overrides | 1 | `custom_security.yaml` | Template override coverage for `--template-dir`. |
| Timestamp suppression | 1 | `simple.yaml` | Fixtures that exercise `--disable-timestamp`. |
Expand Down
1 change: 1 addition & 0 deletions fastapi_code_generator/_types/generate_config_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ class GenerateConfigDict(TypedDict):
]
python_version: NotRequired[Literal['3.10', '3.11', '3.12', '3.13', '3.14']]
specify_tags: NotRequired[str | None]
strict_nullable: NotRequired[bool]
template_dir: NotRequired[str | None]
use_annotated: NotRequired[bool]
8 changes: 8 additions & 0 deletions fastapi_code_generator/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ def main(
None, "--custom-visitor", "-c"
),
disable_timestamp: bool = typer.Option(False, "--disable-timestamp"),
strict_nullable: bool = typer.Option(
False,
"--strict-nullable",
help="Respect explicit OpenAPI nullable flags when generating models.",
),
include_request_argument: bool = typer.Option(
False,
"--include-request-argument",
Expand Down Expand Up @@ -137,6 +142,7 @@ def main(
enum_field_as_literal=enum_field_as_literal or None,
custom_visitors=custom_visitors,
disable_timestamp=disable_timestamp,
strict_nullable=strict_nullable,
include_request_argument=include_request_argument,
generate_routers=generate_routers,
specify_tags=specify_tags,
Expand Down Expand Up @@ -176,6 +182,7 @@ def generate_code(
enum_field_as_literal: Optional[LiteralType] = None,
custom_visitors: Optional[List[Path]] = None,
disable_timestamp: bool = False,
strict_nullable: bool = False,
include_request_argument: bool = False,
generate_routers: Optional[bool] = None,
specify_tags: Optional[str] = None,
Expand Down Expand Up @@ -209,6 +216,7 @@ def generate_code(
dump_resolve_reference_action=data_model_types.dump_resolve_reference_action,
custom_template_dir=model_template_dir,
target_python_version=python_version,
strict_nullable=strict_nullable,
include_request_argument=include_request_argument,
use_annotated=use_annotated,
)
Expand Down
5 changes: 5 additions & 0 deletions fastapi_code_generator/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ class GenerateConfig(BaseModel):
description="Omit timestamp headers from generated files.",
json_schema_extra=cast(Any, _cli_metadata("--disable-timestamp")),
)
strict_nullable: bool = Field(
default=False,
description="Respect explicit OpenAPI nullable flags when generating models.",
json_schema_extra=cast(Any, _cli_metadata("--strict-nullable")),
)
include_request_argument: bool = Field(
default=False,
description=(
Expand Down
28 changes: 26 additions & 2 deletions fastapi_code_generator/prompt_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,17 @@
'type': 'boolean',
'choices': [],
},
{
'name': 'strict_nullable',
'cli_flags': ['--strict-nullable'],
'description': 'Respect explicit OpenAPI nullable flags when '
'generating models.',
'required': False,
'default': False,
'multiple': False,
'type': 'boolean',
'choices': [],
},
{
'name': 'include_request_argument',
'cli_flags': ['--include-request-argument'],
Expand Down Expand Up @@ -261,6 +272,19 @@
],
'input_schema': 'openapi/disable_timestamp/simple.yaml',
},
{
'options': ['--strict-nullable'],
'description': 'Respect explicit OpenAPI nullable flags when '
'generating models.',
'cli_args': [
'--input',
'openapi/default_template/nullable_test.yaml',
'--output',
'app',
'--strict-nullable',
],
'input_schema': 'openapi/default_template/nullable_test.yaml',
},
{
'options': ['--generate-routers'],
'description': 'Generate modular router files from tagged OpenAPI '
Expand Down Expand Up @@ -461,7 +485,7 @@
'\n'
'| Format | Status | Evidence | Notes |\n'
'|--------|--------|----------|-------|\n'
'| OpenAPI YAML | tested | `tests/data/openapi/**/*.yaml` (23 '
'| OpenAPI YAML | tested | `tests/data/openapi/**/*.yaml` (24 '
'fixtures) | Primary fixture format exercised under '
'`tests/data/openapi/**/*.yaml`. |\n'
'| OpenAPI JSON | tested | '
Expand All @@ -477,7 +501,7 @@
'\n'
'| Suite | Fixtures | Example files | Notes |\n'
'|-------|----------|---------------|-------|\n'
'| Default template | 16 | `body_and_parameters.yaml`, '
'| Default template | 17 | `body_and_parameters.yaml`, '
'`content_in_parameters.yaml`, '
'`content_in_parameters_inline.yaml` | Core single-file '
'generation scenarios exercised by the main CLI tests. |\n'
Expand Down
24 changes: 24 additions & 0 deletions tests/data/expected/openapi/default_template/nullable_test/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# generated by fastapi-codegen:
# filename: nullable_test.yaml
# timestamp: 2020-06-19T00:00:00+00:00

from __future__ import annotations

from fastapi import FastAPI

from .models import User

app = FastAPI(
version='1.0.0',
title='Nullable Test API',
description='API for testing nullable field behavior',
servers=[{'url': 'http://api.example.com/v1'}],
)


@app.get('/users', response_model=User)
def get_user_details() -> User:
"""
Get user details
"""
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# generated by fastapi-codegen:
# filename: nullable_test.yaml
# timestamp: 2020-06-19T00:00:00+00:00

from __future__ import annotations

from typing import Optional

from pydantic import BaseModel, Field


class User(BaseModel):
id: int
username: str
email: Optional[str] = Field(
None, description="User's email address (explicitly nullable)"
)
phone: str = Field(..., description="User's phone number (explicitly non-nullable)")
nickname: Optional[str] = Field(
None, description="User's nickname (optional, not nullable under strict mode)"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# generated by fastapi-codegen:
# filename: nullable_test.yaml
# timestamp: 2020-06-19T00:00:00+00:00

from __future__ import annotations

from fastapi import FastAPI

from .models import User

app = FastAPI(
version='1.0.0',
title='Nullable Test API',
description='API for testing nullable field behavior',
servers=[{'url': 'http://api.example.com/v1'}],
)


@app.get('/users', response_model=User)
def get_user_details() -> User:
"""
Get user details
"""
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# generated by fastapi-codegen:
# filename: nullable_test.yaml
# timestamp: 2020-06-19T00:00:00+00:00

from __future__ import annotations

from typing import Optional

from pydantic import BaseModel, Field


class User(BaseModel):
id: int
username: str
email: Optional[str] = Field(
None, description="User's email address (explicitly nullable)"
)
phone: str = Field(..., description="User's phone number (explicitly non-nullable)")
nickname: Optional[str] = Field(
None, description="User's nickname (optional, not nullable under strict mode)"
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
44 changes: 44 additions & 0 deletions tests/data/openapi/default_template/nullable_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
openapi: "3.0.0"
info:
version: 1.0.0
title: Nullable Test API
description: API for testing nullable field behavior
servers:
- url: http://api.example.com/v1
paths:
/users:
get:
summary: Get user details
operationId: getUserDetails
responses:
"200":
description: User details
content:
application/json:
schema:
$ref: "#/components/schemas/User"
components:
schemas:
User:
type: object
required:
- id
- phone
- username
properties:
id:
type: integer
format: int64
username:
type: string
email:
type: string
nullable: true
description: User's email address (explicitly nullable)
phone:
type: string
nullable: false
description: User's phone number (explicitly non-nullable)
nickname:
type: string
description: User's nickname (optional, not nullable under strict mode)
27 changes: 27 additions & 0 deletions tests/main/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,33 @@ def test_disable_timestamp(oas_file: Path, output_dir: Path) -> None:
)


@pytest.mark.cli_doc(
options=["--strict-nullable"],
option_description="Respect explicit OpenAPI nullable flags when generating models.",
cli_args=[
"--input",
"openapi/default_template/nullable_test.yaml",
"--output",
"app",
"--strict-nullable",
],
input_schema="openapi/default_template/nullable_test.yaml",
golden_output="openapi/default_template/nullable_test_strict/models.py",
)
@freeze_time("2020-06-19")
def test_generate_with_strict_nullable(output_dir: Path) -> None:
run_cli_and_assert(
input_path=DATA_PATH
/ OPEN_API_DEFAULT_TEMPLATE_DIR_NAME
/ "nullable_test.yaml",
output_path=output_dir,
expected_path=EXPECTED_OPENAPI_PATH
/ "default_template"
/ "nullable_test_strict",
extra_args=["--strict-nullable"],
)


@pytest.mark.cli_doc(
options=["--generate-routers"],
option_description="Generate modular router files from tagged OpenAPI operations.",
Expand Down
Loading