Skip to content

Commit

Permalink
feat: Support ResponseSpec(..., examples=[...]) (litestar-org#3068)
Browse files Browse the repository at this point in the history
Allows to define custom examples for the responses via `ResponseSpec`.

The examples set this way are always generated locally, for each response:

```json
{
  "paths": {
    "/": {
      "get": {
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {...},
                "examples": ...
```

Examples that go within the schema definition cannot be set by this.
  • Loading branch information
mtvx committed Feb 13, 2024
1 parent c152369 commit 4dca9f6
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 6 deletions.
17 changes: 12 additions & 5 deletions litestar/_openapi/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
from typing import TYPE_CHECKING, Any, Iterator

from litestar._openapi.schema_generation import SchemaCreator
from litestar._openapi.schema_generation.utils import get_formatted_examples
from litestar.enums import MediaType
from litestar.exceptions import HTTPException, ValidationException
from litestar.openapi.spec import Example, OpenAPIResponse
from litestar.openapi.spec import Example, OpenAPIResponse, Reference
from litestar.openapi.spec.enums import OpenAPIFormat, OpenAPIType
from litestar.openapi.spec.header import OpenAPIHeader
from litestar.openapi.spec.media_type import OpenAPIMediaType
Expand Down Expand Up @@ -240,14 +241,20 @@ def create_additional_responses(self) -> Iterator[tuple[str, OpenAPIResponse]]:
prefer_alias=False,
generate_examples=additional_response.generate_examples,
)
schema = schema_creator.for_field_definition(
FieldDefinition.from_annotation(additional_response.data_container)
)
field_def = FieldDefinition.from_annotation(additional_response.data_container)
schema = schema_creator.for_field_definition(field_def)

examples: dict[str, Example | Reference] | None
if additional_response.examples:
examples = dict(get_formatted_examples(field_def, additional_response.examples))
else:
examples = None

yield (
str(status_code),
OpenAPIResponse(
description=additional_response.description,
content={additional_response.media_type: OpenAPIMediaType(schema=schema)},
content={additional_response.media_type: OpenAPIMediaType(schema=schema, examples=examples)},
),
)

Expand Down
3 changes: 3 additions & 0 deletions litestar/openapi/datastructures.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from litestar.enums import MediaType

if TYPE_CHECKING:
from litestar.openapi.spec import Example
from litestar.types import DataContainerType


Expand All @@ -24,3 +25,5 @@ class ResponseSpec:
"""A description of the response."""
media_type: MediaType = field(default=MediaType.JSON)
"""Response media type."""
examples: list[Example] | None = field(default=None)
"""A list of Example models."""
24 changes: 23 additions & 1 deletion tests/unit/test_openapi/test_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from litestar.handlers import HTTPRouteHandler
from litestar.openapi.config import OpenAPIConfig
from litestar.openapi.datastructures import ResponseSpec
from litestar.openapi.spec import OpenAPIHeader, OpenAPIMediaType, Reference, Schema
from litestar.openapi.spec import Example, OpenAPIHeader, OpenAPIMediaType, Reference, Schema
from litestar.openapi.spec.enums import OpenAPIType
from litestar.response import File, Redirect, Stream, Template
from litestar.response.base import T
Expand Down Expand Up @@ -416,6 +416,28 @@ def handler() -> DataclassPerson:
assert responses["400"].description == "Overwritten response"


def test_additional_responses_with_custom_examples(create_factory: CreateFactoryFixture) -> None:
@get(responses={200: ResponseSpec(DataclassPerson, examples=[Example(value={"string": "example", "number": 1})])})
def handler() -> DataclassPerson:
return DataclassPersonFactory.build()

factory = create_factory(handler)
responses = factory.create_additional_responses()
status_code, response = next(responses)
assert response.content
assert response.content["application/json"].examples == {
"dataclassperson-example-1": Example(
value={
"string": "example",
"number": 1,
}
),
}

with pytest.raises(StopIteration):
next(responses)


def test_create_response_for_response_subclass(create_factory: CreateFactoryFixture) -> None:
class CustomResponse(Response[T]):
pass
Expand Down

0 comments on commit 4dca9f6

Please sign in to comment.