From 0b2f19eae35178f7efaa150a067ce432654d41eb Mon Sep 17 00:00:00 2001 From: Mathieu Hinderyckx Date: Fri, 4 Jul 2025 10:58:19 +0200 Subject: [PATCH 1/5] Add top-level externalDocs support --- .../event_handler/api_gateway.py | 18 ++++++++++++++++++ .../event_handler/openapi/config.py | 4 ++++ 2 files changed, 22 insertions(+) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index be74ece69ea..8c63e16cc0e 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -90,6 +90,7 @@ SecurityScheme, Server, Tag, + ExternalDocumentation, ) from aws_lambda_powertools.event_handler.openapi.params import Dependant from aws_lambda_powertools.event_handler.openapi.swagger_ui.oauth2 import ( @@ -1714,6 +1715,7 @@ def get_openapi_schema( license_info: License | None = None, security_schemes: dict[str, SecurityScheme] | None = None, security: list[dict[str, list[str]]] | None = None, + external_documentation: ExternalDocumentation | None = None, openapi_extensions: dict[str, Any] | None = None, ) -> OpenAPI: """ @@ -1745,6 +1747,8 @@ def get_openapi_schema( 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. + external_documentation: ExternalDocumentation, optional + Additional external documentation for the API. openapi_extensions: Dict[str, Any], optional Additional OpenAPI extensions as a dictionary. @@ -1775,6 +1779,7 @@ def get_openapi_schema( 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 + external_documentation = external_documentation or self.openapi_config.external_documentation openapi_extensions = openapi_extensions or self.openapi_config.openapi_extensions from pydantic.json_schema import GenerateJsonSchema @@ -1811,6 +1816,7 @@ def get_openapi_schema( "info": info, "servers": self._get_openapi_servers(servers), "security": self._get_openapi_security(security, security_schemes), + "external_docs": external_documentation, **openapi_extensions, } @@ -1921,6 +1927,7 @@ def get_openapi_json_schema( license_info: License | None = None, security_schemes: dict[str, SecurityScheme] | None = None, security: list[dict[str, list[str]]] | None = None, + external_documentation: ExternalDocumentation | None = None, openapi_extensions: dict[str, Any] | None = None, ) -> str: """ @@ -1952,6 +1959,8 @@ def get_openapi_json_schema( 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. + external_documentation: ExternalDocumentation, optional + Additional external documentation for the API. openapi_extensions: Dict[str, Any], optional Additional OpenAPI extensions as a dictionary. @@ -1977,6 +1986,7 @@ def get_openapi_json_schema( license_info=license_info, security_schemes=security_schemes, security=security, + external_documentation=external_documentation, openapi_extensions=openapi_extensions, ), by_alias=True, @@ -1998,6 +2008,7 @@ def configure_openapi( license_info: License | None = None, security_schemes: dict[str, SecurityScheme] | None = None, security: list[dict[str, list[str]]] | None = None, + external_documentation: ExternalDocumentation | None = None, openapi_extensions: dict[str, Any] | None = None, ): """Configure OpenAPI specification settings for the API. @@ -2031,6 +2042,8 @@ def configure_openapi( 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. + external_documentation: ExternalDocumentation, optional + A link to external documentation for the API. openapi_extensions: Dict[str, Any], optional Additional OpenAPI extensions as a dictionary. @@ -2064,6 +2077,7 @@ def configure_openapi( license_info=license_info, security_schemes=security_schemes, security=security, + external_documentation=external_documentation, openapi_extensions=openapi_extensions, ) @@ -2088,6 +2102,7 @@ def enable_swagger( security: list[dict[str, list[str]]] | None = None, oauth2_config: OAuth2Config | None = None, persist_authorization: bool = False, + external_documentation: ExternalDocumentation | None = None, openapi_extensions: dict[str, Any] | None = None, ): """ @@ -2131,6 +2146,8 @@ def enable_swagger( The OAuth2 configuration for the Swagger UI. persist_authorization: bool, optional Whether to persist authorization data on browser close/refresh. + external_documentation: ExternalDocumentation, optional + A link to external documentation for the API. openapi_extensions: dict[str, Any], optional Additional OpenAPI extensions as a dictionary. """ @@ -2183,6 +2200,7 @@ def swagger_handler(): license_info=license_info, security_schemes=security_schemes, security=security, + external_documentation=external_documentation, openapi_extensions=openapi_extensions, ) diff --git a/aws_lambda_powertools/event_handler/openapi/config.py b/aws_lambda_powertools/event_handler/openapi/config.py index 597362d1ef9..788a913a34e 100644 --- a/aws_lambda_powertools/event_handler/openapi/config.py +++ b/aws_lambda_powertools/event_handler/openapi/config.py @@ -16,6 +16,7 @@ SecurityScheme, Server, Tag, + ExternalDocumentation, ) @@ -51,6 +52,8 @@ class OpenAPIConfig: 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. + external_documentation: ExternalDocumentation, optional + A link to external documentation for the API. openapi_extensions: Dict[str, Any], optional Additional OpenAPI extensions as a dictionary. @@ -77,4 +80,5 @@ class OpenAPIConfig: license_info: License | None = None security_schemes: dict[str, SecurityScheme] | None = None security: list[dict[str, list[str]]] | None = None + external_documentation: ExternalDocumentation | None = None openapi_extensions: dict[str, Any] | None = None From f790623ca8963968ee18daa2189578e9b1a2cdff Mon Sep 17 00:00:00 2001 From: Mathieu Hinderyckx Date: Fri, 4 Jul 2025 11:12:47 +0200 Subject: [PATCH 2/5] add formal test --- .../event_handler/api_gateway.py | 6 ++-- .../test_openapi_external_documentation.py | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 tests/functional/event_handler/_pydantic/test_openapi_external_documentation.py diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 8c63e16cc0e..ee5bd46984a 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -85,12 +85,12 @@ ) from aws_lambda_powertools.event_handler.openapi.models import ( Contact, + ExternalDocumentation, License, OpenAPI, SecurityScheme, Server, Tag, - ExternalDocumentation, ) from aws_lambda_powertools.event_handler.openapi.params import Dependant from aws_lambda_powertools.event_handler.openapi.swagger_ui.oauth2 import ( @@ -1816,10 +1816,12 @@ def get_openapi_schema( "info": info, "servers": self._get_openapi_servers(servers), "security": self._get_openapi_security(security, security_schemes), - "external_docs": external_documentation, **openapi_extensions, } + if external_documentation: + output["external_docs"] = external_documentation + components: dict[str, dict[str, Any]] = {} paths: dict[str, dict[str, Any]] = {} operation_ids: set[str] = set() diff --git a/tests/functional/event_handler/_pydantic/test_openapi_external_documentation.py b/tests/functional/event_handler/_pydantic/test_openapi_external_documentation.py new file mode 100644 index 00000000000..98fe328baf9 --- /dev/null +++ b/tests/functional/event_handler/_pydantic/test_openapi_external_documentation.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from aws_lambda_powertools.event_handler.api_gateway import APIGatewayRestResolver +from aws_lambda_powertools.event_handler.openapi.models import ExternalDocumentation, Server + + +def test_openapi_schema_no_external_documentation(): + app = APIGatewayRestResolver() + + schema = app.get_openapi_schema(title="Hello API", version="1.0.0") + assert not schema.externalDocs + + +def test_openapi_schema_external_documentation(): + app = APIGatewayRestResolver() + + schema = app.get_openapi_schema( + title="Hello API", + version="1.0.0", + external_documentation=ExternalDocumentation( + description="Find out more about this API", + url="https://example.org/docs", + ), + ) + + assert schema.externalDocs + assert schema.externalDocs.description == "Find out more about this API" + assert schema.externalDocs.url == "https://example.org/docs" From 911e300687cda333c0ce8aa9d12ae83fc2f61b5b Mon Sep 17 00:00:00 2001 From: Mathieu Hinderyckx Date: Fri, 4 Jul 2025 15:47:45 +0200 Subject: [PATCH 3/5] cleanup import --- .../_pydantic/test_openapi_external_documentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/event_handler/_pydantic/test_openapi_external_documentation.py b/tests/functional/event_handler/_pydantic/test_openapi_external_documentation.py index 98fe328baf9..b28a790c777 100644 --- a/tests/functional/event_handler/_pydantic/test_openapi_external_documentation.py +++ b/tests/functional/event_handler/_pydantic/test_openapi_external_documentation.py @@ -1,7 +1,7 @@ from __future__ import annotations from aws_lambda_powertools.event_handler.api_gateway import APIGatewayRestResolver -from aws_lambda_powertools.event_handler.openapi.models import ExternalDocumentation, Server +from aws_lambda_powertools.event_handler.openapi.models import ExternalDocumentation def test_openapi_schema_no_external_documentation(): From 1888d84d9214ddb3f745f3a5e9c36eb2b06f236a Mon Sep 17 00:00:00 2001 From: Mathieu Hinderyckx Date: Fri, 4 Jul 2025 16:35:31 +0200 Subject: [PATCH 4/5] Update aws_lambda_powertools/event_handler/api_gateway.py Co-authored-by: Leandro Damascena Signed-off-by: Mathieu Hinderyckx --- aws_lambda_powertools/event_handler/api_gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index ee5bd46984a..17b8da641d3 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -1820,7 +1820,7 @@ def get_openapi_schema( } if external_documentation: - output["external_docs"] = external_documentation + output["externalDocs"] = external_documentation components: dict[str, dict[str, Any]] = {} paths: dict[str, dict[str, Any]] = {} From 29cc623516984d99b19f08714db623624602d909 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Fri, 4 Jul 2025 17:20:58 +0100 Subject: [PATCH 5/5] Fix test --- aws_lambda_powertools/event_handler/openapi/config.py | 2 +- .../_pydantic/test_openapi_external_documentation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aws_lambda_powertools/event_handler/openapi/config.py b/aws_lambda_powertools/event_handler/openapi/config.py index 788a913a34e..83fe2156a57 100644 --- a/aws_lambda_powertools/event_handler/openapi/config.py +++ b/aws_lambda_powertools/event_handler/openapi/config.py @@ -12,11 +12,11 @@ if TYPE_CHECKING: from aws_lambda_powertools.event_handler.openapi.models import ( Contact, + ExternalDocumentation, License, SecurityScheme, Server, Tag, - ExternalDocumentation, ) diff --git a/tests/functional/event_handler/_pydantic/test_openapi_external_documentation.py b/tests/functional/event_handler/_pydantic/test_openapi_external_documentation.py index b28a790c777..680e998b8bd 100644 --- a/tests/functional/event_handler/_pydantic/test_openapi_external_documentation.py +++ b/tests/functional/event_handler/_pydantic/test_openapi_external_documentation.py @@ -25,4 +25,4 @@ def test_openapi_schema_external_documentation(): assert schema.externalDocs assert schema.externalDocs.description == "Find out more about this API" - assert schema.externalDocs.url == "https://example.org/docs" + assert str(schema.externalDocs.url) == "https://example.org/docs"