
Tight is a microframework created and optimized for serverless runtimes. With tight-cli
and tight
you can quickly scaffold serverless applications that are conventional, testable by default and free of boilerplate.
Tight currently supports AWS Lambda and the Python2.7 runtime.
Introduction¶
Tight is a microframework created and optimized for serverless runtimes. With tight-cli
and tight
you can quickly scaffold serverless applications that are conventional, testable-by-default and free of boilerplate.
Tight currently supports AWS Lambda and the Python2.7 runtime.
Overview¶
The modules provided by tight
make it easy to write lambda functions that act as REST resource controllers. Simply author your function, following Tight’s conventional naming and directory schemes (don’t worry, tight-cli
can generate these files and directories for you!):
my_app/app/functions/my_controller/handler.py:
import tight.providers.aws.controllers.lambda_proxy_event as lambda_proxy
@lambda_proxy.get
def get_handler(*args, **kwargs):
return {
'statusCode': 200,
'body': {
'hello': 'world'
}
}
@lambda_proxy.post
def post_handler(*args, **kwargs):
event = kwargs.pop('event')
# Process request event...
# ...
return {
'statusCode': 201,
}
@lambda_proxy.put
def put_handler(*args, **kwargs):
event = kwargs.pop('event')
return {
'statusCode': 200,
}
@lambda_proxy.patch
def patch_handler(*args, **kwargs):
pass
@lambda_proxy.delete
def delete_handler(*args, **kwargs):
pass
Import and run a tight
app (as generated by tight generate app my_app
:
my_app/app_index.py:
# Project structure generated by `tight generate app my_app`
# Dependencies installed by running `tight pip install --requirements`
from app.vendored.tight.providers.aws.lambda_app import app as app
app.run()
And call the controller on the module defined by app_index.py:
app_index.my_controller
And as mentioned the tight-cli
command line tool will scaffold services, functions with test stubs and much more!
Create an application:

Create functions with tests:

Motivation & Philosophy¶
Tight is inspired by tools and frameworks such as Sinatra, Ember, Flask, Chalice, and Serverless – to name just a few. Tight aspires to help you and your team build servereless applications in a repeatable and conventional manner. To achieve this goal Tight provides two distinct packages: tight and tight-cli.
The core package, tight, provides modules that map request events to REST style resource handlers – this makes it easy to author declarative resource controllers that group method handlers logically and legibly. Additional modules help you interact with external services, like DynamoDb, in an intuitive and fluent manner. Finally, a suite of test helpers makes it easy to record and simulate HTTP requests and AWS SDK calls.
The Tight command line tool, tight_cli, helps you quickly scaffold and test Tight applications and functions. The tutorials walk through every tight-cli
command and will demonstrate how to:
- Generate application directories and files
- Generate functions and tests
- Install and manage application dependencies
- Install, configure, and run a local instance of DynamoDb.
The tight-cli
package does the dirty work of preparing your application for predictable and hassle-free deployments. With tight-cli
you can easily generate CloudFormation compatible DynamoDb schemas from application model definitions as well as create deployable artifacts that can be used with a variety of deployment strategies. Tight does not make assumptions or prescriptions about which approach you and your team follow; Tight apps can be deployed directly through the AWS console or in conjunction with other tools and services.
Tight doesn’t try to enforce a specific application deployment process, rather it allows you to get your application to a deployable state quickly and in a convention-over-configuration manner. This is where Tight differs with existing serverless-specific tools like Serverless, Chalice, Gordon and Zappa. Another significant divergence from other frameworks and tools (all of which have inspired Tight in some way!) is that Tight doesn’t provide a mechanism to invoke or run applications locally via a development server or some other “simulated” service. This is because Tight makes writing tests easy which in turn makes building resilient apps and services approachable and frictionless.
Installation¶
The tight
and tight-cli
packages are both available to install via Github while the packages are in preview.
tight-cli
¶
$ pip install git+git://github.com/lululemon/tight-cli.git#egg=tight-cli
tight
¶
$ pip install git+git://github.com/lululemon/tight.git#egg=tight
Quickstart¶
For a through introduction to Tight and all that it offers read through and follow the tutorials. This quickstart guide is designed to show you the sequence of commands you have to run to get a new project up and running and ready for development.
Install¶
Install tight-cli
via, Git.
$ pip install git+git://github.com/lululemon/tight-cli.git#egg=tight-cli
Generate a Tight App¶
$ tight generate app my_service
Install Environment Dependencies¶
$ cd my_service
$ pip install -r requirements.txt
Install Application Dependencies¶
$ cd my_service
$ tight pip install --requirements
Generate Environment File¶
$ cd my_service
$ tight generate env
{CI: false, NAME: my-service, STAGE: dev}
Generate a Function & Tests¶
$ cd my_service
$ tight generate function my_function
============================================= test session starts =============================================
platform darwin -- Python 2.7.10, pytest-3.0.5, py-1.4.32, pluggy-0.4.0
rootdir: /Users/michael/Development/my_service, inifile:
collected 2 items
tests/functions/integration/my_function/test_integration_my_function.py .
tests/functions/unit/my_function/test_unit_my_function.py .
========================================== 2 passed in 0.12 seconds ===========================================
Successfully generated function and tests!
Tutorial¶
Setup¶
To get up and running with tight-cli
you’ll have to install the package to a virtual environment. Fore more information about setting up and installing virtual environments, visit virtualenv.
Virtual Environment¶
Create a new virtual environment:
$ mkvirtualenv my_tight_app_env
Or activate an existing environment:
$ workon my_tight_app_env
The commands mkvirtualenv
and workon
are provided by virtualenvwrapper.
Package Installation¶
Currently, the tight-cli
package is installed via git. Once you are in your virtual environment you can install the package by issuing the following command:
$ pip install git+git://github.com/lululemon/tight-cli.git#egg=tight-cli
You are now ready to start building a Tight app!
Generate An App¶
Tight has been designed to aid in fast and repeatable development of microservices. As such, Tight provides a suite of commands that generate files and directories that are common to all projects. The very first generate command that we are going to explore is tight generate app
.
Generate Project Directory¶
Navigate to the location where you want to create your app. I like to keep my code projects in the Development
directory in my home location, so I’ll head that way.
$ cd ~/Development
Now I’m going to use tight generate app
to generate a new project. Simply provide a name as the command’s only argument:
$ tight generate app tight_app
Your app has been generated! To verify, you can list the contents of the tight_app
directory.
drwxr-xr-x 12 user group 408B Jan 2 14:56 .
drwxr-xr-x@ 24 user group 816B Jan 2 14:56 ..
-rw-r--r-- 1 user group 143B Dec 25 17:07 .gitignore
drwxr-xr-x 8 user group 272B Jan 2 14:56 app
-rw-r--r-- 1 user group 76B Dec 25 16:38 app_index.py
-rw-r--r-- 1 user group 476B Jan 2 14:56 conftest.py
-rw-r--r-- 1 user group 56B Dec 26 02:28 env.dist.yml
-rw-r--r-- 1 user group 60B Dec 17 18:03 requirements-vendor.txt
-rw-r--r-- 1 user group 40B Dec 17 18:05 requirements.txt
drwxr-xr-x 4 user group 136B Dec 23 12:39 schemas
drwxr-xr-x 4 user group 136B Dec 23 11:22 tests
-rw-r--r-- 1 user group 54B Jan 2 14:56 tight.yml
The Anatomy of a Default App¶
app/¶
The app directory, not surprisingly, contains your application’s runtime logic. This directory organizes your application’s function code along with dependencies and share libraries.
List the contents of the directory to see what was created:
$ ls -la app
drwxr-xr-x 8 user group 272B Jan 2 14:56 .
drwxr-xr-x 13 user group 442B Jan 2 14:57 ..
-rw-r--r-- 1 user group 339B Dec 23 11:22 __init__.py
drwxr-xr-x 3 user group 102B Dec 17 17:25 functions
drwxr-xr-x 3 user group 102B Dec 23 11:22 lib
drwxr-xr-x 3 user group 102B Dec 23 10:46 models
drwxr-xr-x 3 user group 102B Dec 23 10:46 serializers
drwxr-xr-x 3 user group 102B Jan 2 14:56 vendored
app/__init__.py¶
The app
directory’s __init__.py
file augments the current environment so that application dependencies are discoverable for import.
The generator currently produces the following file:
import sys, os
here = os.path.dirname(os.path.realpath(__file__))
sys.path = [os.path.join(here, "./vendored")] + sys.path
sys.path = [os.path.join(here, "./lib")] + sys.path
sys.path = [os.path.join(here, "./models")] + sys.path
sys.path = [os.path.join(here, "./serializers")] + sys.path
sys.path = [os.path.join(here, "../")] + sys.path
These statements are what allow function code to import packages in the vendored
, lib
, models
, and serializers
directories without having to use relative imports. If you do not wish to alter the environment’s import path, the contents of this file can be removed. However, do not remove the file completely.
app/functions/¶
The functions directory is where your application’s business logic lives. Later in the tutorial, when we start creating functions, we’ll explain the naming and file conventions that should be followed within the functions
directory.
For now all you need to know is that the directory is identified as a package, since it has a blank __init__.py
file.
app/lib/¶
The lib
directory is where you should keep modules and packages that are shared across functions but that aren’t installable via pip.
app/models/¶
The models
directory is where the domain objects that your application manipulates should be stored. Like the function
directory, files placed in the model
directory should conform to Tight’s conventions.
Modules in this directory should define a single model and the name of the model and file should be the same.
Imagine creating an Account
model:
$ ls -la app/models
drwxr-xr-x 4 user group 136B Jan 2 15:27 .
drwxr-xr-x 8 user group 272B Jan 2 14:56 ..
-rw-r--r-- 1 user group 80B Jan 2 15:27 Account.py
-rw-r--r-- 1 user group 460B Dec 23 10:46 __init__.py
$ less Account.py
def Account(id):
""" Account factory """
return {
'id': id
}
app/models/__init__.py¶
Unlike the other directories that get created inside of app
, the __init__.py
file inside of models
is not empty. This file will loop through the files in the directory and automatically import the models that are defined. So long as the convention described above is followed, you will be able to succinctly import models into function modules.
The Acccount
model defined above would be imported like so:
from Account import Account
app/serializers/¶
Tight encourages you to maintain serialization logic separately from model modules. As such, Tight provides a location where serializers can be kept.
app_index.py¶
This is the module that is used to route Lambda events to the correct function.
from app.vendored.tight.providers.aws.lambda_app import app as app
app.run()
The function tight.providers.aws.lambda_app.run
collects functions from app/functions
and sets attributes on the module for each function found. This means that when you go to configure your Lambda function within AWS, you can refer to module attributes that mirror functions:
app_index.a_function_in_your_app
This will call the handler
function on the module located at app/functions/a_function_in_your_app/handler.py
.
conftest.py¶
conftest.py
provides the minimum confugration needed to run function tests. The module also imports the tight.core.test_helpers
module, which exposes test fixtures and other goodies to help you start writing tests right away.
env.dist.yml¶
This file contains the default values for the environment variables that your application expects. You shouldn’t store sensitive or environment specific values here. By default, this file specifies that the environment variables, CI
and STAGE
are expected:
# Define environment variables here
CI: False
STAGE: dev
As your application evolves remember to update this file with new names:
# Define environment variables here
CI: False
STAGE: dev
SOME_API_KEY: <optionally provide a default value>
requirements-vendor.txt¶
Specify pip package dependencies, which will be installed to app/vendored
by default.
requirements.txt¶
Specify pip package dependencies that are to be installed to the virtual environment. Typically this is where you’ll define dependencies that are required for developing and testing your app.
Dependencies specified here will not be packaged with your application artifact.
schemas/¶
This directory will contain CloudFormation compatible DynamoDb schemas, which can be auto-generated from model definitions.
tests/¶
Tight really wants to help you develop your application test-first. It would be a tragedy and an embarrasment if Tight didn’t provide you a place to store your tests. Once we stater generating functions, we’ll dive deeper into the structure of this directory.
tight.yml¶
tight.yml
is this Tight app’s configuration file. There’s nothing too fancy about it and throughout the course of the tutorial, you’ll rarely have to modify it. Just be aware that it exists and that it is the location from which the command line tool pulls the application name. Every time a tight
command is run, this file is discovered and parsed and the values it defines are used throughout various commands.
Install Dependencies¶
Now that our application structure has been scaffolded, it’s time to install our dependencies. First we’ll install our virtual environment depedencies and then we’ll install our application specific dependencies.
Environment Dependencies¶
Install environment dependencies just as you would for any other virtual environment.
$ pip install -r requirements.txt
App Dependencies¶
Application dependencies are also installed via pip
but we need to be sure that they get installed to the correct location so that when our application artifact is deployed, any third-party libraries that your application relies on are available. To install your application dependencies, navigate to your project root and run tight pip install --requirements
:
$ tight pip install --requirements
When you run this command, you’ll notice that at the very end of the run you are notified that the boto3
and botocore
packages have been removed from the app/vendored
directory. This is because both packages are supplied by the AWS Lamba execution environment. Generally, you shouldn’t include these packages in your application artifact.
Conclusion¶
By now, you have scaffolded your first Tight app and should have a basic grasp of the purpose and reason for the auto-generated files and directories.
You also learned how to install your application and virtual environment dependencies.
Continue reading to learn about how Tight helps you initialize and manage application environment variables.
Models and Data¶
DynamoDb Support¶
Generate a Model¶
Install DynamoDb Locally¶
Running DynamoDb Locally¶
Testing DynamoDb Interactions¶
Creating an Artifact¶
tight-cli
¶
The tight-cli
package is one of two components, which together form Tight: the toolset that helps you build event driven applications for serverless runtimes. tight-cli
helps you scaffold and maintain Tight apps. This document describes the available commands exposed by tight-cli
. For a more thorough discussion of how to use tight-cli
to create and manage your application visit the tutorials.
Once installed, you can invoke tight-cli
simply by calling tight
from the command line:
$ tight
Usage: tight [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
dynamo
generate
pip
tight generate
¶
The generate group currently supports two sub-commands: app
and function
. Use these commands to quickly scaffold your application, functions, and tests.
tight generate app
¶
Usage: tight generate app [OPTIONS] NAME
Options:
--provider TEXT Platform providers
--type TEXT Provider app type
--target TEXT Location where app will be created.
--help Show this message and exit.
tight generate app
only currently supports the default values for provider
and type
. Therefore, NAME
is really all that is currently required. If you don’t want to generate the app in the current directory, you can override the write target by specifying the --target
option.
$ tight generate app my_service
$ cd my_service
$ ls -la
drwxr-xr-x 12 user group 408B Dec 30 14:40 .
drwxr-xr-x@ 24 user group 816B Dec 30 14:40 ..
-rw-r--r-- 1 user group 143B Dec 25 17:07 .gitignore
drwxr-xr-x 8 user group 272B Dec 23 11:22 app
-rw-r--r-- 1 user group 76B Dec 25 16:38 app_index.py
-rw-r--r-- 1 user group 477B Dec 30 14:40 conftest.py
-rw-r--r-- 1 user group 56B Dec 26 02:28 env.dist.yml
-rw-r--r-- 1 user group 60B Dec 17 18:03 requirements-vendor.txt
-rw-r--r-- 1 user group 40B Dec 17 18:05 requirements.txt
drwxr-xr-x 4 user group 136B Dec 23 12:39 schemas
drwxr-xr-x 4 user group 136B Dec 23 11:22 tests
-rw-r--r-- 1 user group 55B Dec 30 14:40 tight.yml
tight generate function
¶
Quickly generate a function and test stubs in a Tight app.
Usage: tight generate function [OPTIONS] NAME
Options:
--provider [aws] Platform providers
--type [lambda_proxy] Function type
--help Show this message and exit.
This command will generate a function module and will also stub integration and unit tests for the generated module:
$ tight generate function my_controller
============================================= test session starts =============================================
platform darwin -- Python 2.7.10, pytest-3.0.5, py-1.4.32, pluggy-0.4.0
rootdir: /Users/michael/Development/my_service, inifile:
collected 2 items
tests/functions/integration/my_controller/test_integration_my_controller.py .
tests/functions/unit/my_controller/test_unit_my_controller.py .
========================================== 2 passed in 0.10 seconds ===========================================
Successfully generated function and tests!
This command generates the following files and directories:
|-app/
|---functions/
|-----my_controller/
|-------handler.py
|-tests/
|---functions/
|-----unit/
|-------my_controller/
|---------test_unit_my_controller.py
|-----integration/
|-------my_controller/
|---------expectations/
|-----------test_get_method.yml
|---------placebos/
|---------test_integration_my_controller.py
The contents of the generated files:
app/functions/my_controller/handler.py
from tight.providers.aws.clients import dynamo_db
import tight.providers.aws.controllers.lambda_proxy_event as lambda_proxy
db = dynamo_db.connect()
@lambda_proxy.get
def get_handler(*args, **kwargs):
return {
'statusCode': 200,
'body': {
'hello': 'world'
}
}
@lambda_proxy.post
def post_handler(*args, **kwargs):
pass
@lambda_proxy.put
def put_handler(*args, **kwargs):
pass
@lambda_proxy.patch
def patch_handler(*args, **kwargs):
pass
@lambda_proxy.options
def options_handler(*args, **kwargs):
pass
@lambda_proxy.delete
def delete_handler(*args, **kwargs):
pass
tests/functions/integration/my_controller/test_integration_my_controller.py
import os, json
here = os.path.dirname(os.path.realpath(__file__))
from tight.core.test_helpers import playback, record, expected_response_body
def test_get_method(app, dynamo_db_session):
playback(__file__, dynamo_db_session, test_get_method.__name__)
context = {}
event = {
'httpMethod': 'GET'
}
actual_response = app.my_controller(event, context)
actual_response_body = json.loads(actual_response['body'])
expected_response = expected_response_body(here, 'expectations/test_get_method.yml', actual_response)
assert actual_response['statusCode'] == 200, 'The response statusCode is 200'
assert actual_response_body == expected_response, 'Expected response body matches the actual response body.'
tests/functions/integration/my_controller/expectations/test_get_method.yml
body: '{"hello":"world"}'
headers: {Access-Control-Allow-Origin: '*'}
statusCode: 200
tests/functions/unit/my_controller/test_unit_my_controller.py
def test_no_boom():
module = __import__('app.functions.my_controller.handler')
assert module
tight generate model
¶
Generate a Flywheel model and write to app/models
.
Usage: tight generate model [OPTIONS] NAME
Options:
--help Show this message and exit.
Example:
$ tight generate model account
$ cd app/models
$ ls
-rw-r--r-- 1 user group 390B Dec 30 15:28 Account.py
-rw-r--r-- 1 user group 460B Dec 23 10:46 __init__.py
The generated model:
from flywheel import Model, Field, Engine
import os
# DynamoDB Model
class Account(Model):
__metadata__ = {
'_name': '%s-%s-accounts' % (os.environ['NAME'], os.environ['STAGE']),
'throughput': {
'read': 1,
'write': 1
}
}
id = Field(type=unicode, hash_key=True)
# Constructor
def __init__(self, id):
self.id = id
tight generate env
¶
This command will generate a env.yml
file, merging values defined in env.dist.yml
and values in the current shell environment.
$ tight generate env
{CI: false, NAME: my-service, STAGE: dev}
tight pip
¶
tight pip
is a lightweight command that helps manage dependencies in the context of a Tight app.
tight pip install
¶
Usage: tight pip install [OPTIONS] [PACKAGE_NAME]...
Options:
--requirements / --no-requirements Defaults to --no-requirements
--requirements-file [``CWD``] Requirements file location
--target [``tight.yml::vendor_dir``] Target directory.
--help Show this message and exit.
Typically, after generating an app you’ll want to run tight pip install --requirements
from the application root directory. This will install the dependencies to the app/vendored
directory and then remove the boto3
and botocore
packages; these libraries should not be shipped woth your app since they are provided by AWS in the default Lambda environment.
As you are developing a Tight app, you will undoubtedly need to install additional pip
packages. You have two options for installing new dependencies. You can either add the dependency to requirements-vendor.txt
and re-run tight pip install --requirements
or you can run tight pip install PACKAGE_NAME
, which will install the dependencies to app/vendored
and then append PACKAGE_NAME
to requirements-vendor.txt
.
tight dynamo
¶
One of Tight’s primary goals is to make it quick and easy to scaffold RESTful APIs. To help achieve this goal, tight-cli
provides a group of commands that helps you manage, run, and test interactions with DynamoDB.
tight dynamo installdb
¶
Run this command to download and expand the latest stable version of DynamoDB. The downloaded tarball will be extracted to the directory dynamo_db
.
tight dynamo rundb
¶
This command will run the version of DynamoDB which was downloaded via tight dynamo installdb
. This command runs dynamo using a shared database file which is written to dynamo_db/shared-local-instance.db
.
This file is deleted on startup if it exsits.
Additionally, this command will traverse the app/models
directory and automatically generate tables for models. Models should be instances of Flywheel models.
Before executing this command, you should have run tight generate env
or otherwise have defined app/env.yml
.
$ tight dynamo rundb
Initializing DynamoDB Local with the following configuration:
Port: 8000
InMemory: false
DbPath: ./dynamo_db
SharedDb: true
shouldDelayTransientStatuses: false
CorsParams: *
This engine has the following tables [u'my-service-dev-accounts']
As demonstrated in the example above, the command will report on the tables generated from auto-discovered model classes.
tight dynamo generateschema
¶
This command will generate CloudFormation compatible DynamoDB resources from Flywheel models.
Given the following model:
from flywheel import Model, Field, Engine
import os
# DynamoDB Model
class Account(Model):
__metadata__ = {
'_name': '%s-%s-accounts' % (os.environ['NAME'], os.environ['STAGE']),
'throughput': {
'read': 1,
'write': 1
}
}
id = Field(type=unicode, hash_key=True)
# Constructor
def __init__(self, id):
self.id = id
Running tight dynamo generateschema
will write a YAML file to app/schemas/dynamo
:
$ tight dynamo generateschema
$ cd schemas/dynamo
$ ls
-rw-r--r-- 1 user group 265B Dec 30 15:55 accounts.yml
The contents of acounts.yml
will be:
Properties:
AttributeDefinitions:
- {AttributeName: id, AttributeType: S}
KeySchema:
- {AttributeName: id, KeyType: HASH}
ProvisionedThroughput: {ReadCapacityUnits: 1, WriteCapacityUnits: 1}
TableName: my-service-dev-accounts
Type: AWS::DynamoDB::Table
tight
¶
tight.providers
¶
The tight
package currently supports only one provider: AWS.
tight.providers.aws.lambda_app.app
¶
Use this package to create an entry point module. Typical usage is quite simple, as demonstrated in the following code example. Read on to learn about how module internals work.
from app.vendored.tight.providers.aws.lambda_app import app
app.run()
When creating an app using tight-cli
a file, app_index.py, will be automatically generated which will contain code like the snippet above.
-
tight.providers.aws.lambda_app.app.
collect_controllers
()[source]¶ ” Inspect the application directory structure and discover controller modules.
Given the following directory structure, located at
TIGHT.APP_ROOT
:|-app_index.py |-app/ |---functions/ |-----controller_a/ |-------handler.py |-----controller_b/ |-------handler.py |-----not_a_controller/ |-------some_module.py
Descend into
TIGHT.APP_ROOT/app/functions
and collect the names of directories that contain a file namedhandler.py
. The directory structure above would produce the return value:['controller_a', 'controller_b']
Return type: list Returns: A list of application controller names.
-
tight.providers.aws.lambda_app.app.
create
(current_module)[source]¶ Attach functions to the app entry module.
Introspect the application function root and create function attributes on the provided module that map to each application controller. An application controller is defined as any directory in the app root that contains a handler.py file. The name of the controller is the enclosing directory.
Given the following app structure:
|-app_index.py |-app/ |---functions/ |-----controller_a/ |-------handler.py |-----controller_b/ |-------handler.py |-----not_a_controller/ |-------some_module.py
The controller names collected would be:
controller_a
andcontroller_b
Notice that not_a_controller is omitted because there is no handler.py file in the directory.
Assuming that
app_index.py
is the module from whichcreate
is called, the result would be thatapp_index.py
will behave as if it had been statically defined as:def controller_a(controller_module_path, controller_name, event, context): controller_module_path # 'app.functions.controller_a.handler' controller_name # controller_a callback = importlib.import_module(controller_module_path, 'handler') return callback.handler(event, context, **kwargs) def controller_b(controller_module_path, controller_name, event, context): controller_module_path # 'app.functions.controller_b.handler' controller_name # controller_b callback = importlib.import_module(controller_module_path, 'handler') return callback.handler(event, context, **kwargs)
This means that the handler value provided to lambda can follow the format:
'app_index.controller_a' 'app_index.controller_b'
So long as
app.functions.controller_a.handler
andapp.functions.controller_b.handler
define functions that are decorated bytight.providers.aws.controllers.lambda_proxy_event
the call toapp_index.controller_a
orapp_index.controller_b
will in turn call the correct handler for the request method by mappingevent['httpMethod']
to the correct module function.