diff --git a/.github/workflows/quality_check_pydanticv2.yml b/.github/workflows/quality_check_pydanticv2.yml index 8855a90b3f6..d0af2934986 100644 --- a/.github/workflows/quality_check_pydanticv2.yml +++ b/.github/workflows/quality_check_pydanticv2.yml @@ -58,7 +58,9 @@ jobs: python-version: ${{ matrix.python-version }} cache: "poetry" - name: Replacing Pydantic v1 with v2 > 2.0.3 - run: poetry add "pydantic=^2.0.3" + run: | + rm -rf poetry.lock + poetry add "pydantic=^2.0.3" - name: Install dependencies run: make dev - name: Test with pytest diff --git a/CHANGELOG.md b/CHANGELOG.md index b37aef446cf..7fdb78d7b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,53 @@ ## Bug Fixes +* **event_handler:** escape OpenAPI schema on Swagger UI ([#3606](https://github.com/aws-powertools/powertools-lambda-python/issues/3606)) + +## Code Refactoring + +* **event-handler:** Inject CSS and JS files into SwaggerUI route when no custom CDN is used. ([#3562](https://github.com/aws-powertools/powertools-lambda-python/issues/3562)) +* **event_handler:** fix BedrockAgentResolver docstring ([#3645](https://github.com/aws-powertools/powertools-lambda-python/issues/3645)) + +## Documentation + +* **homepage:** add banner about Python 3.7 deprecation ([#3618](https://github.com/aws-powertools/powertools-lambda-python/issues/3618)) +* **i-made-this:** added new article on how to create a serverless API with CDK and Powertools ([#3605](https://github.com/aws-powertools/powertools-lambda-python/issues/3605)) + +## Features + +* **event_handler:** add support for additional response models ([#3591](https://github.com/aws-powertools/powertools-lambda-python/issues/3591)) +* **event_handler:** add support to download OpenAPI spec file ([#3571](https://github.com/aws-powertools/powertools-lambda-python/issues/3571)) +* **event_source:** Add support for S3 batch operations ([#3572](https://github.com/aws-powertools/powertools-lambda-python/issues/3572)) +* **event_source:** Add support for policyLevel field in CloudWatch Logs event and parser ([#3624](https://github.com/aws-powertools/powertools-lambda-python/issues/3624)) +* **idempotency:** adding redis as idempotency backend ([#2567](https://github.com/aws-powertools/powertools-lambda-python/issues/2567)) + +## Maintenance + +* **ci:** update boto3 library version to 1.26.164+ ([#3632](https://github.com/aws-powertools/powertools-lambda-python/issues/3632)) +* **deps:** bump jinja2 from 3.1.2 to 3.1.3 in /docs ([#3620](https://github.com/aws-powertools/powertools-lambda-python/issues/3620)) +* **deps:** bump redis from 4.6.0 to 5.0.1 ([#3613](https://github.com/aws-powertools/powertools-lambda-python/issues/3613)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 1 update ([#3639](https://github.com/aws-powertools/powertools-lambda-python/issues/3639)) +* **deps:** bump gitpython from 3.1.37 to 3.1.41 in /docs ([#3610](https://github.com/aws-powertools/powertools-lambda-python/issues/3610)) +* **deps:** bump squidfunk/mkdocs-material from `2f29d71` to `58eef6c` in /docs ([#3633](https://github.com/aws-powertools/powertools-lambda-python/issues/3633)) +* **deps-dev:** bump aws-cdk from 2.118.0 to 2.120.0 ([#3627](https://github.com/aws-powertools/powertools-lambda-python/issues/3627)) +* **deps-dev:** bump sentry-sdk from 1.39.1 to 1.39.2 ([#3614](https://github.com/aws-powertools/powertools-lambda-python/issues/3614)) +* **deps-dev:** bump ruff from 0.1.11 to 0.1.13 ([#3625](https://github.com/aws-powertools/powertools-lambda-python/issues/3625)) +* **deps-dev:** bump aws-cdk from 2.120.0 to 2.121.1 ([#3634](https://github.com/aws-powertools/powertools-lambda-python/issues/3634)) +* **deps-dev:** bump cfn-lint from 0.83.7 to 0.83.8 ([#3603](https://github.com/aws-powertools/powertools-lambda-python/issues/3603)) +* **deps-dev:** bump gitpython from 3.1.40 to 3.1.41 ([#3611](https://github.com/aws-powertools/powertools-lambda-python/issues/3611)) +* **deps-dev:** bump jinja2 from 3.1.2 to 3.1.3 ([#3619](https://github.com/aws-powertools/powertools-lambda-python/issues/3619)) + + + +## [v2.31.0] - 2024-01-05 +## Bug Fixes + * **ci:** fail dispatch analytics job when Lambda call fails ([#3579](https://github.com/aws-powertools/powertools-lambda-python/issues/3579)) ## Code Refactoring * **parameters:** add overload signatures for get_parameter and get_parameters ([#3534](https://github.com/aws-powertools/powertools-lambda-python/issues/3534)) +* **parser:** Improve error message when parsing models and envelopes ([#3587](https://github.com/aws-powertools/powertools-lambda-python/issues/3587)) ## Documentation @@ -24,22 +66,23 @@ ## Maintenance +* version bump * **ci:** Remove dev dependencies locked to Pydantic v1 within the Pydantic v2 workflow. ([#3582](https://github.com/aws-powertools/powertools-lambda-python/issues/3582)) -* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#3544](https://github.com/aws-powertools/powertools-lambda-python/issues/3544)) -* **deps:** bump fastjsonschema from 2.19.0 to 2.19.1 ([#3567](https://github.com/aws-powertools/powertools-lambda-python/issues/3567)) -* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.2 to 3.0.3 ([#3536](https://github.com/aws-powertools/powertools-lambda-python/issues/3536)) -* **deps:** bump actions/setup-node from 4.0.0 to 4.0.1 ([#3535](https://github.com/aws-powertools/powertools-lambda-python/issues/3535)) -* **deps:** bump actions/dependency-review-action from 3.1.4 to 3.1.5 ([#3592](https://github.com/aws-powertools/powertools-lambda-python/issues/3592)) * **deps:** bump squidfunk/mkdocs-material from `9af3b7e` to `2f29d71` in /docs ([#3559](https://github.com/aws-powertools/powertools-lambda-python/issues/3559)) * **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 4 updates ([#3593](https://github.com/aws-powertools/powertools-lambda-python/issues/3593)) +* **deps:** bump actions/setup-node from 4.0.0 to 4.0.1 ([#3535](https://github.com/aws-powertools/powertools-lambda-python/issues/3535)) +* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.2 to 3.0.3 ([#3536](https://github.com/aws-powertools/powertools-lambda-python/issues/3536)) +* **deps:** bump actions/dependency-review-action from 3.1.4 to 3.1.5 ([#3592](https://github.com/aws-powertools/powertools-lambda-python/issues/3592)) +* **deps:** bump the layer-balancer group in /layer/scripts/layer-balancer with 2 updates ([#3544](https://github.com/aws-powertools/powertools-lambda-python/issues/3544)) +* **deps:** bump fastjsonschema from 2.19.0 to 2.19.1 ([#3567](https://github.com/aws-powertools/powertools-lambda-python/issues/3567)) * **deps-dev:** bump ruff from 0.1.8 to 0.1.9 ([#3550](https://github.com/aws-powertools/powertools-lambda-python/issues/3550)) * **deps-dev:** bump aws-cdk from 2.115.0 to 2.116.1 ([#3553](https://github.com/aws-powertools/powertools-lambda-python/issues/3553)) +* **deps-dev:** bump aws-cdk from 2.117.0 to 2.118.0 ([#3589](https://github.com/aws-powertools/powertools-lambda-python/issues/3589)) * **deps-dev:** bump cfn-lint from 0.83.6 to 0.83.7 ([#3554](https://github.com/aws-powertools/powertools-lambda-python/issues/3554)) -* **deps-dev:** bump pytest from 7.4.3 to 7.4.4 ([#3576](https://github.com/aws-powertools/powertools-lambda-python/issues/3576)) * **deps-dev:** bump ruff from 0.1.9 to 0.1.10 ([#3583](https://github.com/aws-powertools/powertools-lambda-python/issues/3583)) -* **deps-dev:** bump ruff from 0.1.10 to 0.1.11 ([#3588](https://github.com/aws-powertools/powertools-lambda-python/issues/3588)) -* **deps-dev:** bump aws-cdk from 2.117.0 to 2.118.0 ([#3589](https://github.com/aws-powertools/powertools-lambda-python/issues/3589)) +* **deps-dev:** bump pytest from 7.4.3 to 7.4.4 ([#3576](https://github.com/aws-powertools/powertools-lambda-python/issues/3576)) * **deps-dev:** bump aws-cdk from 2.116.1 to 2.117.0 ([#3565](https://github.com/aws-powertools/powertools-lambda-python/issues/3565)) +* **deps-dev:** bump ruff from 0.1.10 to 0.1.11 ([#3588](https://github.com/aws-powertools/powertools-lambda-python/issues/3588)) @@ -4225,7 +4268,8 @@ * Merge pull request [#5](https://github.com/aws-powertools/powertools-lambda-python/issues/5) from jfuss/feat/python38 -[Unreleased]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.30.2...HEAD +[Unreleased]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.31.0...HEAD +[v2.31.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.30.2...v2.31.0 [v2.30.2]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.30.1...v2.30.2 [v2.30.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.30.0...v2.30.1 [v2.30.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v2.29.1...v2.30.0 diff --git a/Makefile b/Makefile index 0de65f2d48c..80c89f72961 100644 --- a/Makefile +++ b/Makefile @@ -8,13 +8,13 @@ dev: pip install --upgrade pip pre-commit poetry poetry config --local virtualenvs.in-project true @$(MAKE) dev-version-plugin - poetry install --extras "all datamasking-aws-sdk" + poetry install --extras "all datamasking-aws-sdk redis" pre-commit install dev-gitpod: pip install --upgrade pip poetry @$(MAKE) dev-version-plugin - poetry install --extras "all datamasking-aws-sdk" + poetry install --extras "all datamasking-aws-sdk redis" pre-commit install format: diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 79e194e3719..9260ede43e9 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -37,6 +37,9 @@ from aws_lambda_powertools.event_handler.openapi.types import ( COMPONENT_REF_PREFIX, METHODS_WITH_BODY, + OpenAPIResponse, + OpenAPIResponseContentModel, + OpenAPIResponseContentSchema, validation_error_definition, validation_error_response_definition, ) @@ -273,7 +276,7 @@ def __init__( cache_control: Optional[str], summary: Optional[str], description: Optional[str], - responses: Optional[Dict[int, Dict[str, Any]]], + responses: Optional[Dict[int, OpenAPIResponse]], response_description: Optional[str], tags: Optional[List[str]], operation_id: Optional[str], @@ -303,7 +306,7 @@ def __init__( The OpenAPI summary for this route description: Optional[str] The OpenAPI description for this route - responses: Optional[Dict[int, Dict[str, Any]]] + responses: Optional[Dict[int, OpenAPIResponse]] The OpenAPI responses for this route response_description: Optional[str] The OpenAPI response description for this route @@ -442,7 +445,7 @@ def dependant(self) -> "Dependant": if self._dependant is None: from aws_lambda_powertools.event_handler.openapi.dependant import get_dependant - self._dependant = get_dependant(path=self.openapi_path, call=self.func) + self._dependant = get_dependant(path=self.openapi_path, call=self.func, responses=self.responses) return self._dependant @@ -501,11 +504,54 @@ def _get_openapi_path( # Add the response to the OpenAPI operation if self.responses: - # If the user supplied responses, we use them and don't set a default 200 response + for status_code in list(self.responses): + response = self.responses[status_code] + + # Case 1: there is not 'content' key + if "content" not in response: + response["content"] = { + "application/json": self._openapi_operation_return( + param=dependant.return_param, + model_name_map=model_name_map, + field_mapping=field_mapping, + ), + } + + # Case 2: there is a 'content' key + else: + # Need to iterate to transform any 'model' into a 'schema' + for content_type, payload in response["content"].items(): + new_payload: OpenAPIResponseContentSchema + + # Case 2.1: the 'content' has a model + if "model" in payload: + # Find the model in the dependant's extra models + return_field = next( + filter( + lambda model: model.type_ is cast(OpenAPIResponseContentModel, payload)["model"], + self.dependant.response_extra_models, + ), + ) + if not return_field: + raise AssertionError("Model declared in custom responses was not found") + + new_payload = self._openapi_operation_return( + param=return_field, + model_name_map=model_name_map, + field_mapping=field_mapping, + ) + + # Case 2.2: the 'content' has a schema + else: + # Do nothing! We already have what we need! + new_payload = payload + + response["content"][content_type] = new_payload + operation["responses"] = self.responses else: # Set the default 200 response - responses = operation.setdefault("responses", self.responses or {}) + responses = operation.setdefault("responses", {}) success_response = responses.setdefault(200, {}) success_response["description"] = self.response_description or _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION success_response["content"] = {"application/json": {"schema": {}}} @@ -682,7 +728,7 @@ def _openapi_operation_return( Tuple["ModelField", Literal["validation", "serialization"]], "JsonSchemaValue", ], - ) -> Dict[str, Any]: + ) -> OpenAPIResponseContentSchema: """ Returns the OpenAPI operation return. """ @@ -832,7 +878,7 @@ def route( cache_control: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, - responses: Optional[Dict[int, Dict[str, Any]]] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, tags: Optional[List[str]] = None, operation_id: Optional[str] = None, @@ -890,7 +936,7 @@ def get( cache_control: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, - responses: Optional[Dict[int, Dict[str, Any]]] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, tags: Optional[List[str]] = None, operation_id: Optional[str] = None, @@ -943,7 +989,7 @@ def post( cache_control: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, - responses: Optional[Dict[int, Dict[str, Any]]] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, tags: Optional[List[str]] = None, operation_id: Optional[str] = None, @@ -997,7 +1043,7 @@ def put( cache_control: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, - responses: Optional[Dict[int, Dict[str, Any]]] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, tags: Optional[List[str]] = None, operation_id: Optional[str] = None, @@ -1051,7 +1097,7 @@ def delete( cache_control: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, - responses: Optional[Dict[int, Dict[str, Any]]] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, tags: Optional[List[str]] = None, operation_id: Optional[str] = None, @@ -1104,7 +1150,7 @@ def patch( cache_control: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, - responses: Optional[Dict[int, Dict[str, Any]]] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, tags: Optional[List[str]] = None, operation_id: Optional[str] = None, @@ -1592,28 +1638,9 @@ def enable_swagger( middlewares: List[Callable[..., Response]], optional List of middlewares to be used for the swagger route. """ + from aws_lambda_powertools.event_handler.openapi.compat import model_json from aws_lambda_powertools.event_handler.openapi.models import Server - if not swagger_base_url: - - @self.get("/swagger.js", include_in_schema=False) - def swagger_js(): - body = Path.open(Path(__file__).parent / "openapi" / "swagger_ui" / "swagger-ui-bundle.min.js").read() - return Response( - status_code=200, - content_type="text/javascript", - body=body, - ) - - @self.get("/swagger.css", include_in_schema=False) - def swagger_css(): - body = Path.open(Path(__file__).parent / "openapi" / "swagger_ui" / "swagger-ui.min.css").read() - return Response( - status_code=200, - content_type="text/css", - body=body, - ) - @self.get(path, middlewares=middlewares, include_in_schema=False) def swagger_handler(): base_path = self._get_base_path() @@ -1622,12 +1649,15 @@ def swagger_handler(): swagger_js = f"{swagger_base_url}/swagger-ui-bundle.min.js" swagger_css = f"{swagger_base_url}/swagger-ui.min.css" else: - swagger_js = f"{base_path}/swagger.js" - swagger_css = f"{base_path}/swagger.css" + # We now inject CSS and JS into the SwaggerUI file + swagger_js = Path.open( + Path(__file__).parent / "openapi" / "swagger_ui" / "swagger-ui-bundle.min.js", + ).read() + swagger_css = Path.open(Path(__file__).parent / "openapi" / "swagger_ui" / "swagger-ui.min.css").read() openapi_servers = servers or [Server(url=(base_path or "/"))] - spec = self.get_openapi_json_schema( + spec = self.get_openapi_schema( title=title, version=version, openapi_version=openapi_version, @@ -1640,7 +1670,28 @@ def swagger_handler(): license_info=license_info, ) - body = generate_swagger_html(spec, swagger_js, swagger_css) + # The .replace('', '<\\/') part is necessary to prevent a potential issue where the JSON string contains + # or similar tags. Escaping the forward slash in as <\/ ensures that the JSON does not + # inadvertently close the script tag, and the JSON remains a valid string within the JavaScript code. + escaped_spec = model_json( + spec, + by_alias=True, + exclude_none=True, + indent=2, + ).replace("", "<\\/") + + # Check for query parameters; if "format" is specified as "json", + # respond with the JSON used in the OpenAPI spec + # Example: https://www.example.com/swagger?format=json + query_params = self.current_event.query_string_parameters or {} + if query_params.get("format") == "json": + return Response( + status_code=200, + content_type="application/json", + body=escaped_spec, + ) + + body = generate_swagger_html(escaped_spec, path, swagger_js, swagger_css, swagger_base_url) return Response( status_code=200, @@ -1657,7 +1708,7 @@ def route( cache_control: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, - responses: Optional[Dict[int, Dict[str, Any]]] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, tags: Optional[List[str]] = None, operation_id: Optional[str] = None, @@ -2105,6 +2156,9 @@ def _get_fields_from_routes(routes: Sequence[Route]) -> List["ModelField"]: if route.dependant.return_param: responses_from_routes.append(route.dependant.return_param) + 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 @@ -2127,7 +2181,7 @@ def route( cache_control: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, - responses: Optional[Dict[int, Dict[str, Any]]] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, response_description: Optional[str] = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, tags: Optional[List[str]] = None, operation_id: Optional[str] = None, @@ -2216,7 +2270,7 @@ def route( cache_control: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, - responses: Optional[Dict[int, Dict[str, Any]]] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, tags: Optional[List[str]] = None, operation_id: Optional[str] = None, diff --git a/aws_lambda_powertools/event_handler/bedrock_agent.py b/aws_lambda_powertools/event_handler/bedrock_agent.py index f292a11519a..9c65547d9a2 100644 --- a/aws_lambda_powertools/event_handler/bedrock_agent.py +++ b/aws_lambda_powertools/event_handler/bedrock_agent.py @@ -66,6 +66,8 @@ def simple_get(): @tracer.capture_lambda_handler def lambda_handler(event, context): return app.resolve(event, context) + ``` + """ current_event: BedrockAgentEvent diff --git a/aws_lambda_powertools/event_handler/openapi/dependant.py b/aws_lambda_powertools/event_handler/openapi/dependant.py index e22eb535a7e..418a86e083c 100644 --- a/aws_lambda_powertools/event_handler/openapi/dependant.py +++ b/aws_lambda_powertools/event_handler/openapi/dependant.py @@ -24,6 +24,7 @@ create_response_field, get_flat_dependant, ) +from aws_lambda_powertools.event_handler.openapi.types import OpenAPIResponse, OpenAPIResponseContentModel """ This turns the opaque function signature into typed, validated models. @@ -145,6 +146,7 @@ def get_dependant( path: str, call: Callable[..., Any], name: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, ) -> Dependant: """ Returns a dependant model for a handler function. A dependant model is a model that contains @@ -158,6 +160,8 @@ def get_dependant( The handler function name: str, optional The name of the handler function + responses: List[Dict[int, OpenAPIResponse]], optional + The list of extra responses for the handler function Returns ------- @@ -195,6 +199,34 @@ def get_dependant( else: add_param_to_fields(field=param_field, dependant=dependant) + _add_return_annotation(dependant, endpoint_signature) + _add_extra_responses(dependant, responses) + + return dependant + + +def _add_extra_responses(dependant: Dependant, responses: Optional[Dict[int, OpenAPIResponse]]): + # Also add the optional extra responses to the dependant model. + if not responses: + return + + for response in responses.values(): + for schema in response.get("content", {}).values(): + if "model" in schema: + response_field = analyze_param( + param_name="return", + annotation=cast(OpenAPIResponseContentModel, schema)["model"], + value=None, + is_path_param=False, + is_response_param=True, + ) + if response_field is None: + raise AssertionError("Response field is None for response model") + + dependant.response_extra_models.append(response_field) + + +def _add_return_annotation(dependant: Dependant, endpoint_signature: inspect.Signature): # If the return annotation is not empty, add it to the dependant model. return_annotation = endpoint_signature.return_annotation if return_annotation is not inspect.Signature.empty: @@ -210,8 +242,6 @@ def get_dependant( dependant.return_param = param_field - return dependant - def is_body_param(*, param_field: ModelField, is_path_param: bool) -> bool: """ diff --git a/aws_lambda_powertools/event_handler/openapi/params.py b/aws_lambda_powertools/event_handler/openapi/params.py index bd542ba7932..78426cbc7c9 100644 --- a/aws_lambda_powertools/event_handler/openapi/params.py +++ b/aws_lambda_powertools/event_handler/openapi/params.py @@ -49,6 +49,7 @@ def __init__( cookie_params: Optional[List[ModelField]] = None, body_params: Optional[List[ModelField]] = None, return_param: Optional[ModelField] = None, + response_extra_models: Optional[List[ModelField]] = None, name: Optional[str] = None, call: Optional[Callable[..., Any]] = None, request_param_name: Optional[str] = None, @@ -64,6 +65,7 @@ def __init__( self.cookie_params = cookie_params or [] self.body_params = body_params or [] self.return_param = return_param or None + self.response_extra_models = response_extra_models or [] self.request_param_name = request_param_name self.websocket_param_name = websocket_param_name self.http_connection_param_name = http_connection_param_name diff --git a/aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py b/aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py index d8ffb0efa19..fcd644f39f5 100644 --- a/aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py +++ b/aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py @@ -1,16 +1,28 @@ -def generate_swagger_html(spec: str, js_url: str, css_url: str) -> str: +def generate_swagger_html(spec: str, path: str, swagger_js: str, swagger_css: str, swagger_base_url: str) -> str: """ Generate Swagger UI HTML page Parameters ---------- spec: str - The OpenAPI spec in the JSON format + The OpenAPI spec + path: str + The path to the Swagger documentation js_url: str The URL to the Swagger UI JavaScript file css_url: str The URL to the Swagger UI CSS file """ + + # If Swagger base URL is present, generate HTML content with linked CSS and JavaScript files + # If no Swagger base URL is provided, include CSS and JavaScript directly in the HTML + if swagger_base_url: + swagger_css_content = f"" + swagger_js_content = f"" + else: + swagger_css_content = f"" + swagger_js_content = f"" + return f""" @@ -21,7 +33,7 @@ def generate_swagger_html(spec: str, js_url: str, css_url: str) -> str: http-equiv="Cache-control" content="no-cache, no-store, must-revalidate" /> - + {swagger_css_content}
@@ -30,7 +42,7 @@ def generate_swagger_html(spec: str, js_url: str, css_url: str) -> str: - +{swagger_js_content}