2022-10-02

AWS Chalice

What is AWS Chalice

AWS Chalice is a framework for developing serverless applications centered on Lambda, which can also automate integration with API Gateway, making it easy to develop REST APIs. It can also automate settings for using AWS Lambda, such as triggering from events such as S3 and SQS.

How to use AWS Chalice

Installation

Install Chalice with the following command.

$ pip install chalice

Set up AWS credentials as needed.

$ aws configure

Create a Chalice project named helloworld.

$ chalice new-project helloworld

The following files are generated by the creation of a project.

.
├── .chalice
│   └── config.json
├── .gitignore
├── app.py
└── requirements.txt

The important files here are app.py and config.json. The app.py file describes the logic of the application, and the config.json file describes the configuration items of the application.

The app.py is as follows: accessing the API Gateway endpoint / with the GET method returns the response {'hello': 'world'}.

app.py
from chalice import Chalice

app = Chalice(app_name = 'helloworld')

@app.route('/')
def index():
    return {'hello': 'world'}

At the time of project creation, config.json looks like following

config.json
{
  "version": "2.0",
  "app_name": "helloworld",
  "stages": {
    "dev": {
      "api_gateway_stage": "api",
      "autogen_policy": true
    }
  }
}

In stages you can set any environment or environment variable. In autogen_policy you can set whether Chalice will automatically create IAM roles. If you set autogen_policy to false, you can set iam_policy_file to the path of the policy file you created. As an example, you can edit config.json as follows

config.json
{
  "version": "2.0",
  "app_name": "helloworld",
  "autogen_policy": false,
  "iam_policy_file": "custom-policy.json",
  "stages": {
    "dev": {
      "api_gateway_stage": "dev",
      "environment_variables": {
        "ENV": "dev"
      }
    },
    "prod": {
      "api_gateway_stage": "prod",
      "environment_variables": {
        "ENV": "prod"
      }
    }
  }
}

Local execution

You can execute the Chalice project locally with the following command.

$ chalice local

Deploy

You can easily deploy your project with the following command. If necessary, you can select an AWS profile or a stage configured in config.json.

$ chalice deploy
$ chalice deploy --profile chalice
$ chalice deploy --state dev

After successful deployment, the following resources will be automatically created on AWS.

  • IAM role
  • Lambda function
  • API Gateway

Delete resources

To delete the deployed Chalice project, execute the following command.

$ chalice delete

Enable CORS

You can easily enable CORS by adding cors=True to app.py.

app.py
@app.route('/', methods = ['GET'], cors = True)

Enable Cognito authentication

If you want to restrict access to the API by AWS Cognito authentication, write the following code.

app.py
from chalice import CognitoUserPoolAuthorizer

authorizer = CognitoUserPoolAuthorizer("MyPool", provider_arns=[POOL_ARN])

@app.route('/', methods = ['GET'], authorizer=authorizer)

Then, when you submit a request to the API, you can access it by adding a valid Cognito ID token to the Authorization header.

Decorators

Chalice can use decorators to integrate Lambda with other AWS services. Patterns include

  • AWS Lambda:@app.lambda_function
  • AWS Lambda + API Gateway :@app.route
  • AWS Lambda + Amazon CloudWatch Events:@app.schedule
  • AWS Lambda + Amazon S3:@app.on_s3_event
  • AWS Lambda + Amazon SQS:@app.on_sqs_message
  • AWS Lambda + Amazon SNS:@app.on_sns_message

Splitting app.py

When developing APIs in Chalice, app.py becomes bloated as the size of the program grows, leading to poor productivity and maintainability. One solution is to use Chalice's Blueprints feature, which allows you to split app.py into arbitrary units.

For example, suppose you have the following app.py.

app.py
from chalice import Chalice
app = Chalice(app_name='helloworld')

@app.route('/a')
def a():
    return {'hello': 'a'}

@app.route('/b')
def b():
    return {'hello': 'b'}

Now app.py will be separate. The current directory structure is as follows.

helloworld/
├── app.py
└── requirements.txt

Cut the directory as follows: Chalice requires home-made modules to be stored in a directory named chalicelib.

helloworld/
├── app.py
├── chalicelib
│   ├── __init__.py
│   └── blueprints
│     ├── __init__.py
│     ├── a.py
│     └── b.py
└── requirements.txt

Migrate the endpoints of app.py to a.py and b.py respectively.

chalicelib/blueprints/a.py
from chalice import Blueprint
extra_routes = Blueprint(__name__)

@extra_routes.route('/a')
def a():
    return {'hello': 'a'}
chalicelib/blueprints/b.py
from chalice import Blueprint
extra_routes = Blueprint(__name__)

@extra_routes.route('/b')
def b():
    return {'hello': 'b'}

Modify app.py as follows

app.py
from chalice import Chalice
from chalicelib.blueprints.a import extra_routes as a    # 追加
from chalicelib.blueprints.b import extra_routes as b    # 追加

app = Chalice(app_name='helloworld')
app.register_blueprint(a)
app.register_blueprint(b)

Now we can split app.py!

Chalice Testing

Chalice provides a test client called chalice.test that can be used to test Chalice applications. With this client, you can directly call Lambda functions and event handlers, as well as test the REST API.

First, install pytest with the following command.

$ pip install pytest

Then cut the directory as follows

helloworld/
├── app.py
├── tests
│   ├── __init__.py
│   ├── conftest.py
│   └── test_app.py
└── requirements.txt

Put the following code in conftest.py.

tests/conftest.py
from typing import Generator

import pytest
from app import app
from chalice.test import Client

@pytest.fixture(scope="module")
def client() -> Generator:
    with Client(app) as c:
        yield c

In test_app.py, you will write the code for pytest. For example, the code would be as follows.

tests/test_app.py
from chalice.test import Client
from app import app

def test_helloworld(client: Client):
    response = client.http.get("/")
    assert response.status_code == 200

You can run the test with the following command

$ py.test tests/test_app.py

========================= test session starts ==========================
platform darwin -- Python 3.7.3, pytest-5.3.1, py-1.5.3, pluggy-0.12.0
rootdir: /tmp/testclient
plugins: hypothesis-4.43.1, cov-2.8.1
collected 2 items

test_app.py ..                                                    [100%]

========================= 2 passed in 0.32s ============================

References

https://aws.github.io/chalice/
https://aws.github.io/chalice/topics/blueprints.html
https://aws.github.io/chalice/topics/testing.html

Ryusei Kakujo

researchgatelinkedingithub

Focusing on data science for mobility

Bench Press 100kg!