Views

A view function in chalice is the function attached to an @app.route() decorator. In the example below, index is the view function:

from chalice import Chalice

app = Chalice(app_name='helloworld')


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

View Function Parameters

A view function's parameters correspond to the number of captured URL parameters specified in the @app.route call. In the example above, the route / specifies no captured parameters so the index view function accepts no parameters. However, in the view function below, a single URL parameter, {city} is specified, so the view function must accept a single parameter:

from chalice import Chalice

app = Chalice(app_name='helloworld')


@app.route('/cities/{city}')
def index(city):
    return {'city': city}

This indicates that the value of {city} is variable, and whatever value is provided in the URL is passed to the index view function. For example:

GET /cities/seattle   --> index('seattle')
GET /cities/portland  --> index('portland')

If you want to access any other metdata of the incoming HTTP request, you can use the app.current_request property, which is an instance of the the Request class.

View Function Return Values

The response returned back to the client depends on the behavior of the view function. There are several options available:

  • Returning an instance of Response. This gives you complete control over what gets returned back to the customer.
  • A bytes type response body must have a Content-Type header value that is present in the app.api.binary_types list in order to be handled properly.
  • Any other return value will be serialized as JSON and sent back as the response body with content type application/json.
  • Any subclass of ChaliceViewError will result in an HTTP response being returned with the status code associated with that response, and a JSON response body containing a Code and a Message. This is discussed in more detail below.
  • Any other exception raised will result in a 500 HTTP response. The body of that response depends on whether debug mode is enabled.

Error Handling

Chalice provides a built in set of exception classes that map to common HTTP errors including:

  • BadRequestError- returns a status code of 400
  • UnauthorizedError- returns a status code of 401
  • ForbiddenError- returns a status code of 403
  • NotFoundError- returns a status code of 404
  • ConflictError- returns a status code of 409
  • TooManyRequestsError- returns a status code of 429
  • ChaliceViewError- returns a status code of 500

You can raise these anywhere in your view functions and chalice will convert these to the appropriate HTTP response. The default chalice error responses will send the error back as application/json with the response body containing a Code corresponding to the exception class name and a Message key corresponding to the string provided when the exception was instantiated. For example:

from chalice import Chalice
from chalice import BadRequestError

app = Chalice(app_name="badrequset")

@app.route('/badrequest')
def badrequest():
    raise BadRequestError("This is a bad request")

This view function will generate the following HTTP response:

$ http https://endpoint/dev/badrequest
HTTP/1.1 400 Bad Request

{
    "Code": "BadRequestError",
    "Message": "This is a bad request"
}

In addition to the built in chalice exceptions, you can use the Response class to customize the HTTP errors if you prefer to either not have JSON error responses or customize the JSON response body for errors. For example:

from chalice import Chalice, Response

app = Chalice(app_name="badrequest")

@app.route('/badrequest')
def badrequest():
    return Response(message='Plain text error message',
                    headers={'Content-Type': 'text/plain'},
                    status_code=400)

Specifying HTTP Methods

So far, our examples have only allowed GET requests. It's actually possible to support additional HTTP methods. Here's an example of a view function that supports PUT:

@app.route('/resource/{value}', methods=['PUT'])
def put_test(value):
    return {"value": value}

We can test this method using the http command:

$ http PUT https://endpoint/dev/resource/foo
HTTP/1.1 200 OK

{
    "value": "foo"
}

Note that the methods kwarg accepts a list of methods. Your view function will be called when any of the HTTP methods you specify are used for the specified resource. For example:

@app.route('/myview', methods=['POST', 'PUT'])
def myview():
    pass

The above view function will be called when either an HTTP POST or PUT is sent to /myview as shown below:

POST /myview   --> myview()
PUT /myview  --> myview()

Alternatively if you do not want to share the same view function across multiple HTTP methods for the same route url, you may define separate view functions to the same route url but have the view functions differ by HTTP method. For example:

@app.route('/myview', methods=['POST'])
def myview_post():
    pass

@app.route('/myview', methods=['PUT'])
def myview_put():
    pass

This setup will route all HTTP POST's to /myview to the myview_post() view function and route all HTTP PUT's to /myview to the myview_put() view function as shown below:

POST /myview   --> myview_post()
PUT /myview  --> myview_put()

If you do chose to use separate view functions for the same route path, it is important to know:

  • View functions that share the same route cannot have the same names. For example, two view functions that both share the same route path cannot both be named view().
  • View functions that share the same route cannot overlap in supported HTTP methods. For example if two view function both share the same route path, they both cannot contain 'PUT' in their route methods list.
  • View functions that share the same route path and have CORS configured cannot have differing CORS configuration. For example, if two view functions that both share the same route path, the route configuration for one of the view functions cannot set cors=True while having the route configuration of the other view function be set to cors=app.CORSConfig(allow_origin='https://foo.example.com').

Binary Content

Chalice supports binary payloads through its app.api.binary_types list. Any type in this list is considered a binary Content-Type. Whenever a request with a Content-Type header is encountered that matches an entry in the binary_types list, its body will be available as a bytes type on the property app.current_request.raw_body. Similarly, in order to send binary data back in a response, simply set your Content-Type header to something present in the binary_types list. Note that you can override the default types by modifying the app.api.binary_types list at the module level.

Here is an example app which simply echos back binary content:

from chalice import Chalice, Response

app = Chalice(app_name="binary-response")

@app.route('/bin-echo', methods=['POST'],
           content_types=['application/octet-stream'])
def bin_echo():
    raw_request_body = app.current_request.raw_body
    return Response(body=raw_request_body,
                    status_code=200,
                    headers={'Content-Type': 'application/octet-stream'})

You can see this app echo back binary data sent to it:

$ echo -n -e "\xFE\xED" | http POST $(chalice url)bin-echo \
  Accept:application/octet-stream Content-Type:application/octet-stream | xxd
0000000: feed                                     ..

Note that both the Accept and Content-Type header are required. If you fail to set the Content-Type header on the request will result in a 415 UnsupportedMediaType error. Care must be taken when configuring what content_types a route accepts, they must all be valid binary types, or they must all be non-binary types. The Accept header must also be set if the data returned is to be the raw binary, if is omitted the call return a 400 Bad Request response.

For example, here is the same call as above without the Accept header:

$ echo -n -e "\xFE\xED" | http POST  $(chalice url)bin-echo \
  Content-Type:application/octet-stream
HTTP/1.1 400 Bad Request
Connection: keep-alive
Content-Length: 270
Content-Type: application/json
Date: Sat, 27 May 2017 07:09:51 GMT

{
  "Code": "BadRequest",
  "Message": "Request did not specify an Accept header with
    application/octet-stream, The response has a Content-Type of
    application/octet-stream. If a response has a binary Content-Type then
    the request must specify an Accept header that matches."
}

Usage Recommendations

If you want to return a JSON response body, just return the corresponding python types directly. You don't need to use the Response class. Chalice will automatically convert this to a JSON HTTP response as a convenience for you.

Use the Response class when you want to return non-JSON content, or when you want to inject custom HTTP headers to your response.

For errors, raise the built in ChaliceViewError subclasses (e.g BadRequestError, NotFoundError, ConflictError etc) when you want to return a HTTP error response with a preconfigured JSON body containing a Code and Message.

Use the Response class when you want to customize the error responses to either return a different JSON error response body, or to return an HTTP response that's not application/json.