Section 1: Create initial Todo application¶
For the rest of this workshop, we will be building a serverless Todo application. The application will allow for creating Todo’s, getting Todo’s, updating Todo’s, and deleting Todo’s. In terms of the REST API, it will consist of the following:
HTTP Method |
URI Path |
Description |
|---|---|---|
|
|
Gets a list of all Todo’s |
|
|
Creates a new Todo |
|
|
Gets a specific Todo |
|
|
Deletes a specific Todo |
|
|
Updates the state of a Todo |
Furthermore, a Todo will have the following schema:
{
"description": {"type": "str"},
"uid": {"type: "str"},
"state": {"type: "str", "enum": ["unstarted", "started", "completed"]},
"metadata": {
"type": "object"
},
"username": {"type": "str"}
}
This step will focus on how to build a simple in-memory version of the Todo application. For this section we will be doing the following to create this version of the application:
Install Chalice¶
This step will ensure that chalice is installed in your virtualenv.
Instructions¶
Install
chaliceinside of your virtualenv:$ pip install chalice
Create a new Chalice project¶
Create the new Chalice project for the Todo application.
Instructions¶
Create a new Chalice project called
mytodowith thenew-projectcommand:$ chalice new-project mytodo
Verification¶
To ensure that the project was created, list the contents of the newly created
mytodo directory:
$ ls mytodo
app.py requirements.txt
It should contain an app.py file and a requirements.txt file.
Add the starting app.py¶
Copy a boilerplate app.py file to begin working on the Todo application
Instructions¶
If you have not already done so, clone the repository for this workshop:
$ git clone https://github.com/aws-samples/chalice-workshop.git
Copy the over the
app.pyfile to themytodoChalice application:$ cp ../chalice-workshop/code/todo-app/part1/01-new-project/app.py mytodo/app.py
Verification¶
To verify that the boilerplate application is working correctly, move into
the mytodo application directory and run chalice local to spin up
a version of the application running locally:
$ cd mytodo
$ chalice local
Serving on localhost:8000
In a separate terminal window now install httpie:
$ pip install httpie
And make an HTTP request to application running the localhost:
$ http localhost:8000/todos
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json
Date: Thu, 19 Oct 2017 23:31:12 GMT
Server: BaseHTTP/0.3 Python/2.7.10
[]
This should return an empty list back as there are no Todo’s currently in the application.
Add a route for creating a Todo¶
Add a route for creating a Todo.
Instructions¶
Open the
app.pyin an editor of your choiceAt the bottom of the
app.pyfile add a function calledadd_new_todo()Decorate the
add_new_todo()function with aroutethat only acceptsPOSTto the URI/todos.In the
add_new_todo()function use theapp.current_request.json_bodyto add the Todo (which includes its description and metadata) to the database.In the
add_new_todo()functionreturnthe ID of the Todo that was added in the database.
1@app.route('/todos', methods=['POST'])
2def add_new_todo():
3 body = app.current_request.json_body
4 return get_app_db().add_item(
5 description=body['description'],
6 metadata=body.get('metadata'),
7 )
Verification¶
To verify that the new route works, run chalice local and in a separate
terminal window run the following using httpie:
$ echo '{"description": "My first Todo", "metadata": {}}' | http POST localhost:8000/todos
HTTP/1.1 200 OK
Content-Length: 36
Content-Type: application/json
Date: Thu, 19 Oct 2017 23:44:24 GMT
Server: BaseHTTP/0.3 Python/2.7.10
8cc673f0-7dd3-4e9d-a20b-245fcd34859d
This will return the ID of the Todo. For this example, it is 8cc673f0-7dd3-4e9d-a20b-245fcd34859d.
Now check that it is now listed when you retrieve all Todos:
$ http localhost:8000/todos
HTTP/1.1 200 OK
Content-Length: 142
Content-Type: application/json
Date: Thu, 19 Oct 2017 23:46:53 GMT
Server: BaseHTTP/0.3 Python/2.7.10
[
{
"description": "My first Todo",
"metadata": {},
"state": "unstarted",
"uid": "8cc673f0-7dd3-4e9d-a20b-245fcd34859d",
"username": "default"
}
]
Add a route for getting a specific Todo¶
Add a route for getting a specific Todo.
Instructions¶
In the
app.py, add a function calledget_todo()that accepts auidas a parameter.Decorate the
get_todo()function with aroutethat only acceptsGETto the URI/todos/{uid}.In the
get_todo()functionreturnthe specific Todo item from the database using theuidfunction parameter.
1@app.route('/todos/{uid}', methods=['GET']) 2def get_todo(uid): 3 return get_app_db().get_item(uid)
Verification¶
To verify that the new route works, run chalice local and in a separate
terminal window run the following using httpie:
$ echo '{"description": "My first Todo", "metadata": {}}' | http POST localhost:8000/todos
HTTP/1.1 200 OK
Content-Length: 36
Content-Type: application/json
Date: Thu, 19 Oct 2017 23:44:24 GMT
Server: BaseHTTP/0.3 Python/2.7.10
8cc673f0-7dd3-4e9d-a20b-245fcd34859d
Now use the returned ID 8cc673f0-7dd3-4e9d-a20b-245fcd34859d to request
the specific Todo:
$ http localhost:8000/todos/8cc673f0-7dd3-4e9d-a20b-245fcd34859d
HTTP/1.1 200 OK
Content-Length: 140
Content-Type: application/json
Date: Thu, 19 Oct 2017 23:52:35 GMT
Server: BaseHTTP/0.3 Python/2.7.10
{
"description": "My first Todo",
"metadata": {},
"state": "unstarted",
"uid": "8cc673f0-7dd3-4e9d-a20b-245fcd34859d",
"username": "default"
}
Add a route for deleting a specific Todo¶
Add a route for deleting a specific Todo.
Instructions¶
In the
app.py, add a function calleddelete_todo()that accepts auidas a parameter.Decorate the
delete_todo()function with aroutethat only acceptsDELETEto the URI/todos/{uid}.In the
delete_todo()function delete the Todo from the database using theuidfunction parameter.
1@app.route('/todos/{uid}', methods=['DELETE'])
2def delete_todo(uid):
3 return get_app_db().delete_item(uid)
Verification¶
To verify that the new route works, run chalice local and in a separate
terminal window run the following using httpie:
$ echo '{"description": "My first Todo", "metadata": {}}' | http POST localhost:8000/todos
HTTP/1.1 200 OK
Content-Length: 36
Content-Type: application/json
Date: Thu, 19 Oct 2017 23:44:24 GMT
Server: BaseHTTP/0.3 Python/2.7.10
8cc673f0-7dd3-4e9d-a20b-245fcd34859d
Now check that it is now listed when you retrieve all Todos:
$ http localhost:8000/todos
HTTP/1.1 200 OK
Content-Length: 142
Content-Type: application/json
Date: Thu, 19 Oct 2017 23:46:53 GMT
Server: BaseHTTP/0.3 Python/2.7.10
[
{
"description": "My first Todo",
"metadata": {},
"state": "unstarted",
"uid": "8cc673f0-7dd3-4e9d-a20b-245fcd34859d",
"username": "default"
}
]
Now use the returned ID 8cc673f0-7dd3-4e9d-a20b-245fcd34859d to delete
the specific Todo:
$ http DELETE localhost:8000/todos/8cc673f0-7dd3-4e9d-a20b-245fcd34859d
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: application/json
Date: Thu, 19 Oct 2017 23:57:32 GMT
Server: BaseHTTP/0.3 Python/2.7.10
null
Now if all of the Todo’s are listed, it will no longer be present:
$ http localhost:8000/todos
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json
Date: Thu, 19 Oct 2017 23:31:12 GMT
Server: BaseHTTP/0.3 Python/2.7.10
[]
Add a route for updating the state of a specific Todo¶
Add a route for updating the state of a specific Todo.
Instructions¶
In the
app.py, add a function calledupdate_todo()that accepts auidas a parameter.Decorate the
update_todo()function with aroutethat only acceptsPUTto the URI/todos/{uid}.In the
update_todo()function use theapp.current_requestto update the Todo (which includes its description, metadata, and state) in the database for theuidprovided.
1@app.route('/todos/{uid}', methods=['PUT'])
2def update_todo(uid):
3 body = app.current_request.json_body
4 get_app_db().update_item(
5 uid,
6 description=body.get('description'),
7 state=body.get('state'),
8 metadata=body.get('metadata'))
Verification¶
To verify that the new route works, run chalice local and in a separate
terminal window run the following using httpie:
$ echo '{"description": "My first Todo", "metadata": {}}' | http POST localhost:8000/todos
HTTP/1.1 200 OK
Content-Length: 36
Content-Type: application/json
Date: Thu, 19 Oct 2017 23:44:24 GMT
Server: BaseHTTP/0.3 Python/2.7.10
de9a4981-f7fd-4639-97fb-2af247f20d79
Now determine the state of this newly added Todo:
$ http localhost:8000/todos/de9a4981-f7fd-4639-97fb-2af247f20d79
HTTP/1.1 200 OK
Content-Length: 140
Content-Type: application/json
Date: Fri, 20 Oct 2017 00:03:26 GMT
Server: BaseHTTP/0.3 Python/2.7.10
{
"description": "My first Todo",
"metadata": {},
"state": "unstarted",
"uid": "de9a4981-f7fd-4639-97fb-2af247f20d79",
"username": "default"
}
Update the state of this Todo to started:
$ echo '{"state": "started"}' | http PUT localhost:8000/todos/de9a4981-f7fd-4639-97fb-2af247f20d79
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: application/json
Date: Fri, 20 Oct 2017 00:05:07 GMT
Server: BaseHTTP/0.3 Python/2.7.10
null
Ensure that the Todo has the started state when described:
$ http localhost:8000/todos/de9a4981-f7fd-4639-97fb-2af247f20d79
HTTP/1.1 200 OK
Content-Length: 138
Content-Type: application/json
Date: Fri, 20 Oct 2017 00:05:54 GMT
Server: BaseHTTP/0.3 Python/2.7.10
{
"description": "My first Todo",
"metadata": {},
"state": "started",
"uid": "de9a4981-f7fd-4639-97fb-2af247f20d79",
"username": "default"
}
Final Code¶
When you are done your final code should look like this:
1from uuid import uuid4
2
3from chalice import Chalice
4
5
6app = Chalice(app_name='mytodo')
7app.debug = True
8_DB = None
9DEFAULT_USERNAME = 'default'
10
11
12class InMemoryTodoDB(object):
13 def __init__(self, state=None):
14 if state is None:
15 state = {}
16 self._state = state
17
18 def list_all_items(self):
19 all_items = []
20 for username in self._state:
21 all_items.extend(self.list_items(username))
22 return all_items
23
24 def list_items(self, username=DEFAULT_USERNAME):
25 return self._state.get(username, {}).values()
26
27 def add_item(self, description, metadata=None, username=DEFAULT_USERNAME):
28 if username not in self._state:
29 self._state[username] = {}
30 uid = str(uuid4())
31 self._state[username][uid] = {
32 'uid': uid,
33 'description': description,
34 'state': 'unstarted',
35 'metadata': metadata if metadata is not None else {},
36 'username': username
37 }
38 return uid
39
40 def get_item(self, uid, username=DEFAULT_USERNAME):
41 return self._state[username][uid]
42
43 def delete_item(self, uid, username=DEFAULT_USERNAME):
44 del self._state[username][uid]
45
46 def update_item(self, uid, description=None, state=None,
47 metadata=None, username=DEFAULT_USERNAME):
48 item = self._state[username][uid]
49 if description is not None:
50 item['description'] = description
51 if state is not None:
52 item['state'] = state
53 if metadata is not None:
54 item['metadata'] = metadata
55
56
57def get_app_db():
58 global _DB
59 if _DB is None:
60 _DB = InMemoryTodoDB()
61 return _DB
62
63
64@app.route('/todos', methods=['GET'])
65def get_todos():
66 return get_app_db().list_items()
67
68
69@app.route('/todos', methods=['POST'])
70def add_new_todo():
71 body = app.current_request.json_body
72 return get_app_db().add_item(
73 description=body['description'],
74 metadata=body.get('metadata'),
75 )
76
77
78@app.route('/todos/{uid}', methods=['GET'])
79def get_todo(uid):
80 return get_app_db().get_item(uid)
81
82
83@app.route('/todos/{uid}', methods=['DELETE'])
84def delete_todo(uid):
85 return get_app_db().delete_item(uid)
86
87
88@app.route('/todos/{uid}', methods=['PUT'])
89def update_todo(uid):
90 body = app.current_request.json_body
91 get_app_db().update_item(
92 uid,
93 description=body.get('description'),
94 state=body.get('state'),
95 metadata=body.get('metadata'))