API response

Returning a dataclass from our DB layer to the API layer establishes a contract between these two pieces of code. Any time they want to communicate, the contract needs to be respected.

APIs are forever

But the same type of contract should exist between the API layer and its users. That way the users know what to expect as a response to their requests. We could go ahead and implement the response data mapping reference but as always, simple is better than complex.

Our API response does not need to add anything to the DBResponse class since it contains everything that the end-user (browser) will need. Everything except status code.

Our API must handle errors gracefully and return standard error codes.

import logging

from os import getenv
from chalice import Chalice, Response
from chalicelib.db import get_app_db
from dataclasses import asdict

try:
    from dotenv import load_dotenv

    load_dotenv()
except ImportError:
    pass

first_name = getenv("WORKSHOP_NAME", "ivica")  # replace with your own name of course

app = Chalice(app_name=f"{first_name}-savealife")
app.log.setLevel(logging.DEBUG)


@app.route("/donor/signup", methods=["POST"])
def donor_signup():
    body = app.current_request.json_body

    app.log.debug(f"Received JSON payload: {body}")

    db_response = get_app_db().donor_signup(body)

    app.log.debug(f"DBResponse: {db_response}")

    return Response(
        body=asdict(db_response),
        status_code=200 if db_response.success else 400
    )


@app.route("/donation/create", methods=["POST"])
def donation_create():
    body = app.current_request.json_body

    app.log.debug(f"Received JSON payload: {body}")

    db_response = get_app_db().donation_create(body)

    app.log.debug(f"DBResponse: {db_response}")

    return Response(
        body=asdict(db_response),
        status_code=200 if db_response.success else 400
    )

Shown above is an example of using the Chalice Response class to return our custom response. Since we agreed that the DBResponse data structure contains all the needed data, the only variable in our case is the status_code field that is highlighted. We rely on the DBResponse.success attribute to tell us whether the database operation was successful and our status_code reflects that.

Python dataclasses are not JSON-serializable so if you forgot to wrap it into a asdict() function, a TypeError will be raised:

In action locally

Notice the HTTP/1.1 200 OK signifing that the status_code is 200.

http POST :8000/donor/signup first_name=ivica email="ivica@server.com" type="A+" city="Amsterdam"

resulting in:

Raising an exception with raise Exception("This is a simulated exception"):

http POST :8000/donor/signup first_name=ivica email="ivica@server.com" type="A+" city="Amsterdam"

results in:

In action on AWS

chalice deploy and let’s see what happens when we invoke the /donor/signup endpoint:

http POST $(chalice url)/donor/signup first_name=ivica email="ivica@server.com" type="A+" city="Amsterdam"

And to simulate an error I have deleted the DynamoDB table that is used by the application.

http POST $(chalice url)/donor/signup first_name=ivica email="ivica@server.com" type="A+" city="Amsterdam"

results in: