From f66d01a693c0a9f4e5d5808abce562bb982b2ea3 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Tue, 4 May 2021 10:41:36 +0200 Subject: [PATCH 01/20] docs: add initial skeleton --- docs/core/event_handler/api_gateway.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 7ee1785f9d0..928f0164db4 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -3,7 +3,9 @@ title: API Gateway description: Core utility --- -Event handler for AWS API Gateway and Application Loader Balancers. +Event handler for AWS API Gateway and Application Loader Balancer (ALB). + +!!! todo "Change proxy types enum to match PascalCase" ### Key Features @@ -17,6 +19,28 @@ Event handler for AWS API Gateway and Application Loader Balancers. * Support function returns a Response object which give fine-grained control of the headers * JSON encoding of Decimals +## Getting started + +!!! todo "Supported event types" + +### Required resources + +!!! todo "API Gateway proxy template" + +### Resolver decorator + +### Path expressions + +### CORS + +## Advanced + +### Fine grained responses + +### Binary responses + +### Testing your code + ## Examples > TODO - Break on into smaller examples From e0939ca3a0109dd3e7352854e22ba36a9bbf2cb9 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Tue, 4 May 2021 10:52:11 +0200 Subject: [PATCH 02/20] fix(logger): external links in new tab --- docs/core/logger.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/core/logger.md b/docs/core/logger.md index f8e806aa6b4..a544bf91e4b 100644 --- a/docs/core/logger.md +++ b/docs/core/logger.md @@ -53,7 +53,7 @@ Key | Example | Note **message**: `Any` | `Collecting payment` | Unserializable JSON values are casted as `str` **timestamp**: `str` | `2021-05-03 10:20:19,650+0200` | Timestamp with milliseconds, by default uses local timezone **service**: `str` | `payment` | Service name defined, by default `service_undefined` -**xray_trace_id**: `str` | `1-5759e988-bd862e3fe1be46a994272793` | When [tracing is enabled](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html), it shows X-Ray Trace ID +**xray_trace_id**: `str` | `1-5759e988-bd862e3fe1be46a994272793` | When [tracing is enabled](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html){target="_blank"}, it shows X-Ray Trace ID **sampling_rate**: `float` | `0.1` | When enabled, it shows sampling rate in percentage e.g. 10% **exception_name**: `str` | `ValueError` | When `logger.exception` is used and there is an exception **exception**: `str` | `Traceback (most recent call last)..` | When `logger.exception` is used and there is an exception @@ -644,7 +644,7 @@ You might want to continue to use the same date formatting style, or override `l Logger allows you to either change the format or suppress the following keys altogether at the initialization: `location`, `timestamp`, `level`, `xray_trace_id`. === "lambda_handler.py" - > We honour standard [logging library string formats](https://docs.python.org/3/howto/logging.html#displaying-the-date-time-in-messages). + > We honour standard [logging library string formats](https://docs.python.org/3/howto/logging.html#displaying-the-date-time-in-messages){target="_blank"}. ```python hl_lines="7 10" from aws_lambda_powertools import Logger @@ -849,7 +849,7 @@ For **replacing the formatter entirely**, you can subclass `BasePowertoolsFormat #### Bring your own JSON serializer -By default, Logger uses `json.dumps` and `json.loads` as serializer and deserializer respectively. There could be scenarios where you are making use of alternative JSON libraries like [orjson](https://github.com/ijl/orjson). +By default, Logger uses `json.dumps` and `json.loads` as serializer and deserializer respectively. There could be scenarios where you are making use of alternative JSON libraries like [orjson](https://github.com/ijl/orjson){target="_blank"}. As parameters don't always translate well between them, you can pass any callable that receives a `Dict` and return a `str`: @@ -943,7 +943,7 @@ This is a Pytest sample that provides the minimum information necessary for Logg ``` !!! tip - If you're using pytest and are looking to assert plain log messages, do check out the built-in [caplog fixture](https://docs.pytest.org/en/latest/how-to/logging.html). + If you're using pytest and are looking to assert plain log messages, do check out the built-in [caplog fixture](https://docs.pytest.org/en/latest/how-to/logging.html){target="_blank"}. ### Pytest live log feature From b6bf174e2cf159e8b6f8d0a9b78af85d3cc3eeca Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Tue, 4 May 2021 10:52:31 +0200 Subject: [PATCH 03/20] fix(appsync): remove old comment --- docs/core/event_handler/appsync.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/core/event_handler/appsync.md b/docs/core/event_handler/appsync.md index 3f61a4ad311..67ad1999285 100644 --- a/docs/core/event_handler/appsync.md +++ b/docs/core/event_handler/appsync.md @@ -7,8 +7,6 @@ Event handler for AWS AppSync Direct Lambda Resolver and Amplify GraphQL Transfo ### Key Features - - * Automatically parse API arguments to function arguments * Choose between strictly match a GraphQL field name or all of them to a function * Integrates with [Data classes utilities](../../utilities/data_classes.md){target="_blank"} to access resolver and identity information From 01bb059ea80176bd4a94ba64f1a98804321b7e43 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Tue, 4 May 2021 10:58:49 +0200 Subject: [PATCH 04/20] docs: add key features --- docs/core/event_handler/api_gateway.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 928f0164db4..726953e54fb 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -3,21 +3,21 @@ title: API Gateway description: Core utility --- -Event handler for AWS API Gateway and Application Loader Balancer (ALB). +Event handler for Amazon API Gateway REST/HTTP APIs and Application Loader Balancer (ALB). !!! todo "Change proxy types enum to match PascalCase" ### Key Features -* Routes - `@app.get("/foo")` -* Path expressions - `@app.delete("/delete/")` -* Cors - `@app.post("/make_foo", cors=True)` or via `CORSConfig` and builtin CORS preflight route -* Base64 encode binary - `@app.get("/logo.png")` -* Gzip Compression - `@app.get("/large-json", compress=True)` -* Cache-control - `@app.get("/foo", cache_control="max-age=600")` -* Rest API simplification with function returns a Dict -* Support function returns a Response object which give fine-grained control of the headers -* JSON encoding of Decimals +* Lightweight routing to reduce boilerplate for API Gateway REST/HTTP API and ALB +* Seamless support for CORS, binary and Gzip compression +* Integrates with [Data classes utilities](../../utilities/data_classes.md){target="_blank"} to easily access event and identity information +* Built-in support for Decimals JSON encoding +* Support for dynamic path expressions + +> Rest API simplification with function returns a Dict +> Support function returns a Response object which give fine-grained control of the headers + ## Getting started From 3bcba996a479fb82696e7cb87029d6b15be4157f Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Tue, 4 May 2021 13:23:58 +0200 Subject: [PATCH 05/20] docs: add example infra --- docs/core/event_handler/api_gateway.md | 62 +++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 726953e54fb..77fcdcfeb77 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -7,6 +7,8 @@ Event handler for Amazon API Gateway REST/HTTP APIs and Application Loader Balan !!! todo "Change proxy types enum to match PascalCase" +!!! todo "Update `route` methods to include an example in docstring to improve developer experience" + ### Key Features * Lightweight routing to reduce boilerplate for API Gateway REST/HTTP API and ALB @@ -15,20 +17,66 @@ Event handler for Amazon API Gateway REST/HTTP APIs and Application Loader Balan * Built-in support for Decimals JSON encoding * Support for dynamic path expressions -> Rest API simplification with function returns a Dict -> Support function returns a Response object which give fine-grained control of the headers - - ## Getting started -!!! todo "Supported event types" - ### Required resources -!!! todo "API Gateway proxy template" +You must have an existing [API Gateway Proxy integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html){target="_blank"} or [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html){target="_blank"} configured to invoke your Lambda function. There is no additional permissions or dependencies required to use this utility. + +This is the sample infrastructure we are using for the initial examples in this section. + +=== "template.yml" + + ```yaml + AWSTemplateFormatVersion: '2010-09-09' + Transform: AWS::Serverless-2016-10-31 + Description: Hello world event handler API Gateway + + Globals: + Function: + Timeout: 5 + Runtime: python3.8 + Tracing: Active + Environment: + Variables: + LOG_LEVEL: INFO + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 + POWERTOOLS_LOGGER_LOG_EVENT: true + POWERTOOLS_METRICS_NAMESPACE: MyServerlessApplication + POWERTOOLS_SERVICE_NAME: hello + + Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + Handler: app.lambda_handler + CodeUri: hello_world + Description: Hello World function + Events: + HelloUniverse: + Type: Api + Properties: + Path: /hello + Method: GET + HelloYou: + Type: Api + Properties: + Path: /hello/{name} + Method: GET + + Outputs: + HelloWorldApigwURL: + Description: "API Gateway endpoint URL for Prod environment for Hello World Function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello" + + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + ``` ### Resolver decorator + ### Path expressions ### CORS From 24affe861b14b63c154391853bf16e705d63f4bf Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Tue, 4 May 2021 18:06:11 +0200 Subject: [PATCH 06/20] docs: add getting started example --- docs/core/event_handler/api_gateway.md | 126 ++++++++++++++++++++++--- 1 file changed, 114 insertions(+), 12 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 77fcdcfeb77..5a65e256bfd 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -33,6 +33,8 @@ This is the sample infrastructure we are using for the initial examples in this Description: Hello world event handler API Gateway Globals: + Api: + TracingEnabled: true Function: Timeout: 5 Runtime: python3.8 @@ -52,17 +54,17 @@ This is the sample infrastructure we are using for the initial examples in this Handler: app.lambda_handler CodeUri: hello_world Description: Hello World function - Events: - HelloUniverse: - Type: Api - Properties: - Path: /hello - Method: GET - HelloYou: - Type: Api - Properties: - Path: /hello/{name} - Method: GET + Events: + HelloUniverse: + Type: Api + Properties: + Path: /hello + Method: GET + HelloYou: + Type: Api + Properties: + Path: /hello/{name} + Method: GET Outputs: HelloWorldApigwURL: @@ -74,7 +76,107 @@ This is the sample infrastructure we are using for the initial examples in this Value: !GetAtt HelloWorldFunction.Arn ``` -### Resolver decorator +### API Gateway decorator + +You can define your functions to match a path and HTTP method, when you use the decorator `ApiGatewayResolver`. + +Here's an example where we have two separate functions to resolve two paths: `/hello` and `/hello/{name}`. + +=== "app.py" + + ```python hl_lines="3 7 9 18" + from aws_lambda_powertools import Logger, Tracer + from aws_lambda_powertools.logging import correlation_paths + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver + + tracer = Tracer() + logger = Logger() + app = ApiGatewayResolver() # by default API Gateway REST API (v1) + + @app.get("/hello") + @tracer.capture_method + def get_hello_universe(): + return {"message": "hello universe"} + + # You can continue to use other utilities just as before + @logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST) + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` +=== "hello_event.json" + + This utility uses `path` and `httpMethod` to route to the right function. This helps make unit tests and local invocation easier too. + + ```json hl_lines="4-5" + { + "body": "hello", + "resource": "/hello", + "path": "/hello", + "httpMethod": "GET", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "multiValueQueryStringParameters": {}, + "pathParameters": { + "hello": "/hello" + }, + "stageVariables": {}, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": {}, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "Prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "25/Jul/2020:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/Prod/hello", + "resourcePath": "/hello", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } + } + ``` + +#### API Gateway HTTP API + + +#### ALB ### Path expressions From 0d97c6ebf713c70dad8ac9c1131f9a81e23bfbe1 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Tue, 4 May 2021 18:14:04 +0200 Subject: [PATCH 07/20] docs: add HTTP API and ALB example --- docs/core/event_handler/api_gateway.md | 51 +++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 5a65e256bfd..34e6d2367b4 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -173,11 +173,60 @@ Here's an example where we have two separate functions to resolve two paths: `/h } ``` -#### API Gateway HTTP API +#### HTTP API +When using API Gateway HTTP API to front your Lambda functions, you can instruct `ApiGatewayResolver` to conform with their contract via `proxy_type` param: + +=== "app.py" + + ```python hl_lines="3 7" + from aws_lambda_powertools import Logger, Tracer + from aws_lambda_powertools.logging import correlation_paths + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver, ProxyEventType + + tracer = Tracer() + logger = Logger() + app = ApiGatewayResolver(proxy_type=ProxyEventType.http_api_v2) + + @app.get("/hello") + @tracer.capture_method + def get_hello_universe(): + return {"message": "hello universe"} + + # You can continue to use other utilities just as before + @logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP) + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` #### ALB +When using ALB to front your Lambda functions, you can instruct `ApiGatewayResolver` to conform with their contract via `proxy_type` param: + +=== "app.py" + + ```python hl_lines="3 7" + from aws_lambda_powertools import Logger, Tracer + from aws_lambda_powertools.logging import correlation_paths + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver, ProxyEventType + + tracer = Tracer() + logger = Logger() + app = ApiGatewayResolver(proxy_type=ProxyEventType.alb_event) + + @app.get("/hello") + @tracer.capture_method + def get_hello_universe(): + return {"message": "hello universe"} + + # You can continue to use other utilities just as before + @logger.inject_lambda_context(correlation_id_path=correlation_paths.APPLICATION_LOAD_BALANCER) + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + ### Path expressions From c33e2a1f803e7a04536d9c8b36805179e4910421 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 5 May 2021 09:38:26 +0200 Subject: [PATCH 08/20] docs: add dynamic routes section --- docs/core/event_handler/api_gateway.md | 64 ++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index fb3ce027083..7b19e766049 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -63,7 +63,12 @@ This is the sample infrastructure we are using for the initial examples in this HelloYou: Type: Api Properties: - Path: /hello/{name} + Path: /hello/{name} # see Dynamic routes section + Method: GET + CustomMessage: + Type: Api + Properties: + Path: /{message}/{name} # see Dynamic routes section Method: GET Outputs: @@ -80,11 +85,13 @@ This is the sample infrastructure we are using for the initial examples in this You can define your functions to match a path and HTTP method, when you use the decorator `ApiGatewayResolver`. -Here's an example where we have two separate functions to resolve two paths: `/hello` and `/hello/{name}`. +Here's an example where we have two separate functions to resolve two paths: `/hello`. + +!!! info "We automatically serialize `Dict` responses as JSON and set content-type to `application/json`" === "app.py" - ```python hl_lines="3 7 9 18" + ```python hl_lines="3 7 9 12 18" from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools.logging import correlation_paths from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver @@ -227,8 +234,57 @@ When using ALB to front your Lambda functions, you can instruct `ApiGatewayResol return app.resolve(event, context) ``` +### Dynamic routes + +You can use `/path/{dynamic_value}` when configuring dynamic URL paths. This allows you to define such dynamic value as part of your function signature. + +=== "app.py" + + ```python hl_lines="9 11" + from aws_lambda_powertools import Logger, Tracer + from aws_lambda_powertools.logging import correlation_paths + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver -### Path expressions + tracer = Tracer() + logger = Logger() + app = ApiGatewayResolver() + + @app.get("/hello/") + @tracer.capture_method + def get_hello_you(name): + return {"message": f"hello {name}}"} + + # You can continue to use other utilities just as before + @logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST) + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + +You can also nest paths as configured earlier in [our sample infrastructure](#required-resources): `/{message}/{name}`. + +=== "app.py" + + ```python hl_lines="9 11" + from aws_lambda_powertools import Logger, Tracer + from aws_lambda_powertools.logging import correlation_paths + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver + + tracer = Tracer() + logger = Logger() + app = ApiGatewayResolver() + + @app.get("//") + @tracer.capture_method + def get_message(message, name): + return {"message": f"{message}, {name}}"} + + # You can continue to use other utilities just as before + @logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST) + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` ### CORS From e78e94da1f346a04d069bcf7a5f1b0375e839655 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 5 May 2021 12:52:42 +0200 Subject: [PATCH 09/20] docs: add CORS section; correct typehint --- .../event_handler/api_gateway.py | 12 ++-- docs/core/event_handler/api_gateway.md | 60 +++++++++++++++++++ 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 9dba4219a95..16a0e28a1e8 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -66,9 +66,9 @@ class CORSConfig(object): def __init__( self, allow_origin: str = "*", - allow_headers: List[str] = None, - expose_headers: List[str] = None, - max_age: int = None, + allow_headers: Optional[List[str]] = None, + expose_headers: Optional[List[str]] = None, + max_age: Optional[int] = None, allow_credentials: bool = False, ): """ @@ -77,13 +77,13 @@ def __init__( allow_origin: str The value of the `Access-Control-Allow-Origin` to send in the response. Defaults to "*", but should only be used during development. - allow_headers: str + allow_headers: Optional[List[str]] The list of additional allowed headers. This list is added to list of built in allowed headers: `Authorization`, `Content-Type`, `X-Amz-Date`, `X-Api-Key`, `X-Amz-Security-Token`. - expose_headers: str + expose_headers: Optional[List[str]] A list of values to return for the Access-Control-Expose-Headers - max_age: int + max_age: Optional[int] The value for the `Access-Control-Max-Age` allow_credentials: bool A boolean value that sets the value of `Access-Control-Allow-Credentials` diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 7b19e766049..8c0fc1582c2 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -35,6 +35,10 @@ This is the sample infrastructure we are using for the initial examples in this Globals: Api: TracingEnabled: true + Cors: # see CORS section + AllowOrigin: "'https://example.com'" + AllowHeaders: "'Content-Type,Authorization,X-Amz-Date'" + MaxAge: "'300'" Function: Timeout: 5 Runtime: python3.8 @@ -288,6 +292,62 @@ You can also nest paths as configured earlier in [our sample infrastructure](#re ### CORS +You can configure CORS at the `ApiGatewayResolver` constructor via `cors` parameter using the `CORSConfig` class. + +This will ensure that CORS headers are always returned as part of the response when your functions match the path invoked. + +=== "app.py" + + ```python hl_lines="9 11" + from aws_lambda_powertools import Logger, Tracer + from aws_lambda_powertools.logging import correlation_paths + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver, CORSConfig + + tracer = Tracer() + logger = Logger() + + cors_config = CORSConfig(allow_origin="https://example.com", max_age=300) + app = ApiGatewayResolver(cors=cors_config) + + @app.get("/hello/") + @tracer.capture_method + def get_hello_you(name): + return {"message": f"hello {name}}"} + + @app.get("/hello", cors=False) # optionally exclude CORS from response, if needed + @tracer.capture_method + def get_hello_no_cors_needed(): + return {"message": "hello, no CORS needed for this path ;)"} + + # You can continue to use other utilities just as before + @logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST) + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + +!!! tip "Optionally disable class on a per path basis with `cors=False` parameter" + +#### Defaults + +For convenience, these are the default values when using `CORSConfig` to enable CORS: + +!!! warning "Always configure `allow_origin` when using in production" + +Key | Value | Note +------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- +**[allow_origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin){target="_blank"}**: `str` | `*` | Only use the default value for development. **Never use `*` for production** unless your use case requires it +**[allow_headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers){target="_blank"}**: `List[str]` | `[Authorization, Content-Type, X-Amz-Date, X-Api-Key, X-Amz-Security-Token]` | Additional headers will be appended to the default list for your convenience +**[expose_headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers){target="_blank"}**: `List[str]` | `[]` | Any additional header beyond the [safe listed by CORS specification](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header){target="_blank"}. +**[max_age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age){target="_blank"}**: `int` | `` | Only for pre-flight requests if you choose to have your function to handle it instead of API Gateway +**[allow_credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials){target="_blank"}**: `bool` | `False` | Only necessary when you need to expose cookies, authorization headers or TLS client certificates. + +#### Pre-flight + +Pre-flight (OPTIONS) calls are typically handled at the API Gateway level as per [our sample infrastructure](#required-resources), no Lambda integration necessary. However, ALB expects you to handle pre-flight requests. + +For convenience, we automatically handle that for you as long as you [setup CORS in the constructor level](#cors). + ## Advanced ### Fine grained responses From 1a44e3c2ff92d8b77109586e4bab9608fa34750a Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 5 May 2021 13:41:21 +0200 Subject: [PATCH 10/20] docs: add request details section --- docs/core/event_handler/api_gateway.md | 70 +++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 8c0fc1582c2..b15524bd5c2 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -290,6 +290,62 @@ You can also nest paths as configured earlier in [our sample infrastructure](#re return app.resolve(event, context) ``` +### Accessing request details + +By integrating with [Data classes utilities](../../utilities/data_classes.md){target="_blank"}, you have access to request details, Lambda context and also some convenient methods. + +These are made available in the response returned when instantiating `ApiGatewayResolver`, for example `app.current_event` and `app.lambda_context`. + +#### Query strings and payload + +Within `app.current_event` property, you can access query strings as dictionary via `query_string_parameters`, or by name via `get_query_string_value` method. + +You can access the raw payload via `body` property, or if it's a JSON string you can quickly deserialize it via `json_body` property. + +=== "app.py" + + ```python hl_lines="7-9 11" + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver + + app = ApiGatewayResolver() + + @app.get("/hello") + def get_hello_you(): + query_strings_as_dict = app.current_event.query_string_parameters + json_payload = app.current_event.json_body + payload = app.current_event.body + + name = app.current_event.get_query_string_value(name="name", default_value="") + return {"message": f"hello {name}}"} + + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + +#### Headers + +Similarly to [Query strings](#query-strings), you can access headers as dictionary via `app.current_event.headers`, or by name via `get_header_value`. + +=== "app.py" + + ```python hl_lines="7-8" + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver + + app = ApiGatewayResolver() + + @app.get("/hello") + def get_hello_you(): + headers_as_dict = app.current_event.headers + name = app.current_event.get_header_value(name="X-Name", default_value="") + + return {"message": f"hello {name}}"} + + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + +## Advanced + ### CORS You can configure CORS at the `ApiGatewayResolver` constructor via `cors` parameter using the `CORSConfig` class. @@ -328,6 +384,12 @@ This will ensure that CORS headers are always returned as part of the response w !!! tip "Optionally disable class on a per path basis with `cors=False` parameter" +#### Pre-flight + +Pre-flight (OPTIONS) calls are typically handled at the API Gateway level as per [our sample infrastructure](#required-resources), no Lambda integration necessary. However, ALB expects you to handle pre-flight requests. + +For convenience, we automatically handle that for you as long as you [setup CORS in the constructor level](#cors). + #### Defaults For convenience, these are the default values when using `CORSConfig` to enable CORS: @@ -342,14 +404,6 @@ Key | Value | Note **[max_age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age){target="_blank"}**: `int` | `` | Only for pre-flight requests if you choose to have your function to handle it instead of API Gateway **[allow_credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials){target="_blank"}**: `bool` | `False` | Only necessary when you need to expose cookies, authorization headers or TLS client certificates. -#### Pre-flight - -Pre-flight (OPTIONS) calls are typically handled at the API Gateway level as per [our sample infrastructure](#required-resources), no Lambda integration necessary. However, ALB expects you to handle pre-flight requests. - -For convenience, we automatically handle that for you as long as you [setup CORS in the constructor level](#cors). - -## Advanced - ### Fine grained responses ### Binary responses From 19c9dbdcb2c9d8d31a382f96b21e6c5f3fe7d8b2 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 5 May 2021 19:38:42 +0200 Subject: [PATCH 11/20] docs: add compress and binary feat --- .../event_handler/api_gateway.py | 14 +- docs/core/event_handler/api_gateway.md | 340 +++++------------- .../event_handler/test_api_gateway.py | 2 +- 3 files changed, 98 insertions(+), 258 deletions(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 16a0e28a1e8..c31d6238d96 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -361,11 +361,9 @@ def _to_response(result: Union[Dict, Response]) -> Response: """ if isinstance(result, Response): return result - elif isinstance(result, dict): - return Response( - status_code=200, - content_type="application/json", - body=json.dumps(result, separators=(",", ":"), cls=Encoder), - ) - else: # Tuple[int, str, Union[bytes, str]] - return Response(*result) + + return Response( + status_code=200, + content_type="application/json", + body=json.dumps(result, separators=(",", ":"), cls=Encoder), + ) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index b15524bd5c2..4b648ff8a04 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -39,6 +39,8 @@ This is the sample infrastructure we are using for the initial examples in this AllowOrigin: "'https://example.com'" AllowHeaders: "'Content-Type,Authorization,X-Amz-Date'" MaxAge: "'300'" + BinaryMediaTypes: # see Binary responses + - '*~1*' # converts to */* for any binary type Function: Timeout: 5 Runtime: python3.8 @@ -406,309 +408,149 @@ Key | Value | Note ### Fine grained responses -### Binary responses - -### Testing your code +### Compress -## Examples +You can compress with gzip and base64 encode your responses via `compress` parameter. -> TODO - Break on into smaller examples - -### All in one example +!!! warning "The client must send the `Accept-Encoding` header, otherwise a normal response will be sent" === "app.py" -```python -from decimal import Decimal -import json -from typing import Dict, Tuple - -from aws_lambda_powertools import Tracer -from aws_lambda_powertools.utilities.data_classes import APIGatewayProxyEvent -from aws_lambda_powertools.event_handler.api_gateway import ( - ApiGatewayResolver, - CORSConfig, - ProxyEventType, - Response, -) - -tracer = Tracer() -# Other supported proxy_types: "APIGatewayProxyEvent", "APIGatewayProxyEventV2", "ALBEvent" -app = ApiGatewayResolver( - proxy_type=ProxyEventType.APIGatewayProxyEvent, - cors=CORSConfig( - allow_origin="https://www.example.com/", - expose_headers=["x-exposed-response-header"], - allow_headers=["x-custom-request-header"], - max_age=100, - allow_credentials=True, - ) -) - - -@app.get("/foo", compress=True) -def get_foo() -> Tuple[int, str, str]: - # Matches on http GET and proxy path "/foo" - # and return status code: 200, content-type: text/html and body: Hello - return 200, "text/html", "Hello" - - -@app.get("/logo.png") -def get_logo() -> Tuple[int, str, bytes]: - # Base64 encodes the return bytes body automatically - logo: bytes = load_logo() - return 200, "image/png", logo - - -@app.post("/make_foo", cors=True) -def make_foo() -> Tuple[int, str, str]: - # Matches on http POST and proxy path "/make_foo" - post_data: dict = app.current_event.json_body - return 200, "application/json", json.dumps(post_data["value"]) - - -@app.delete("/delete/") -def delete_foo(uid: str) -> Tuple[int, str, str]: - # Matches on http DELETE and proxy path starting with "/delete/" - assert isinstance(app.current_event, APIGatewayProxyEvent) - assert app.current_event.request_context.authorizer.claims is not None - assert app.current_event.request_context.authorizer.claims["username"] == "Mike" - return 200, "application/json", json.dumps({"id": uid}) - - -@app.get("/hello/") -def hello_user(username: str) -> Tuple[int, str, str]: - return 200, "text/html", f"Hello {username}!" - - -@app.get("/rest") -def rest_fun() -> Dict: - # Returns a statusCode: 200, Content-Type: application/json and json.dumps dict - # and handles the serialization of decimals to json string - return {"message": "Example", "second": Decimal("100.01")} - - -@app.get("/foo3") -def foo3() -> Response: - return Response( - status_code=200, - content_type="application/json", - headers={"custom-header": "value"}, - body=json.dumps({"message": "Foo3"}), - ) - - -@tracer.capture_lambda_handler -def lambda_handler(event, context) -> Dict: - return app.resolve(event, context) -``` - -### Compress examples + ```python hl_lines="5 7" + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver, Response -=== "app.py" + app = ApiGatewayResolver() - ```python - from aws_lambda_powertools.event_handler.api_gateway import ( - ApiGatewayResolver - ) + @app.get("/hello", compress=True) + def get_hello_you(): + return {"message": "hello universe"} - app = ApiGatewayResolver() + def lambda_handler(event, context): + return app.resolve(event, context) + ``` - @app.get("/foo", compress=True) - def get_foo() -> Tuple[int, str, str]: - # Matches on http GET and proxy path "/foo" - # and return status code: 200, content-type: text/html and body: Hello - return 200, "text/html", "Hello" - ``` +=== "sample_request.json" -=== "GET /foo: request" - ```json + ```json { "headers": { "Accept-Encoding": "gzip" }, "httpMethod": "GET", - "path": "/foo" + "path": "/hello", + ... } ``` -=== "GET /foo: response" +=== "response.json" ```json { - "body": "H4sIAAAAAAACE/NIzcnJBwCCidH3BQAAAA==", + "body": "H4sIAAAAAAACE6tWyk0tLk5MT1WyUspIzcnJVyjNyyxLLSpOVaoFANha8kEcAAAA", "headers": { "Content-Encoding": "gzip", - "Content-Type": "text/html" + "Content-Type": "application/json" }, "isBase64Encoded": true, "statusCode": 200 } ``` -### CORS examples +### Binary responses -=== "app.py" +For convenience, we automatically base64 encode binary responses. You can also use in combination with `compress` parameter if your client supports gzip. - ```python - from aws_lambda_powertools.event_handler.api_gateway import ( - ApiGatewayResolver, - CORSConfig, - ) - - app = ApiGatewayResolver( - proxy_type=ProxyEventType.http_api_v1, - cors=CORSConfig( - allow_origin="https://www.example.com/", - expose_headers=["x-exposed-response-header"], - allow_headers=["x-custom-request-header"], - max_age=100, - allow_credentials=True, - ) - ) - - @app.post("/make_foo", cors=True) - def make_foo() -> Tuple[int, str, str]: - # Matches on http POST and proxy path "/make_foo" - post_data: dict = app. current_event.json_body - return 200, "application/json", json.dumps(post_data["value"]) - ``` +Like `compress` feature, the client must send the `Accept` header with the correct media type. -=== "OPTIONS /make_foo" +!!! warning "This feature requires API Gateway to configure binary media types, see [our sample infrastructure](#required-resources) for reference" - ```json - { - "httpMethod": "OPTIONS", - "path": "/make_foo" - } - ``` +=== "app.py" -=== "<< OPTIONS /make_foo" + ```python hl_lines="4 7 11" + import os + from pathlib import Path - ```json - { - "body": null, - "headers": { - "Access-Control-Allow-Credentials": "true", - "Access-Control-Allow-Headers": "Authorization,Content-Type,X-Amz-Date,X-Amz-Security-Token,X-Api-Key,x-custom-request-header", - "Access-Control-Allow-Methods": "OPTIONS,POST", - "Access-Control-Allow-Origin": "https://www.example.com/", - "Access-Control-Expose-Headers": "x-exposed-response-header", - "Access-Control-Max-Age": "100" - }, - "isBase64Encoded": false, - "statusCode": 204 - } - ``` + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver, Response -=== "POST /make_foo" + app = ApiGatewayResolver() + logo_file: bytes = Path(os.getenv("LAMBDA_TASK_ROOT") + "/logo.svg").read_bytes() - ```json - { - "body": "{\"value\": \"Hello World\"}", - "httpMethod": "POST", - "path": "/make_foo" - } - ``` + @app.get("/logo") + def get_logo(): + return Response(status_code=200, content_type="image/svg+xml", body=logo_file) -=== "<< POST /make_foo" + def lambda_handler(event, context): + return app.resolve(event, context) + ``` - ```json +=== "logo.svg" + ```xml + + + + + + + + + + + + + ``` +=== "sample_request.json" + + ```json { - "body": "\"Hello World\"", "headers": { - "Access-Control-Allow-Credentials": "true", - "Access-Control-Allow-Headers": "Authorization,Content-Type,X-Amz-Date,X-Amz-Security-Token,X-Api-Key,x-custom-request-header", - "Access-Control-Allow-Origin": "https://www.example.com/", - "Access-Control-Expose-Headers": "x-exposed-response-header", - "Access-Control-Max-Age": "100", - "Content-Type": "application/json" + "Accept": "image/svg+xml" }, - "isBase64Encoded": false, - "statusCode": 200 - } - ``` - -### Simple rest example - -=== "app.py" - - ```python - from aws_lambda_powertools.event_handler.api_gateway import ( - ApiGatewayResolver - ) - - app = ApiGatewayResolver() - - @app.get("/rest") - def rest_fun() -> Dict: - # Returns a statusCode: 200, Content-Type: application/json and json.dumps dict - # and handles the serialization of decimals to json string - return {"message": "Example", "second": Decimal("100.01")} - ``` - -=== "GET /rest: request" - - ```json - { "httpMethod": "GET", - "path": "/rest" + "path": "/logo", + ... } ``` -=== "GET /rest: response" +=== "response.json" ```json { - "body": "{\"message\":\"Example\",\"second\":\"100.01\"}", + "body": "H4sIAAAAAAACE3VXa2scRxD87ID/w+byKTCzN899yFZMLBLHYEMg4K9BHq0l4c2duDudZIf891TVrPwiMehmd+fR3dXV1eOnz+7/mpvjtNtfbzenK9+6VTNtyvbienN5uro9vLPD6tlPj797+r21zYtpM+3OD9vdSfPzxfbt1Lyc59v9QZ8aP7au9ab5482L5pf7m+3u0Pw+317al5um1cc31chJ07XONc9vr+eLxv3YNNby/P3x8ks3/Kq5vjhdvTr/MO3+xAu83OxPV1eHw83Jen13d9fexXa7u1wH59wam5clJ/fz9eb9fy304ziuNYulpyt3c79qPtTx8XePmuP1dPd8y4nGNdGlxg9h1ewPH+bpdDVtzt/Ok317Xt5f7ra3m4uTzXTXfLHyicyf7G/OC5bf7Kb9tDtOKwXGI5rDhxtMHKb7w7rs95x41O4P7u931/N88sOv+vfkn/rV66vd3c7TyXScNtuLiydlvr75+su3O5+uZYkmL3n805vzw1VT5vM9cIOpVQM8Xw9dm0yHn+JMbHvj+IoRiJuhHYtrBxPagPfBpLbDmmD6NuB7NpxzWttpDG3EKd46vAfr29HE2XZtxMYABx4VzIxY2VmvnaMN2jkW642zAdPZRkyms76DndGZPpthgEt9MvB0wEJM91gacUpsvc3c3eO4sYXJHuf52A42jNjEp2qXRzjrMzaENtngLGOwCS4krO7xzXscoIeR4WFLNpFbEo7GNrhdOhkEGElrgUyCx3gokQYAHMOLxjvFVY1XVDNQy0AKkx4PgPSIjcALv8QDf0He9NZ3BaEFhTdgInESMPKBMwAemzxTZT1zgFP5vRekOJTg8zucquEvCULsXOx1hjY5bWKuAh1fFkbuIGABa71+4cuRcMHfuiboMB6Kw8gGW5mQtDUwBa1f4s/Kd6+1iD8oplyIvq9oebEFYBOKsXi+ORNEJBKLbBhaXzIcZ0YGbgMF9IAkdG9I4Y/N65RhaYCLi+morPSipK8RMlmdIgahbFR+s2UF+Gpe3ieip6/kayCbkHpYRUp6QgH6MGFEgLuiFQHbviLO/DkdEGkbk4ljsawtR7J1zIAFk0aTioBBpIQYbmWNJArqKQlXxh9UoSQXjZxFIGoGFmzSPM/8FD+w8IDNmxG+l1pwlr5Ey/rwzP1gay1mG5Ykj6/GrpoIRZOMYqR3GiudHijAFJPJiePVCGBr2mIlE0bEUKpIMFrQwjCEcQabB4pOmJVyPolCYWEnYJZVyU+VE4JrQC56cPWtpfSVHfhkJD60RDy6foYyRNv1NZlCXoh/YwM05C7rEU0sitKERehqrLkiYCrhvcSO53VFrzxeAqB0UxHzbMFPb/q+1ltVRoITiTnNKRWm0ownRlbpFUu/iI5uYRMEoMb/kLt+yR3BSq98xtkQXElWl5h1yg6nvcz5SrVFta1UHTz3v4koIEzIVPgRKlkkc44ykipJsip7kVMWdICDFPBMMoOwUhlbRb23NX/UjqHYesi4sK2OmDhaWpLKiE1YzxbCsUhATZUlb2q7iBX7Kj/Kc80atEz66yWyXorhGTIkRqnrSURu8fWhdNIFKT7B8UnNJPIUwYLgLVHkOD7knC4rjNpFeturrBRRbmtHkpTh5VVIncmBnYlpjhT3HhMUd1urK0rQE7AE14goJdFRWBYZHyUIcLLm3AuhwF5qO7Zg4B+KTodiJCaSOMN4SXbRC+pR1Vs8FEZGOcnCtKvNvnC/aoiKj2+dekO1GdS4VMfAQo2++KXOonIgf5ifoo6hOkm6EFDP8pItNXvVpFNdxiNErThVXG1UQXHEz/eEYWk/jEmCRcyyaKtWKbVSr1YNc6rytcLnq6AORazytbMa9nqOutgYdUPmGL72nyKmlzxMVcjpPLPdE7cC1MlQQkpyZHasjPbRFVpJ+mNPqlcln6Tekk5lg7cd/9CbJMkkXFInSmrcw4PHQS1p0HZSANa6s8CqNiN/Qh7hI0vVfK7aj6u1Lnq67n173/P1vhd6Nf+ETgJLgSyjjYGpj2SVD3JM96PM+xRRZYcMtV8NJHKn3bW+pUydGMFg1CMelUSIgjwj4nGUVULDxxJJM1zvsM/q0uZ5TQggwFnoRanI9h76gcSJDPYLz5dA/y/EgXnygRcGostStqFXv0KdD7qP6MYUTKVXr1uhEzty8QP5plqDXbZuk1mtuUZGv3jtg8JIFKHTJrt6H9AduN4TAE6q95qzMEikMmkVRq+bKQXrC0cfUrdm7h5+8b8YjP8Cgadmu5INAAA=", "headers": { - "Content-Type": "application/json" + "Content-Type": "image/svg+xml" }, - "isBase64Encoded": false, + "isBase64Encoded": true, "statusCode": 200 } ``` -### Custom response - -=== "app.py" - - ```python - from aws_lambda_powertools.event_handler.api_gateway import ( - ApiGatewayResolver - ) - - app = ApiGatewayResolver() - - @app.get("/foo3") - def foo3() -> Response: - return Response( - status_code=200, - content_type="application/json", - headers={"custom-header": "value"}, - body=json.dumps({"message": "Foo3"}), - ) - ``` - -=== "GET /foo3: request" - - ```json - { - "httpMethod": "GET", - "path": "/foo3" - } - ``` +### Testing your code -=== "GET /foo3: response" - ```json - { - "body": "{\"message\": \"Foo3\"}", - "headers": { - "Content-Type": "application/json", - "custom-header": "value" - }, - "isBase64Encoded": false, - "statusCode": 200 - } - ``` +## FAQ diff --git a/tests/functional/event_handler/test_api_gateway.py b/tests/functional/event_handler/test_api_gateway.py index 05c74895eea..c91f9ee3c48 100644 --- a/tests/functional/event_handler/test_api_gateway.py +++ b/tests/functional/event_handler/test_api_gateway.py @@ -35,7 +35,7 @@ def test_alb_event(): def foo(): assert isinstance(app.current_event, ALBEvent) assert app.lambda_context == {} - return 200, TEXT_HTML, "foo" + return Response(200, TEXT_HTML, "foo") # WHEN calling the event handler result = app(load_event("albEvent.json"), {}) From 48b43fd3b38a4fc5bbf54dc24f60e27de5fe071f Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 5 May 2021 19:57:19 +0200 Subject: [PATCH 12/20] docs: add fine grained response section --- docs/core/event_handler/api_gateway.md | 39 +++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 4b648ff8a04..50a621cf0b1 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -408,6 +408,43 @@ Key | Value | Note ### Fine grained responses +You can use the `Response` class to have full control over the response, for example you might want to add additional headers or set a custom Content-type. + +=== "app.py" + + ```python hl_lines="10-14" + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver, Response + + app = ApiGatewayResolver() + + @app.get("/hello") + def get_hello_you(): + payload = json.dumps({"message": "I'm a teapot"}) + custom_headers = {"X-Custom": "X-Value"} + + return Response(status_code=418, + content_type="application/json", + body=payload, + headers=custom_headers + ) + + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + +=== "response.json" + + ```json + { + "body": "{\"message\":\"I\'m a teapot\"}", + "headers": { + "Content-Type": "application/json", + "X-Custom": "X-Value" + }, + "isBase64Encoded": false, + "statusCode": 418 + } + ### Compress You can compress with gzip and base64 encode your responses via `compress` parameter. @@ -417,7 +454,7 @@ You can compress with gzip and base64 encode your responses via `compress` param === "app.py" ```python hl_lines="5 7" - from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver, Response + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver app = ApiGatewayResolver() From a164291016259c4e868653d9c1fe304dfc3356e2 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 5 May 2021 20:24:22 +0200 Subject: [PATCH 13/20] fix: cors default behaviour; sample req/rep --- .../event_handler/api_gateway.py | 12 ++-- docs/core/event_handler/api_gateway.md | 66 ++++++++++++++++++- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index c31d6238d96..e5d909f718a 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -247,27 +247,27 @@ def __init__(self, proxy_type: Enum = ProxyEventType.APIGatewayProxyEvent, cors: self._cors = cors self._cors_methods: Set[str] = {"OPTIONS"} - def get(self, rule: str, cors: bool = False, compress: bool = False, cache_control: str = None): + def get(self, rule: str, cors: bool = True, compress: bool = False, cache_control: str = None): """Get route decorator with GET `method`""" return self.route(rule, "GET", cors, compress, cache_control) - def post(self, rule: str, cors: bool = False, compress: bool = False, cache_control: str = None): + def post(self, rule: str, cors: bool = True, compress: bool = False, cache_control: str = None): """Post route decorator with POST `method`""" return self.route(rule, "POST", cors, compress, cache_control) - def put(self, rule: str, cors: bool = False, compress: bool = False, cache_control: str = None): + def put(self, rule: str, cors: bool = True, compress: bool = False, cache_control: str = None): """Put route decorator with PUT `method`""" return self.route(rule, "PUT", cors, compress, cache_control) - def delete(self, rule: str, cors: bool = False, compress: bool = False, cache_control: str = None): + def delete(self, rule: str, cors: bool = True, compress: bool = False, cache_control: str = None): """Delete route decorator with DELETE `method`""" return self.route(rule, "DELETE", cors, compress, cache_control) - def patch(self, rule: str, cors: bool = False, compress: bool = False, cache_control: str = None): + def patch(self, rule: str, cors: bool = True, compress: bool = False, cache_control: str = None): """Patch route decorator with PATCH `method`""" return self.route(rule, "PATCH", cors, compress, cache_control) - def route(self, rule: str, method: str, cors: bool = False, compress: bool = False, cache_control: str = None): + def route(self, rule: str, method: str, cors: bool = True, compress: bool = False, cache_control: str = None): """Route decorator includes parameter `method`""" def register_resolver(func: Callable): diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 50a621cf0b1..05b86effab7 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -39,7 +39,7 @@ This is the sample infrastructure we are using for the initial examples in this AllowOrigin: "'https://example.com'" AllowHeaders: "'Content-Type,Authorization,X-Amz-Date'" MaxAge: "'300'" - BinaryMediaTypes: # see Binary responses + BinaryMediaTypes: # see Binary responses section - '*~1*' # converts to */* for any binary type Function: Timeout: 5 @@ -186,6 +186,19 @@ Here's an example where we have two separate functions to resolve two paths: `/h } ``` +=== "response.json" + + ```json + { + "statusCode": 200, + "headers": { + "Content-Type": "application/json" + }, + "body": "{\"message\":\"hello universe\"}", + "isBase64Encoded": false + } + ``` + #### HTTP API When using API Gateway HTTP API to front your Lambda functions, you can instruct `ApiGatewayResolver` to conform with their contract via `proxy_type` param: @@ -267,6 +280,17 @@ You can use `/path/{dynamic_value}` when configuring dynamic URL paths. This all return app.resolve(event, context) ``` +=== "sample_request.json" + + ```json + { + "resource": "/hello/{name}", + "path": "/hello/lessa", + "httpMethod": "GET", + ... + } + ``` + You can also nest paths as configured earlier in [our sample infrastructure](#required-resources): `/{message}/{name}`. === "app.py" @@ -292,6 +316,17 @@ You can also nest paths as configured earlier in [our sample infrastructure](#re return app.resolve(event, context) ``` +=== "sample_request.json" + + ```json + { + "resource": "/{message}/{name}", + "path": "/hi/michael", + "httpMethod": "GET", + ... + } + ``` + ### Accessing request details By integrating with [Data classes utilities](../../utilities/data_classes.md){target="_blank"}, you have access to request details, Lambda context and also some convenient methods. @@ -384,6 +419,35 @@ This will ensure that CORS headers are always returned as part of the response w return app.resolve(event, context) ``` +=== "response.json" + + ```json + { + "statusCode": 200, + "headers": { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "https://www.example.com", + "Access-Control-Allow-Headers": "Authorization,Content-Type,X-Amz-Date,X-Amz-Security-Token,X-Api-Key" + }, + "body": "{\"message\":\"hello lessa\"}", + "isBase64Encoded": false + } + ``` + +=== "response_no_cors.json" + + ```json + { + "statusCode": 200, + "headers": { + "Content-Type": "application/json" + }, + "body": "{\"message\":\"hello lessa\"}", + "isBase64Encoded": false + } + ``` + + !!! tip "Optionally disable class on a per path basis with `cors=False` parameter" #### Pre-flight From 6a0a70836b3afda80ce4cd99622beefb42f88497 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 5 May 2021 20:38:29 +0200 Subject: [PATCH 14/20] docs: add testing section --- docs/core/event_handler/api_gateway.md | 55 +++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 05b86effab7..b3c768490e5 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -651,7 +651,60 @@ Like `compress` feature, the client must send the `Accept` header with the corre } ``` -### Testing your code +## Testing your code + +You can test your routes by passing a proxy event request where `path` and `httpMethod`. + +=== "test_app.py" + + ```python hl_lines="18-24" + from dataclasses import dataclass + + import pytest + import app + + @pytest.fixture + def lambda_context(): + @dataclass + class LambdaContext: + function_name: str = "test" + memory_limit_in_mb: int = 128 + invoked_function_arn: str = "arn:aws:lambda:eu-west-1:809313241:function:test" + aws_request_id: str = "52fdfc07-2182-154f-163f-5f0f9a621d72" + + return LambdaContext() + + def test_lambda_handler(lambda_context): + minimal_event = { + "path": "/hello", + "httpMethod": "GET" + "requestContext": { # correlation ID + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef" + } + } + + app.lambda_handler(minimal_event, lambda_context) + ``` + +=== "app.py" + + ```python + from aws_lambda_powertools import Logger + from aws_lambda_powertools.logging import correlation_paths + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver + + logger = Logger() + app = ApiGatewayResolver() # by default API Gateway REST API (v1) + + @app.get("/hello") + def get_hello_universe(): + return {"message": "hello universe"} + + # You can continue to use other utilities just as before + @logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST) + def lambda_handler(event, context): + return app.resolve(event, context) + ``` ## FAQ From b5cc41099e427ee864319871aa12342ef3944025 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 5 May 2021 20:43:09 +0200 Subject: [PATCH 15/20] docs: add FAQ on Chalice --- docs/core/event_handler/api_gateway.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index b3c768490e5..a8caa1ae3ad 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -706,5 +706,10 @@ You can test your routes by passing a proxy event request where `path` and `http return app.resolve(event, context) ``` - ## FAQ + +**What's the difference between this utility and frameworks like Chalice?** + +Chalice is a full featured microframework that manages application and infrastructure. This utility, however, is largely focused on routing to reduce boilerplate and expects you to setup and manage infrastructure with your framework of choice. + +That said, [Chalice has native integration with Lambda Powertools](https://aws.github.io/chalice/topics/middleware.html){target="_blank"} if you're looking for a more opinionated and web framework feature set. From 99616a34c4358f070242a3f4586bc7536a6fd25e Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 5 May 2021 20:52:28 +0200 Subject: [PATCH 16/20] docs: explicit on sample infra --- docs/core/event_handler/api_gateway.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index a8caa1ae3ad..a95b1e1d6f9 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -23,7 +23,7 @@ Event handler for Amazon API Gateway REST/HTTP APIs and Application Loader Balan You must have an existing [API Gateway Proxy integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html){target="_blank"} or [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html){target="_blank"} configured to invoke your Lambda function. There is no additional permissions or dependencies required to use this utility. -This is the sample infrastructure we are using for the initial examples in this section. +This is the sample infrastructure for API Gateway we are using for the examples in this documentation. === "template.yml" From dc01e40583d03c132c9048207288afb10fe960e9 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 5 May 2021 21:21:32 +0200 Subject: [PATCH 17/20] docs: correct docstring for VSCode --- .../event_handler/api_gateway.py | 213 +++++++++++++----- 1 file changed, 161 insertions(+), 52 deletions(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index e5d909f718a..ce3be72c89e 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -28,37 +28,37 @@ class CORSConfig(object): Simple cors example using the default permissive cors, not this should only be used during early prototyping - >>> from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver - >>> - >>> app = ApiGatewayResolver() - >>> - >>> @app.get("/my/path", cors=True) - >>> def with_cors(): - >>> return {"message": "Foo"} + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver + + app = ApiGatewayResolver() + + @app.get("/my/path", cors=True) + def with_cors(): + return {"message": "Foo"} Using a custom CORSConfig where `with_cors` used the custom provided CORSConfig and `without_cors` do not include any cors headers. - >>> from aws_lambda_powertools.event_handler.api_gateway import ( - >>> ApiGatewayResolver, CORSConfig - >>> ) - >>> - >>> cors_config = CORSConfig( - >>> allow_origin="https://wwww.example.com/", - >>> expose_headers=["x-exposed-response-header"], - >>> allow_headers=["x-custom-request-header"], - >>> max_age=100, - >>> allow_credentials=True, - >>> ) - >>> app = ApiGatewayResolver(cors=cors_config) - >>> - >>> @app.get("/my/path", cors=True) - >>> def with_cors(): - >>> return {"message": "Foo"} - >>> - >>> @app.get("/another-one") - >>> def without_cors(): - >>> return {"message": "Foo"} + from aws_lambda_powertools.event_handler.api_gateway import ( + ApiGatewayResolver, CORSConfig + ) + + cors_config = CORSConfig( + allow_origin="https://wwww.example.com/", + expose_headers=["x-exposed-response-header"], + allow_headers=["x-custom-request-header"], + max_age=100, + allow_credentials=True, + ) + app = ApiGatewayResolver(cors=cors_config) + + @app.get("/my/path", cors=True) + def with_cors(): + return {"message": "Foo"} + + @app.get("/another-one") + def without_cors(): + return {"message": "Foo"} """ _REQUIRED_HEADERS = ["Authorization", "Content-Type", "X-Amz-Date", "X-Api-Key", "X-Amz-Security-Token"] @@ -207,27 +207,26 @@ class ApiGatewayResolver: -------- Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator - >>> from aws_lambda_powertools import Tracer - >>> from aws_lambda_powertools.event_handler.api_gateway import ( - >>> ApiGatewayResolver - >>> ) - >>> - >>> tracer = Tracer() - >>> app = ApiGatewayResolver() - >>> - >>> @app.get("/get-call") - >>> def simple_get(): - >>> return {"message": "Foo"} - >>> - >>> @app.post("/post-call") - >>> def simple_post(): - >>> post_data: dict = app.current_event.json_body - >>> return {"message": post_data["value"]} - >>> - >>> @tracer.capture_lambda_handler - >>> def lambda_handler(event, context): - >>> return app.resolve(event, context) + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver + + tracer = Tracer() + app = ApiGatewayResolver() + + @app.get("/get-call") + def simple_get(): + return {"message": "Foo"} + @app.post("/post-call") + def simple_post(): + post_data: dict = app.current_event.json_body + return {"message": post_data["value"]} + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` """ current_event: BaseProxyEvent @@ -248,23 +247,133 @@ def __init__(self, proxy_type: Enum = ProxyEventType.APIGatewayProxyEvent, cors: self._cors_methods: Set[str] = {"OPTIONS"} def get(self, rule: str, cors: bool = True, compress: bool = False, cache_control: str = None): - """Get route decorator with GET `method`""" + """Get route decorator with GET `method` + + Examples + -------- + Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator + + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver + >>> + tracer = Tracer() + app = ApiGatewayResolver() + + @app.get("/get-call") + def simple_get(): + return {"message": "Foo"} + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + """ return self.route(rule, "GET", cors, compress, cache_control) def post(self, rule: str, cors: bool = True, compress: bool = False, cache_control: str = None): - """Post route decorator with POST `method`""" + """Post route decorator with POST `method` + + Examples + -------- + Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator + + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver + + tracer = Tracer() + app = ApiGatewayResolver() + + @app.post("/post-call") + def simple_post(): + post_data: dict = app.current_event.json_body + return {"message": post_data["value"]} + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + """ return self.route(rule, "POST", cors, compress, cache_control) def put(self, rule: str, cors: bool = True, compress: bool = False, cache_control: str = None): - """Put route decorator with PUT `method`""" + """Put route decorator with PUT `method` + + Examples + -------- + Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator + + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver + + tracer = Tracer() + app = ApiGatewayResolver() + + @app.put("/put-call") + def simple_post(): + put_data: dict = app.current_event.json_body + return {"message": put_data["value"]} + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + """ return self.route(rule, "PUT", cors, compress, cache_control) def delete(self, rule: str, cors: bool = True, compress: bool = False, cache_control: str = None): - """Delete route decorator with DELETE `method`""" + """Delete route decorator with DELETE `method` + + Examples + -------- + Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator + + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver + + tracer = Tracer() + app = ApiGatewayResolver() + + @app.delete("/delete-call") + def simple_delete(): + return {"message": "deleted"} + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + """ return self.route(rule, "DELETE", cors, compress, cache_control) def patch(self, rule: str, cors: bool = True, compress: bool = False, cache_control: str = None): - """Patch route decorator with PATCH `method`""" + """Patch route decorator with PATCH `method` + + Examples + -------- + Simple example with a custom lambda handler using the Tracer capture_lambda_handler decorator + + ```python + from aws_lambda_powertools import Tracer + from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver + + tracer = Tracer() + app = ApiGatewayResolver() + + @app.patch("/patch-call") + def simple_patch(): + patch_data: dict = app.current_event.json_body + patch_data["value"] = patched + + return {"message": patch_data} + + @tracer.capture_lambda_handler + def lambda_handler(event, context): + return app.resolve(event, context) + ``` + """ return self.route(rule, "PATCH", cors, compress, cache_control) def route(self, rule: str, method: str, cors: bool = True, compress: bool = False, cache_control: str = None): From 91f9744f4f7bd01f6526509157d95eac4e816dfc Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 5 May 2021 21:44:59 +0200 Subject: [PATCH 18/20] fix: add debugging statements --- .../event_handler/api_gateway.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index ce3be72c89e..a99394b10f7 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -1,5 +1,6 @@ import base64 import json +import logging import re import zlib from enum import Enum @@ -10,6 +11,8 @@ from aws_lambda_powertools.utilities.data_classes.common import BaseProxyEvent from aws_lambda_powertools.utilities.typing import LambdaContext +logger = logging.getLogger(__name__) + class ProxyEventType(Enum): """An enumerations of the supported proxy event types.""" @@ -170,6 +173,7 @@ def _compress(self): """Compress the response body, but only if `Accept-Encoding` headers includes gzip.""" self.response.headers["Content-Encoding"] = "gzip" if isinstance(self.response.body, str): + logger.debug("Converting string response to bytes before compressing it") self.response.body = bytes(self.response.body, "utf-8") gzip = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16) self.response.body = gzip.compress(self.response.body) + gzip.flush() @@ -190,6 +194,7 @@ def build(self, event: BaseProxyEvent, cors: CORSConfig = None) -> Dict[str, Any self._route(event, cors) if isinstance(self.response.body, bytes): + logger.debug("Encoding bytes response with base64") self.response.base64_encoded = True self.response.body = base64.b64encode(self.response.body).decode() return { @@ -256,7 +261,7 @@ def get(self, rule: str, cors: bool = True, compress: bool = False, cache_contro ```python from aws_lambda_powertools import Tracer from aws_lambda_powertools.event_handler.api_gateway import ApiGatewayResolver - >>> + tracer = Tracer() app = ApiGatewayResolver() @@ -380,8 +385,10 @@ def route(self, rule: str, method: str, cors: bool = True, compress: bool = Fals """Route decorator includes parameter `method`""" def register_resolver(func: Callable): + logger.debug(f"Adding route using rule {rule} and method {method.upper()}") self._routes.append(Route(method, self._compile_regex(rule), func, cors, compress, cache_control)) if cors: + logger.debug(f"Registering method {method.upper()} to Allow Methods in CORS") self._cors_methods.add(method.upper()) return func @@ -417,9 +424,12 @@ def _compile_regex(rule: str): def _to_proxy_event(self, event: Dict) -> BaseProxyEvent: """Convert the event dict to the corresponding data class""" if self._proxy_type == ProxyEventType.APIGatewayProxyEvent: + logger.debug("Converting event to API Gateway REST API contract") return APIGatewayProxyEvent(event) if self._proxy_type == ProxyEventType.APIGatewayProxyEventV2: + logger.debug("Converting event to API Gateway HTTP API contract") return APIGatewayProxyEventV2(event) + logger.debug("Converting event to ALB contract") return ALBEvent(event) def _resolve(self) -> ResponseBuilder: @@ -431,17 +441,21 @@ def _resolve(self) -> ResponseBuilder: continue match: Optional[re.Match] = route.rule.match(path) if match: + logger.debug("Found a registered route. Calling function") return self._call_route(route, match.groupdict()) + logger.debug(f"No match found for path {path} and method {method}") return self._not_found(method) def _not_found(self, method: str) -> ResponseBuilder: """Called when no matching route was found and includes support for the cors preflight response""" headers = {} if self._cors: + logger.debug("CORS is enabled, updating headers.") headers.update(self._cors.to_dict()) - if method == "OPTIONS": # Preflight + if method == "OPTIONS": # Pre-flight + logger.debug("Pre-flight request detected. Returning CORS with null response") headers["Access-Control-Allow-Methods"] = ",".join(sorted(self._cors_methods)) return ResponseBuilder(Response(status_code=204, content_type=None, headers=headers, body=None)) @@ -471,6 +485,7 @@ def _to_response(result: Union[Dict, Response]) -> Response: if isinstance(result, Response): return result + logger.debug("Simple response detected, serializing return before constructing final response") return Response( status_code=200, content_type="application/json", From bcdf6220e65bb48cad6d2f54ab8fb7bfdbf14c60 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 5 May 2021 21:48:37 +0200 Subject: [PATCH 19/20] fix: update tests to reflect new cors behaviour --- tests/functional/event_handler/test_api_gateway.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/event_handler/test_api_gateway.py b/tests/functional/event_handler/test_api_gateway.py index c91f9ee3c48..354a89305e1 100644 --- a/tests/functional/event_handler/test_api_gateway.py +++ b/tests/functional/event_handler/test_api_gateway.py @@ -363,7 +363,7 @@ def test_custom_cors_config(): def get_with_cors(): return {} - @app.get("/another-one") + @app.get("/another-one", cors=False) def another_one(): return {} @@ -434,7 +434,7 @@ def foo_cors(): def foo_delete_cors(): ... - @app.post("/foo") + @app.post("/foo", cors=False) def post_no_cors(): ... From 6bcbf83fa72bb4e6942228eafc965b47e6a428ba Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 5 May 2021 21:52:54 +0200 Subject: [PATCH 20/20] docs: add Beta banner --- docs/core/event_handler/api_gateway.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index a95b1e1d6f9..8551f0b3cf6 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -5,9 +5,7 @@ description: Core utility Event handler for Amazon API Gateway REST/HTTP APIs and Application Loader Balancer (ALB). -!!! todo "Change proxy types enum to match PascalCase" - -!!! todo "Update `route` methods to include an example in docstring to improve developer experience" +!!! info "This is currently in Beta as we want to hear feedback on UX." ### Key Features