Skip to content

Commit 0773bcc

Browse files
Adding specific method for OpenAPI configuration
1 parent 3d9ceb2 commit 0773bcc

File tree

5 files changed

+277
-4
lines changed

5 files changed

+277
-4
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ complexity-baseline:
8484
$(info Maintenability index)
8585
poetry run radon mi aws_lambda_powertools
8686
$(info Cyclomatic complexity index)
87-
poetry run xenon --max-absolute C --max-modules A --max-average A aws_lambda_powertools --exclude aws_lambda_powertools/shared/json_encoder.py,aws_lambda_powertools/utilities/validation/base.py
87+
poetry run xenon --max-absolute C --max-modules A --max-average A aws_lambda_powertools --exclude aws_lambda_powertools/shared/json_encoder.py,aws_lambda_powertools/utilities/validation/base.py,aws_lambda_powertools/event_handler/api_gateway.py
8888

8989
#
9090
# Use `poetry version <major>/<minor></patch>` for version bump

aws_lambda_powertools/event_handler/api_gateway.py

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from aws_lambda_powertools.event_handler import content_types
2020
from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError
21+
from aws_lambda_powertools.event_handler.openapi.config import OpenAPIConfig
2122
from aws_lambda_powertools.event_handler.openapi.constants import DEFAULT_API_VERSION, DEFAULT_OPENAPI_VERSION
2223
from aws_lambda_powertools.event_handler.openapi.exceptions import RequestValidationError, SchemaValidationError
2324
from aws_lambda_powertools.event_handler.openapi.types import (
@@ -1530,6 +1531,7 @@ def __init__(
15301531
self.context: dict = {} # early init as customers might add context before event resolution
15311532
self.processed_stack_frames = []
15321533
self._response_builder_class = ResponseBuilder[BaseProxyEvent]
1534+
self.openapi_config = OpenAPIConfig() # starting an empty dataclass
15331535

15341536
# Allow for a custom serializer or a concise json serialization
15351537
self._serializer = serializer or partial(json.dumps, separators=(",", ":"), cls=Encoder)
@@ -1544,7 +1546,7 @@ def __init__(
15441546
def get_openapi_schema(
15451547
self,
15461548
*,
1547-
title: str = "Powertools API",
1549+
title: str = "Powertools for AWS Lambda (Python) API",
15481550
version: str = DEFAULT_API_VERSION,
15491551
openapi_version: str = DEFAULT_OPENAPI_VERSION,
15501552
summary: str | None = None,
@@ -1596,6 +1598,29 @@ def get_openapi_schema(
15961598
The OpenAPI schema as a pydantic model.
15971599
"""
15981600

1601+
# DEPRECATION: Will be removed in v4.0.0. Use configure_api() instead.
1602+
# Maintained for backwards compatibility.
1603+
# See: https://github.com/aws-powertools/powertools-lambda-python/issues/6122
1604+
if title == "Powertools for AWS Lambda (Python) API" and self.openapi_config.title:
1605+
title = self.openapi_config.title
1606+
1607+
if version == DEFAULT_API_VERSION and self.openapi_config.version:
1608+
version = self.openapi_config.version
1609+
1610+
if openapi_version == DEFAULT_OPENAPI_VERSION and self.openapi_config.openapi_version:
1611+
openapi_version = self.openapi_config.openapi_version
1612+
1613+
summary = summary or self.openapi_config.summary
1614+
description = description or self.openapi_config.description
1615+
tags = tags or self.openapi_config.tags
1616+
servers = servers or self.openapi_config.servers
1617+
terms_of_service = terms_of_service or self.openapi_config.terms_of_service
1618+
contact = contact or self.openapi_config.contact
1619+
license_info = license_info or self.openapi_config.license_info
1620+
security_schemes = security_schemes or self.openapi_config.security_schemes
1621+
security = security or self.openapi_config.security
1622+
openapi_extensions = openapi_extensions or self.openapi_config.openapi_extensions
1623+
15991624
from aws_lambda_powertools.event_handler.openapi.compat import (
16001625
GenerateJsonSchema,
16011626
get_compat_model_name_map,
@@ -1726,7 +1751,7 @@ def _determine_openapi_version(openapi_version: str):
17261751
def get_openapi_json_schema(
17271752
self,
17281753
*,
1729-
title: str = "Powertools API",
1754+
title: str = "Powertools for AWS Lambda (Python) API",
17301755
version: str = DEFAULT_API_VERSION,
17311756
openapi_version: str = DEFAULT_OPENAPI_VERSION,
17321757
summary: str | None = None,
@@ -1777,6 +1802,7 @@ def get_openapi_json_schema(
17771802
str
17781803
The OpenAPI schema as a JSON serializable dict.
17791804
"""
1805+
17801806
from aws_lambda_powertools.event_handler.openapi.compat import model_json
17811807

17821808
return model_json(
@@ -1800,6 +1826,89 @@ def get_openapi_json_schema(
18001826
indent=2,
18011827
)
18021828

1829+
def configure_openapi(
1830+
self,
1831+
title: str = "Powertools for AWS Lambda (Python) API",
1832+
version: str = DEFAULT_API_VERSION,
1833+
openapi_version: str = DEFAULT_OPENAPI_VERSION,
1834+
summary: str | None = None,
1835+
description: str | None = None,
1836+
tags: list[Tag | str] | None = None,
1837+
servers: list[Server] | None = None,
1838+
terms_of_service: str | None = None,
1839+
contact: Contact | None = None,
1840+
license_info: License | None = None,
1841+
security_schemes: dict[str, SecurityScheme] | None = None,
1842+
security: list[dict[str, list[str]]] | None = None,
1843+
openapi_extensions: dict[str, Any] | None = None,
1844+
):
1845+
"""Configure OpenAPI specification settings for the API.
1846+
1847+
Sets up the OpenAPI documentation configuration that can be later used
1848+
when enabling Swagger UI or generating OpenAPI specifications.
1849+
1850+
Parameters
1851+
----------
1852+
title: str
1853+
The title of the application.
1854+
version: str
1855+
The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API
1856+
openapi_version: str, default = "3.0.0"
1857+
The version of the OpenAPI Specification (which the document uses).
1858+
summary: str, optional
1859+
A short summary of what the application does.
1860+
description: str, optional
1861+
A verbose explanation of the application behavior.
1862+
tags: list[Tag, str], optional
1863+
A list of tags used by the specification with additional metadata.
1864+
servers: list[Server], optional
1865+
An array of Server Objects, which provide connectivity information to a target server.
1866+
terms_of_service: str, optional
1867+
A URL to the Terms of Service for the API. MUST be in the format of a URL.
1868+
contact: Contact, optional
1869+
The contact information for the exposed API.
1870+
license_info: License, optional
1871+
The license information for the exposed API.
1872+
security_schemes: dict[str, SecurityScheme]], optional
1873+
A declaration of the security schemes available to be used in the specification.
1874+
security: list[dict[str, list[str]]], optional
1875+
A declaration of which security mechanisms are applied globally across the API.
1876+
openapi_extensions: Dict[str, Any], optional
1877+
Additional OpenAPI extensions as a dictionary.
1878+
1879+
Example
1880+
--------
1881+
>>> api.configure_openapi(
1882+
... title="My API",
1883+
... version="1.0.0",
1884+
... description="API for managing resources",
1885+
... contact=Contact(
1886+
... name="API Support",
1887+
... email="[email protected]"
1888+
... )
1889+
... )
1890+
1891+
See Also
1892+
--------
1893+
enable_swagger : Method to enable Swagger UI using these configurations
1894+
OpenAPIConfig : Data class containing all OpenAPI configuration options
1895+
"""
1896+
self.openapi_config = OpenAPIConfig(
1897+
title=title,
1898+
version=version,
1899+
openapi_version=openapi_version,
1900+
summary=summary,
1901+
description=description,
1902+
tags=tags,
1903+
servers=servers,
1904+
terms_of_service=terms_of_service,
1905+
contact=contact,
1906+
license_info=license_info,
1907+
security_schemes=security_schemes,
1908+
security=security,
1909+
openapi_extensions=openapi_extensions,
1910+
)
1911+
18031912
def enable_swagger(
18041913
self,
18051914
*,
@@ -1867,6 +1976,7 @@ def enable_swagger(
18671976
openapi_extensions: dict[str, Any], optional
18681977
Additional OpenAPI extensions as a dictionary.
18691978
"""
1979+
18701980
from aws_lambda_powertools.event_handler.openapi.compat import model_json
18711981
from aws_lambda_powertools.event_handler.openapi.models import Server
18721982
from aws_lambda_powertools.event_handler.openapi.swagger_ui import (
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
from typing import TYPE_CHECKING, Any
5+
6+
from aws_lambda_powertools.event_handler.openapi.constants import DEFAULT_API_VERSION, DEFAULT_OPENAPI_VERSION
7+
8+
if TYPE_CHECKING:
9+
from aws_lambda_powertools.event_handler.openapi.models import (
10+
Contact,
11+
License,
12+
SecurityScheme,
13+
Server,
14+
Tag,
15+
)
16+
17+
18+
@dataclass
19+
class OpenAPIConfig:
20+
"""Configuration class for OpenAPI specification.
21+
22+
This class holds all the necessary configuration parameters to generate an OpenAPI specification.
23+
24+
Parameters
25+
----------
26+
title: str
27+
The title of the application.
28+
version: str
29+
The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API
30+
openapi_version: str, default = "3.0.0"
31+
The version of the OpenAPI Specification (which the document uses).
32+
summary: str, optional
33+
A short summary of what the application does.
34+
description: str, optional
35+
A verbose explanation of the application behavior.
36+
tags: list[Tag, str], optional
37+
A list of tags used by the specification with additional metadata.
38+
servers: list[Server], optional
39+
An array of Server Objects, which provide connectivity information to a target server.
40+
terms_of_service: str, optional
41+
A URL to the Terms of Service for the API. MUST be in the format of a URL.
42+
contact: Contact, optional
43+
The contact information for the exposed API.
44+
license_info: License, optional
45+
The license information for the exposed API.
46+
security_schemes: dict[str, SecurityScheme]], optional
47+
A declaration of the security schemes available to be used in the specification.
48+
security: list[dict[str, list[str]]], optional
49+
A declaration of which security mechanisms are applied globally across the API.
50+
openapi_extensions: Dict[str, Any], optional
51+
Additional OpenAPI extensions as a dictionary.
52+
53+
Example
54+
--------
55+
>>> config = OpenAPIConfig(
56+
... title="My API",
57+
... version="1.0.0",
58+
... description="This is my API description",
59+
... contact=Contact(name="API Support", email="[email protected]"),
60+
... servers=[Server(url="https://api.example.com/v1")]
61+
... )
62+
"""
63+
64+
title: str = "Powertools for AWS Lambda (Python) API"
65+
version: str = DEFAULT_API_VERSION
66+
openapi_version: str = DEFAULT_OPENAPI_VERSION
67+
summary: str | None = None
68+
description: str | None = None
69+
tags: list[Tag | str] | None = None
70+
servers: list[Server] | None = None
71+
terms_of_service: str | None = None
72+
contact: Contact | None = None
73+
license_info: License | None = None
74+
security_schemes: dict[str, SecurityScheme] | None = None
75+
security: list[dict[str, list[str]]] | None = None
76+
openapi_extensions: dict[str, Any] | None = None
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import json
2+
3+
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
4+
5+
6+
def test_export_openapi_schema_with_custom_configuration():
7+
# GIVEN an API Gateway resolver with OpenAPI validation enabled
8+
app = APIGatewayRestResolver(enable_validation=True)
9+
10+
# GIVEN custom OpenAPI configuration
11+
openapi_title = "My API"
12+
openapi_myapi_version = "1.1.1-alpha"
13+
app.configure_openapi(title=openapi_title, version=openapi_myapi_version)
14+
15+
# WHEN we have a simple handler
16+
@app.get("/")
17+
def handler():
18+
pass
19+
20+
# WHEN we get the schema
21+
schema = app.get_openapi_schema()
22+
23+
# THEN the schema should contain our custom configuration
24+
assert schema.info.title == openapi_title
25+
assert schema.info.version == openapi_myapi_version
26+
27+
28+
def test_prioritize_direct_parameters_over_stored_configuration():
29+
30+
# GIVEN
31+
stored_config = {
32+
"title": "Stored API Title",
33+
"version": "1.0.0",
34+
}
35+
36+
direct_params = {
37+
"title": "Direct API Title",
38+
"version": "2.0.0",
39+
}
40+
41+
# GIVEN an API Gateway resolver with OpenAPI validation enabled
42+
app = APIGatewayRestResolver(enable_validation=True)
43+
44+
app.configure_openapi(**stored_config)
45+
46+
# WHEN we have a simple handler
47+
@app.get("/")
48+
def handler():
49+
pass
50+
51+
# WHEN we get the schema with direct params
52+
schema = app.get_openapi_schema(**direct_params)
53+
54+
# THEN direct parameters must override stored configuration
55+
assert schema.info.title == direct_params["title"]
56+
assert schema.info.version == direct_params["version"]
57+
58+
59+
def test_export_openapi_schema_with_custom_configuration_and_json_export():
60+
# GIVEN an API Gateway resolver with OpenAPI validation enabled
61+
app = APIGatewayRestResolver(enable_validation=True)
62+
63+
# GIVEN custom OpenAPI configuration
64+
openapi_title = "My API"
65+
openapi_myapi_version = "1.1.1-alpha"
66+
openapi_version = "3.1.2"
67+
openapi_description = "My descrition"
68+
app.configure_openapi(
69+
title=openapi_title,
70+
version=openapi_myapi_version,
71+
openapi_version=openapi_version,
72+
description=openapi_description,
73+
)
74+
75+
# WHEN we have a simple handler
76+
@app.get("/")
77+
def handler():
78+
pass
79+
80+
# WHEN we get the schema
81+
schema = json.loads(app.get_openapi_json_schema())
82+
83+
# THEN the schema should contain our custom configuration
84+
assert schema["info"]["title"] == openapi_title
85+
assert schema["info"]["version"] == openapi_myapi_version
86+
assert schema["openapi"] == openapi_version
87+
assert schema["info"]["description"] == openapi_description

tests/functional/event_handler/_pydantic/test_openapi_params.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def handler():
3232
raise NotImplementedError()
3333

3434
schema = app.get_openapi_schema()
35-
assert schema.info.title == "Powertools API"
35+
assert schema.info.title == "Powertools for AWS Lambda (Python) API"
3636
assert schema.info.version == "1.0.0"
3737

3838
assert len(schema.paths.keys()) == 1

0 commit comments

Comments
 (0)