# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['aws_lambda_api_event_utils']

package_data = \
{'': ['*']}

install_requires = \
['jsonschema[jsonschema]>=4.2.1,<5.0.0']

setup_kwargs = {
    'name': 'aws-lambda-api-event-utils',
    'version': '0.1.0',
    'description': 'Lightweight processing of API events for AWS Lambda',
    'long_description': '# aws-lambda-api-event-utils\n\nThere are many other libraries, in Python and in other languages, for handling HTTP events in Lambda.\nSo why this library?\n\nAll the libraries I have seen are oriented towards providing traditional HTTP response handling, in the web server style, for Lambda functions.\nThey are often based on an actual web framework, generally provide routing, etc.\n\nThis library does none of that.\n\nIt is intended to be used in the serverless native style, where routing and validation logic is pushed into API Gateway whenever possible.\nIt is intended to make API handlers more Pythonic and less webserver-like, while retaining the use of proxy integrations with API Gateway.\n\nThe functionality in this library can be put into two categories:\n* More Pythonic handlers: interact less with the raw proxy request and response objects, deal with client errors by raising Python exceptions, make JSON payloads easier.\n* Request validation: while as much validation as possible should be done at the API Gateway layer, there are situations where you may need to validate the request in the Lambda function instead. The code in your function should be able to rely on the request being validated.\n\nNote that REST APIs using the Lambda proxy integration does not fully validate request payloads ([read details here](https://rboyd.dev/089999bf-b973-42ed-9796-6167539269b8)), and HTTP APIs do not do any validation.\n\n# Examples\n\n```python\nfrom aws_lambda_api_event_utils import *\n\nimport datetime\nimport decimal\n\n@api_event_handler\ndef handler(event, context):\n    try:\n        # some code\n    except SomePayloadError:\n        # this will re-raise as an APIErrorResponse so that it can be caught by\n        # the decorator and converted to a response to API Gateway using the\n        # given status code and a JSON response body with an error code set to\n        # the exception class name and an error message set to the\n        # stringified exception\n        APIErrorResponse.re_raise_as(400)\n\n    try:\n        # some code\n    except SomeOtherPayloadError:\n        # To expose fewer internal details, use this standard exception\n        # which results in a status 400, a generic error code,\n        # and the given error message\n        # There\'s also APIErrorResponse.from_status_code() to create generic\n        # exceptions for other status codes\n        raise InvalidRequestError("Something\'s wrong with the request.")\n\n    try:\n        # some code\n    except SomeServiceError:\n        # In general, server-side problems should be raised as regular exceptions\n        # causing the Lambda function to error out. This automatically\n        # means API Gateway will return 500 and you\'ll see these errors in your\n        # Lambda function metrics\n        raise\n\n    # more code\n\n    # The decorator will convert this to a response with status code 200,\n    # like the HTTP API Lambda proxy integration does automatically\n    # but it works with REST APIs too, and it does more:\n    # - JSON serialization of datetimes and decimals is provided\n    # - returning a string will be converted to a text/plain response\n    # - returning bytes will be converted to an application/octet-stream response\n    return {\n        "my_field": "my_value",\n        "timestamp": datetime.datetime.now(datetime.timezone.utc),\n        "dynamodb_number": decimal.Decimal("8.1")\n    }\n```\n\n```python\nfrom aws_lambda_api_event_utils import *\n\n# Make subclasses of APIErrorResponse for your own purposes\n# The error code is the class name by default\n# The error message can be static or a template\n# The template can use any fields of the exception\n# or any keywords passed to the constructor\nclass FailedProcessing(APIErrorResponse):\n    STATUS_CODE = 400\n    ERROR_MESSAGE_TEMPLATE = "Failed during {step}"\n\n# ApiErrorResponses caught by the decorator will log the internal message\n# using this callable or logging.Logger\nAPIErrorResponse.DECORATOR_LOGGER = print\n\n@api_event_handler\ndef handler(event, context):\n    try:\n        # some code\n    except Exception as e:\n        # This will be caught by the decorator and converted to a response\n        # You must provide an internal message suitable for logging\n        # even if APIErrorResponse.DECORATOR_LOGGER is not set\n        # keyword arguments are stored and can be referenced in\n        # ERROR_MESSAGE_TEMPLATE\n        raise MyError(f"Processing failed: {e}", step="step 1")\n\n    return {"status": "success"}\n```\n\n```python\nfrom aws_lambda_api_event_utils import *\n\n@api_event_handler\ndef handler(event, context):\n    # set response headers and cookies through the Lambda context object\n    # this works for both returning a value and raising an APIErrorResponse\n    context.api_response_headers = {"my_header_key": "my_header_value"}\n    return {"my_field": "my_value"}\n```\n\n```python\n# JSON schema validation requires the jsonschema package\n# it is not required by this package by default\n# install it separately or install this package with the\n# jsonschema extra as:\n# aws-lambda-api-event-utils[jsonschema]\nfrom aws_lambda_api_event_utils import *\n\nSCHEMA = {\n    "type": "object",\n    "properties": {\n        "some_field": {\n            "type": "string",\n        }\n    },\n    "required": ["some_field"]\n}\n\n# any decorator from the package provides the base functionality\n# of @api_event_handler\n# Invalid JSON and JSON that violates the schema will generate\n# an error response\n@json_body(SCHEMA)\ndef handler(event, context):\n    payload = event["body"] # parsed and validated JSON\n\n    try:\n        # some code\n    except SomeError:\n        APIErrorResponse.re_raise_as(400)\n\n    return {"status": "success"}\n```\n\n```python\nfrom aws_lambda_api_event_utils import *\n\n# @api_event_handler is the "no-validation" decorator\n# using any decorator provides the APIErrorResponse and\n# handler-return-value processing\n@event_format_version(API_GW_20)\n@method("POST")\n@path("/my/path")\n@headers(keys=["x-api-key"])\n@query_parameters(keys=["my_param"])\n@json_body(enforce_content_type=True) # require Content-Type: application/json header\ndef handler(event, context):\n    my_param = event["queryStringParameters"]["my_param"]\n    payload = event["body"] # parsed and validated JSON\n\n    # do work with my_param and payload\n\n    return {"status": "success"}\n```\n\n```python\nimport os, http, boto3\nfrom aws_lambda_api_event_utils import *\n\nS3_BUCKET_NAME = os.environ["S3_BUCKET_NAME"]\n\n# why use a session? read an explainer:\n# https://ben11kehoe.medium.com/boto3-sessions-and-why-you-should-use-them-9b094eb5ca8e\nBOTO3_SESSION = boto3.Session()\nS3_CLIENT = BOTO3_SESSION.client("s3")\n\n# maybe this route is /obj/{param}\n# the regex can constrain it further\n# and capturing groups become path parameters\n@method("GET")\n@path_regex("/data/obj-(?P<s3_key>\\w+)")\n@headers(keys=["x-api-key"])\ndef handler(event, context):\n    api_key = event["headers"]["x-api-key"]\n    s3_key = event["pathParameters"]["s3_key"]\n\n    if not is_authorized(api_key, s3_key):\n        # generic errors for all HTTP 4XX and 5XX status codes\n        raise APIErrorResponse.from_status_code(http.HTTPStatus.FORBIDDEN)\n\n    presigned_url = S3_CLIENT.generate_presigned_url("get_object",\n        Params={\n            "Bucket": S3_BUCKET_NAME,\n            "Key": s3_key\n        },\n        ExpiresIn=30\n    )\n\n    # create an appropriate redirect response\n    # format version can be provided implicitly as the Lambda function\n    # input event rather than having to provide it explicitly\n    return make_redirect(\n        http.HTTPStatus.TEMPORARY_REDIRECT,\n        presigned_url,\n        format_version=event\n    )\n```\n\n# Installation\n\n```\npip install aws-lambda-api-utils\n\n# with jsonschema validation support\npip install aws-lambda-api-utils[jsonschema]\n```\n\n# API event formats\n\nAWS does not define a global event format convention.\nAPI Gateway Lambda proxy events, which are the focus of this library, come in three styles: the REST API format and HTTP API format version 1.0, which are essentially the same, and HTTP API format version 2.0.\n\nThis library defines the `FormatVersion` enum, which represents these as two formats: `FormatVersion.APIGW_10` and `FormatVersion.APIGW_20`.\n\nThe `get_event_format_version()` function takes an event and returns the event format version, or `None` if the event format version cannot be determined.\nBy default, the event format version is cached within the event (the key used for this is available in the `EVENT_FORMAT_VERSION_CACHE_KEY` module field) to speed up multiple calls to `get_event_format_version()` (e.g., across multiple validations).\nThis can be disabled in this function by setting `disable_cache=True`; it cannot be disabled when using validation functions or decorators.\n\n# Decorators\n\nUsing at least one decorator from this package is required to get the `APIErrorResponse` and handler return value processing described below.\nUsing one or more validation decorators is sufficient; if no validation decorators are needed, `@api_event_handler` provides this functionality as a standalone decorator.\n\n# Request body processing\n\nTo retrieve the body from the event, use the `get_body()` function.\nThis returns the body value as-is if it has already been parsed into an object (e.g., by the `@json_body` decorator), as `bytes` if `isBase64` is set to `True` in the event, or as a string otherwise.\nTo validate that the event contains a binary body or not, set the `type` parameter to `BodyType.str` or `BodyType.bytes`, which will cause `PayloadBinaryTypeError` to be raised if it doesn\'t match.\n\nTo parse the body as JSON and optionally validate the JSON, use the `@json_body` decorator or the `get_json_body()` function.\n\nThis takes an optional JSON schema, which will only work if the package has been installed with the `jsonschema` extra, or the `jsonschema` package has been installed separately.\n`get_json_body()` returns the parsed and validated JSON body; it does not modify the event.\n`@json_body` replaces the body in the event with the parsed and validated JSON body; it can be retrieved directly or with the `get_body()` function.\n\nWithout a schema, the decorator can be used with or without parentheses.\n\nIf the body cannot be parsed as JSON, `get_json_body()` raises `PayloadJSONDecodeError`.\nThe exception will have an `internal_message` that includes the `json.JSONDecodeError`.\nThe error response is status 400, the error code is `InvalidPayload`, and the error message says the payload must be JSON.\n\nBy default, HTTP methods that are not expected to have request bodies, like GET, do not cause an error.\nSet `enforce_on_optional_methods=True` to enforce that any request has a valid JSON body.\n\nFor convenience, you can enforce that the `Content-Type` header is set to `application/json` by setting `enforce_on_optional_methods=True`.\nThis uses the [`validate_content_type()` function](docs/validators.md#Content-type).\n\nIf the body can be parsed as JSON, but does not validate against the provided schema, `get_json_body()` raises `PayloadSchemaViolationError`.\nThe exception will have an `internal_message` that includes the schema error.\nThe error response is status 400, the error code is `InvalidPayload`, and the error message provides the schema error.\n\n# Handler return value processing\n\nWhen a decorator is used on the Lambda function handler, the return value from the handler is inspected.\nIf it is a `dict` containing a `"statusCode"` field, it is considered to be a response and returned intact.\n\nOtherwise, it constructs a response using `make_response()`, with the following logic:\n* The status code is 200.\n* If the return value is of type `bytes`, it will be base64-encoded and a default content-type of `application/octet-stream` will be applied.\n* If the return value is of type `str`, it will be used as-is and a default content-type of `text/plain` will be applied.\n* Otherwise, the return value will be JSON-serialized and a default content-type of `application/json` will be applied.\n\nWhen serializing to JSON, objects of `datetime.datetime`, `datetime.date`, and `datetime.time` and `decimal.Decimal` classes are handled.\nBy default, the `datetime` classes are serialized with their `isoformat()` methods and UTC timezones are converted from using an `+00:00` offset to the plain `Z` suffix.\nBy default, `decimal.Decimal` is serialized as a float.\nThis can be changed with the `set_default_json_serialization_options()` function, and also `make_response()` can be provided with a `JSONSerializationOptions` override directly.\n\nHeaders and cookies can be set in the `api_response_headers` and `api_response_cookies` fields on the Lambda context object (the decorator creates these fields).\n\n# Redirects\n\nA redirect response can be generated using the `make_redirect()` function, which takes a 3XX status code and a URL to redirect to.\n\n# `APIErrorResponse`\nThe requirement of the handler returing a structured when an error occurs is not particularly Pythonic; instead, we should raise an exception and it should cauase the right thing to happen.\nThis functionality is provided by the `APIErrorResponse` exception class.\nAn `APIErrorResponse` subclass has a status code, and knows how to create the response to return to API Gateway through the `get_response()` method.\n\nThe most basic usage is when you catch an exception that should cause an error to be returned to the client, you can call `APIErrorResponse.re_raise_as()` in the `except` block, providing the status code for the response.\nThis will pick up the active exception and use it for the response body ([see below](#Error-response-body) for error response body details): the error code will be the exception class name, and the error message will be the stringified exception.\nYou can provide an internal message for logging ([see below](#Error-logging)), or it will default to a string containing the error code and error message.\n\nYou can create your own subclasses of `APIErrorResponse` to make exceptions that will be caught by the decorators and turned into error responses as defined by the subclass.\nThis package also provides a generic `InvalidRequestError` exception (an `APIErrorResponse` subclass), which has a status code of 400 and an error code of `InvalidRequest`, and an error message you provide.\nYou can additionally call `APIErrorResponse.from_status_code()` to generate a generic exception instance for a given 4XX or 5XX status code.\n\nValidators in this package raise subclasses of `APIErrorResponse` for validation failures; see the docs for each validator for more information.\n\nWhen using a decorator to catch `APIErrorResponse` exceptions, headers and cookies for the response can be set from within the handler using the `api_response_headers` and `api_response_cookies` fields on the Lambda context object (the decorator creates these fields).\n\n## Error logging\n\nTo log `APIErrorResponse` exceptions caught in a decorator, you can set `APIErrorResponse.DECORATOR_LOGGER` to a callable (e.g., `print`) or a `logging.Logger` object.\nThis will log the `internal_message` field of the `APIErrorResponse` exception.\nBy default, this does not include a traceback; set `APIErrorResponse.DECORATOR_LOGGER_TRACEBACK` to `True` to include one.\n\n## Response generation\n\nThe response is generated with the `get_response()` method.\nThis method must be provided an `format_version` to determine the format of the response; this can either be a format version directly, or the Lambda function input event (to determine the format version from).\nIt can optionally take a `body`, `headers`, and `cookies` to pass to the class methods that determine those values for the response.\n\nBy default, the response body generated for an `APIErrorResponse` looks like the following:\n\n```json\n{\n    "Error": {\n        "Code": "<error code>",\n        "Message": "<error message>"\n    }\n}\n```\n\nThese field names can be changed by altering the `ERROR_PARENT_FIELD`, `ERROR_CODE_FIELD`, and `ERROR_MESSAGE_FIELD` class fields on `APIErrorResponse`; if `ERROR_PARENT_FIELD` is set to `None`, the error code and error message will be put at the top level.\n\n## Subclassing `APIErrorResponse`\n\nCreate your own subclasses of `APIErrorResponse` to represent your own error conditions and how they should be mapped into an API response.\n\nThe minimal subclass looks like this:\n```python\nclass MyError(APIErrorResponse):\n    STATUS_CODE = 400 # the status code for the response\n\n    ERROR_MESSAGE = "My error message" # fixed message\n\n# usage:\n# APIErrorResponse requires an internal message be provided to its constructor.\nraise MyError("This is the internal error message for logging")\n```\n\nThe error code defaults to the class name, but can be set explicitly with the `ERROR_CODE` class field.\n\nThe error message can be a string template using the `ERROR_MESSAGE_TEMPLATE` field, rather than a fixed value.\nThe template uses the standard `str.format()` method.\nIt can reference any instance fields.\nFor convenience, any keyword arguments provided to the `APIErrorResponse` constructor are stored and can also be referenced in the template, meaning you don\'t need to define your own constructor.\n\n```python\nclass MyError(APIErrorResponse):\n    STATUS_CODE = 400 # the status code for the response\n\n    ERROR_MESSAGE_TEMPLATE = "My error message: {msg}" # fixed message\n\n# usage:\nraise MyError("This is the internal error message for logging",\n    msg="value for the error message template")\n```\n\n### Headers and cookies\nOverride the `get_headers()` and `get_cookies()` methods to set the headers and cookies in the response.\nThey each take the value provided to `get_response()` as input.\n\n### Error response body\nThe response body is constructed in the `get_body()` method; the default implementation uses the `get_error_code()` and `get_error_message()` fields with the `make_error_body()` class method.\n\nThe `get_error_code()` method has a default implementation that uses the `ERROR_CODE` class field if it is set, falling back to the exception class name.\n\nThe `get_error_message()` method has a default implementation that uses the `ERROR_MESSAGE` class field if it is set, otherwise using the `ERROR_MESSAGE_TEMPLATE` class field if it is set, calling the string `format()` method with `vars(self)` as inputs (that is, you can reference fields from the exception in the template).\nIf neither `ERROR_MESSAGE` or `ERROR_MESSAGE_TEMPLATE` are set, the message `An error occurred.` is used.\n\nThe `make_error_body()` class method constructs a body of the following form, taking the error code and message as input:\n```json\n{\n  "Error": {\n    "Code": "MyErrorCode",\n    "Message": "My error message."\n  }\n}\n```\nYou can change the error code field name with the `ERROR_CODE_FIELD` class field, and the error message field name with the `ERROR_MESSAGE_FIELD` class field.\nYou can change the top-level field name by setting the `ERROR_PARENT_FIELD` class field, or by setting it to `None` the error code and message fields will be set at the top level.\n\n# Validators\n\nValidation is provided with both a decorator API and a functional API.\nSee the full documenation on validators [here](docs/validators.md).\n\n# Creating responses directly\n\nThe `make_response()` function formats a response for the function according to a given format version.\nThe format version must be specified, but can be given implicitly by setting `format_version` to the Lambda function input event.\n\nIf the `body` arugment is not a string or bytes, it will be serialized to JSON, and a `Content-Type` header of `application/json` will be added.\n\nThe function signature is as follows:\n```python\nmake_response(\n    status_code: Union[int, http.HTTPStatus],\n    body: Optional[Any],\n    *,\n    format_version: Union[FormatVersion, Dict],\n    headers: Optional[Dict[str, Union[str, List[str]]]] = None,\n    cookies: Optional[List[str]] = None,\n    json_serialization_options: Optional[JSONSerializationOptions] = None,\n) -> Dict:\n```\n\nWhen your function raises an exception that should be turned into a response, but you can\'t or don\'t want to modify the code to make that exception an `APIErrorResponse` subclass ([see above](#subclassing-errorresponse) for details), you can use the `APIErrorResponse.make_response_from_exception()` class method.\nThis takes a status code and exception, and creates the error response with the error code set to the exception class name and the error message set to the stringified exception.\nIf the exception is an `APIErrorResponse` subclass, the result will be a call to its `get_response()` method, but it will raise an error if the status codes don\'t match.\nThe method signature is as follows:\n\n```python\nAPIErrorResponse.make_response_from_exception(\n    status_code: Union[int, http.HTTPStatus],\n    exception: Exception,\n    *,\n    format_version: Union[FormatVersion, Dict],\n    headers: Optional[Dict[str, Union[str, List[str]]]] = None,\n    cookies: Optional[List[str]] = None,\n) -> Dict:\n```\n',
    'author': 'Ben Kehoe',
    'author_email': 'ben@kehoe.io',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/benkehoe/aws-lambda-api-event-utils',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
