{ "info": { "author": "Rodrigo Farias Rezino", "author_email": "rodrigofrezino@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7" ], "description": "# Strong AWS Lambda\n\nAWS lambdas are a very important feature from AWS platform, due to the intense use of it by the clients and also internally in order to enable other features.\nDue to the intensive usage of it, I'd like to make its surrounds a bit more comfortable to develop on. \nAs a developer with a big static programming language background, I created this module to bring the advantages of \na static type language to the python lambda world (especially custom resources) + automate some repetitive code we always create while\nusing lambdas, like parsing the event input information, guarantee the correct result, etc.\n\n\n## Usage\nThe solution is based in some new decorators, which brings extra features for the lambda handlers.\nAs you might know, the first params for a lambda handler or a custom resource handler is and object event. \nWhich in lambda can be anything but for custom resource is for sure a dictionary with values.\n\n\nThis library convert the event to an object of a given contract you defined and call the function using the new object.\nBefore that it execute all the static typing and consistency with between the given params and the expected params.\n\n\nThe contract must to be a `dataclass` where the name of the fields will be the name of the keys you want to exist in\nthe call to your lambda/custom resource. In case the conversion fails a `ValueError` exception will raise informing\nwhich fields were missing in the `event` params. The message will raise as:\n\n`ValueError: Keys ['X', 'Y', 'Z'] not found in event`, where `X`, `Y` and `Z` are the fields which couldn't be found in \nthe `event` param.\n\nThis also checks for the types you define. If in your contract you expect `field1` as `str` but the information is \nsent as `int` for example. The conversion will fail and raise the given exception:\n\n`WrongTypeError: wrong type for field \"X\" - should be \"str\" instead of \"int\"`, where `X` will be the \n\nThe conversions are use as engine the `dacite` project. So if you want to check more information, how the mapping \nfrom and dictionary to `dataclass` works and what are the possibilities. \nCheck their project [here](https://github.com/konradhalas/dacite) \n\n\n### Lambdas\nYou just need to add the decorator `strong_aws_lambda` and set the param `contract_class` with your desired contract \ndataclass on your lambda handler.\nCheck the example below where we have the contract class `FooContract`. Keep in mind that your contract classes \nmust to have the decorator `dataclass`.\n\n```python\nfrom dataclasses import dataclass\nfrom typing import List\nfrom aws_lambda_context import LambdaContext\nfrom strong_aws_pkg import strong_aws_lambda\n\n\n@dataclass\nclass FooContract:\n field1: str\n field2: int\n items: List[str]\n\n\n@strong_aws_lambda(contract_class=FooContract)\ndef lambda_handler(event: FooContract, context: LambdaContext):\n print(f'The field is for sure in event object and its type is a str. The value is {event.field1}')\n print(f'The field is for sure in event object and its type is an int. The value is {event.field2}')\n print(f'And there are {len(event.items)} in the items object')\n [print(f'And the value of the item is {item}') for item in event.items]\n \n# Just mocking a call the way AWS might to do to the function in order to check its behave.\nif __name__ == \"__main__\":\n input = dict(field1='test1', field2=1, items=['value1', 'value2', 'value3'])\n lambda_handler(input, LambdaContext())\n\n invalid_input = dict(unknown_field='test1', unknown_field2=1, other_items=['value1', 'value2', 'value3'])\n lambda_handler(invalid_input, LambdaContext())\n```\n\nThe console output for this code will be:\n```text\nThe field is for sure in event object and its type is a str. The value is test1\nThe field is for sure in event object and its type is an int. The value is 1\nAnd there are 3 objects in the items object\nAnd the value of the item is value1\nAnd the value of the item is value2\nAnd the value of the item is value3\nKeys ['field1', 'field2', 'items'] not found in event\n```\n\n### Custom Resources\nCustom resources have a bit more complicated situation as they need to communicate back to AWS in order to give the \ninformation about the CloudFormation Stack its changing.\nHere is where you have more gain using this library, as it will ensure that all the information needed will exist\nin the in and out contract.\n\nFor example, even if you forget to add the `Status` in your return, this this library will wrap it into a understandable \nobject where AWS can act accordingly without blocking the finalization of the action.\n\nFor this decorator you need to set to parameters:\n1. `contract_class`: You need to pass your defined contract class which must to have the decorator `@dataclass(frozen=True)`, \nand inherit from `BaseResourceProperties`.\n1. `handle_untreated_exceptions` (default value is `true`): Tell to the decorator function what to do to untreated exceptions. If it's `true` it will\nwrap the exception message into a expected AWS format. If `false` it won't change the result and you will have problems to \nexecute further iterations in stack this custom resource have been created.\n\nThe reason the `dataclass` decorator has its attribute `frozen` se to `true` is due to the fact we want to have \nimmutability in our contract objects. \n\nGiven the python code example:\n```python\nfrom dataclasses import dataclass\nfrom typing import List\n\nfrom aws_lambda_context import LambdaContext\nfrom cfn_lambda_handler import Handler\n\nfrom strong_aws_pkg import AwsRequestContract, BaseResourceProperties, BaseResultContract, StatusResult, \\\n strong_aws_lambda_custom_resource\n\nhandler = Handler()\n\n\n###########################\n# Example of handler create\n###########################\n\n@dataclass(frozen=True)\nclass HandlerCreateContract(BaseResourceProperties):\n CustomParam1: str\n CustomParam2: List[int]\n\n\n@dataclass(frozen=True)\nclass HandlerCreateResultContract(BaseResultContract):\n CustomParams1: str\n\n\n@handler.create\n@strong_aws_lambda_custom_resource(contract_class=HandlerCreateContract, handle_untreated_exceptions=True)\ndef handler_create(custom_params: HandlerCreateContract, context: LambdaContext,\n aws_params: AwsRequestContract) -> HandlerCreateResultContract:\n print(custom_params)\n print(context)\n print(aws_params)\n return HandlerCreateResultContract(Status=StatusResult.Success, CustomParams1='Everything went fine :) Cheers!')\n\n\n###########################\n# Example of handler update\n###########################\n\n@dataclass(frozen=True)\nclass HandlerUpdateContract(BaseResourceProperties):\n Field1: str\n\n\n@handler.update\n@strong_aws_lambda_custom_resource(HandlerUpdateContract)\ndef handler_update(custom_params: HandlerUpdateContract, context: LambdaContext,\n aws_params: AwsRequestContract) -> BaseResultContract:\n raise Exception('Unexpected error')\n\n\n###########################\n# Example of handler delete\n###########################\n\n@dataclass(frozen=True)\nclass HandlerDeleteContract(BaseResourceProperties):\n Reason: str\n\n\n@handler.delete\n@strong_aws_lambda_custom_resource(HandlerDeleteContract)\ndef handler_delete(event: HandlerDeleteContract, context: LambdaContext,\n aws_params: AwsRequestContract) -> BaseResultContract:\n if event.Reason:\n print(f'Deleting stack because {event.Reason}')\n else:\n print(f'Deleting stack')\n\n return BaseResultContract(StatusResult.Success)\n\n\n# Just mocking a call the way AWS might to do to the function in order to check its behave.\nif __name__ == \"__main__\":\n print('------- Calling handler_create')\n # Input example got from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html\n aws_input_param = {\n \"RequestType\": \"Create\",\n \"ResponseURL\": \"http://pre-signed-S3-url-for-response\",\n \"StackId\": \"arn:aws:cloudformation:us-west-2:123456789012:stack/stack-name/guid\",\n \"RequestId\": \"unique id for this create request\",\n \"ResourceType\": \"Custom::TestResource\",\n \"LogicalResourceId\": \"MyTestResource\",\n \"ResourceProperties\": {\n \"CustomParam1\": \"Value\",\n \"CustomParam2\": [1, 2, 3]\n }\n }\n result = handler_create(aws_input_param, LambdaContext())\n print(result)\n\n print('------- Calling handler_update')\n aws_update_param = {\n \"RequestType\": \"Update\",\n \"ResponseURL\": \"http://pre-signed-S3-url-for-response\",\n \"StackId\": \"arn:aws:cloudformation:us-west-2:123456789012:stack/stack-name/guid\",\n \"RequestId\": \"unique id for this create request\",\n \"ResourceType\": \"Custom::TestResource\",\n \"LogicalResourceId\": \"MyTestResource\",\n \"ResourceProperties\": {\n \"Field1\": \"Value\"\n }\n }\n result = handler_update(aws_update_param, LambdaContext())\n print(result)\n\n print('------- Calling handler_delete')\n aws_delete_param = {\n \"RequestType\": \"Delete\",\n \"ResponseURL\": \"http://pre-signed-S3-url-for-response\",\n \"StackId\": \"arn:aws:cloudformation:us-west-2:123456789012:stack/stack-name/guid\",\n \"RequestId\": \"unique id for this create request\",\n \"ResourceType\": \"Custom::TestResource\",\n \"LogicalResourceId\": \"MyTestResource\",\n \"ResourceProperties\": {\n \"Reason\": \"All work done\"\n }\n }\n result = handler_delete(aws_delete_param, LambdaContext())\n print(result)\n\n```\n\nYou will get this output in your console:\n```text\n------- Calling handler_create\nHandlerCreateContract(CustomParam1='Value', CustomParam2=[1, 2, 3])\n\nAwsRequestContract(RequestType='Create', ResponseURL='http://pre-signed-S3-url-for-response', StackId='arn:aws:cloudformation:us-west-2:123456789012:stack/stack-name/guid', ResourceType='Custom::TestResource', LogicalResourceId='MyTestResource')\n{\"Status\": \"SUCCESS\", \"CustomParams1\": \"Everything went fine :) Cheers!\"}\n------- Calling handler_update\n{\"Status\": \"FAILED\", \"Reason\": \"Unexpected error\"}\n------- Calling handler_delete\nDeleting stack because All work done\n{\"Status\": \"SUCCESS\"}\n```\n\nAs you my have noticed the `handler_update` was raising and not treating an exception. This is bad because AWS \nexpects an answer in a specific way. A dictionary with at least a key `Status`, with value `FAILED` or `SUCCESS`. \nAs in this case the `handle_untreated_exceptions` param was set to `true`, the result is a well formatted object:\n\n `{\"Status\": \"FAILED\", \"Reason\": \"Unexpected error\"}`\n\n```python\n@handler.update\n@strong_aws_lambda_custom_resource(HandlerUpdateContract, handle_untreated_exceptions=False)\ndef handler_update(custom_params: HandlerUpdateContract, context: LambdaContext,\n aws_params: AwsRequestContract) -> BaseResultContract:\n ...\n ...\n ...\n```\n\n\n## Further Reading\nIf you are not very familiar with the terms I mentioned above, I put some links together in order to bring more clarity \nto the topics. \n1. [Static vs Dynamic Typing](https://hackernoon.com/i-finally-understand-static-vs-dynamic-typing-and-you-will-too-ad0c2bd0acc7)\n1. [The benefits of static typing without static typing in Python](https://pawelmhm.github.io/python/static/typing/type/annotations/2016/01/23/typing-python3.html\n) \n1. [AWS Lambda](https://aws.amazon.com/lambda/)\n1. [AWS Custom Resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html)\n1. [typing Python \u2014 Support for type hints](https://docs.python.org/3/library/typing.html)\n1. [Immutable objects](https://en.wikipedia.org/wiki/Immutable_object)\n1. [Why Immutability Matters](https://pasztor.at/blog/why-immutability-matters)\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "", "keywords": "", "license": "MIT", "maintainer": "Rodrigo Farias Rezino", "maintainer_email": "rodrigofrezino@gmail.com", "name": "strong-aws-lambda.contract", "package_url": "https://pypi.org/project/strong-aws-lambda.contract/", "platform": "", "project_url": "https://pypi.org/project/strong-aws-lambda.contract/", "project_urls": null, "release_url": "https://pypi.org/project/strong-aws-lambda.contract/0.2.2/", "requires_dist": [ "dacite (>=1.0,<2.0)", "aws-lambda-context (>=1.1,<2.0)" ], "requires_python": ">=3.6,<4.0", "summary": "Extra lambda utils", "version": "0.2.2", "yanked": false, "yanked_reason": null }, "last_serial": 6018507, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "91f1f48da2c9418430fb03dc97e53146", "sha256": "62f6a1357d7403a96aa57e3898bdd4af50159449d660fcb5bd950c9d8c530ac7" }, "downloads": -1, "filename": "strong_aws_lambda.contract-0.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "91f1f48da2c9418430fb03dc97e53146", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.7,<4.0", "size": 4480, "upload_time": "2019-08-23T14:22:44", "upload_time_iso_8601": "2019-08-23T14:22:44.734007Z", "url": "https://files.pythonhosted.org/packages/61/2c/5f1085d6cb6f3ed3f03a55ef4ea15a2e83d8bf5c7f2d99567642b5f2a64f/strong_aws_lambda.contract-0.1.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "17f79ac8667a70b24139fef0316befc5", "sha256": "4bbef61f9e7461dc4a0603f83d92b1a881ed7604d80cef5fa4e4fcc8156397f2" }, "downloads": -1, "filename": "strong-aws-lambda.contract-0.1.0.tar.gz", "has_sig": false, "md5_digest": "17f79ac8667a70b24139fef0316befc5", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.7,<4.0", "size": 3363, "upload_time": "2019-08-23T14:22:42", "upload_time_iso_8601": "2019-08-23T14:22:42.459942Z", "url": "https://files.pythonhosted.org/packages/07/e9/05b6244aefd27c12f60b5fd9bc8ba761c6d1dd1924095df4d8c694dc1f67/strong-aws-lambda.contract-0.1.0.tar.gz", "yanked": false, "yanked_reason": null } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "4c44d90848c24eafb348c1bf1a494490", "sha256": "80e22e8566c626dbfba5598ce379b754e0292ca58712882f73fa172eaf30fd77" }, "downloads": -1, "filename": "strong_aws_lambda.contract-0.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "4c44d90848c24eafb348c1bf1a494490", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6,<4.0", "size": 10256, "upload_time": "2019-09-01T21:31:57", "upload_time_iso_8601": "2019-09-01T21:31:57.802532Z", "url": "https://files.pythonhosted.org/packages/38/ef/31c8b75178faf220acf256df7d2f359aea9c5c03f90ac5b018cc04839268/strong_aws_lambda.contract-0.2.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "c4af61f41170f421c3d53c11dcb50090", "sha256": "f7c45f8ca1f4e3703115fecee898e68586efaef664742d68d26f13dc7e3dbae2" }, "downloads": -1, "filename": "strong-aws-lambda.contract-0.2.0.tar.gz", "has_sig": false, "md5_digest": "c4af61f41170f421c3d53c11dcb50090", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6,<4.0", "size": 12023, "upload_time": "2019-09-01T21:31:56", "upload_time_iso_8601": "2019-09-01T21:31:56.068707Z", "url": "https://files.pythonhosted.org/packages/85/4f/2e868493a4cc9ecc2d3d421a7d8bf55534782febbe153416f2ac21f15cc9/strong-aws-lambda.contract-0.2.0.tar.gz", "yanked": false, "yanked_reason": null } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "09f5dd906d6e07362c2ae63883d42fbc", "sha256": "22ccc86de64df05d1ebe8003902e9073bcea47584137d9ed9cfccd03b3fde324" }, "downloads": -1, "filename": "strong_aws_lambda.contract-0.2.1-py3-none-any.whl", "has_sig": false, "md5_digest": "09f5dd906d6e07362c2ae63883d42fbc", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6,<4.0", "size": 9435, "upload_time": "2019-10-23T12:52:54", "upload_time_iso_8601": "2019-10-23T12:52:54.407388Z", "url": "https://files.pythonhosted.org/packages/3f/07/1ce9235263c0e4ef8495dc2fb99182b46fc764e3a387c5a824e1ed69ec44/strong_aws_lambda.contract-0.2.1-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "be9236ca24320cb0df772d68a17871d8", "sha256": "cf5d80d561aa331415b813879b366e64ed0f191b49031d2695ed81000f61de8f" }, "downloads": -1, "filename": "strong-aws-lambda.contract-0.2.1.tar.gz", "has_sig": false, "md5_digest": "be9236ca24320cb0df772d68a17871d8", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6,<4.0", "size": 11579, "upload_time": "2019-10-23T12:52:52", "upload_time_iso_8601": "2019-10-23T12:52:52.801940Z", "url": "https://files.pythonhosted.org/packages/87/21/9e3f8f2bb83a3e1d178584213599ded978cb075861fefcac6b6a0b6a35da/strong-aws-lambda.contract-0.2.1.tar.gz", "yanked": false, "yanked_reason": null } ], "0.2.2": [ { "comment_text": "", "digests": { "md5": "239cb449648faf3bbabdacd9dcee8a44", "sha256": "3db48690a4a2fd56d68c608eb58f4c6ef79f531f9ad4e16fe0ee63908f0f874d" }, "downloads": -1, "filename": "strong_aws_lambda.contract-0.2.2-py3-none-any.whl", "has_sig": false, "md5_digest": "239cb449648faf3bbabdacd9dcee8a44", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6,<4.0", "size": 9466, "upload_time": "2019-10-23T13:12:32", "upload_time_iso_8601": "2019-10-23T13:12:32.195445Z", "url": "https://files.pythonhosted.org/packages/03/18/945c94a284ffae2643af8fb177eacdbd07e50925464dfc63724bc50ff68d/strong_aws_lambda.contract-0.2.2-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "58858869237d462a7dcf9799dd000bac", "sha256": "ae03765e9d1821cf4a72615f4b4c9d6807f5af6f9368b2c088810cdd1968e1be" }, "downloads": -1, "filename": "strong-aws-lambda.contract-0.2.2.tar.gz", "has_sig": false, "md5_digest": "58858869237d462a7dcf9799dd000bac", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6,<4.0", "size": 11654, "upload_time": "2019-10-23T13:12:30", "upload_time_iso_8601": "2019-10-23T13:12:30.544555Z", "url": "https://files.pythonhosted.org/packages/8d/48/f68730e175311e6ae4def263c2de7db4c9a2e0d2015a74a15f5e6009979e/strong-aws-lambda.contract-0.2.2.tar.gz", "yanked": false, "yanked_reason": null } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "239cb449648faf3bbabdacd9dcee8a44", "sha256": "3db48690a4a2fd56d68c608eb58f4c6ef79f531f9ad4e16fe0ee63908f0f874d" }, "downloads": -1, "filename": "strong_aws_lambda.contract-0.2.2-py3-none-any.whl", "has_sig": false, "md5_digest": "239cb449648faf3bbabdacd9dcee8a44", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6,<4.0", "size": 9466, "upload_time": "2019-10-23T13:12:32", "upload_time_iso_8601": "2019-10-23T13:12:32.195445Z", "url": "https://files.pythonhosted.org/packages/03/18/945c94a284ffae2643af8fb177eacdbd07e50925464dfc63724bc50ff68d/strong_aws_lambda.contract-0.2.2-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "58858869237d462a7dcf9799dd000bac", "sha256": "ae03765e9d1821cf4a72615f4b4c9d6807f5af6f9368b2c088810cdd1968e1be" }, "downloads": -1, "filename": "strong-aws-lambda.contract-0.2.2.tar.gz", "has_sig": false, "md5_digest": "58858869237d462a7dcf9799dd000bac", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6,<4.0", "size": 11654, "upload_time": "2019-10-23T13:12:30", "upload_time_iso_8601": "2019-10-23T13:12:30.544555Z", "url": "https://files.pythonhosted.org/packages/8d/48/f68730e175311e6ae4def263c2de7db4c9a2e0d2015a74a15f5e6009979e/strong-aws-lambda.contract-0.2.2.tar.gz", "yanked": false, "yanked_reason": null } ], "vulnerabilities": [] }