From 0ed147eb7de0aee474937421074b86565f55f68f Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Fri, 5 Jan 2024 20:55:56 +0200 Subject: [PATCH 1/8] feature: add swagger --- cdk/service/api_construct.py | 24 ++++++++++++++---- cdk/service/constants.py | 4 +++ service/handlers/handle_create_order.py | 27 ++++++++++++--------- service/handlers/utils/rest_api_resolver.py | 14 ++--------- tests/integration/test_create_order.py | 6 ++--- tests/utils.py | 6 ++--- 6 files changed, 46 insertions(+), 35 deletions(-) diff --git a/cdk/service/api_construct.py b/cdk/service/api_construct.py index 1756c696..1e4bf2b1 100644 --- a/cdk/service/api_construct.py +++ b/cdk/service/api_construct.py @@ -20,10 +20,24 @@ def __init__(self, scope: Construct, id_: str, appconfig_app_name: str, is_produ self.lambda_role = self._build_lambda_role(self.api_db.db, self.api_db.idempotency_db) self.common_layer = self._build_common_layer() self.rest_api = self._build_api_gw() - api_resource: aws_apigateway.Resource = self.rest_api.root.add_resource('api').add_resource(constants.GW_RESOURCE) + api_resource: aws_apigateway.Resource = self.rest_api.root.add_resource('api') + orders_resource = api_resource.add_resource(constants.GW_RESOURCE) self.create_order_func = self._add_post_lambda_integration( - api_resource, self.lambda_role, self.api_db.db, appconfig_app_name, self.api_db.idempotency_db + orders_resource, self.lambda_role, self.api_db.db, appconfig_app_name, self.api_db.idempotency_db ) + + # GET /swagger + swagger_resource: aws_apigateway.Resource = self.rest_api.root.add_resource(constants.SWAGGER_RESOURCE) + swagger_resource.add_method(http_method='GET', integration=aws_apigateway.LambdaIntegration(handler=self.create_order_func)) + # GET /swagger.css + swagger_resource_css = self.rest_api.root.add_resource(constants.SWAGGER_CSS_RESOURCE) + swagger_resource_css.add_method(http_method='GET', integration=aws_apigateway.LambdaIntegration(handler=self.create_order_func)) + # GET /swagger.js + swagger_resource_js = self.rest_api.root.add_resource(constants.SWAGGER_JS_RESOURCE) + swagger_resource_js.add_method(http_method='GET', integration=aws_apigateway.LambdaIntegration(handler=self.create_order_func)) + + CfnOutput(self, id=constants.SWAGGER_URL, value=f'{self.rest_api.url}swagger').override_logical_id(constants.SWAGGER_URL) + self.monitoring = CrudMonitoring(self, id_, self.rest_api, self.api_db.db, self.api_db.idempotency_db, [self.create_order_func]) if is_production_env: @@ -92,7 +106,7 @@ def _build_common_layer(self) -> PythonLayerVersion: ) def _add_post_lambda_integration( - self, api_name: aws_apigateway.Resource, role: iam.Role, db: dynamodb.Table, appconfig_app_name: str, idempotency_table: dynamodb.Table + self, api_resource: aws_apigateway.Resource, role: iam.Role, db: dynamodb.Table, appconfig_app_name: str, idempotency_table: dynamodb.Table ) -> _lambda.Function: lambda_function = _lambda.Function( self, @@ -102,7 +116,7 @@ def _add_post_lambda_integration( handler='service.handlers.handle_create_order.lambda_handler', environment={ constants.POWERTOOLS_SERVICE_NAME: constants.SERVICE_NAME, # for logger, tracer and metrics - constants.POWER_TOOLS_LOG_LEVEL: 'DEBUG', # for logger + constants.POWER_TOOLS_LOG_LEVEL: 'INFO', # for logger 'CONFIGURATION_APP': appconfig_app_name, # for feature flags 'CONFIGURATION_ENV': constants.ENVIRONMENT, # for feature flags 'CONFIGURATION_NAME': constants.CONFIGURATION_NAME, # for feature flags @@ -124,5 +138,5 @@ def _add_post_lambda_integration( ) # POST /api/orders/ - api_name.add_method(http_method='POST', integration=aws_apigateway.LambdaIntegration(handler=lambda_function)) + api_resource.add_method(http_method='POST', integration=aws_apigateway.LambdaIntegration(handler=lambda_function)) return lambda_function diff --git a/cdk/service/constants.py b/cdk/service/constants.py index 351e23d9..f7a99fe5 100644 --- a/cdk/service/constants.py +++ b/cdk/service/constants.py @@ -9,6 +9,10 @@ APIGATEWAY = 'Apigateway' MONITORING_TOPIC = 'MonitoringTopic' GW_RESOURCE = 'orders' +SWAGGER_RESOURCE = 'swagger' +SWAGGER_CSS_RESOURCE = 'swagger.css' +SWAGGER_JS_RESOURCE = 'swagger.js' +SWAGGER_URL = 'SwaggerURL' LAMBDA_LAYER_NAME = 'common' API_HANDLER_LAMBDA_MEMORY_SIZE = 128 # MB API_HANDLER_LAMBDA_TIMEOUT = 10 # seconds diff --git a/service/handlers/handle_create_order.py b/service/handlers/handle_create_order.py index 7f66869b..18f40c9e 100644 --- a/service/handlers/handle_create_order.py +++ b/service/handlers/handle_create_order.py @@ -1,10 +1,10 @@ from typing import Any from aws_lambda_env_modeler import get_environment_variables, init_environment_variables +from aws_lambda_powertools.event_handler.openapi.params import Body from aws_lambda_powertools.logging import correlation_paths from aws_lambda_powertools.metrics import MetricUnit -from aws_lambda_powertools.utilities.parser import parse -from aws_lambda_powertools.utilities.parser.envelopes import ApiGatewayEnvelope +from aws_lambda_powertools.shared.types import Annotated from aws_lambda_powertools.utilities.typing import LambdaContext from service.handlers.models.dynamic_configuration import MyConfiguration @@ -17,22 +17,25 @@ from service.models.output import CreateOrderOutput -@app.post(ORDERS_PATH) -def handle_create_order() -> dict[str, Any]: +@app.post( + ORDERS_PATH, + summary='Retrieves a todo item', + description='Loads a todo item identified by the `todo_id`', + response_description='The todo object', + responses={ + 200: CreateOrderOutput.model_json_schema(), + 501: {'error': 'internal server error'}, + }, + tags=['Crud'], +) +def handle_create_order(create_input: Annotated[CreateOrderRequest, Body(embed=False, media_type='application/json')]) -> dict[str, Any]: env_vars: MyHandlerEnvVars = get_environment_variables(model=MyHandlerEnvVars) logger.debug('environment variables', env_vars=env_vars.model_dump()) + logger.info('got create order request', order=create_input.model_dump()) my_configuration = parse_configuration(model=MyConfiguration) logger.debug('fetched dynamic configuration', configuration=my_configuration.model_dump()) - # we want to extract and parse the HTTP body from the api gw envelope - create_input: CreateOrderRequest = parse( - event=app.current_event.raw_event, - model=CreateOrderRequest, - envelope=ApiGatewayEnvelope, - ) - logger.info('got create order request', order=create_input.model_dump()) - metrics.add_metric(name='ValidCreateOrderEvents', unit=MetricUnit.Count, value=1) response: CreateOrderOutput = create_order( order_request=create_input, diff --git a/service/handlers/utils/rest_api_resolver.py b/service/handlers/utils/rest_api_resolver.py index d5fa0ea4..078bbd53 100644 --- a/service/handlers/utils/rest_api_resolver.py +++ b/service/handlers/utils/rest_api_resolver.py @@ -2,14 +2,14 @@ from http import HTTPStatus from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response, content_types -from pydantic import ValidationError from service.handlers.utils.observability import logger from service.models.exceptions import DynamicConfigurationException, InternalServerException ORDERS_PATH = '/api/orders/' -app = APIGatewayRestResolver() +app = APIGatewayRestResolver(enable_validation=True) +app.enable_swagger(path='/swagger') @app.exception_handler(DynamicConfigurationException) @@ -22,16 +22,6 @@ def handle_dynamic_config_error(ex: DynamicConfigurationException): # receives ) -@app.exception_handler(ValidationError) -def handle_input_validation_error(ex: ValidationError): # receives exception raised - logger.exception('event failed input validation') - return Response( - status_code=HTTPStatus.BAD_REQUEST, - content_type=content_types.APPLICATION_JSON, - body=json.dumps({'error': 'invalid input'}), # readiness: change pydantic error to a user friendly error - ) - - @app.exception_handler(InternalServerException) def handle_internal_server_error(ex: InternalServerException): # receives exception raised logger.exception('finished handling request with internal error') diff --git a/tests/integration/test_create_order.py b/tests/integration/test_create_order.py index 2f2ea114..30ba26bd 100644 --- a/tests/integration/test_create_order.py +++ b/tests/integration/test_create_order.py @@ -98,10 +98,10 @@ def test_handler_bad_request(mocker): # When: The order creation lambda_handler is called with invalid input response = call_create_order(generate_api_gw_event({'order_item_count': 5})) - # Then: Validate the response is a bad request and error message is correct - assert response['statusCode'] == HTTPStatus.BAD_REQUEST + # Then: Validate the response is a unprocessable entity and error message is correct + assert response['statusCode'] == HTTPStatus.UNPROCESSABLE_ENTITY body_dict = json.loads(response['body']) - assert body_dict == {'error': 'invalid input'} + assert body_dict.get('detail') == [{'loc': ['body', 'customer_name'], 'type': 'missing'}] def test_handler_failed_appconfig_fetch(mocker): diff --git a/tests/utils.py b/tests/utils.py index 758c5623..60868b9b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -32,7 +32,7 @@ def generate_api_gw_event(body: Optional[dict[str, Any]]) -> dict[str, Any]: 'resource': '/api/orders', 'path': '/api/orders', 'httpMethod': 'POST', - 'headers': {'Header1': 'value1', 'Header2': 'value2'}, + 'headers': {'Content-Type': 'application/json', 'Header2': 'value2'}, 'multiValueHeaders': {'Header1': ['value1'], 'Header2': ['value1', 'value2']}, 'queryStringParameters': {'parameter1': 'value1', 'parameter2': 'value'}, 'multiValueQueryStringParameters': {'parameter1': ['value1', 'value2'], 'parameter2': ['value']}, @@ -76,8 +76,8 @@ def generate_api_gw_event(body: Optional[dict[str, Any]]) -> dict[str, Any]: }, 'pathParameters': None, 'stageVariables': None, - 'body': 'Hello from Lambda!' if body is None else json.dumps(body), - 'isBase64Encoded': True, + 'body': '' if body is None else json.dumps(body), + 'isBase64Encoded': False, } From 689e8b32c6c2ee8e92d6b931a7b0ce9d048ae6d3 Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Fri, 5 Jan 2024 20:59:25 +0200 Subject: [PATCH 2/8] summary --- service/handlers/handle_create_order.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/service/handlers/handle_create_order.py b/service/handlers/handle_create_order.py index 18f40c9e..e70d89a4 100644 --- a/service/handlers/handle_create_order.py +++ b/service/handlers/handle_create_order.py @@ -19,14 +19,14 @@ @app.post( ORDERS_PATH, - summary='Retrieves a todo item', - description='Loads a todo item identified by the `todo_id`', - response_description='The todo object', + summary='Create an order', + description='Create an order identified by the body payload`', + response_description='The created order', responses={ 200: CreateOrderOutput.model_json_schema(), 501: {'error': 'internal server error'}, }, - tags=['Crud'], + tags=['CRUD'], ) def handle_create_order(create_input: Annotated[CreateOrderRequest, Body(embed=False, media_type='application/json')]) -> dict[str, Any]: env_vars: MyHandlerEnvVars = get_environment_variables(model=MyHandlerEnvVars) From 456e518558585cbfe2538bddbbc19b74e1fac4d1 Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Fri, 5 Jan 2024 22:42:39 +0200 Subject: [PATCH 3/8] a --- Makefile | 2 +- docs/swagger/openapi.json | 117 ++++++++++++++++++++ docs/swagger/swagger.md | 6 + mkdocs.yml | 15 +-- pyproject.toml | 1 + service/handlers/handle_create_order.py | 18 ++- service/handlers/utils/rest_api_resolver.py | 10 +- service/models/output.py | 6 + tests/e2e/test_create_order.py | 6 +- tests/integration/test_create_order.py | 2 + 10 files changed, 152 insertions(+), 31 deletions(-) create mode 100644 docs/swagger/openapi.json create mode 100644 docs/swagger/swagger.md diff --git a/Makefile b/Makefile index deea4ab8..69aaf995 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ integration: e2e: poetry run pytest tests/e2e --cov-config=.coveragerc --cov=service --cov-report xml -pr: deps pre-commit complex lint lint-docs unit deploy coverage-tests e2e +pr: deps format pre-commit complex lint lint-docs unit deploy coverage-tests e2e coverage-tests: poetry run pytest tests/unit tests/integration --cov-config=.coveragerc --cov=service --cov-report xml diff --git a/docs/swagger/openapi.json b/docs/swagger/openapi.json new file mode 100644 index 00000000..0bbe9817 --- /dev/null +++ b/docs/swagger/openapi.json @@ -0,0 +1,117 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "AWS Lambda Handler Cookbook", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/prod" + } + ], + "paths": { + "/api/orders": { + "post": { + "tags": [ + "CRUD" + ], + "summary": "Create an order", + "description": "Create an order identified by the body payload`", + "operationId": "handle_create_order_api_orders_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateOrderRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "The created order", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateOrderOutput" + } + } + } + }, + "501": { + "description": "internal server error", + "content": { + "application/json": { + "schema": { + "properties": { + "error": { + "type": "string", + "title": "Error", + "default": "internal server error" + } + }, + "type": "object", + "title": "InternalServerErrorOutput" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "CreateOrderOutput": { + "properties": { + "name": { + "type": "string", + "maxLength": 20, + "minLength": 1, + "title": "Name" + }, + "item_count": { + "type": "integer", + "exclusiveMinimum": 0, + "title": "Item Count" + }, + "id": { + "type": "string", + "maxLength": 36, + "minLength": 36, + "title": "Id" + } + }, + "type": "object", + "required": [ + "name", + "item_count", + "id" + ], + "title": "CreateOrderOutput" + }, + "CreateOrderRequest": { + "properties": { + "customer_name": { + "type": "string", + "maxLength": 20, + "minLength": 1, + "title": "Customer Name" + }, + "order_item_count": { + "type": "integer", + "exclusiveMinimum": 0, + "title": "Order Item Count" + } + }, + "type": "object", + "required": [ + "customer_name", + "order_item_count" + ], + "title": "CreateOrderRequest" + } + } + } +} \ No newline at end of file diff --git a/docs/swagger/swagger.md b/docs/swagger/swagger.md new file mode 100644 index 00000000..95276d1b --- /dev/null +++ b/docs/swagger/swagger.md @@ -0,0 +1,6 @@ +--- +title: Swagger +description: Swagger Documentation +--- + +!!swagger openapi.json!! diff --git a/mkdocs.yml b/mkdocs.yml index b4d1462c..24f3ac49 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,6 +9,7 @@ nav: - Getting Started: getting_started.md - CDK: cdk.md - Pipeline: pipeline.md + - Swagger: swagger/swagger.md - Best Practices: - best_practices/logger.md - best_practices/tracer.md @@ -80,19 +81,7 @@ copyright: Copyright © 2023 Ran Isenberg plugins: - git-revision-date - search - - glightbox: - touchNavigation: true - loop: false - effect: zoom - slide_effect: slide - width: 100% - height: auto - zoomable: true - draggable: true - skip_classes: - - custom-skip-class-name - auto_caption: false - caption_position: bottom + - render_swagger extra_css: - stylesheets/extra.css diff --git a/pyproject.toml b/pyproject.toml index 50dab0c2..9445ce32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ mypy = "*" types-requests = "*" toml = "*" poetry-plugin-export = "*" +mkdocs-render-swagger-plugin = "*" [tool.pytest.ini_options] testpaths = "tests" diff --git a/service/handlers/handle_create_order.py b/service/handlers/handle_create_order.py index e70d89a4..a49d795f 100644 --- a/service/handlers/handle_create_order.py +++ b/service/handlers/handle_create_order.py @@ -14,7 +14,7 @@ from service.handlers.utils.rest_api_resolver import ORDERS_PATH, app from service.logic.create_order import create_order from service.models.input import CreateOrderRequest -from service.models.output import CreateOrderOutput +from service.models.output import CreateOrderOutput, InternalServerErrorOutput @app.post( @@ -23,12 +23,18 @@ description='Create an order identified by the body payload`', response_description='The created order', responses={ - 200: CreateOrderOutput.model_json_schema(), - 501: {'error': 'internal server error'}, + 200: { + 'description': 'The created order', + 'content': {'application/json': {'schema': {'$ref': '#/components/schemas/CreateOrderOutput'}}}, + }, + 501: { + 'description': 'Internal server error', + 'content': {'application/json': {'schema': InternalServerErrorOutput.model_json_schema()}}, + }, }, - tags=['CRUD'], + tags=['CRUD', 'Orders'], ) -def handle_create_order(create_input: Annotated[CreateOrderRequest, Body(embed=False, media_type='application/json')]) -> dict[str, Any]: +def handle_create_order(create_input: Annotated[CreateOrderRequest, Body(embed=False, media_type='application/json')]) -> CreateOrderOutput: env_vars: MyHandlerEnvVars = get_environment_variables(model=MyHandlerEnvVars) logger.debug('environment variables', env_vars=env_vars.model_dump()) logger.info('got create order request', order=create_input.model_dump()) @@ -44,7 +50,7 @@ def handle_create_order(create_input: Annotated[CreateOrderRequest, Body(embed=F ) logger.info('finished handling create order request') - return response.model_dump() + return response @init_environment_variables(model=MyHandlerEnvVars) diff --git a/service/handlers/utils/rest_api_resolver.py b/service/handlers/utils/rest_api_resolver.py index 078bbd53..343677d4 100644 --- a/service/handlers/utils/rest_api_resolver.py +++ b/service/handlers/utils/rest_api_resolver.py @@ -1,10 +1,10 @@ -import json from http import HTTPStatus from aws_lambda_powertools.event_handler import APIGatewayRestResolver, Response, content_types from service.handlers.utils.observability import logger from service.models.exceptions import DynamicConfigurationException, InternalServerException +from service.models.output import InternalServerErrorOutput ORDERS_PATH = '/api/orders/' @@ -16,9 +16,7 @@ def handle_dynamic_config_error(ex: DynamicConfigurationException): # receives exception raised logger.exception('failed to load dynamic configuration from AppConfig') return Response( - status_code=HTTPStatus.INTERNAL_SERVER_ERROR, - content_type=content_types.APPLICATION_JSON, - body=json.dumps({'error': 'internal server error'}), + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, content_type=content_types.APPLICATION_JSON, body=InternalServerErrorOutput().model_dump() ) @@ -26,7 +24,5 @@ def handle_dynamic_config_error(ex: DynamicConfigurationException): # receives def handle_internal_server_error(ex: InternalServerException): # receives exception raised logger.exception('finished handling request with internal error') return Response( - status_code=HTTPStatus.INTERNAL_SERVER_ERROR, - content_type=content_types.APPLICATION_JSON, - body=json.dumps({'error': 'internal server error'}), + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, content_type=content_types.APPLICATION_JSON, body=InternalServerErrorOutput().model_dump() ) diff --git a/service/models/output.py b/service/models/output.py index 20959aa4..1c5562d1 100644 --- a/service/models/output.py +++ b/service/models/output.py @@ -1,3 +1,5 @@ +from pydantic import BaseModel + from service.models.order import Order @@ -6,3 +8,7 @@ # The output can be a subject of what order contains, i.e just the id class CreateOrderOutput(Order): pass + + +class InternalServerErrorOutput(BaseModel): + error: str = 'internal server error' diff --git a/tests/e2e/test_create_order.py b/tests/e2e/test_create_order.py index 304f0c5c..c16fc3d4 100644 --- a/tests/e2e/test_create_order.py +++ b/tests/e2e/test_create_order.py @@ -49,7 +49,5 @@ def test_handler_bad_request(api_gw_url): # When: Making a POST request to the API Gateway URL response = requests.post(api_gw_url, data=body_str) - # Then: Validate the response status is 400 BAD REQUEST and body is empty - assert response.status_code == HTTPStatus.BAD_REQUEST - body_dict = json.loads(response.text) - assert body_dict == {'error': 'invalid input'} + # Then: Validate the response status is 422 UNPROCESSABLE_ENTITY + assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY diff --git a/tests/integration/test_create_order.py b/tests/integration/test_create_order.py index 30ba26bd..efda1904 100644 --- a/tests/integration/test_create_order.py +++ b/tests/integration/test_create_order.py @@ -89,6 +89,8 @@ def test_internal_server_error(mocker, table_name: str): # Then: Ensure the response reflects an internal server error assert response['statusCode'] == HTTPStatus.INTERNAL_SERVER_ERROR + body_dict = json.loads(response['body']) + assert body_dict == {'error': 'internal server error'} def test_handler_bad_request(mocker): From 70bdf8596099e25f791104a6b5ac88b990450206 Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Fri, 12 Jan 2024 09:52:14 +0200 Subject: [PATCH 4/8] fixes --- README.md | 1 + docs/index.md | 1 + service/handlers/handle_create_order.py | 4 ++-- service/handlers/utils/rest_api_resolver.py | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cebfdeea..264a9300 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ This project aims to reduce cognitive load and answer these questions for you by - REST API protected by WAF with four AWS managed rules in production deployment - CloudWatch dashboards - High level and low level including CloudWatch alarms - Unit, infrastructure, security, integration and end to end tests. +- Automatically generated OpenAPI endpoint: /swagger with Pydnatic schemas for both requests and responses ## CDK Deployment diff --git a/docs/index.md b/docs/index.md index 27aaa9e3..c4d7f408 100644 --- a/docs/index.md +++ b/docs/index.md @@ -46,6 +46,7 @@ This project aims to reduce cognitive load and answer these questions for you by - Idempotent API - REST API protected by WAF with four AWS managed rules in production deployment - Unit, infrastructure, security, integration and E2E tests. +- Automatically generated OpenAPI endpoint: /swagger with Pydnatic schemas for both requests and responses The GitHub template project can be found at [https://github.com/ran-isenberg/aws-lambda-handler-cookbook](https://github.com/ran-isenberg/aws-lambda-handler-cookbook){:target="_blank" rel="noopener"}. diff --git a/service/handlers/handle_create_order.py b/service/handlers/handle_create_order.py index a49d795f..d8570ca3 100644 --- a/service/handlers/handle_create_order.py +++ b/service/handlers/handle_create_order.py @@ -20,7 +20,7 @@ @app.post( ORDERS_PATH, summary='Create an order', - description='Create an order identified by the body payload`', + description='Create an order identified by the body payload', response_description='The created order', responses={ 200: { @@ -32,7 +32,7 @@ 'content': {'application/json': {'schema': InternalServerErrorOutput.model_json_schema()}}, }, }, - tags=['CRUD', 'Orders'], + tags=['CRUD'], ) def handle_create_order(create_input: Annotated[CreateOrderRequest, Body(embed=False, media_type='application/json')]) -> CreateOrderOutput: env_vars: MyHandlerEnvVars = get_environment_variables(model=MyHandlerEnvVars) diff --git a/service/handlers/utils/rest_api_resolver.py b/service/handlers/utils/rest_api_resolver.py index 343677d4..603a3a58 100644 --- a/service/handlers/utils/rest_api_resolver.py +++ b/service/handlers/utils/rest_api_resolver.py @@ -9,7 +9,7 @@ ORDERS_PATH = '/api/orders/' app = APIGatewayRestResolver(enable_validation=True) -app.enable_swagger(path='/swagger') +app.enable_swagger(path='/swagger', title='AWS Lambda Handler Cookbook - Orders Service') @app.exception_handler(DynamicConfigurationException) From 19453f7d4160a1e136f9458b78b076f831cd11ab Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Fri, 19 Jan 2024 17:38:21 +0200 Subject: [PATCH 5/8] a --- service/handlers/handle_create_order.py | 10 +++++++--- service/models/output.py | 10 ++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/service/handlers/handle_create_order.py b/service/handlers/handle_create_order.py index d8570ca3..969a77ed 100644 --- a/service/handlers/handle_create_order.py +++ b/service/handlers/handle_create_order.py @@ -14,7 +14,7 @@ from service.handlers.utils.rest_api_resolver import ORDERS_PATH, app from service.logic.create_order import create_order from service.models.input import CreateOrderRequest -from service.models.output import CreateOrderOutput, InternalServerErrorOutput +from service.models.output import CreateOrderOutput, InternalServerErrorOutput, InvalidRestApiRequest @app.post( @@ -25,11 +25,15 @@ responses={ 200: { 'description': 'The created order', - 'content': {'application/json': {'schema': {'$ref': '#/components/schemas/CreateOrderOutput'}}}, + 'content': {'application/json': {'model': CreateOrderOutput}}, + }, + 442: { + 'description': 'Invalid create order request', + 'content': {'application/json': {'model': InvalidRestApiRequest}}, }, 501: { 'description': 'Internal server error', - 'content': {'application/json': {'schema': InternalServerErrorOutput.model_json_schema()}}, + 'content': {'application/json': {'model': InternalServerErrorOutput}}, }, }, tags=['CRUD'], diff --git a/service/models/output.py b/service/models/output.py index 1c5562d1..bd7bf374 100644 --- a/service/models/output.py +++ b/service/models/output.py @@ -1,3 +1,5 @@ +from typing import List + from pydantic import BaseModel from service.models.order import Order @@ -12,3 +14,11 @@ class CreateOrderOutput(Order): class InternalServerErrorOutput(BaseModel): error: str = 'internal server error' + + +class Error(BaseModel): + x: str + + +class InvalidRestApiRequest(BaseModel): + details: List[Error] From e0d4062f095fe0763c362b34f43b1d5ca5dec851 Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Mon, 22 Jan 2024 21:52:49 +0200 Subject: [PATCH 6/8] a --- .pre-commit-config.yaml | 2 +- cdk/service/api_construct.py | 27 +- .../configuration/configuration_construct.py | 4 +- docs/swagger/openapi.json | 279 +++++++++++------- package-lock.json | 8 +- package.json | 2 +- poetry.lock | 272 +++++++++-------- service/models/input.py | 6 +- service/models/order.py | 8 +- service/models/output.py | 14 +- 10 files changed, 366 insertions(+), 256 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 119a9e93..d4fba087 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: exclude: "^(?!helpers/)" - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.1.13 + rev: v0.1.14 hooks: # Run the Ruff linter. - id: ruff diff --git a/cdk/service/api_construct.py b/cdk/service/api_construct.py index 1e4bf2b1..fe5a1f06 100644 --- a/cdk/service/api_construct.py +++ b/cdk/service/api_construct.py @@ -25,25 +25,26 @@ def __init__(self, scope: Construct, id_: str, appconfig_app_name: str, is_produ self.create_order_func = self._add_post_lambda_integration( orders_resource, self.lambda_role, self.api_db.db, appconfig_app_name, self.api_db.idempotency_db ) - - # GET /swagger - swagger_resource: aws_apigateway.Resource = self.rest_api.root.add_resource(constants.SWAGGER_RESOURCE) - swagger_resource.add_method(http_method='GET', integration=aws_apigateway.LambdaIntegration(handler=self.create_order_func)) - # GET /swagger.css - swagger_resource_css = self.rest_api.root.add_resource(constants.SWAGGER_CSS_RESOURCE) - swagger_resource_css.add_method(http_method='GET', integration=aws_apigateway.LambdaIntegration(handler=self.create_order_func)) - # GET /swagger.js - swagger_resource_js = self.rest_api.root.add_resource(constants.SWAGGER_JS_RESOURCE) - swagger_resource_js.add_method(http_method='GET', integration=aws_apigateway.LambdaIntegration(handler=self.create_order_func)) - - CfnOutput(self, id=constants.SWAGGER_URL, value=f'{self.rest_api.url}swagger').override_logical_id(constants.SWAGGER_URL) - + self._build_swagger_endpoints(rest_api=self.rest_api, dest_func=self.create_order_func) self.monitoring = CrudMonitoring(self, id_, self.rest_api, self.api_db.db, self.api_db.idempotency_db, [self.create_order_func]) if is_production_env: # add WAF self.waf = WafToApiGatewayConstruct(self, f'{id_}waf', self.rest_api) + def _build_swagger_endpoints(self, rest_api: aws_apigateway.RestApi, dest_func: _lambda.Function) -> None: + # GET /swagger + swagger_resource: aws_apigateway.Resource = rest_api.root.add_resource(constants.SWAGGER_RESOURCE) + swagger_resource.add_method(http_method='GET', integration=aws_apigateway.LambdaIntegration(handler=dest_func)) + # GET /swagger.css + swagger_resource_css = rest_api.root.add_resource(constants.SWAGGER_CSS_RESOURCE) + swagger_resource_css.add_method(http_method='GET', integration=aws_apigateway.LambdaIntegration(handler=dest_func)) + # GET /swagger.js + swagger_resource_js = rest_api.root.add_resource(constants.SWAGGER_JS_RESOURCE) + swagger_resource_js.add_method(http_method='GET', integration=aws_apigateway.LambdaIntegration(handler=dest_func)) + + CfnOutput(self, id=constants.SWAGGER_URL, value=f'{rest_api.url}swagger').override_logical_id(constants.SWAGGER_URL) + def _build_api_gw(self) -> aws_apigateway.RestApi: rest_api: aws_apigateway.RestApi = aws_apigateway.RestApi( self, diff --git a/cdk/service/configuration/configuration_construct.py b/cdk/service/configuration/configuration_construct.py index ab2f1548..8d7c1d7b 100644 --- a/cdk/service/configuration/configuration_construct.py +++ b/cdk/service/configuration/configuration_construct.py @@ -29,14 +29,14 @@ def __init__(self, scope: Construct, id_: str, environment: str, service_name: s self.config_app = appconfig.Application( self, id=self.app_name, - name=self.app_name, + application_name=self.app_name, ) self.config_env = appconfig.Environment( self, id=f'{id_}env', application=self.config_app, - name=environment, + environment_name=environment, ) # zero minutes, zero bake, 100 growth all at once diff --git a/docs/swagger/openapi.json b/docs/swagger/openapi.json index 0bbe9817..32d50db7 100644 --- a/docs/swagger/openapi.json +++ b/docs/swagger/openapi.json @@ -1,117 +1,196 @@ { "openapi": "3.0.0", "info": { - "title": "AWS Lambda Handler Cookbook", - "version": "1.0.0" + "title": "AWS Lambda Handler Cookbook - Orders Service", + "version": "1.0.0" }, "servers": [ - { - "url": "/prod" - } + { + "url": "/prod" + } ], "paths": { - "/api/orders": { - "post": { - "tags": [ - "CRUD" - ], - "summary": "Create an order", - "description": "Create an order identified by the body payload`", - "operationId": "handle_create_order_api_orders_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateOrderRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "The created order", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateOrderOutput" - } - } - } - }, - "501": { - "description": "internal server error", - "content": { - "application/json": { - "schema": { - "properties": { - "error": { - "type": "string", - "title": "Error", - "default": "internal server error" - } - }, - "type": "object", - "title": "InternalServerErrorOutput" - } - } - } - } + "/api/orders": { + "post": { + "tags": [ + "CRUD" + ], + "summary": "Create an order", + "description": "Create an order identified by the body payload", + "operationId": "handle_create_order_api_orders_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateOrderRequest" } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "The created order", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateOrderOutput" + } + } + } + }, + "442": { + "description": "Invalid create order request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRestApiRequest" + } + } + } + }, + "501": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorOutput" + } + } + } } + } } + } }, "components": { - "schemas": { - "CreateOrderOutput": { - "properties": { - "name": { - "type": "string", - "maxLength": 20, - "minLength": 1, - "title": "Name" - }, - "item_count": { - "type": "integer", - "exclusiveMinimum": 0, - "title": "Item Count" - }, - "id": { - "type": "string", - "maxLength": 36, - "minLength": 36, - "title": "Id" - } - }, - "type": "object", - "required": [ - "name", - "item_count", - "id" - ], - "title": "CreateOrderOutput" + "schemas": { + "CreateOrderOutput": { + "properties": { + "name": { + "type": "string", + "maxLength": 20, + "minLength": 1, + "title": "Name", + "description": "Customer name" }, - "CreateOrderRequest": { - "properties": { - "customer_name": { - "type": "string", - "maxLength": 20, - "minLength": 1, - "title": "Customer Name" - }, - "order_item_count": { - "type": "integer", - "exclusiveMinimum": 0, - "title": "Order Item Count" - } + "item_count": { + "type": "integer", + "exclusiveMinimum": 0.0, + "title": "Item Count", + "description": "Amount of items in order" + }, + "id": { + "type": "string", + "maxLength": 36, + "minLength": 36, + "title": "Id", + "description": "Order ID as UUID" + } + }, + "type": "object", + "required": [ + "name", + "item_count", + "id" + ], + "title": "CreateOrderOutput" + }, + "CreateOrderRequest": { + "properties": { + "customer_name": { + "type": "string", + "maxLength": 20, + "minLength": 1, + "title": "Customer Name", + "description": "Customer name" + }, + "order_item_count": { + "type": "integer", + "exclusiveMinimum": 0.0, + "title": "Order Item Count", + "description": "Amount of items in order" + } + }, + "type": "object", + "required": [ + "customer_name", + "order_item_count" + ], + "title": "CreateOrderRequest" + }, + "InternalServerErrorOutput": { + "properties": { + "error": { + "type": "string", + "title": "Error", + "description": "Error description", + "default": "internal server error" + } + }, + "type": "object", + "title": "InternalServerErrorOutput" + }, + "InvalidRestApiRequest": { + "properties": { + "details": { + "items": { + "$ref": "#/components/schemas/PydanticError" + }, + "type": "array", + "title": "Details", + "description": "Error details" + } + }, + "type": "object", + "required": [ + "details" + ], + "title": "InvalidRestApiRequest" + }, + "PydanticError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Loc", + "description": "Error location" + }, + "type": { + "type": "string", + "title": "Type", + "description": "Error type" + }, + "msg": { + "anyOf": [ + { + "type": "string" }, - "type": "object", - "required": [ - "customer_name", - "order_item_count" - ], - "title": "CreateOrderRequest" + { + "type": "null" + } + ], + "title": "Msg", + "description": "Error message" } + }, + "type": "object", + "required": [ + "loc", + "type", + "msg" + ], + "title": "PydanticError" } + } } -} \ No newline at end of file + } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5c1db746..9b63121e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,13 +5,13 @@ "packages": { "": { "dependencies": { - "aws-cdk": "2.121.1" + "aws-cdk": "2.122.0" } }, "node_modules/aws-cdk": { - "version": "2.121.1", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.121.1.tgz", - "integrity": "sha512-T8UFuNGDnXNmcHagCGeQ8xQA3zU/o2ENuyGXO/ho+U3KyYs7tnWIr++ovrp2FvhiBpmBv6SuKiueBA6OW7utBw==", + "version": "2.122.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.122.0.tgz", + "integrity": "sha512-WqiVTedcuW4LjH4WqtQncliUdeDa9j9xgu3II8Qd1HmCZotbzBorYIHDvOJ+m3ovIzd9DL+hNq9PPUqxtBe0VQ==", "bin": { "cdk": "bin/cdk" }, diff --git a/package.json b/package.json index bc2e9478..b944b22d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "aws-cdk": "2.121.1" + "aws-cdk": "2.122.0" } } \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index d1e8ed9e..912b63b2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -32,17 +32,17 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "aws-cdk-asset-awscli-v1" -version = "2.2.201" +version = "2.2.202" description = "A library that contains the AWS CLI for use in Lambda Layers" optional = false -python-versions = "~=3.7" +python-versions = "~=3.8" files = [ - {file = "aws-cdk.asset-awscli-v1-2.2.201.tar.gz", hash = "sha256:88d1c269fd5cf8c9f6e0464ed22e2d4f269dfd5b36b8c4d37687bdba9c269839"}, - {file = "aws_cdk.asset_awscli_v1-2.2.201-py3-none-any.whl", hash = "sha256:56fe2ef91d3c8d33559aa32d2130e5f35f23af1fb82f06648ebbc82ffe0a5879"}, + {file = "aws-cdk.asset-awscli-v1-2.2.202.tar.gz", hash = "sha256:3ef87d6530736b3a7b0f777fe3b4297994dd40c3ce9306d95f80f48fb18036e8"}, + {file = "aws_cdk.asset_awscli_v1-2.2.202-py3-none-any.whl", hash = "sha256:96205ea2e5e132ec52fabfff37ea25b9b859498f167d05b32564c949822cd331"}, ] [package.dependencies] -jsii = ">=1.91.0,<2.0.0" +jsii = ">=1.93.0,<2.0.0" publication = ">=0.0.3" typeguard = ">=2.13.3,<2.14.0" @@ -80,17 +80,17 @@ typeguard = ">=2.13.3,<2.14.0" [[package]] name = "aws-cdk-aws-appconfig-alpha" -version = "2.121.1a0" +version = "2.122.0a0" description = "An experimental construct library for AWS AppConfig." optional = false python-versions = "~=3.8" files = [ - {file = "aws-cdk.aws-appconfig-alpha-2.121.1a0.tar.gz", hash = "sha256:749abe415aefcd5825030b0bf0a8f61af6fe29afdf97f47e19b5951efbb5473c"}, - {file = "aws_cdk.aws_appconfig_alpha-2.121.1a0-py3-none-any.whl", hash = "sha256:7abb4342b0fc190109dc35de5498402df033b10ddb160fb0c40180761708b28a"}, + {file = "aws-cdk.aws-appconfig-alpha-2.122.0a0.tar.gz", hash = "sha256:145637ae25b8760c3ca836b173927cb8f9226ef1eb04b87ca3b05d29f6c1599d"}, + {file = "aws_cdk.aws_appconfig_alpha-2.122.0a0-py3-none-any.whl", hash = "sha256:952f7b84fd6ae8fa618e3ddebe38c1f8347d42f1702ff63b8f694cec1edd96ce"}, ] [package.dependencies] -aws-cdk-lib = ">=2.121.1,<3.0.0" +aws-cdk-lib = ">=2.122.0,<3.0.0" constructs = ">=10.0.0,<11.0.0" jsii = ">=1.94.0,<2.0.0" publication = ">=0.0.3" @@ -98,17 +98,17 @@ typeguard = ">=2.13.3,<2.14.0" [[package]] name = "aws-cdk-aws-lambda-python-alpha" -version = "2.121.1a0" +version = "2.122.0a0" description = "The CDK Construct Library for AWS Lambda in Python" optional = false python-versions = "~=3.8" files = [ - {file = "aws-cdk.aws-lambda-python-alpha-2.121.1a0.tar.gz", hash = "sha256:eed47c21ebe777112c51d89368d31ab1a68b05d3d9b31f6fd9ad1c26b90d4fe8"}, - {file = "aws_cdk.aws_lambda_python_alpha-2.121.1a0-py3-none-any.whl", hash = "sha256:d3e2b1457b9866870d17b61fc354d5378c1af7657b770c576c34ccf94d2c1917"}, + {file = "aws-cdk.aws-lambda-python-alpha-2.122.0a0.tar.gz", hash = "sha256:a053333b346da6eed20c5dbe177ae85a1858849780aa05a0b5896f1646f88e1e"}, + {file = "aws_cdk.aws_lambda_python_alpha-2.122.0a0-py3-none-any.whl", hash = "sha256:8b282c8c3a79e7b1ce19bb4694db850adf56d1f49a16f698a6a679725dbfc566"}, ] [package.dependencies] -aws-cdk-lib = ">=2.121.1,<3.0.0" +aws-cdk-lib = ">=2.122.0,<3.0.0" constructs = ">=10.0.0,<11.0.0" jsii = ">=1.94.0,<2.0.0" publication = ">=0.0.3" @@ -116,17 +116,17 @@ typeguard = ">=2.13.3,<2.14.0" [[package]] name = "aws-cdk-aws-redshift-alpha" -version = "2.121.1a0" +version = "2.122.0a0" description = "The CDK Construct Library for AWS::Redshift" optional = false python-versions = "~=3.8" files = [ - {file = "aws-cdk.aws-redshift-alpha-2.121.1a0.tar.gz", hash = "sha256:c1847f90742618247ee628c5408cda44e4ae20c5cc8ec27978340e21e1dc3ece"}, - {file = "aws_cdk.aws_redshift_alpha-2.121.1a0-py3-none-any.whl", hash = "sha256:959dcc8aa4d4f8579ef09aa50146fd48ca813c5c9c0d8d0590b84be1e9a8b54a"}, + {file = "aws-cdk.aws-redshift-alpha-2.122.0a0.tar.gz", hash = "sha256:f4b04b6b7e48f8c7e33fd93b8072abe00f08c2f429f1b84b77d3936448ccdad1"}, + {file = "aws_cdk.aws_redshift_alpha-2.122.0a0-py3-none-any.whl", hash = "sha256:289c0be5b541e361bd86c03e0b93c070f0baffc8889a81929e192190d86c6027"}, ] [package.dependencies] -aws-cdk-lib = ">=2.121.1,<3.0.0" +aws-cdk-lib = ">=2.122.0,<3.0.0" constructs = ">=10.0.0,<11.0.0" jsii = ">=1.94.0,<2.0.0" publication = ">=0.0.3" @@ -134,13 +134,13 @@ typeguard = ">=2.13.3,<2.14.0" [[package]] name = "aws-cdk-lib" -version = "2.121.1" +version = "2.122.0" description = "Version 2 of the AWS Cloud Development Kit library" optional = false python-versions = "~=3.8" files = [ - {file = "aws-cdk-lib-2.121.1.tar.gz", hash = "sha256:2081ce974124f303348901719f791957ce6aa6b184c6fee30b988bc09f0b1bdd"}, - {file = "aws_cdk_lib-2.121.1-py3-none-any.whl", hash = "sha256:3ad1573000dca9360a8fbf50fd5d835910f729deaec4848438e5faa1bf79949e"}, + {file = "aws-cdk-lib-2.122.0.tar.gz", hash = "sha256:562eb5cb0f2a5111a71a4a3b727d5d077c0eaaa55dc1748922ccdb3f25ef49cd"}, + {file = "aws_cdk_lib-2.122.0-py3-none-any.whl", hash = "sha256:322a230864076738da8d138b0dbf2050a31c0caa7f27d8da128d8174150aaebe"}, ] [package.dependencies] @@ -168,13 +168,13 @@ pydantic = ">=2.0.0,<3.0.0" [[package]] name = "aws-lambda-powertools" -version = "2.31.0" +version = "2.32.0" description = "Powertools for AWS Lambda (Python) is a developer toolkit to implement Serverless best practices and increase developer velocity." optional = false python-versions = ">=3.7.4,<4.0.0" files = [ - {file = "aws_lambda_powertools-2.31.0-py3-none-any.whl", hash = "sha256:082762c0939caa670d69b8988142710029bfac050dbd428af0d12570b8a6c7be"}, - {file = "aws_lambda_powertools-2.31.0.tar.gz", hash = "sha256:d628e32919379fde4cd238578bc47fcc60a43f742bd8daa68533bc5202eae378"}, + {file = "aws_lambda_powertools-2.32.0-py3-none-any.whl", hash = "sha256:26e04dee027854e47fe8f9764863731d2511193054eac4629d2e66e46eac76f5"}, + {file = "aws_lambda_powertools-2.32.0.tar.gz", hash = "sha256:56422df30f633ad679abcc69b1b8a35212da18eecdc3fbaf5e6ca7fb8cc0e498"}, ] [package.dependencies] @@ -183,10 +183,11 @@ typing-extensions = ">=4.6.2,<5.0.0" [package.extras] all = ["aws-xray-sdk (>=2.8.0,<3.0.0)", "fastjsonschema (>=2.14.5,<3.0.0)", "pydantic (>=1.8.2,<2.0.0)"] -aws-sdk = ["boto3 (>=1.20.32,<2.0.0)"] +aws-sdk = ["boto3 (>=1.26.164,<2.0.0)"] datadog = ["datadog-lambda (>=4.77,<6.0)"] datamasking-aws-sdk = ["aws-encryption-sdk (>=3.1.1,<4.0.0)"] parser = ["pydantic (>=1.8.2,<2.0.0)"] +redis = ["redis (>=4.4,<6.0)"] tracer = ["aws-xray-sdk (>=2.8.0,<3.0.0)"] validation = ["fastjsonschema (>=2.14.5,<3.0.0)"] @@ -221,17 +222,17 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "boto3" -version = "1.34.20" +version = "1.34.23" description = "The AWS SDK for Python" optional = false python-versions = ">= 3.8" files = [ - {file = "boto3-1.34.20-py3-none-any.whl", hash = "sha256:a21da54634bd09dcad9e80d106512b6aabe493b1d4260688180156ef27afedc9"}, - {file = "boto3-1.34.20.tar.gz", hash = "sha256:7f662b0c833e7a4d1272b7ec60ded3f14affd54d08620b708ba3abeb0e49d15e"}, + {file = "boto3-1.34.23-py3-none-any.whl", hash = "sha256:364f942d38da283031cde08c46c9282129fd9ebf96fa244f2709886c31ccd49a"}, + {file = "boto3-1.34.23.tar.gz", hash = "sha256:2c96f6a4e9ce2f4d31fc7ab47a2b3a1808063fa3837d7d8548eb2031380f7498"}, ] [package.dependencies] -botocore = ">=1.34.20,<1.35.0" +botocore = ">=1.34.23,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -240,13 +241,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.20" +version = "1.34.23" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">= 3.8" files = [ - {file = "botocore-1.34.20-py3-none-any.whl", hash = "sha256:f931f13d03e94b3350ad898b21ae2d40240f6571e8a8cdaa487951b51fe3a1fd"}, - {file = "botocore-1.34.20.tar.gz", hash = "sha256:e944bc085222a13359933f4c0a1cce228bdd8aa90e1f2274e94bd55f561db307"}, + {file = "botocore-1.34.23-py3-none-any.whl", hash = "sha256:1980411306593bbc2b0cd9b8d1dcacbd418b758077b82f68b932070ad902cfe9"}, + {file = "botocore-1.34.23.tar.gz", hash = "sha256:898fa169679782f396613f50a88b9b033845625c931275832063266110ea4297"}, ] [package.dependencies] @@ -336,13 +337,13 @@ ujson = ["ujson (>=5.7.0)"] [[package]] name = "cdk-monitoring-constructs" -version = "7.4.0" +version = "7.6.0" description = "cdk-monitoring-constructs" optional = false python-versions = "~=3.8" files = [ - {file = "cdk-monitoring-constructs-7.4.0.tar.gz", hash = "sha256:1588b016357ca5d2e3472bca4576ee794c2dd8f0740fa8f5d0a3e28f880635c0"}, - {file = "cdk_monitoring_constructs-7.4.0-py3-none-any.whl", hash = "sha256:1853cc63920f160be6ba110e4486e4033124332a584d5d4d21abf5ea0c36d300"}, + {file = "cdk-monitoring-constructs-7.6.0.tar.gz", hash = "sha256:9b51eab90ceae5d6f7dde0a03c19f15e6125c0623543e4800b658adcc0d89d0c"}, + {file = "cdk_monitoring_constructs-7.6.0-py3-none-any.whl", hash = "sha256:ecd7476b5e8de93217743ebf80f4cc62b68a13427f2f4008cb882f1bf06655d6"}, ] [package.dependencies] @@ -355,17 +356,17 @@ typeguard = ">=2.13.3,<2.14.0" [[package]] name = "cdk-nag" -version = "2.28.11" +version = "2.28.16" description = "Check CDK v2 applications for best practices using a combination on available rule packs." optional = false python-versions = "~=3.8" files = [ - {file = "cdk-nag-2.28.11.tar.gz", hash = "sha256:2e708e310f04ffc93e408a810e2927c260eb773ac86883c1f86867bcd53d142b"}, - {file = "cdk_nag-2.28.11-py3-none-any.whl", hash = "sha256:ac2da2d1f341110f47b0e91639a5b9d035c7d2f4a48200ddc099973c276e4e7a"}, + {file = "cdk-nag-2.28.16.tar.gz", hash = "sha256:359a153c2cf209763c340ea97507de0896220e1c26f5173854c70aaff3a1ca9c"}, + {file = "cdk_nag-2.28.16-py3-none-any.whl", hash = "sha256:406cc69f99e1f1b2266b75aeaca3e932d0188da53569e42ff6437e3f242fcc40"}, ] [package.dependencies] -aws-cdk-lib = ">=2.78.0,<3.0.0" +aws-cdk-lib = ">=2.116.0,<3.0.0" constructs = ">=10.0.5,<11.0.0" jsii = ">=1.94.0,<2.0.0" publication = ">=0.0.3" @@ -745,22 +746,23 @@ files = [ [[package]] name = "dnspython" -version = "2.4.2" +version = "2.5.0" description = "DNS toolkit" optional = false -python-versions = ">=3.8,<4.0" +python-versions = ">=3.8" files = [ - {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, - {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"}, + {file = "dnspython-2.5.0-py3-none-any.whl", hash = "sha256:6facdf76b73c742ccf2d07add296f178e629da60be23ce4b0a9c927b1e02c3a6"}, + {file = "dnspython-2.5.0.tar.gz", hash = "sha256:a0034815a59ba9ae888946be7ccca8f7c157b286f8455b379c692efb51022a15"}, ] [package.extras] -dnssec = ["cryptography (>=2.6,<42.0)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=5.0.3)", "mypy (>=1.0.1)", "pylint (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "sphinx (>=7.0.0)", "twine (>=4.0.0)", "wheel (>=0.41.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.25.1)"] doq = ["aioquic (>=0.9.20)"] -idna = ["idna (>=2.1,<4.0)"] -trio = ["trio (>=0.14,<0.23)"] -wmi = ["wmi (>=1.5.1,<2.0.0)"] +idna = ["idna (>=2.1)"] +trio = ["trio (>=0.14)"] +wmi = ["wmi (>=1.5.1)"] [[package]] name = "dulwich" @@ -1141,61 +1143,71 @@ testing = ["coverage", "pyyaml"] [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.4" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, + {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, ] [[package]] @@ -1305,6 +1317,22 @@ files = [ {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, ] +[[package]] +name = "mkdocs-render-swagger-plugin" +version = "0.1.1" +description = "MKDocs plugin for rendering swagger & openapi files." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_render_swagger_plugin-0.1.1-py3-none-any.whl", hash = "sha256:1b0f4f92bf69d7e0d56b13f520fdad072ddd1f4ccf031f11706d45d831d4df7b"}, +] + +[package.dependencies] +mkdocs = ">=1.4" + +[package.extras] +dev = ["coverage", "flake8", "mypy", "pyyaml"] + [[package]] name = "more-itertools" version = "10.2.0" @@ -1429,13 +1457,13 @@ reports = ["lxml"] [[package]] name = "mypy-boto3-dynamodb" -version = "1.34.0" -description = "Type annotations for boto3.DynamoDB 1.34.0 service generated with mypy-boto3-builder 7.21.0" +version = "1.34.23" +description = "Type annotations for boto3.DynamoDB 1.34.23 service generated with mypy-boto3-builder 7.23.1" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mypy-boto3-dynamodb-1.34.0.tar.gz", hash = "sha256:c0d98d7e83b0bc22e5039f703889fb96202d818171c4206fd31e665a37654e84"}, - {file = "mypy_boto3_dynamodb-1.34.0-py3-none-any.whl", hash = "sha256:76869c3fec882ddeeaca485074e302bf38c3b61103664d665dfed9425234ff75"}, + {file = "mypy-boto3-dynamodb-1.34.23.tar.gz", hash = "sha256:cb91ccdfce1bcf24e1cb1db82ab803302b3d6c7c2f2e94cdeba32f7b90cbe3e9"}, + {file = "mypy_boto3_dynamodb-1.34.23-py3-none-any.whl", hash = "sha256:f87f595601120a3103a03a68437fffb2fba30adb1a963465cf2d8ae01e737d50"}, ] [[package]] @@ -2309,28 +2337,28 @@ requests = ">=2.0.1,<3.0.0" [[package]] name = "ruff" -version = "0.1.13" +version = "0.1.14" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.13-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e3fd36e0d48aeac672aa850045e784673449ce619afc12823ea7868fcc41d8ba"}, - {file = "ruff-0.1.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9fb6b3b86450d4ec6a6732f9f60c4406061b6851c4b29f944f8c9d91c3611c7a"}, - {file = "ruff-0.1.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b13ba5d7156daaf3fd08b6b993360a96060500aca7e307d95ecbc5bb47a69296"}, - {file = "ruff-0.1.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9ebb40442f7b531e136d334ef0851412410061e65d61ca8ce90d894a094feb22"}, - {file = "ruff-0.1.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226b517f42d59a543d6383cfe03cccf0091e3e0ed1b856c6824be03d2a75d3b6"}, - {file = "ruff-0.1.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5f0312ba1061e9b8c724e9a702d3c8621e3c6e6c2c9bd862550ab2951ac75c16"}, - {file = "ruff-0.1.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2f59bcf5217c661254bd6bc42d65a6fd1a8b80c48763cb5c2293295babd945dd"}, - {file = "ruff-0.1.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6894b00495e00c27b6ba61af1fc666f17de6140345e5ef27dd6e08fb987259d"}, - {file = "ruff-0.1.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1600942485c6e66119da294c6294856b5c86fd6df591ce293e4a4cc8e72989"}, - {file = "ruff-0.1.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ee3febce7863e231a467f90e681d3d89210b900d49ce88723ce052c8761be8c7"}, - {file = "ruff-0.1.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dcaab50e278ff497ee4d1fe69b29ca0a9a47cd954bb17963628fa417933c6eb1"}, - {file = "ruff-0.1.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f57de973de4edef3ad3044d6a50c02ad9fc2dff0d88587f25f1a48e3f72edf5e"}, - {file = "ruff-0.1.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a36fa90eb12208272a858475ec43ac811ac37e91ef868759770b71bdabe27b6"}, - {file = "ruff-0.1.13-py3-none-win32.whl", hash = "sha256:a623349a505ff768dad6bd57087e2461be8db58305ebd5577bd0e98631f9ae69"}, - {file = "ruff-0.1.13-py3-none-win_amd64.whl", hash = "sha256:f988746e3c3982bea7f824c8fa318ce7f538c4dfefec99cd09c8770bd33e6539"}, - {file = "ruff-0.1.13-py3-none-win_arm64.whl", hash = "sha256:6bbbc3042075871ec17f28864808540a26f0f79a4478c357d3e3d2284e832998"}, - {file = "ruff-0.1.13.tar.gz", hash = "sha256:e261f1baed6291f434ffb1d5c6bd8051d1c2a26958072d38dfbec39b3dda7352"}, + {file = "ruff-0.1.14-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:96f76536df9b26622755c12ed8680f159817be2f725c17ed9305b472a757cdbb"}, + {file = "ruff-0.1.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab3f71f64498c7241123bb5a768544cf42821d2a537f894b22457a543d3ca7a9"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7060156ecc572b8f984fd20fd8b0fcb692dd5d837b7606e968334ab7ff0090ab"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a53d8e35313d7b67eb3db15a66c08434809107659226a90dcd7acb2afa55faea"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bea9be712b8f5b4ebed40e1949379cfb2a7d907f42921cf9ab3aae07e6fba9eb"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2270504d629a0b064247983cbc495bed277f372fb9eaba41e5cf51f7ba705a6a"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80258bb3b8909b1700610dfabef7876423eed1bc930fe177c71c414921898efa"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:653230dd00aaf449eb5ff25d10a6e03bc3006813e2cb99799e568f55482e5cae"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b3acc6c4e6928459ba9eb7459dd4f0c4bf266a053c863d72a44c33246bfdbf"}, + {file = "ruff-0.1.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6b3dadc9522d0eccc060699a9816e8127b27addbb4697fc0c08611e4e6aeb8b5"}, + {file = "ruff-0.1.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1c8eca1a47b4150dc0fbec7fe68fc91c695aed798532a18dbb1424e61e9b721f"}, + {file = "ruff-0.1.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:62ce2ae46303ee896fc6811f63d6dabf8d9c389da0f3e3f2bce8bc7f15ef5488"}, + {file = "ruff-0.1.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b2027dde79d217b211d725fc833e8965dc90a16d0d3213f1298f97465956661b"}, + {file = "ruff-0.1.14-py3-none-win32.whl", hash = "sha256:722bafc299145575a63bbd6b5069cb643eaa62546a5b6398f82b3e4403329cab"}, + {file = "ruff-0.1.14-py3-none-win_amd64.whl", hash = "sha256:e3d241aa61f92b0805a7082bd89a9990826448e4d0398f0e2bc8f05c75c63d99"}, + {file = "ruff-0.1.14-py3-none-win_arm64.whl", hash = "sha256:269302b31ade4cde6cf6f9dd58ea593773a37ed3f7b97e793c8594b262466b67"}, + {file = "ruff-0.1.14.tar.gz", hash = "sha256:ad3f8088b2dfd884820289a06ab718cde7d38b94972212cc4ba90d5fbc9955f3"}, ] [[package]] @@ -2769,4 +2797,4 @@ requests = ">=2.0,<3.0" [metadata] lock-version = "2.0" python-versions = "^3.12.0" -content-hash = "31b8193264087917409dc07b9838e37bca51e87f7ddad5df00d0c88c8069616a" +content-hash = "36ca1a5ea0aa911ad5b83c85d6599b3c17d0004897eb192970cce82ee43e7e06" diff --git a/service/models/input.py b/service/models/input.py index 38ae915c..ecbdb8cf 100644 --- a/service/models/input.py +++ b/service/models/input.py @@ -1,8 +1,8 @@ from typing import Annotated -from pydantic import BaseModel, Field, PositiveInt +from pydantic import BaseModel, Field class CreateOrderRequest(BaseModel): - customer_name: Annotated[str, Field(min_length=1, max_length=20)] - order_item_count: PositiveInt + customer_name: Annotated[str, Field(min_length=1, max_length=20, description='Customer name')] + order_item_count: Annotated[int, Field(gt=0, description='Amount of items in order')] diff --git a/service/models/order.py b/service/models/order.py index 5edbfc79..bcb22c25 100644 --- a/service/models/order.py +++ b/service/models/order.py @@ -1,7 +1,7 @@ from typing import Annotated from uuid import UUID -from pydantic import BaseModel, Field, PositiveInt +from pydantic import BaseModel, Field from pydantic.functional_validators import AfterValidator @@ -30,11 +30,11 @@ def validate_product_id(product_id: str) -> str: return product_id -OrderId = Annotated[str, Field(min_length=36, max_length=36), AfterValidator(validate_product_id)] +OrderId = Annotated[str, Field(min_length=36, max_length=36, description='Order ID as UUID'), AfterValidator(validate_product_id)] """Unique Product ID, represented and validated as a UUID string.""" class Order(BaseModel): - name: Annotated[str, Field(min_length=1, max_length=20)] - item_count: PositiveInt + name: Annotated[str, Field(min_length=1, max_length=20, description='Customer name')] + item_count: Annotated[int, Field(gt=0, description='Amount of items in order')] id: OrderId diff --git a/service/models/output.py b/service/models/output.py index bd7bf374..037f2ab8 100644 --- a/service/models/output.py +++ b/service/models/output.py @@ -1,6 +1,6 @@ -from typing import List +from typing import Annotated, List, Optional, Union -from pydantic import BaseModel +from pydantic import BaseModel, Field from service.models.order import Order @@ -13,12 +13,14 @@ class CreateOrderOutput(Order): class InternalServerErrorOutput(BaseModel): - error: str = 'internal server error' + error: Annotated[str, Field(description='Error description')] = 'internal server error' -class Error(BaseModel): - x: str +class PydanticError(BaseModel): + loc: Annotated[List[Union[str, int]], Field(description='Error location')] + type: Annotated[str, Field(description='Error type')] + msg: Annotated[Optional[str], Field(description='Error message')] class InvalidRestApiRequest(BaseModel): - details: List[Error] + details: Annotated[List[PydanticError], Field(description='Error details')] From 67eca4449112a2df90500a5bb958d7e2a2f286c6 Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Tue, 23 Jan 2024 23:02:03 +0200 Subject: [PATCH 7/8] a --- .github/workflows/main-serverless-service.yml | 5 +++-- .github/workflows/pr-serverless-service.yml | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main-serverless-service.yml b/.github/workflows/main-serverless-service.yml index f58239c4..ecfbc2d4 100644 --- a/.github/workflows/main-serverless-service.yml +++ b/.github/workflows/main-serverless-service.yml @@ -73,9 +73,10 @@ jobs: - name: Codecov uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 with: + token: ${{ secrets.CODECOV_TOKEN }} files: ./coverage.xml - fail_ci_if_error: false # optional (default = false) - verbose: false # optional (default = false) + fail_ci_if_error: yes # optional (default = false) + verbose: yes # optional (default = false) - name: Run E2E tests run: make e2e env: diff --git a/.github/workflows/pr-serverless-service.yml b/.github/workflows/pr-serverless-service.yml index 7fd3a0a7..1a57bc3d 100644 --- a/.github/workflows/pr-serverless-service.yml +++ b/.github/workflows/pr-serverless-service.yml @@ -94,9 +94,10 @@ jobs: - name: Codecov uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 with: + token: ${{ secrets.CODECOV_TOKEN }} files: ./coverage.xml - fail_ci_if_error: false # optional (default = false) - verbose: false # optional (default = false) + fail_ci_if_error: yes # optional (default = false) + verbose: yes # optional (default = false) - name: Run E2E tests run: make e2e - name: Destroy stack From b58d84d5e20e3d142910b6b68e2ca728340ea8c8 Mon Sep 17 00:00:00 2001 From: Ran Isenberg Date: Tue, 23 Jan 2024 23:19:25 +0200 Subject: [PATCH 8/8] a --- mkdocs.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index 24f3ac49..928ff14f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -82,6 +82,20 @@ plugins: - git-revision-date - search - render_swagger + - glightbox: + touchNavigation: true + loop: false + effect: zoom + slide_effect: slide + width: 100% + height: auto + zoomable: true + draggable: true + skip_classes: + - custom-skip-class-name + auto_caption: false + caption_position: bottom + extra_css: - stylesheets/extra.css