Skip to content

Commit

Permalink
fix(OpenAPI): Document unconsumed path parameters (litestar-org#3295)
Browse files Browse the repository at this point in the history
* Document unconsumed path parameters
  • Loading branch information
provinzkraut authored Mar 30, 2024
1 parent 576bae9 commit 3ea7c15
Show file tree
Hide file tree
Showing 5 changed files with 505 additions and 440 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ repos:
- id: end-of-file-fixer
- id: mixed-line-ending
- id: trailing-whitespace
exclude: "tests/unit/test_openapi/test_typescript_converter/test_converter.py"
- repo: https://github.com/provinzkraut/unasyncd
rev: "v0.7.1"
hooks:
Expand Down
15 changes: 13 additions & 2 deletions litestar/_openapi/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def __init__(
self.parameters = ParameterCollection(route_handler)
self.dependency_providers = route_handler.resolve_dependencies()
self.layered_parameters = route_handler.resolve_layered_parameters()
self.path_parameters_names = {p.name for p in path_parameters}
self.path_parameters: dict[str, PathParameterDefinition] = {p.name: p for p in path_parameters}

def create_parameter(self, field_definition: FieldDefinition, parameter_name: str) -> Parameter:
"""Create an OpenAPI Parameter instance for a field definition.
Expand All @@ -111,7 +111,7 @@ def create_parameter(self, field_definition: FieldDefinition, parameter_name: st
field_definition.kwarg_definition if isinstance(field_definition.kwarg_definition, ParameterKwarg) else None
)

if parameter_name in self.path_parameters_names:
if parameter_name in self.path_parameters:
param_in = ParamType.PATH
is_required = True
result = self.schema_creator.for_field_definition(field_definition)
Expand Down Expand Up @@ -215,6 +215,17 @@ def create_parameters_for_field_definitions(self, fields: dict[str, FieldDefinit
def create_parameters_for_handler(self) -> list[Parameter]:
"""Create a list of path/query/header Parameter models for the given PathHandler."""
handler_fields = self.route_handler.parsed_fn_signature.parameters
# not all path parameters have to be consumed by the handler. Because even not
# consumed path parameters must still be specified, we create stub parameters
# for the unconsumed ones so a correct OpenAPI schema can be generated
params_not_consumed_by_handler = set(self.path_parameters) - handler_fields.keys()
handler_fields.update(
{
param_name: FieldDefinition.from_kwarg(self.path_parameters[param_name].type, name=param_name)
for param_name in params_not_consumed_by_handler
}
)

self.create_parameters_for_field_definitions(handler_fields)
return self.parameters.list()

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ known-first-party = ["litestar", "tests", "examples"]
"tests/unit/test_contrib/test_sqlalchemy/**/*.*" = ["UP006"]
"tools/**/*.*" = ["D", "ARG", "EM", "TRY", "G", "FBT"]
"tools/prepare_release.py" = ["S603", "S607"]
"tests/unit/test_openapi/test_typescript_converter/test_converter.py" = ["W293"]

[tool.ruff.format]
docstring-code-format = true
Expand Down
19 changes: 19 additions & 0 deletions tests/unit/test_openapi/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from litestar.enums import ParamType
from litestar.openapi.spec import ExternalDocumentation, OpenAPIType, Reference
from litestar.openapi.spec.example import Example
from litestar.openapi.spec.parameter import Parameter as OpenAPIParameter
from litestar.openapi.spec.schema import Schema
from litestar.pagination import ClassicPagination, CursorPagination, OffsetPagination
from litestar.params import KwargDefinition, Parameter, ParameterKwarg
Expand Down Expand Up @@ -573,6 +574,7 @@ def test_default_not_provided_for_kwarg_but_for_field() -> None:


def test_routes_with_different_path_param_types_get_merged() -> None:
# https://github.com/litestar-org/litestar/issues/2700
@get("/{param:int}")
async def get_handler(param: int) -> None:
pass
Expand All @@ -586,3 +588,20 @@ async def post_handler(param: str) -> None:
paths = app.openapi_schema.paths["/{param}"]
assert paths.get is not None
assert paths.post is not None


def test_unconsumed_path_parameters_are_documented() -> None:
# https://github.com/litestar-org/litestar/issues/3290
@get("/{param:str}")
async def handler() -> None:
pass

app = Litestar([handler])
params = app.openapi_schema.paths["/{param}"].get.parameters # type: ignore[index, union-attr]
assert params
assert len(params) == 1
param = params[0]
assert isinstance(param, OpenAPIParameter)
assert param.name == "param"
assert param.required is True
assert param.param_in is ParamType.PATH
Loading

0 comments on commit 3ea7c15

Please sign in to comment.