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
chalice
inside 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
mytodo
with thenew-project
command:$ 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.py
file to themytodo
Chalice 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.py
in an editor of your choiceAt the bottom of the
app.py
file add a function calledadd_new_todo()
Decorate the
add_new_todo()
function with aroute
that only acceptsPOST
to the URI/todos
.In the
add_new_todo()
function use theapp.current_request.json_body
to add the Todo (which includes its description and metadata) to the database.In the
add_new_todo()
functionreturn
the ID of the Todo that was added in the database.
1 2 3 4 5 6 7 | @app.route('/todos', methods=['POST'])
def add_new_todo():
body = app.current_request.json_body
return get_app_db().add_item(
description=body['description'],
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
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 auid
as a parameter.Decorate the
get_todo()
function with aroute
that only acceptsGET
to the URI/todos/{uid}
.In the
get_todo()
functionreturn
the specific Todo item from the database using theuid
function parameter.
1 2 3 @app.route('/todos/{uid}', methods=['GET']) def get_todo(uid): 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 auid
as a parameter.Decorate the
delete_todo()
function with aroute
that only acceptsDELETE
to the URI/todos/{uid}
.In the
delete_todo()
function delete the Todo from the database using theuid
function parameter.
1 2 3 | @app.route('/todos/{uid}', methods=['DELETE'])
def delete_todo(uid):
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 auid
as a parameter.Decorate the
update_todo()
function with aroute
that only acceptsPUT
to the URI/todos/{uid}
.In the
update_todo()
function use theapp.current_request
to update the Todo (which includes its description, metadata, and state) in the database for theuid
provided.
1 2 3 4 5 6 7 8 | @app.route('/todos/{uid}', methods=['PUT'])
def update_todo(uid):
body = app.current_request.json_body
get_app_db().update_item(
uid,
description=body.get('description'),
state=body.get('state'),
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | from uuid import uuid4
from chalice import Chalice
app = Chalice(app_name='mytodo')
app.debug = True
_DB = None
DEFAULT_USERNAME = 'default'
class InMemoryTodoDB(object):
def __init__(self, state=None):
if state is None:
state = {}
self._state = state
def list_all_items(self):
all_items = []
for username in self._state:
all_items.extend(self.list_items(username))
return all_items
def list_items(self, username=DEFAULT_USERNAME):
return self._state.get(username, {}).values()
def add_item(self, description, metadata=None, username=DEFAULT_USERNAME):
if username not in self._state:
self._state[username] = {}
uid = str(uuid4())
self._state[username][uid] = {
'uid': uid,
'description': description,
'state': 'unstarted',
'metadata': metadata if metadata is not None else {},
'username': username
}
return uid
def get_item(self, uid, username=DEFAULT_USERNAME):
return self._state[username][uid]
def delete_item(self, uid, username=DEFAULT_USERNAME):
del self._state[username][uid]
def update_item(self, uid, description=None, state=None,
metadata=None, username=DEFAULT_USERNAME):
item = self._state[username][uid]
if description is not None:
item['description'] = description
if state is not None:
item['state'] = state
if metadata is not None:
item['metadata'] = metadata
def get_app_db():
global _DB
if _DB is None:
_DB = InMemoryTodoDB()
return _DB
@app.route('/todos', methods=['GET'])
def get_todos():
return get_app_db().list_items()
@app.route('/todos', methods=['POST'])
def add_new_todo():
body = app.current_request.json_body
return get_app_db().add_item(
description=body['description'],
metadata=body.get('metadata'),
)
@app.route('/todos/{uid}', methods=['GET'])
def get_todo(uid):
return get_app_db().get_item(uid)
@app.route('/todos/{uid}', methods=['DELETE'])
def delete_todo(uid):
return get_app_db().delete_item(uid)
@app.route('/todos/{uid}', methods=['PUT'])
def update_todo(uid):
body = app.current_request.json_body
get_app_db().update_item(
uid,
description=body.get('description'),
state=body.get('state'),
metadata=body.get('metadata'))
|