Restful Web Api: With Python, Flask and Mongo
Restful Web Api: With Python, Flask and Mongo
Nicola Iarocci
mercoledì 4 luglio 2012
Good Morning.
l l g e t to
we’
s e l a te r
the
How I Explained
REST to My Wife
by Ryan Tomayko
http://tomayko.com/writings/rest-to-my-wife
Representational State
Transfer (REST)
by Roy Thomas Fielding
http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run(debug=True)
@app.route('/post/<int:post_id>')
def show_post(post_id):
return 'Post %d' % post_id
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run(debug=True)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run(debug=True)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run(debug=True)
Table Collection
Index Index
Client Mongo
JSON JSON
accepted media type (BSON)
almost.
?where={x: 3, y: "foo”}
• setup is a breeze
• lightweight
• fast inserts, updates and queries
• excellent documentation
• great support by 10gen
• great community
mercoledì 4 luglio 2012
A Great Introduction To MongoDB
The Little
MongoDB Book
by Karl Seguin
http://openmymind.net/2011/3/28/The-Little-MongoDB-Book/
Il Piccolo
Libro di MongoDB
by Karl Seguin, traduzione di
Nicola Iarocci
http://nicolaiarocci.com/il-piccolo-libro-di-mongodb-edizione-italiana/
@app.route('/<collection>')
def collection(collection):
if collection in DOMAIN.keys():
(...)
abort(404)
api.example.com/contacts
api.example.com/invoices
etc.
@app.route('/<collection>')
def collection(collection):
if collection in DOMAIN.keys():
(...)
abort(404)
validation
dictonary
@app.route('/<collection>')
def collection(collection):
if collection in DOMAIN.keys():
(...)
abort(404)
we don’t know
this collection,
return a 404
@app.route('/<regex("[\w]*[Ss]"):collection>')
def collection(collection):
if collection in DOMAIN.keys():
(...)
abort(404)
class RegexConverter(BaseConverter):
def __init__(self, url_map, *items):
super(RegexConverter, self).__init__(url_map)
self.regex = items[0]
app.url_map.converters['regex'] = RegexConverter
@app.route('/<regex("[\w]*[Ss]"):collection>/<lookup>')
@app.route('/<regex("[\w]*[Ss]"):collection>'
'/<regex("[a-f0-9]{24}"):object_id>')
def document(collection, lookup=None, object_id=None):
(...)
http://api.example.com/v1/contacts/CUST12345
@app.route('/<regex("[\w]*[Ss]"):collection>/<lookup>')
@app.route('/<regex("[\w]*[Ss]"):collection>'
'/<regex("[a-f0-9]{24}"):object_id>')
def document(collection, lookup=None, object_id=None):
(...)
http://api.example.com/v1/contacts/4f46445fc88e201858000000
@app.route('/<regex("[\w]*[Ss]"):collection>/<lookup>')
@app.route('/<regex("[\w]*[Ss]"):collection>'
'/<regex("[a-f0-9]{24}"):object_id>')
def document(collection, lookup=None, object_id=None):
(...)
dep en d s o n t h e
reque s t a n d n o t
the i d en t i f i er
mercoledì 4 luglio 2012
Accepted Media Types
mapping supported media types to
corresponding renderer functions
corresponding JSON
internet media type
corresponding XML
internet media types
def json_renderer(**data):
return json.dumps(data, cls=APIEncoder)
def json_renderer(**data):
return json.dumps(data, cls=APIEncoder)
def json_renderer(**data):
return json.dumps(data, cls=APIEncoder)
def json_renderer(**data):
return json.dumps(data, cls=APIEncoder)
def json_renderer(**data):
return json.dumps(data, cls=APIEncoder)
we let json/simplejson
handle the other data types
flask’s make_response()
returns a WSGI response
object wich we can use
to attach headers
!"!#"$%&'((#)('%*+,",-.-$/-.
mercoledì 4 luglio 2012
Flask-MimeRender
Render Functions
render_json = jsonify
render_xml = lambda message: '<message>%s</message>' % message
render_txt = lambda message: message
render_html = lambda message: '<html><body>%s</body></html>' % \
message
@app.route('/')
@mimerender(
default = 'html',
html = render_html,
xml = render_xml,
json = render_json,
txt = render_txt
)
def index():
if request.method == 'GET':
return {'message': 'Hello, World!'}
def get_collection(collection):
where = request.args.get('where')
if where:
args['spec'] = json.loads(where, object_hook=datetime_parser)
(...)
response = {}
documents = []
cursor = db(collection).find(**args)
for document in cursor:
documents.append(document)
def get_collection(collection):
where = request.args.get('where')
if where:
args['spec'] = json.loads(where, object_hook=datetime_parser)
(...)
response = {}
documents = []
cursor = db(collection).find(**args)
for document in cursor:
documents.append(document) as the query already comes
in as a Mongo expression:
response[collection] = documents
return prep_response(response) {“age”: {“$gt”: 20}}
we simply convert it to
JSON.
def get_collection(collection):
where = request.args.get('where')
if where:
args['spec'] = json.loads(where, object_hook=datetime_parser)
(...)
response = {}
documents = []
cursor = db(collection).find(**args)
for document in cursor:
documents.append(document)
response[collection] = documents
return prep_response(response) String-to-datetime
conversion is obtained via
the object_hook mechanism
def get_collection(collection):
where = request.args.get('where')
if where:
args['spec'] = json.loads(where, object_hook=datetime_parser)
(...)
response = {}
documents = []
cursor = db(collection).find(**args)
for document in cursor:
documents.append(document)
response[collection] = documents
find() accepts a python dict
return prep_response(response)
as query expression, and
returns a cursor we can
iterate
def get_collection(collection):
where = request.args.get('where')
if where:
args['spec'] = json.loads(where, object_hook=datetime_parser)
(...)
response = {}
documents = []
cursor = db(collection).find(**args)
for document in cursor:
documents.append(document)
response[collection] = documents
return prep_response(response) finally, we encode the
response dict with the
requested MIME media-type
def datetime_parser(dct):
for k, v in dct.items():
if isinstance(v, basestring) and re.search("\ UTC", v):
try:
dct[k] = datetime.datetime.strptime(v, DATE_FORMAT)
except:
pass
return dct
object_hook is usually used
to deserialize JSON to
classes (rings a ORM bell?)
def datetime_parser(dct):
for k, v in dct.items():
if isinstance(v, basestring) and re.search("\ UTC", v):
try:
dct[k] = datetime.datetime.strptime(v, DATE_FORMAT)
except:
pass
return dct
the resulting dct now has
datetime values instead of
string representations of
dates
def datetime_parser(dct):
for k, v in dct.items():
if isinstance(v, basestring) and re.search("\ UTC", v):
try:
dct[k] = datetime.datetime.strptime(v, DATE_FORMAT)
except:
pass
return dct
the function receives a dict
representing the decoded
JSON
def datetime_parser(dct):
for k, v in dct.items():
if isinstance(v, basestring) and re.search("\ UTC", v):
try:
dct[k] = datetime.datetime.strptime(v, DATE_FORMAT)
except:
pass
return dct
strings matching the RegEx
(which probably should be
better defined)...
def datetime_parser(dct):
for k, v in dct.items():
if isinstance(v, basestring) and re.search("\ UTC", v):
try:
dct[k] = datetime.datetime.strptime(v, DATE_FORMAT)
except:
pass
return dct
def datetime_parser(dct):
for k, v in dct.items():
if isinstance(v, basestring) and re.search("\ UTC", v):
try:
dct[k] = datetime.datetime.strptime(v, DATE_FORMAT)
except:
pass
return dct
if conversion fails we
assume that we are dealing a
normal, legit string
RFC5789
response_item = {}
object_id = original[ID_FIELD]
# Validation
validate(value, collection, object_id)
response_item['validation'] = value['validation']
if value['validation']['response'] != VALIDATION_ERROR:
# Perform the update
updates = {"$set": value['doc']}
db(collection).update({"_Id": ObjectId(object_id)}, updates)
response_item[ID_FIELD] = object_id
return prep_response(response_item)
response_item = {}
object_id = original[ID_FIELD]
# Validation
validate(value, collection, object_id)
response_item['validation'] = value['validation']
if value['validation']['response'] != VALIDATION_ERROR:
# Perform the update
updates = {"$set": value['doc']}
db(collection).update({"_Id": ObjectId(object_id)}, updates)
response_item[ID_FIELD] = object_id
return prep_response(response_item)
response_item = {}
object_id = original[ID_FIELD]
# Validation
validate(value, collection, object_id)
response_item['validation'] = value['validation']
if value['validation']['response'] != VALIDATION_ERROR:
# Perform the update
updates = {"$set": value['doc']}
db(collection).update({"_Id": ObjectId(object_id)}, updates)
response_item[ID_FIELD] = object_id
return prep_response(response_item)
response_item = {}
object_id = original[ID_FIELD]
# Validation
validate(value, collection, object_id)
response_item['validation'] = value['validation']
if value['validation']['response'] != VALIDATION_ERROR:
# Perform the update
updates = {"$set": value['doc']}
db(collection).update({"_Id": ObjectId(object_id)}, updates)
response_item[ID_FIELD] = object_id
return prep_response(response_item)
response_item = {}
object_id = original[ID_FIELD]
# Validation
validate(value, collection, object_id)
response_item['validation'] = value['validation']
if value['validation']['response'] != VALIDATION_ERROR:
# Perform the update
updates = {"$set": value['doc']}
db(collection).update({"_Id": ObjectId(object_id)}, updates)
response_item[ID_FIELD] = object_id
return prep_response(response_item)
response_item = {}
object_id = original[ID_FIELD]
# Validation
validate(value, collection, object_id)
response_item['validation'] = value['validation']
if value['validation']['response'] != VALIDATION_ERROR:
# Perform the update
updates = {"$set": value['doc']}
db(collection).update({"_Id": ObjectId(object_id)}, updates)
response_item[ID_FIELD] = object_id
return prep_response(response_item)
response_item = {}
object_id = original[ID_FIELD]
# Validation
validate(value, collection, object_id)
response_item['validation'] = value['validation']
if value['validation']['response'] != VALIDATION_ERROR:
# Perform the update
updates = {"$set": value['doc']}
db(collection).update({"_Id": ObjectId(object_id)}, updates)
response_item[ID_FIELD] = object_id
return prep_response(response_item)
response_item = {}
object_id = original[ID_FIELD]
# Validation
validate(value, collection, object_id)
response_item['validation'] = value['validation']
if value['validation']['response'] != VALIDATION_ERROR:
# Perform the update
updates = {"$set": value['doc']}
db(collection).update({"_Id": ObjectId(object_id)}, updates)
response_item[ID_FIELD] = object_id
return prep_response(response_item)
response_item = {}
object_id = original[ID_FIELD]
# Validation
validate(value, collection, object_id)
response_item['validation'] = value['validation']
if value['validation']['response'] != VALIDATION_ERROR:
# Perform the update
updates = {"$set": value['doc']}
db(collection).update({"_Id": ObjectId(object_id)}, updates)
response_item[ID_FIELD] = object_id
return prep_response(response_item)
AKA
So What?
if_modified_since = request.headers.get('If-Modified-Since')
if if_modified_since:
last_modified = document[LAST_UPDATED]
if last_modified <= if_modified_since:
return prep_response(dict(), status=304)
response[collection.rstrip('s')] = document
return prep_response(response, last_modified, etag)
abort(404)
retrieve the document from
the database
if_modified_since = request.headers.get('If-Modified-Since')
if if_modified_since:
last_modified = document[LAST_UPDATED]
if last_modified <= if_modified_since:
return prep_response(dict(), status=304)
response[collection.rstrip('s')] compute
= document
ETag for the current
return prep_response(response, last_modified, etag)
representation. We test ETag
abort(404)
first, as it is a stronger
validator
if_modified_since = request.headers.get('If-Modified-Since')
if if_modified_since:
last_modified = document[LAST_UPDATED]
if last_modified <= if_modified_since:
return prep_response(dict(), status=304)
response[collection.rstrip('s')] = document
return prep_response(response, last_modified, etag)
abort(404)
retrieve If-None-Match ETag
from request header
if_modified_since = request.headers.get('If-Modified-Since')
if if_modified_since:
last_modified = document[LAST_UPDATED]
if last_modified <= if_modified_since:
return prep_response(dict(), status=304)
response[collection.rstrip('s')] = document
return prep_response(response, last_modified, etag)
abort(404) if client and server
representations match,
return a 304 Not Modified
if_modified_since = request.headers.get('If-Modified-Since')
if if_modified_since:
last_modified = document[LAST_UPDATED]
if last_modified <= if_modified_since:
return prep_response(dict(), status=304)
response[collection.rstrip('s')] = document
likewise, if
return prep_response(response, last_modified, the resource
etag)
abort(404) has not been modified since
If-Modifed-Since,
return 304 Not Modified
Cache-Control, a directive
for HTTP/1.1 clients (and
later) -RFC2616
#5
HATEOAS
“Hypertext As The Engine Of Application State”
@app.route('/', methods=['GET'])
def home():
response = {}
links = []
for collection in DOMAIN.keys():
links.append("<link rel='child' title='%(name)s'"
"href='%(collectionURI)s' />" %
{'name': collection,
'collectionURI': collection_URI(collection)})
response['links'] = links
return response
@app.route('/', methods=['GET'])
def home():
response = {}
links = []
for collection in DOMAIN.keys():
links.append("<link rel='child' title='%(name)s'"
"href='%(collectionURI)s' />" %
{'name': collection,
'collectionURI': collection_URI(collection)})
response['links'] = links
return response
@app.route('/', methods=['GET'])
def home():
response = {}
links = []
for collection in DOMAIN.keys():
links.append("<link rel='child' title='%(name)s'"
"href='%(collectionURI)s' />" %
{'name': collection,
'collectionURI': collection_URI(collection)})
response['links'] = links
return response
Just kidding!
ev
’ t e!
sn o k
ti i y j
m