11## Basics first - Hello world
2- This is the first commit: c8c736c87020a
2+ __ SHA1: __ c8c736c87020a
33
44run this and visit [ localhost:5500] ( http://localhost:5500 )
55
6- ``` python
6+ ``` bash
77python app.py
88```
99
1010## REST
11+ __ SHA1:__ d97dc973476324
12+
1113I'm going to start by creating a REST skeleton of what I
1214want to do.
1315
14- __ SHA1:__ 76b25c4e3715
1516``` python
1617import bottle
17- from bottle import route, run
18+ from bottle import run, get, post, delete
1819
19- @route (' /' )
20- @route (' /links' , method = ' GET ' )
20+ @get (' /' )
21+ @get (' /links' )
2122def list_links ():
2223 ''' Return a complete list of all links'''
2324 return ' List of links'
2425
25- @route (' /links/<id>' , method = ' GET ' )
26+ @get (' /links/<id>' )
2627def get_link (id ):
2728 ''' Returns a specific link'''
2829 return ' Link {} ' .format(id )
2930
30- @route (' /links/<id>' , method = ' DELETE ' )
31+ @delete (' /links/<id>' )
3132def delete_link (id ):
3233 ''' Deletes a specific link from the list'''
3334 return ' Link {} deleted' .format(id )
3435
35- @route (' /links' , method = ' POST ' )
36+ @post (' /links' )
3637def add_link ():
3738 ''' Adds a link to the list'''
3839 return ' Link added'
@@ -42,36 +43,37 @@ if __name__ == '__main__':
4243 run(port = 5500 )
4344```
4445
45- ### JSON
46+ ## JSON
47+ __ SHA1__ : 08c71200b96fc7
48+
4649Adding all the methods was really easy. But the REST methods should
4750return JSON, not strings. So let's tweak it so it returns
4851dummy JSON data instead.
4952
50- __ SHA1__ : 957166a1e4
5153``` python
5254import bottle
53- from bottle import route, run
55+ from bottle import run, get, post, delete
5456
55- @route (' /' )
56- @route (' /links' , method = ' GET ' )
57+ @get (' /' )
58+ @get (' /links' )
5759def list_links ():
5860 ''' Return a complete list of all links'''
5961 return dict (links = [])
6062
61- @route (' /links/<id>' , method = ' GET ' )
63+ @get (' /links/<id>' )
6264def get_link (id ):
6365 ''' Returns a specific link'''
6466 return dict (link = {" sha" :" 1111111" ,
6567 " url" :" http://www.google.com" ,
6668 " timestamp" :" 2013-09-19 08:22:19.000" })
6769
68- @route (' /links/<id>' , method = ' DELETE ' )
70+ @delete (' /links/<id>' )
6971def delete_link (id ):
7072 ''' Deletes a specific link from the list.
7173 On success, returns an empty response'''
7274 return {}
7375
74- @route (' /links' , method = ' POST ' )
76+ @post (' /links' )
7577def add_link ():
7678 ''' Adds a link to the list.
7779 On success, returns the entry created.'''
@@ -83,3 +85,138 @@ if __name__ == '__main__':
8385 bottle.debug(True )
8486 run(port = 5500 )
8587```
88+
89+ ## Adding a database
90+ __ SHA1:__ 599d6fda70fbeaa
91+
92+ Getting the skeleton up was really fast and now it's already
93+ time to implement some real data. The data will be stored
94+ in an sqlite database. The database is really simple and created
95+ in _ dbsetup.py:_
96+
97+ ``` python
98+ import sqlite3 as sql
99+ import sys
100+
101+ _CREATE_TABLE = \
102+ """ CREATE TABLE IF NOT EXISTS links
103+ (_id INTEGER PRIMARY KEY,
104+ sha TEXT NOT NULL,
105+ url TEXT NOT NULL,
106+ timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
107+
108+ UNIQUE(url) ON CONFLICT REPLACE,
109+ UNIQUE(sha) ON CONFLICT REPLACE)
110+ """
111+
112+ def init_db (filename = ' test.db' ):
113+ con = sql.connect(filename)
114+ con.row_factory = sql.Row
115+ with con:
116+ cur = con.cursor()
117+ cur.execute(_CREATE_TABLE )
118+
119+ if __name__ == ' __main__' :
120+ if len (sys.argv) > 1 :
121+ init_db(sys.argv[1 ])
122+ else :
123+ init_db()
124+ ```
125+
126+ To make use of it in our app, we import the * bottle_sqlite* plugin
127+ and add some logic to our existing methods:
128+
129+ ``` python
130+ from hashlib import sha1
131+ from bottle import run, get, post, delete, install, HTTPError, request
132+ from bottle_sqlite import SQLitePlugin
133+
134+ from dbsetup import init_db
135+
136+
137+ DBNAME = ' test.db'
138+
139+ init_db(DBNAME )
140+ install(SQLitePlugin(dbfile = DBNAME ))
141+
142+ def to_dict (row ):
143+ return dict (sha = row[' sha' ],
144+ url = row[' url' ],
145+ timestamp = row[' timestamp' ])
146+
147+ @get (' /' )
148+ @get (' /links' )
149+ def list_links (db ):
150+ ''' Return a complete list of all links'''
151+ links = []
152+ for row in db.execute(' SELECT * from links' ):
153+ links.append(to_dict(row))
154+ return dict (links = links)
155+
156+ @get (' /links/<sha>' )
157+ def get_link (db , sha ):
158+ ''' Returns a specific link'''
159+ row = db.execute(' SELECT * from links WHERE sha IS ?' , [sha]).fetchone()
160+ if row:
161+ return dict (link = to_dict(row))
162+
163+ return HTTPError(404 , " No such item" )
164+
165+ @delete (' /links/<sha>' )
166+ def delete_link (db , sha ):
167+ ''' Deletes a specific link from the list.
168+ On success, returns an empty response'''
169+ db.execute(' DELETE FROM links WHERE sha IS ?' , [sha])
170+ if db.total_changes > 0 :
171+ return {}
172+
173+ return HTTPError(404 , " No such item" )
174+
175+ @post (' /links' )
176+ def add_link (db ):
177+ ''' Adds a link to the list.
178+ On success, returns the entry created.'''
179+ # Only accept json data
180+ if request.content_type != ' application/json' :
181+ return HTTPError(415 , " Only json is accepted" )
182+ # Check required fields
183+ if ' url' not in request.json:
184+ return HTTPError(400 , " Must specify a url" )
185+
186+ # Sha is optional, generate if not present
187+ if ' sha' not in request.json:
188+ request.json[' sha' ] = sha1(request.json[' url' ]).hexdigest()
189+
190+ args = [request.json[' url' ],
191+ request.json[' sha' ]]
192+ if ' timestamp' in request.json:
193+ args.append(request.json[' timestamp' ])
194+ stmt = ' INSERT INTO links (url, sha, timestamp) VALUES(?, ?, ?)'
195+ else :
196+ stmt = ' INSERT INTO links (url, sha) VALUES(?, ?)'
197+
198+ db.execute(stmt, args)
199+
200+ return get_link(db, request.json[' sha' ])
201+
202+
203+ if __name__ == ' __main__' :
204+ # Restart server automatically when this file changes
205+ run(host = ' 0.0.0.0' , port = 5500 , reloader = True , debug = True )
206+ ```
207+
208+ Wow. That was fairly straightforward. The one thing that is
209+ missing is a requirement to login.
210+
211+ ## Adding Google authentication and users
212+ To make sure we don't mix user's data, we'll add a column in
213+ the database that will hold the username, e.g. their e-mail.
214+
215+ But we also need people to login with Google, and the server
216+ to verify that, so that people can't just use any e-mail
217+ they'd like.
218+
219+ ### Creating a project with Google
220+ Create projects in api console...
221+
222+ todo
0 commit comments