Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save vinitkumar/0f5abbeb13b3610fda239cda03eded17 to your computer and use it in GitHub Desktop.
Save vinitkumar/0f5abbeb13b3610fda239cda03eded17 to your computer and use it in GitHub Desktop.

Revisions

  1. @alexcasalboni alexcasalboni revised this gist Jan 6, 2018. 1 changed file with 0 additions and 0 deletions.
    Binary file added aws-lambda-static-type-checker.png
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
  2. @alexcasalboni alexcasalboni revised this gist Jan 6, 2018. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions aws-lambda-static-type-checker.md
    Original file line number Diff line number Diff line change
    @@ -43,6 +43,7 @@ Check out the two Lambda Functions below for more examples.
    * Run `mypy YOURFILE.py` or add `mypy` to your IDE linting configuration (for VSCode, you'll need to enable the `python.linting.mypyEnabled` setting)
    * Create a local `lambda_types.py` file (you can find it below) and customize it as needed
    * Learn more about the built-in `typing` module [here](https://docs.python.org/3/library/typing.html)
    * (Just FYI: you can run all the tests below with [Nose](http://nose.readthedocs.io/): `pip install nose` and then simply run `nosetests`)

    ## Tips & Tricks

  3. @alexcasalboni alexcasalboni revised this gist Jan 6, 2018. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions aws-lambda-static-type-checker.md
    Original file line number Diff line number Diff line change
    @@ -51,5 +51,6 @@ Check out the two Lambda Functions below for more examples.
    * Make sure your functions/methods are annotated if you want them to be checked
    * You can use either annotations (natively supported only by Python3, sometimes bring to decreased readability) or special comments (e.g. `# type: str`)
    * You don't have to annotate every single function/file of your codebase to benefit from static type checking (i.e. you could focus on critical or semantically ambiguous/complex sections)
    * Writing tests becomes easier/faster as well since many type-related errors will be detected at "compile time" and you won't have to unit-test them (for example, see the `lambda_repeat.get_output` function below)
    * You can customize `LambdaDict` based on your own event structures and conventions. For example, you may want to use `Dict[str, str]` or `Dict[str, Dict[str, Any]]` instead of `Dict[str, Any]`
    * Thanks to code completion, you won't have to memorize all the [LambdaContext](https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html) attributes and methods (e.g. `context.get_remaining_time_in_millis()`, `context.client_context.client.installation_id`, etc.)
  4. @alexcasalboni alexcasalboni created this gist Jan 6, 2018.
    55 changes: 55 additions & 0 deletions aws-lambda-static-type-checker.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,55 @@
    # How to use Python3 Type Hints in AWS Lambda

    *TL;DR*

    > Static Type Checkers help you find simple (but subtle) bugs in your Python code. Check out `lambda_types.py` and incrementally improve your code base and development/debugging experience with type hints.

    Your Lambda Function code will go from this:

    ```python
    def handler(event, context):
    first_name = event.get('first_name') or 'John'
    last_name = event.get('last_name') or 'Smith'
    return {
    'message': get_message(first_name, last_name),
    }

    def get_message(first_name, last_name):
    return 'Hello {} {}!'.format(first_name, last_name)
    ```

    to this:

    ```python
    def handler(event: LambdaDict, context: LambdaContext) -> LambdaDict:
    first_name: str = event.get('first_name') or 'John'
    last_name: str = event.get('last_name') or 'Smith'
    return {
    'message': get_message(first_name, last_name),
    }


    def get_message(first_name: str, last_name: str):
    return 'Hello {} {}!'.format(first_name, last_name)
    ```

    Check out the two Lambda Functions below for more examples.

    # Instructions

    * Create a Python3 virtual env with `virtualenv venv --python=python3 && source venv/bin/activate`
    * Install [mypy](http://mypy-lang.org/) with `pip install mypy`
    * Run `mypy YOURFILE.py` or add `mypy` to your IDE linting configuration (for VSCode, you'll need to enable the `python.linting.mypyEnabled` setting)
    * Create a local `lambda_types.py` file (you can find it below) and customize it as needed
    * Learn more about the built-in `typing` module [here](https://docs.python.org/3/library/typing.html)

    ## Tips & Tricks

    * Static code annotations will not affect your code execution, as they are only useful for static checks and code completion
    * Of course, static typing works fine with Python classes as well
    * Make sure your functions/methods are annotated if you want them to be checked
    * You can use either annotations (natively supported only by Python3, sometimes bring to decreased readability) or special comments (e.g. `# type: str`)
    * You don't have to annotate every single function/file of your codebase to benefit from static type checking (i.e. you could focus on critical or semantically ambiguous/complex sections)
    * You can customize `LambdaDict` based on your own event structures and conventions. For example, you may want to use `Dict[str, str]` or `Dict[str, Dict[str, Any]]` instead of `Dict[str, Any]`
    * Thanks to code completion, you won't have to memorize all the [LambdaContext](https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html) attributes and methods (e.g. `context.get_remaining_time_in_millis()`, `context.client_context.client.installation_id`, etc.)
    19 changes: 19 additions & 0 deletions lambda_message.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    """ Example #1 """
    import os
    from lambda_types import LambdaDict, LambdaContext

    MSG_TEMPLATE: str = os.environ.get('MSG_TEMPLATE') or 'Hello {} {}!'
    STAGE: str = os.environ.get('stage') or 'dev'


    def handler(event: LambdaDict, context: LambdaContext) -> LambdaDict:
    print('Received event {} for stage {}'.format(event, STAGE))
    first_name: str = event.get('first_name') # optional
    last_name: str = event.get('last_name') # optional
    return {
    'message': get_message(first_name, last_name),
    }


    def get_message(first_name: str = 'John', last_name: str = 'Smith'):
    return MSG_TEMPLATE.format(first_name, last_name)
    19 changes: 19 additions & 0 deletions lambda_repeat.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    """ Example #2 """
    import os
    from lambda_types import LambdaDict, LambdaContext

    N: int = int(os.environ.get('N') or 10)
    STAGE: str = os.environ.get('stage') or 'dev'


    def handler(event: LambdaDict, context: LambdaContext) -> LambdaDict:
    print('Received event {} for stage {}'.format(event, STAGE))
    input: str = event['input'] # required
    return {
    'output': get_output(input, N),
    }


    def get_output(input: str, num: int):
    """ Return the input string repeated N times. """
    return input * num
    39 changes: 39 additions & 0 deletions lambda_types.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,39 @@
    """ Note: this code is used only by the static type checker! """
    from typing import Dict, Any

    LambdaDict = Dict[str, Any]


    class LambdaCognitoIdentity(object):
    cognito_identity_id: str
    cognito_identity_pool_id: str


    class LambdaClientContextMobileClient(object):
    installation_id: str
    app_title: str
    app_version_name: str
    app_version_code: str
    app_package_name: str


    class LambdaClientContext(object):
    client: LambdaClientContextMobileClient
    custom: LambdaDict
    env: LambdaDict


    class LambdaContext(object):
    function_name: str
    function_version: str
    invoked_function_arn: str
    memory_limit_in_mb: int
    aws_request_id: str
    log_group_name: str
    log_stream_name: str
    identity: LambdaCognitoIdentity
    client_context: LambdaClientContext

    @staticmethod
    def get_remaining_time_in_millis() -> int:
    return 0
    89 changes: 89 additions & 0 deletions tests.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,89 @@
    import unittest
    from lambda_types import LambdaDict, LambdaContext
    from lambda_message import handler as handler_message, get_message
    from lambda_repeat import handler as handler_repeat, get_output


    class TestMessageFunction(unittest.TestCase):

    def setUp(self):
    self.context = LambdaContext()

    def test_handler(self) -> None:
    event: LambdaDict = {
    "first_name": "Alex",
    "last_name": "Casalboni",
    }
    result = handler_message(event, self.context)
    self.assertIn('message', result)

    def test_handler_empty(self) -> None:
    event: LambdaDict = {}
    result = handler_message(event, self.context)
    self.assertIn('message', result)

    def test_message_default(self) -> None:
    msg = get_message()
    self.assertIsInstance(msg, str)
    self.assertIn('Hello', msg)
    self.assertIn('John', msg)
    self.assertIn('Smith', msg)
    self.assertTrue(msg.endswith('!'))

    def test_message_firstname(self) -> None:
    msg = get_message(first_name='Charlie')
    self.assertIsInstance(msg, str)
    self.assertIn('Hello', msg)
    self.assertIn('Charlie', msg)
    self.assertIn('Smith', msg)
    self.assertTrue(msg.endswith('!'))

    def test_message_lastname(self) -> None:
    msg = get_message(last_name='Brown')
    self.assertIsInstance(msg, str)
    self.assertIn('Hello', msg)
    self.assertIn('John', msg)
    self.assertIn('Brown', msg)
    self.assertTrue(msg.endswith('!'))

    def test_message(self) -> None:
    msg = get_message(first_name='Charlie', last_name='Brown')
    self.assertIsInstance(msg, str)
    self.assertIn('Hello', msg)
    self.assertIn('Charlie', msg)
    self.assertIn('Brown', msg)
    self.assertTrue(msg.endswith('!'))


    class TestRepeatFunction(unittest.TestCase):

    def setUp(self):
    self.context = LambdaContext()

    def test_handler(self) -> None:
    event: LambdaDict = {
    "input": "NaN",
    }
    result = handler_repeat(event, self.context)
    self.assertIn('output', result)
    self.assertEqual(30, len(result['output']))

    def test_handler_empty(self) -> None:
    event: LambdaDict = {}
    with self.assertRaises(KeyError):
    handler_repeat(event, self.context)

    def test_repeat_empty_string(self) -> None:
    output = get_output('', 100)
    self.assertIsInstance(output, str)
    self.assertEqual(0, len(output))

    def test_repeat_zero(self) -> None:
    output = get_output('hello', 0)
    self.assertIsInstance(output, str)
    self.assertEqual(0, len(output))

    def test_repeat(self) -> None:
    output = get_output('hello', 10)
    self.assertIsInstance(output, str)
    self.assertEqual(50, len(output))