Upload
nicola-iarocci
View
3.796
Download
2
Tags:
Embed Size (px)
DESCRIPTION
Introducing the Eve REST API Framework. FOSDEM 2014, Brussels PyCon Sweden 2014, Stockholm PyCon Italy 2014, Florence Python Meetup, Helsinki EuroPython 2014, Berlin
Citation preview
REST API FOR HUMANS™eveeve
nicolaiarocciCoFounder and Lead Dev @C2K
Open Source • MongoDB Master • Speaking • CoderDojo • PSF
PHILOSOPHY
YOU HAVE DATASTORED SOMEWHERE
YOU NEED A FULL FEATURED REST API
TO EXPOSE YOUR DATA
PIP INSTALL EVETO GET A FEATURE RICH RESTFUL WEB API FOR FREE
POWERED BY
QUICKSTART
#1 run.py
from eve import Eve app = Eve() !
if __name__ == '__main__': app.run()
#2 settings.py# just a couple API endpoints with no custom # schema or rules. Will just dump from people # and books db collections !
DOMAIN = { ‘people’: {} ‘books’: {} }
#3 launch the API
$ python run.py * Running on http://127.0.0.1:5000/
#4 enjoy$ curl -i http://127.0.0.1:5000/people { "_items": [], "_links": { "self": { "href": "127.0.0.1:5000/people", "title": "people" }, "parent": { "href": "127.0.0.1:5000", "title": "home"} } }
#4 enjoy$ curl -i http://127.0.0.1:5000/people { "_items": [], "_links": { "self": { "href": "127.0.0.1:5000/people", "title": “people" }, "parent": { "href": "127.0.0.1:5000", "title": “home"} } }
HATEOAS AT WORK HERE
#4 enjoy$ curl -i http://127.0.0.1:5000/people { "_items": [], "_links": { "self": { "href": "127.0.0.1:5000/people", "title": "people" }, "parent": { "href": "127.0.0.1:5000", "title": "home"} } }
CLIENTS CAN EXPLORE THE API PROGRAMMATICALLY
#4 enjoy$ curl -i http://127.0.0.1:5000/people { "_items": [], "_links": { "self": { "href": "127.0.0.1:5000/people", "title": "people" }, "parent": { "href": "127.0.0.1:5000", "title": "home"} } }
AND EVENTUALLY FILL THEIR UI
#4 enjoy$ curl -i http://127.0.0.1:5000/people { "_items": [], "_links": { "self": { "href": "127.0.0.1:5000/people", "title": "people" }, "parent": { "href": "127.0.0.1:5000", "title": "home"} } }
EMTPY RESOURCE AS WE DIDN’T CONNECT ANY DATASOURCE
settings.py
# let’s connect to a mongo instance !
MONGO_HOST = 'localhost' MONGO_PORT = 27017 MONGO_USERNAME = 'user' MONGO_PASSWORD = 'user' MONGO_DBNAME = ‘apitest'
settings.py# let’s also add some validation rules !
DOMAIN['people']['schema'] = { 'name': { 'type': 'string', 'maxlength': 50, 'unique': True} 'email': { 'type': 'string', 'regex': '^\S+@\S+$'}, 'location': { 'type': 'dict', 'schema': { 'address': {'type': 'string'}, 'city': {'type': 'string'}}}, 'born': {'type': ‘datetime'}}
settings.py# let’s also add some validation rules !
DOMAIN['people']['schema'] = { 'name': { 'type': 'string', 'maxlength': 50, 'unique': True} 'email': { 'type': 'string', 'regex': '^\S+@\S+$'}, 'location': { 'type': 'dict', 'schema': { 'address': {'type': 'string'}, 'city': {'type': 'string'}}}, 'born': {'type': ‘datetime'}}
THIS REGEX SUCKS. DON’T USE IN PRODUCTION
settings.py# allow write access to API endpoints # (default is [‘GET’] for both settings) !
# /people RESOURCE_METHODS = ['GET','POST'] !
# /people/<id> ITEM_METHODS = ['GET','PATCH','PUT','DELETE']
settings.py# allow write access to API endpoints # (default is [‘GET’] for both settings) !
# /people RESOURCE_METHODS = ['GET','POST'] !
# /people/<id> ITEM_METHODS = ['GET','PATCH','PUT','DELETE']
ADD/CREATE ONE OR MORE ITEMS
settings.py# allow write access to API endpoints # (default is [‘GET’] for both settings) !
# /people RESOURCE_METHODS = ['GET', 'POST'] !
# /people/<id> ITEM_METHODS = ['GET','PATCH','PUT','DELETE']
EDIT ITEM
settings.py# allow write access to API endpoints # (default is [‘GET’] for both settings) !
# /people RESOURCE_METHODS = ['GET', 'POST'] !
# /people/<id> ITEM_METHODS = ['GET','PATCH','PUT','DELETE']
REPLACE ITEM
settings.py# allow write access to API endpoints # (default is [‘GET’] for both settings) !
# /people RESOURCE_METHODS = ['GET', 'POST'] !
# /people/<id> ITEM_METHODS = ['GET','PATCH','PUT','DELETE']
YOU GUESSED IT
settings.py# a few more config options !
DOMAIN[‘people’].update( { ‘item_title’: ‘person’, ‘cache_control’: ‘max-age=10,must-revalidate, ‘cache_expires’: 10, ‘additional_lookup’: { ‘url’: ‘regex)”[\w]+”)’, ‘field’: ‘name’ } )
FEATURES
MONGO FILTERS?where={“lastname”: “Doe”}
PYTHON FILTERS?where=lastname==“Doe”
SORTING?sort=[(“total”: -1)]
SORT BY ‘TOTAL’, DESCENDING ORDER
PAGINATION?max_results=20&page=2
MAX 20 RESULTS/PAGE; PAGE 2
PROJECTIONS?projection={"avatar": 0}
RETURN ALL FIELDS BUT ‘AVATAR’
PROJECTIONS?projection={"lastname": 1}
ONLY RETURN ‘LASTNAME’
EMBEDDED RESOURCES?embedded={"author": 1}
NOT EMBEDDED
$ curl -i <url> !
HTTP/1.1 200 OK { "title": "Book Title", "description": "book description", "author": “52da465a5610320002660f94" }
RAW FOREIGN KEY (DEFAULT)
EMBEDDED
$ curl -i <url>?embedded={“author”: 1} !
HTTP/1.1 200 OK { "title": "Book Title", "description": "book description", "author": { “firstname”: “Mark”, “lastname”: “Green”, } }
REQUEST EMBEDDED AUTHOR
EMBEDDED
$ curl -i <url>?embedded={“author”: 1} !
HTTP/1.1 200 OK { "title": "Book Title", "description": "book description", "author": { “firstname”: “Mark”, “lastname”: “Green”, } }
EMBEDDED DOCUMENT
JSON AND XMLBUILT-IN FOR ALL RESPONSES
APPLICATION/JSON[ { "firstname": "Mark", "lastname": "Green", "born": "Sat, 23 Feb 1985 12:00:00 GMT", "role": ["copy", "author"], "location": {"city": "New York", "address": "4925 Lacross Road"}, "_id": "50bf198338345b1c604faf31", "_updated": "Wed, 05 Dec 2012 09:53:07 GMT", "_created": "Wed, 05 Dec 2012 09:53:07 GMT", "_etag": "ec5e8200b8fa0596afe9ca71a87f23e71ca30e2d", }, { "firstname": "John", ... }, ]
APPLICATION/JSON[ { "firstname": "Mark", "lastname": "Green", "born": "Sat, 23 Feb 1985 12:00:00 GMT", "role": ["copy", "author"], "location": {"city": "New York", "address": "4925 Lacross Road"}, "_id": "50bf198338345b1c604faf31", "_updated": "Wed, 05 Dec 2012 09:53:07 GMT", "_created": "Wed, 05 Dec 2012 09:53:07 GMT", "_etag": "ec5e8200b8fa0596afe9ca71a87f23e71ca30e2d", }, { "firstname": "John", ... }, ]
METAFIELDS ARE CONFIGURABLE
APPLICATION/XML<resource href=“localhost:5000/people" title="people"> <resource href="localhost:5000/people/<id>" title="person"> <lastname>Green</lastname> <firstname>Mark</firstname> <born>Wed, 05 Dec 2012 09:53:07 GMT</born> <role>author</role> <role>copy</role> <location> <address>4925 Lacross Road</address> <city>New York</city> </location> <_id>50bf198338345b1c604faf31</_id> <created>Wed, 05 Dec 2012 09:53:07 GMT</created> <updated>Sat, 18 Jan 2014 09:16:10 GMT</updated> <etag>ec5e8200b8fa0596afe9ca71a87f23e71ca30e2d</etag> </resource> ... <resource>
HATEOASHYPERMEDIA AS THE ENGINE OF APPLICATION STATE
HATEOAS{ “_links”: { “self”: { “href”: “/people”, “title”: “people” }, “parent”: { “href”: “/”, “title”: “home”}, “next”: { “href”: “/people?page=2”, “title”: “next page”}, “last”: { “href: “/people?page=10”, “title”: “last page”} } }
DOCUMENT VERSIONS?version=3
?version=all ?version=diffs
FILE STORAGEFILES ARE STORED IN GRIDFS BY DEFAULT
FILE STORAGE / SETTINGS
accounts = { 'name': {'type': 'string'}, 'pic': {'type': 'media'}, … }
FILE STORAGE
$ curl F “name=doe” —F “[email protected]" <url> HTTP/1.1 200 OK !
$ curl -i <url> HTTP/1.1 200 OK { "name": "john", "pic": "/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAA…"}
MULTIPART/DATA-FORM POST
FILE STORAGE
$ curl F “name=doe” —F “[email protected]" <url> HTTP/1.1 200 OK !
$ curl -i <url> HTTP/1.1 200 OK { "name": "john", "pic": "/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAA…"}
FILES RETURNED AS BASE64 STRINGS
FILE STORAGE (WITH META)
$ curl -i <url> HTTP/1.1 200 OK { "name": "john", "pic": { “file”: ”/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAA”, “content_type”: “image/jpeg”, “name”: “profile.jpg”, “length”: 8129 } }
EXTENDED_MEDIA_INFO: TRUE
RATE LIMITINGPOWERED
RATE LIMITING / SETTINGS
# Rate limit on GET requests: # 1 requests 1 minute window (per client) !
RATE_LIMIT_GET = (1, 60)
RATE LIMITING / GET #1
$ curl -i <url> !
HTTP/1.1 200 OK X-RateLimit-Limit: 1 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1390486659
RATE LIMITING / GET #2
$ curl -i <url> !
HTTP/1.1 429 TOO MANY REQUESTS
CONDITIONAL REQUESTSALLOW CLIENTS TO ONLY REQUEST
NON-CACHED CONTENT
IF-MODIFIED-SINCEIf-Modified-Since: Wed, 05 Dec 2012 09:53:07 GMT “Please return modified data since <date> or 304”
IF-NONE-MATCHIf-None-Match:1234567890123456789012345678901234567890
“Please return data if it has changed or 304”
>
BULK INSERTSINSERT MULTIPLE DOCUMENTS WITH A SINGLE REQUEST
BULK INSERTS / REQUEST$ curl -d ‘ [ { "firstname": "barack", "lastname": “obama" }, { "firstname": "mitt", "lastname": “romney” } ]' -H 'Content-Type: application/json’ <url>
BULK INSERTS / RESPONSE[ { "_status": "OK", "_updated": "Thu, 22 Nov 2012 15:22:27 GMT", "_id": "50ae43339fa12500024def5b", "_etag": "749093d334ebd05cf7f2b7dbfb7868605578db2c" "_links": {"self": {"href": “<url>”, "title": "person"}} }, { "_status": "OK", "_updated": "Thu, 22 Nov 2012 15:22:27 GMT", "_id": "50ae43339fa12500024def5c", "_etag": "62d356f623c7d9dc864ffa5facc47dced4ba6907" "_links": {"self": {"href": “<url>", "title": "person"}} } ]
COHERENCE MODE OFF: ONLY META FIELDS ARE RETURNED
BULK INSERTS / RESPONSE[ { "_status": "OK", "_updated": "Thu, 22 Nov 2012 15:22:27 GMT", "_id": "50ae43339fa12500024def5b", "_etag": "749093d334ebd05cf7f2b7dbfb7868605578db2c" "_links": {"self": {"href": “<url>”, "title": “person”}}, "firstname": "barack", "lastname": "obama", ! }, { "_status": "OK", "_updated": "Thu, 22 Nov 2012 15:22:27 GMT", "_id": "50ae43339fa12500024def5c", "_etag": "62d356f623c7d9dc864ffa5facc47dced4ba6907" "_links": {"self": {"href": “<url>", "title": "person"}} "firstname": "mitt", "lastname": "romney", } ]
COHERENCE MODE ON: ALL FIELDS RETURNED
DATA INTEGRITY CONCURRENCY CONTROL
NO OVERWRITING DOCUMENTS WITH OBSOLETE VERSIONS
DATA INTEGRITY / CONCURRENCY
$ curl -X PATCH -i <url> -d '{"firstname": "ronald"}'
HTTP/1.1 403 FORBIDDEN
IF-MATCH MISSING
DATA INTEGRITY / CONCURRENCY
$ curl -X PATCH -i <url> -H "If-Match: <obsolete_etag>” -d '{"firstname": “ronald”}' !
HTTP/1.1 412 PRECONDITION FAILED
ETAG MISMATCH
DATA INTEGRITY / CONCURRENCY
$ curl -X PATCH -i <url> -H “If-Match: 206fb4a39815cc0ebf48b2b52d7…” -d '{"firstname": “ronald"}' !
HTTP/1.1 200 OK
UPDATE ALLOWED IF CLIENT AND SERVER ETAG MATCH
DATA VALIDATION[ { "_status": "ERR", "_issues": {“name”: “value ‘clinton’ not unique”} }, { "_status": “OK", "_updated": "Thu, 22 Nov 2012 15:22:27 GMT”, "_id": “50ae43339fa12500024def5c", "_etag": “62d356f623c7d9dc864ffa5facc47dced4ba6907" "_links": { "self": { "href": “<url>”, "title": “person" } } } ]
AUTHENTICATION AND AUTHORIZATION
BASIC, TOKEN AND HMAC AUTH SUPPORTED
RUNS ON ALL PYTHONS2.6 / 2.7 / 3.3 / 3.4 and PyPy
AND MORECORS. CACHE CONTROL. VERSONING AND MORE.
BSD LICENSEDTEAR IT APART
DEVELOPERS
CUSTOM DATA LAYERSBUILD YOUR OWN DATA LAYER
SQL ALCHEMY (WIP)
SQLALCHEMY (WIP)
@registerSchema('invoices') class Invoices(CommonColumns): __tablename__ = 'invoices' number = db.Column(db.Integer) people = db.Column(db.Integer, db.ForeignKey('people._id'))
ELASTICSERCH
MONGODB (DEFAULT)
AUTHENTICATIONBASIC | TOKEN | HMAC
SECURITY AT A GLANCE
•global authentication
• custom endpoint auth
• public enpoints and methods
• role based access control
• user restricted resource access
three steps Auth
tutorial
#1
IMPORT BASE AUTH CLASS
#2
OVERRIDE CHECK_AUTH() METHOD
#3
PASS CUSTOM CLASS TO THE EVE APP
Done
CUSTOM VALIDATIONEXTEND THE BUILT-IN VALIDATION SYSTEM
CUSTOM VALIDATION
• add custom data types
• add custom validation logic
EVENT HOOKSPLUG CUSTOM ACTIONS INTO THE API LOOP
EVENT HOOKS AT A GLANCE•POST on_insert/on_inserted
•GET on_fetch/on_fetched
•PATCH on_update/on_updated
•PUT on_replace/on_replaced
•DELETE on_delete/on_deteled
• on_pre_<method>; on_post_<method>
TRANSFORM INCOMING DOCUMENTS
CUSTOM FILE STORAGEcustom MediaStorage subclasses to S3, File System, you name it
COMMUNITY
EVE-DOCSGENERATES DOCUMENTATION FOR EVE APIS IN HTML AND JSON FORMATS
CHARLES FLYNN
EVE-DOCS
EVE-MONGOENGINEENABLES MONGOENGINE ORM MODELS TO BE USED AS EVE SCHEMA
STANISLAV HELLER
EVE-ELASTICELASTICSEARCH DATA LAYER FOR EVE REST FRAMEWORK
PETR JASEK
EVE-MOCKERMOCKING TOOL FOR EVE POWERED REST APIS
THOMAS SILEO
{48: <you name here>}
Bryan Cattle Chr is toph Wi tzany Daniele Pizzolli dccrazyboy Dong Wei Ming Florian Rathgeber Francisco Corrales Morales Garrin Kimmell Gianfranco Palumbo Jaroslav Semančík Jean Boussier John Deng Jorge Puente Sarrín Josh Villbrandt Julien Barbot Ken Carpenter Kevin
Bowrin Kracekumar Nicolas Bazire Nicolas Carlier Ondrej
Slinták Petr Jašek Paul Doucet Robert Wlodarczyk Roberto Pasini Ronan Delacroix Roy Smith Ryan Shea Samuel Sutch Stanislav Heller Thomas Sileo Tomasz Jezierski Xavi Cubillas
NOW COOKING
GeoJSONSupport and validation for GeoJSON types
!
Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, GeometricalCollection
JSONP*IN CASE YOUR BROWSER/JS FRAMEWORK CANT HANDLE C.O.R.S.
* PENDING SECURITY REVIEW
JSON-LD / HAL / SIREN*CURSTOM RENDER CLASSES
* MAYBE (UNDER CONSIDERATION)
python-eve.orgJOIN US
eve
nicolaiarocci
Thank you!