Skip to content

feat(event_handler): add support for defining OpenAPI examples in parameters #6086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 17, 2025
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
9 changes: 4 additions & 5 deletions aws_lambda_powertools/event_handler/api_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,11 +552,7 @@ def _get_openapi_path(
operation_responses: dict[int, OpenAPIResponse] = {
422: {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {"$ref": COMPONENT_REF_PREFIX + "HTTPValidationError"},
},
},
"content": {"application/json": {"schema": {"$ref": f"{COMPONENT_REF_PREFIX}HTTPValidationError"}}},
},
}

Expand Down Expand Up @@ -761,6 +757,9 @@ def _openapi_operation_parameters(
if field_info.description:
parameter["description"] = field_info.description

if field_info.openapi_examples:
parameter["examples"] = field_info.openapi_examples

if field_info.deprecated:
parameter["deprecated"] = field_info.deprecated

Expand Down
14 changes: 14 additions & 0 deletions aws_lambda_powertools/event_handler/openapi/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
)

if TYPE_CHECKING:
from aws_lambda_powertools.event_handler.openapi.models import Example
from aws_lambda_powertools.event_handler.openapi.types import CacheKey

"""
Expand Down Expand Up @@ -117,6 +118,7 @@ def __init__(
max_digits: int | None = _Unset,
decimal_places: int | None = _Unset,
examples: list[Any] | None = None,
openapi_examples: dict[str, Example] | None = None,
deprecated: bool | None = None,
include_in_schema: bool = True,
json_schema_extra: dict[str, Any] | None = None,
Expand Down Expand Up @@ -205,8 +207,13 @@ def __init__(
if examples is not None:
kwargs["examples"] = examples

if openapi_examples is not None:
kwargs["openapi_examples"] = openapi_examples

current_json_schema_extra = json_schema_extra or extra

self.openapi_examples = openapi_examples

kwargs.update(
{
"annotation": annotation,
Expand Down Expand Up @@ -262,6 +269,7 @@ def __init__(
max_digits: int | None = _Unset,
decimal_places: int | None = _Unset,
examples: list[Any] | None = None,
openapi_examples: dict[str, Example] | None = None,
deprecated: bool | None = None,
include_in_schema: bool = True,
json_schema_extra: dict[str, Any] | None = None,
Expand Down Expand Up @@ -353,6 +361,7 @@ def __init__(
decimal_places=decimal_places,
deprecated=deprecated,
examples=examples,
openapi_examples=openapi_examples,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
**extra,
Expand Down Expand Up @@ -392,6 +401,7 @@ def __init__(
max_digits: int | None = _Unset,
decimal_places: int | None = _Unset,
examples: list[Any] | None = None,
openapi_examples: dict[str, Example] | None = None,
deprecated: bool | None = None,
include_in_schema: bool = True,
json_schema_extra: dict[str, Any] | None = None,
Expand Down Expand Up @@ -480,6 +490,7 @@ def __init__(
decimal_places=decimal_places,
deprecated=deprecated,
examples=examples,
openapi_examples=openapi_examples,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
**extra,
Expand Down Expand Up @@ -522,6 +533,7 @@ def __init__(
max_digits: int | None = _Unset,
decimal_places: int | None = _Unset,
examples: list[Any] | None = None,
openapi_examples: dict[str, Example] | None = None,
deprecated: bool | None = None,
include_in_schema: bool = True,
json_schema_extra: dict[str, Any] | None = None,
Expand Down Expand Up @@ -616,6 +628,7 @@ def __init__(
decimal_places=decimal_places,
deprecated=deprecated,
examples=examples,
openapi_examples=openapi_examples,
include_in_schema=include_in_schema,
json_schema_extra=json_schema_extra,
**extra,
Expand Down Expand Up @@ -669,6 +682,7 @@ def __init__(
max_digits: int | None = _Unset,
decimal_places: int | None = _Unset,
examples: list[Any] | None = None,
openapi_examples: dict[str, Example] | None = None,
deprecated: bool | None = None,
include_in_schema: bool = True,
json_schema_extra: dict[str, Any] | None = None,
Expand Down
44 changes: 22 additions & 22 deletions docs/core/event_handler/_openapi_customization_parameters.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
<!-- markdownlint-disable MD041 MD043 -->
Whenever you use OpenAPI parameters to validate [query strings](api_gateway.md#validating-query-strings) or [path parameters](api_gateway.md#validating-path-parameters), you can enhance validation and OpenAPI documentation by using any of these parameters:

| Field name | Type | Description |
|-----------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `alias` | `str` | Alternative name for a field, used when serializing and deserializing data |
| `validation_alias` | `str` | Alternative name for a field during validation (but not serialization) |
| `serialization_alias` | `str` | Alternative name for a field during serialization (but not during validation) |
| `description` | `str` | Human-readable description |
| `gt` | `float` | Greater than. If set, value must be greater than this. Only applicable to numbers |
| `ge` | `float` | Greater than or equal. If set, value must be greater than or equal to this. Only applicable to numbers |
| `lt` | `float` | Less than. If set, value must be less than this. Only applicable to numbers |
| `le` | `float` | Less than or equal. If set, value must be less than or equal to this. Only applicable to numbers |
| `min_length` | `int` | Minimum length for strings |
| `max_length` | `int` | Maximum length for strings |
| `pattern` | `string` | A regular expression that the string must match. |
| `strict` | `bool` | If `True`, strict validation is applied to the field. See [Strict Mode](https://docs.pydantic.dev/latest/concepts/strict_mode/){target"_blank" rel="nofollow"} for details |
| `multiple_of` | `float` | Value must be a multiple of this. Only applicable to numbers |
| `allow_inf_nan` | `bool` | Allow `inf`, `-inf`, `nan`. Only applicable to numbers |
| `max_digits` | `int` | Maximum number of allow digits for strings |
| `decimal_places` | `int` | Maximum number of decimal places allowed for numbers |
| `examples` | `List[Any]` | List of examples of the field |
| `deprecated` | `bool` | Marks the field as deprecated |
| `include_in_schema` | `bool` | If `False` the field will not be part of the exported OpenAPI schema |
| `json_schema_extra` | `JsonDict` | Any additional JSON schema data for the schema property |
| Field name | Type | Description |
| --------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `alias` | `str` | Alternative name for a field, used when serializing and deserializing data |
| `validation_alias` | `str` | Alternative name for a field during validation (but not serialization) |
| `serialization_alias` | `str` | Alternative name for a field during serialization (but not during validation) |
| `description` | `str` | Human-readable description |
| `gt` | `float` | Greater than. If set, value must be greater than this. Only applicable to numbers |
| `ge` | `float` | Greater than or equal. If set, value must be greater than or equal to this. Only applicable to numbers |
| `lt` | `float` | Less than. If set, value must be less than this. Only applicable to numbers |
| `le` | `float` | Less than or equal. If set, value must be less than or equal to this. Only applicable to numbers |
| `min_length` | `int` | Minimum length for strings |
| `max_length` | `int` | Maximum length for strings |
| `pattern` | `string` | A regular expression that the string must match. |
| `strict` | `bool` | If `True`, strict validation is applied to the field. See [Strict Mode](https://docs.pydantic.dev/latest/concepts/strict_mode/){target"_blank" rel="nofollow"} for details |
| `multiple_of` | `float` | Value must be a multiple of this. Only applicable to numbers |
| `allow_inf_nan` | `bool` | Allow `inf`, `-inf`, `nan`. Only applicable to numbers |
| `max_digits` | `int` | Maximum number of allow digits for strings |
| `decimal_places` | `int` | Maximum number of decimal places allowed for numbers |
| `openapi_examples` | `dict[str, Example]` | A list of examples to be displayed in the SwaggerUI interface. Avoid using the `examples` field for this purpose. |
| `deprecated` | `bool` | Marks the field as deprecated |
| `include_in_schema` | `bool` | If `False` the field will not be part of the exported OpenAPI schema |
| `json_schema_extra` | `JsonDict` | Any additional JSON schema data for the schema property |
41 changes: 41 additions & 0 deletions tests/functional/event_handler/_pydantic/test_openapi_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,3 +608,44 @@ def handler() -> Todo:
assert "completed" in todo_schema.properties
completed_property = todo_schema.properties["completed"]
assert completed_property.examples == [True]


def test_openapi_with_openapi_example():
app = APIGatewayRestResolver()

first_example = Example(summary="Example1", description="Example1", value="a")
second_example = Example(summary="Example2", description="Example2", value="b")

@app.get("/users", summary="Get Users", operation_id="GetUsers", description="Get paginated users", tags=["Users"])
def handler(
count: Annotated[
int,
Query(
openapi_examples={
"first_example": first_example,
"second_example": second_example,
},
),
] = 1,
):
print(count)
raise NotImplementedError()

schema = app.get_openapi_schema()

get = schema.paths["/users"].get
assert len(get.parameters) == 1
assert get.summary == "Get Users"
assert get.operationId == "GetUsers"
assert get.description == "Get paginated users"
assert get.tags == ["Users"]

parameter = get.parameters[0]
assert parameter.required is False
assert parameter.name == "count"
assert parameter.examples["first_example"] == first_example
assert parameter.examples["second_example"] == second_example
assert parameter.in_ == ParameterInType.query
assert parameter.schema_.type == "integer"
assert parameter.schema_.default == 1
assert parameter.schema_.title == "Count"
Loading