-
Notifications
You must be signed in to change notification settings - Fork 432
feat(event_handler): add custom method for OpenAPI configuration #6204
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
Changes from 9 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
0773bcc
Adding specific method for OpenAPI configuration
leandrodamascena d3b3daa
Adding constants
leandrodamascena db19aa9
Merge branch 'develop' into openapi/common-method
leandrodamascena 3c99c8b
Refactoring examples
leandrodamascena 8e72d8f
Merge branch 'develop' into openapi/common-method
leandrodamascena 698bf7d
Merge branch 'develop' into openapi/common-method
leandrodamascena 24d8eb8
Merge branch 'develop' into openapi/common-method
leandrodamascena 863d6f9
Merge branch 'develop' into openapi/common-method
leandrodamascena c62eab2
Merging from develop
leandrodamascena 6a30901
Addressing Andrea's feedback
leandrodamascena File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,12 @@ | |
|
||
from aws_lambda_powertools.event_handler import content_types | ||
from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError | ||
from aws_lambda_powertools.event_handler.openapi.constants import DEFAULT_API_VERSION, DEFAULT_OPENAPI_VERSION | ||
from aws_lambda_powertools.event_handler.openapi.config import OpenAPIConfig | ||
from aws_lambda_powertools.event_handler.openapi.constants import ( | ||
DEFAULT_API_VERSION, | ||
DEFAULT_OPENAPI_TITLE, | ||
DEFAULT_OPENAPI_VERSION, | ||
) | ||
from aws_lambda_powertools.event_handler.openapi.exceptions import ( | ||
RequestValidationError, | ||
ResponseValidationError, | ||
|
@@ -1537,6 +1542,7 @@ def __init__( | |
self.context: dict = {} # early init as customers might add context before event resolution | ||
self.processed_stack_frames = [] | ||
self._response_builder_class = ResponseBuilder[BaseProxyEvent] | ||
self.openapi_config = OpenAPIConfig() # starting an empty dataclass | ||
self._has_response_validation_error = response_validation_error_http_code is not None | ||
self._response_validation_error_http_code = self._validate_response_validation_error_http_code( | ||
response_validation_error_http_code, | ||
|
@@ -1580,16 +1586,12 @@ def _validate_response_validation_error_http_code( | |
msg = f"'{response_validation_error_http_code}' must be an integer representing an HTTP status code." | ||
raise ValueError(msg) from None | ||
|
||
return ( | ||
response_validation_error_http_code | ||
if response_validation_error_http_code | ||
else HTTPStatus.UNPROCESSABLE_ENTITY | ||
) | ||
return response_validation_error_http_code or HTTPStatus.UNPROCESSABLE_ENTITY | ||
|
||
def get_openapi_schema( | ||
self, | ||
*, | ||
title: str = "Powertools API", | ||
title: str = DEFAULT_OPENAPI_TITLE, | ||
version: str = DEFAULT_API_VERSION, | ||
openapi_version: str = DEFAULT_OPENAPI_VERSION, | ||
summary: str | None = None, | ||
|
@@ -1641,6 +1643,29 @@ def get_openapi_schema( | |
The OpenAPI schema as a pydantic model. | ||
""" | ||
|
||
# DEPRECATION: Will be removed in v4.0.0. Use configure_api() instead. | ||
# Maintained for backwards compatibility. | ||
# See: https://github.com/aws-powertools/powertools-lambda-python/issues/6122 | ||
if title == DEFAULT_OPENAPI_TITLE and self.openapi_config.title: | ||
title = self.openapi_config.title | ||
|
||
if version == DEFAULT_API_VERSION and self.openapi_config.version: | ||
version = self.openapi_config.version | ||
|
||
if openapi_version == DEFAULT_OPENAPI_VERSION and self.openapi_config.openapi_version: | ||
openapi_version = self.openapi_config.openapi_version | ||
|
||
summary = summary or self.openapi_config.summary | ||
description = description or self.openapi_config.description | ||
tags = tags or self.openapi_config.tags | ||
servers = servers or self.openapi_config.servers | ||
terms_of_service = terms_of_service or self.openapi_config.terms_of_service | ||
contact = contact or self.openapi_config.contact | ||
license_info = license_info or self.openapi_config.license_info | ||
security_schemes = security_schemes or self.openapi_config.security_schemes | ||
security = security or self.openapi_config.security | ||
openapi_extensions = openapi_extensions or self.openapi_config.openapi_extensions | ||
|
||
from aws_lambda_powertools.event_handler.openapi.compat import ( | ||
GenerateJsonSchema, | ||
get_compat_model_name_map, | ||
|
@@ -1739,7 +1764,7 @@ def _get_openapi_servers(servers: list[Server] | None) -> list[Server]: | |
|
||
# If the 'servers' property is not provided or is an empty array, | ||
# the default behavior is to return a Server Object with a URL value of "/". | ||
return servers if servers else [Server(url="/")] | ||
return servers or [Server(url="/")] | ||
|
||
@staticmethod | ||
def _get_openapi_security( | ||
|
@@ -1771,7 +1796,7 @@ def _determine_openapi_version(openapi_version: str): | |
def get_openapi_json_schema( | ||
self, | ||
*, | ||
title: str = "Powertools API", | ||
title: str = DEFAULT_OPENAPI_TITLE, | ||
version: str = DEFAULT_API_VERSION, | ||
openapi_version: str = DEFAULT_OPENAPI_VERSION, | ||
summary: str | None = None, | ||
|
@@ -1822,6 +1847,7 @@ def get_openapi_json_schema( | |
str | ||
The OpenAPI schema as a JSON serializable dict. | ||
""" | ||
|
||
from aws_lambda_powertools.event_handler.openapi.compat import model_json | ||
|
||
return model_json( | ||
|
@@ -1845,11 +1871,94 @@ def get_openapi_json_schema( | |
indent=2, | ||
) | ||
|
||
def configure_openapi( | ||
self, | ||
title: str = DEFAULT_OPENAPI_TITLE, | ||
version: str = DEFAULT_API_VERSION, | ||
openapi_version: str = DEFAULT_OPENAPI_VERSION, | ||
summary: str | None = None, | ||
description: str | None = None, | ||
tags: list[Tag | str] | None = None, | ||
servers: list[Server] | None = None, | ||
terms_of_service: str | None = None, | ||
contact: Contact | None = None, | ||
license_info: License | None = None, | ||
security_schemes: dict[str, SecurityScheme] | None = None, | ||
security: list[dict[str, list[str]]] | None = None, | ||
openapi_extensions: dict[str, Any] | None = None, | ||
): | ||
"""Configure OpenAPI specification settings for the API. | ||
|
||
Sets up the OpenAPI documentation configuration that can be later used | ||
when enabling Swagger UI or generating OpenAPI specifications. | ||
|
||
Parameters | ||
---------- | ||
title: str | ||
The title of the application. | ||
version: str | ||
The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API | ||
openapi_version: str, default = "3.0.0" | ||
The version of the OpenAPI Specification (which the document uses). | ||
summary: str, optional | ||
A short summary of what the application does. | ||
description: str, optional | ||
A verbose explanation of the application behavior. | ||
tags: list[Tag, str], optional | ||
A list of tags used by the specification with additional metadata. | ||
servers: list[Server], optional | ||
An array of Server Objects, which provide connectivity information to a target server. | ||
terms_of_service: str, optional | ||
A URL to the Terms of Service for the API. MUST be in the format of a URL. | ||
contact: Contact, optional | ||
The contact information for the exposed API. | ||
license_info: License, optional | ||
The license information for the exposed API. | ||
security_schemes: dict[str, SecurityScheme]], optional | ||
A declaration of the security schemes available to be used in the specification. | ||
security: list[dict[str, list[str]]], optional | ||
A declaration of which security mechanisms are applied globally across the API. | ||
openapi_extensions: Dict[str, Any], optional | ||
Additional OpenAPI extensions as a dictionary. | ||
|
||
Example | ||
-------- | ||
>>> api.configure_openapi( | ||
... title="My API", | ||
... version="1.0.0", | ||
... description="API for managing resources", | ||
... contact=Contact( | ||
... name="API Support", | ||
... email="[email protected]" | ||
... ) | ||
... ) | ||
|
||
See Also | ||
-------- | ||
enable_swagger : Method to enable Swagger UI using these configurations | ||
OpenAPIConfig : Data class containing all OpenAPI configuration options | ||
""" | ||
self.openapi_config = OpenAPIConfig( | ||
title=title, | ||
version=version, | ||
openapi_version=openapi_version, | ||
summary=summary, | ||
description=description, | ||
tags=tags, | ||
servers=servers, | ||
terms_of_service=terms_of_service, | ||
contact=contact, | ||
license_info=license_info, | ||
security_schemes=security_schemes, | ||
security=security, | ||
openapi_extensions=openapi_extensions, | ||
) | ||
|
||
def enable_swagger( | ||
self, | ||
*, | ||
path: str = "/swagger", | ||
title: str = "Powertools for AWS Lambda (Python) API", | ||
title: str = DEFAULT_OPENAPI_TITLE, | ||
version: str = DEFAULT_API_VERSION, | ||
openapi_version: str = DEFAULT_OPENAPI_VERSION, | ||
summary: str | None = None, | ||
|
@@ -1912,6 +2021,7 @@ def enable_swagger( | |
openapi_extensions: dict[str, Any], optional | ||
Additional OpenAPI extensions as a dictionary. | ||
""" | ||
|
||
from aws_lambda_powertools.event_handler.openapi.compat import model_json | ||
from aws_lambda_powertools.event_handler.openapi.models import Server | ||
from aws_lambda_powertools.event_handler.openapi.swagger_ui import ( | ||
|
@@ -2156,10 +2266,7 @@ def _get_base_path(self) -> str: | |
@staticmethod | ||
def _has_debug(debug: bool | None = None) -> bool: | ||
# It might have been explicitly switched off (debug=False) | ||
if debug is not None: | ||
return debug | ||
|
||
return powertools_dev_is_set() | ||
return debug if debug is not None else powertools_dev_is_set() | ||
|
||
@staticmethod | ||
def _compile_regex(rule: str, base_regex: str = _ROUTE_REGEX): | ||
|
@@ -2272,7 +2379,7 @@ def _path_starts_with(path: str, prefix: str): | |
if not isinstance(prefix, str) or prefix == "": | ||
return False | ||
|
||
return path.startswith(prefix + "/") | ||
return path.startswith(f"{prefix}/") | ||
|
||
def _handle_not_found(self, method: str, path: str) -> ResponseBuilder: | ||
"""Called when no matching route was found and includes support for the cors preflight response""" | ||
|
@@ -2543,8 +2650,9 @@ def _get_fields_from_routes(routes: Sequence[Route]) -> list[ModelField]: | |
if route.dependant.response_extra_models: | ||
responses_from_routes.extend(route.dependant.response_extra_models) | ||
|
||
flat_models = list(responses_from_routes + request_fields_from_routes + body_fields_from_routes) | ||
return flat_models | ||
return list( | ||
responses_from_routes + request_fields_from_routes + body_fields_from_routes, | ||
) | ||
|
||
|
||
class Router(BaseRouter): | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
from __future__ import annotations | ||
|
||
from dataclasses import dataclass | ||
from typing import TYPE_CHECKING, Any | ||
|
||
from aws_lambda_powertools.event_handler.openapi.constants import DEFAULT_API_VERSION, DEFAULT_OPENAPI_VERSION | ||
|
||
if TYPE_CHECKING: | ||
from aws_lambda_powertools.event_handler.openapi.models import ( | ||
Contact, | ||
License, | ||
SecurityScheme, | ||
Server, | ||
Tag, | ||
) | ||
|
||
|
||
@dataclass | ||
class OpenAPIConfig: | ||
"""Configuration class for OpenAPI specification. | ||
|
||
This class holds all the necessary configuration parameters to generate an OpenAPI specification. | ||
|
||
Parameters | ||
---------- | ||
title: str | ||
The title of the application. | ||
version: str | ||
The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API | ||
openapi_version: str, default = "3.0.0" | ||
The version of the OpenAPI Specification (which the document uses). | ||
summary: str, optional | ||
A short summary of what the application does. | ||
description: str, optional | ||
A verbose explanation of the application behavior. | ||
tags: list[Tag, str], optional | ||
A list of tags used by the specification with additional metadata. | ||
servers: list[Server], optional | ||
An array of Server Objects, which provide connectivity information to a target server. | ||
terms_of_service: str, optional | ||
A URL to the Terms of Service for the API. MUST be in the format of a URL. | ||
contact: Contact, optional | ||
The contact information for the exposed API. | ||
license_info: License, optional | ||
The license information for the exposed API. | ||
security_schemes: dict[str, SecurityScheme]], optional | ||
A declaration of the security schemes available to be used in the specification. | ||
security: list[dict[str, list[str]]], optional | ||
A declaration of which security mechanisms are applied globally across the API. | ||
openapi_extensions: Dict[str, Any], optional | ||
Additional OpenAPI extensions as a dictionary. | ||
|
||
Example | ||
-------- | ||
>>> config = OpenAPIConfig( | ||
... title="My API", | ||
... version="1.0.0", | ||
... description="This is my API description", | ||
... contact=Contact(name="API Support", email="[email protected]"), | ||
... servers=[Server(url="https://api.example.com/v1")] | ||
... ) | ||
""" | ||
|
||
title: str = "Powertools for AWS Lambda (Python) API" | ||
version: str = DEFAULT_API_VERSION | ||
openapi_version: str = DEFAULT_OPENAPI_VERSION | ||
summary: str | None = None | ||
description: str | None = None | ||
tags: list[Tag | str] | None = None | ||
servers: list[Server] | None = None | ||
terms_of_service: str | None = None | ||
contact: Contact | None = None | ||
license_info: License | None = None | ||
security_schemes: dict[str, SecurityScheme] | None = None | ||
security: list[dict[str, list[str]]] | None = None | ||
openapi_extensions: dict[str, Any] | None = None |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
DEFAULT_API_VERSION = "1.0.0" | ||
DEFAULT_OPENAPI_VERSION = "3.1.0" | ||
DEFAULT_OPENAPI_TITLE = "Powertools for AWS Lambda (Python) API" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,15 @@ | |
from aws_lambda_powertools.utilities.typing import LambdaContext | ||
|
||
app = APIGatewayRestResolver(enable_validation=True) | ||
app.configure_openapi( | ||
title="TODO's API", | ||
version="1.21.3", | ||
summary="API to manage TODOs", | ||
description="This API implements all the CRUD operations for the TODO app", | ||
tags=["todos"], | ||
servers=[Server(url="https://stg.example.org/orders", description="Staging server")], | ||
contact=Contact(name="John Smith", email="[email protected]"), | ||
) | ||
|
||
|
||
@app.get("/todos/<todo_id>") | ||
|
@@ -20,14 +29,4 @@ def lambda_handler(event: dict, context: LambdaContext) -> dict: | |
|
||
|
||
if __name__ == "__main__": | ||
print( | ||
app.get_openapi_json_schema( | ||
title="TODO's API", | ||
version="1.21.3", | ||
summary="API to manage TODOs", | ||
description="This API implements all the CRUD operations for the TODO app", | ||
tags=["todos"], | ||
servers=[Server(url="https://stg.example.org/orders", description="Staging server")], | ||
contact=Contact(name="John Smith", email="[email protected]"), | ||
), | ||
) | ||
print(app.get_openapi_json_schema()) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.