Editor’s Note: This post was originally published in February 2019 and has been updated in September 2020
Serverless is still an unexplored realm for many developers, and the idea of transitioning from traditional servers to serverless may look like a daunting task. Fortunately, the serverless ecosystem has produced several powerful frameworks and abstractions to quickly and easily deploy serverless infrastructure.
In this blog, I’ll be introducing one such microframework for Python, developed by engineers at AWS, called Chalice.
Getting Started
A common stumbling block for developers is having to manage the configuration and deployment of cloud resources like serverless functions. AWS provides Chalice as an installable Python package via pip, allowing a developer to have a deployed serverless application in a matter of minutes.
With Chalice, most of the abovementioned management burden is handled behind the scenes, so developers can focus on the code. Moreover, Chalice shares a lot of similarity with the Flask web microframework in terms of code structure, making it easier for developers who are familiar with Flask to adopt Python serverless development.
Prerequisites
The Chalice GitHub page provides a quickstart tutorial for getting a Chalice project started, but it assumes you already have the following installed and set up:
- python 3 (>~3.7)
- Virtualenv
- An AWS account with API/CLI access and local credentials
There are a couple of additional tools worth considering if you are pursuing Python development:
- virtualenvwrapper makes managing separate virtual environments much easier.
- pyenv is like a virtualenv for Python binaries, allowing you to seamlessly switch between separate Python versions.
Chalice Sample Project
If you just want to follow the AWS-provided quickstart instructions, use the following code:
$ python3 --version
Python 3.7.3
$ python3 -m venv venv37
$ . venv37/bin/activate
If you have virtualenvwrapper installed, it’s a lot simpler:
$ mkvirtualenv chalice
$ pip install chalice
After installing Chalice with either method, verify that it’s working:
$ chalice --help
Usage: chalice [OPTIONS] COMMAND [ARGS]...
Options:
...
Now that Chalice is installed, we can simply create an app using the ”new-project” command. Call the app “hello-world”:
$ chalice new-project hello-world
This creates a new directory with a bare minimum of files necessary to deploy and get your app up and running. Next, navigate to your project directory, and deploy your project to AWS:
$ cd hello-world
$ chalice deploy
You should see an IAM role, Lambda function, and API Gateway deployed to your AWS account:
Creating deployment package.
Creating IAM role: hello-world
Creating lambda function: hello-world
Creating Rest API
Resources deployed:
-Lambda ARN:
arn:aws:lambda:eu-west-2:111111111111:function:project-name
-Rest API URL:
https://xxxxxxxxxx.execute-api.eu-west-2.amazonaws.com/api/
NOTE: I have obfuscated my API URL, as well as my AWS account. You will see unique values for your own output.
Now, try making a request to the API:
$ curl -X GET
https://xxxxxxxxxx.execute-api.eu-west-2.amazonaws.com/api/
You should see the following response:
{"hello":"world"}
In the project directory, you should see a file called “app.py,” which is the basic Python module that defines how the app behaves. The contents of the file should be familiar to anyone who has previously worked with Flask:
from chalice import Chalice
app = Chalice(app_name='hello-world')
@app.route('/')
def index():
return {'hello': 'world'}
Congratulations! You’ve successfully set up and run a Chalice “Hello World” app. You can see how simple it was to set up and deploy your Lambda function, as Chalice handles setting up the required IAM role and policy and gives you a functional API Gateway right away.
In the next section, we’ll try out some more interesting examples with Chalice.
Adding Functionality
In the “hello-world” app, a single “index” function was configured to return ‘hello’: ‘world’ whenever an empty GET request was made against the API endpoint. Let’s now add a little more functionality to our app with a “FizzBuzz” endpoint.
For those not familiar with FizzBuzz, it’s a popular coding exercise often given in programming interviews. This HackerRank exercise is a classic example. For the Chalice app, you’ll be adding a “fizzbuzz” endpoint that returns “Fizz”, “Buzz” or “FizzBuzz” depending on the number passed in the request. If the number is not divisible by 3 or 5, it will return the number instead.First, add the following app route to the “app.py” file:
@app.route('/fizzbuzz/{number}')
def fizzbuzz(number):
number = int(number)
if number % 3 == 0 and number % 5 == 0:
answer = "FizzBuzz elif number % 5 == 0:
answer = "Buzz"
elif number % 3 == 0:
answer = "Fizz"
else:
answer = number
return {'answer': answer}
Now, deploy the project again with chalice deploy and make the following request:
$ curl -X GET
https://xxxxxxxxxx.execute-api.eu-west-2.amazonaws.com/fizzbuzz/15
This will call the `fizzbuzz()` function instead and return:
{"answer":"fizzbuzz"}
Try some different numbers to validate that the API is working as expected:
$ curl -X GET
"https://xxxxxxxxxx.execute-api.eu-west-2.amazonaws.com/fizzbuzz/3"
{"answer":"fizz"}
$ curl -X GET
"https://xxxxxxxxxx.execute-api.eu-west-2.amazonaws.com/fizzbuzz/5"
{"answer":"buzz"}
$ curl -X GET
"https://xxxxxxxxxx.execute-api.eu-west-2.amazonaws.com/fizzbuzz/7"
{"answer":7}
Notice that unlike the first example, which just invoked the top-level API endpoint, we’re passing data to the API in the form of a URL parameter defined as “number” in the function. You can deploy much more extensive, interactive APIs by utilizing parameters.
Let’s say we want to inspect the data we pass to the API in a given request. Chalice provides helpful syntax for inspecting requests, utilizing the “to_dict()” method.
Go ahead and add a new route and function to “app.py” with the following code:
@app.route('/inspect_request', methods=['POST'])
def post_headers():
return app.current_request.to_dict()
Note the “methods” parameter that’s been added. This function will only respond to POST requests, rather than GET or PUT requests. Run a deploy again:
$ chalice deploy
Now, query the “/inspect_request” endpoint, making sure to specify a POST method:
$ curl -X GET
https://xxxxxxxxxx.execute-api.eu-west-2.amazonaws.com/inspect_request
You should receive a JSON response containing all the data passed in your request to the endpoint.
What if you want to inspect what your web browser passes instead? Enter the request URL in your browser:
{"Code":"MethodNotAllowedError","Message":"Unsupported method: GET"}
Whoops! Browser requests are almost always GET requests, and our function isn’t properly configured for that yet. So, update the “inspect_request” function as follows:
@app.route('/inspect_request', methods=['POST', 'GET'])
def post_headers():
return app.current_request.to_dict()
Run another deploy:
$ chalice deploy
And visit the URL with your browser again. Now you should be able to see all the data that your browser passes in a request. Awesome! For cleanup, simply run:
$ chalice delete
Chalice handles removing all the provisioned resources. That’s it, you’re done!
Exploring Additional Features
If you want to test out changes locally, without invoking changes to AWS-hosted infrastructure, Chalice provides a local development server. Simply run the command:
$ chalice local
Now, you have a near fully featured endpoint available at:
http://127.0.0.1:8000/
Any routes and functions that are present in “app.py” can be utilized at the local endpoint and will behave as if they are live in AWS. The development server provides much more verbose logging to troubleshoot any issues your live code might have.
Chalice also supports integrations with Amazon SQS, Amazon SNS, Amazon S3, and Amazon CloudWatch Events. You can set triggers for your Lambda functions to be invoked by any of the aforementioned services.
Making Serverless Easy
AWS Chalice is an easy-to-use framework that enables developers to quickly spin up fully featured REST APIs, backed by Python-based Lambda functions. Rather than having to cope with configuring and deploying cloud resources, developers can have their stack up and serving requests with just a few simple commands.