Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Eve - REST API for Humans™

Eve - REST API for Humans™

Introducing the Eve REST API Framework.

PyCon Web 2018, Munich
PyCon Belarus 2018, Minsk
PiterPy 2016, St. Petersburg
EuroPython 2014, Berlin
Python Meetup, Helsinki
PyCon Italy 2014, Florence
PyCon Sweden 2014, Stockholm
FOSDEM 2014, Brussels

Nicola Iarocci

February 02, 2014
Tweet

More Decks by Nicola Iarocci

Other Decks in Programming

Transcript

  1. NICOLA IAROCCI Co-Founder @ CIR2000 Lead Developer @ gestionaleamica.com Microsoft

    MVP MongoDB Master Open Source Author CoderDojo DevRomagna @nicolaiarocci / nicolaiarocci.com / [email protected]
  2. SETTINGS.PY # just a couple API endpoints with no #

    custom options and validation rules DOMAIN = { 'people': {} 'books': {} }
  3. TEST YOUR API $ curl -i http://localhost:5000/ { "_items": [],

    "_links": { "self": { "href": "127.0.0.1:5000/people", "title": "people" }, "parent": { "href": "127.0.0.1:5000", "title": "home"} } }
  4. $ curl -i http://localhost:5000/ { "_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 TEST YOUR API
  5. $ curl -i http://localhost:5000/ { "_items": [], "_links": { "self":

    { "href": "127.0.0.1:5000/people", "title": "people" }, "parent": { "href": "127.0.0.1:5000", "title": "home"} } } TEST YOUR API CLIENTS CAN EXPLORE THE API PROGRAMMATICALLY
  6. $ curl -i http://localhost:5000/ { "_items": [], "_links": { "self":

    { "href": "127.0.0.1:5000/people", "title": "people" }, "parent": { "href": "127.0.0.1:5000", "title": "home"} } } TEST YOUR API AND EVENTUALLY FILL THEIR UI
  7. $ curl -i http://localhost:5000/ { "_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 TEST YOUR API
  8. SETTINGS.PY # connect to mongo MONGO_HOST = 'localhost' MONGO_PORT =

    27017 MONGO_USERNAME = 'user' MONGO_PASSWORD = 'pw' MONGO_DBNAME = 'apitest' # or (better): MONGO_URI = ‘mongodb://user:pw@localhost:27017/apitest'
  9. # add fields and validation rules for 'people' endpoint 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'}} USE BETTER REGEX IN PRODUCTION SETTINGS.PY
  10. SETTINGS.PY # enable writes. default is ['GET'] # /people RESOURCE_METHODS

    = ['GET','POST'] # /people/<id> ITEM_METHODS = ['GET','PATCH','PUT','DELETE']
  11. # enable writes. default is ['GET'] # /people RESOURCE_METHODS =

    ['GET','POST'] # /people/<id> ITEM_METHODS = ['GET','PATCH','PUT','DELETE'] SETTINGS.PY ADD/CREATE ONE OR MORE ITEMS
  12. # enable writes. default is ['GET'] # /people RESOURCE_METHODS =

    ['GET','POST'] # /people/<id> ITEM_METHODS = ['GET','PATCH','PUT','DELETE'] SETTINGS.PY UPDATE DOCUMENT
  13. # enable writes. default is ['GET'] # /people RESOURCE_METHODS =

    ['GET','POST'] # /people/<id> ITEM_METHODS = ['GET','PATCH','PUT','DELETE'] SETTINGS.PY REPLACE DOCUMENT
  14. # enable writes. default is ['GET'] # /people RESOURCE_METHODS =

    ['GET','POST'] # /people/<id> ITEM_METHODS = ['GET','PATCH','PUT','DELETE'] SETTINGS.PY YOU GUESSED IT
  15. SETTINGS.PY # a few additional configuration options DOMAIN['people'].update( { 'item_title':

    'person', 'cache_control': 'max-age=10,must-revalidate', 'cache_expires': 10, 'additional_lookup': { 'url': 'regex("[\w]+")', 'field': 'name' } )
  16. NOT EMBEDDED $ curl -i <url> HTTP/1.1 200 OK {

    "title": "Book Title", "description": "book description", "author": "52da465a5610320002660f94" } RAW FOREIGN KEY
  17. 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
  18. 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
  19. 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", ... }, ]
  20. [ { "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 METAFIELDS ARE CUSTOMIZABLE
  21. 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>
  22. 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"} } }
  23. FILE UPLOAD $ 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
  24. FILE UPLOAD $ 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
  25. 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: [‘content_type, ‘name’, ‘length’]
  26. FILE STORAGE (DEDICATED ENDPOINT) $ curl -i <url> HTTP/1.1 200

    OK { "name": "john", "pic": "/media/profile.jpg" } } RETURN_MEDIA_AS_URL = True MEDIA_ENDPOINT = "media"
  27. RATE LIMITING / SETTINGS # Set rate limit on GET

    requests: # 1 requests 1 minute window (per client) RATE_LIMIT_GET = (1, 60)
  28. RATE LIMITING / FIRST REQUEST $ curl -i <url> HTTP/1.1

    200 OK X-RateLimit-Limit: 1 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1390486659
  29. BULK INSERTS / REQUEST $ curl -d ' [ {

    "firstname": "barack", "lastname": "obama" }, { "firstname": "mitt", "lastname": "romney" } ]' -H 'Content-Type: application/json' <url>
  30. 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
  31. BULK INSERTS / RESPONSE [ { "_status": "OK", "_updated": "Thu,

    22 Nov 2012 15:22:27 GMT", "_id": "50ae43339fa12500024def5b", "_etag": "749093d334ebd05cf7f2b7dbfb7868605578db2c" "_links": {"self": {"href": "<url>", "title": }}, "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
  32. DATA INTEGRITY AND CONSISTENCY $ curl -X PATCH -i <url>

    -d '{"firstname": "ronald"}' HTTP/1.1 428 PRECONDITION REQUIRED IF-MATCH MISSING
  33. $ curl -X PATCH -i <url> -H "If-Match: <obsolete_etag>" -d

    '{"firstname": "ronald"}' HTTP/1.1 412 PRECONDITION FAILED ETAG MISMATCH DATA INTEGRITY AND CONSISTENCY
  34. $ curl -X PATCH -i <url> -H "If-Match: 206fb4a39815cc0ebf48b2b52d7…” -d

    '{"firstname": "ronald"}' HTTP/1.1 200 OK UPDATE ALLOWED AS CLIENT AND SERVER ETAG ARE MATCHING DATA INTEGRITY AND CONSISTENCY
  35. 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" } } } ] powered by Cerberus python-cerberus.org
  36. GEOJSON SUPPORT AND VALIDATION FOR GEOJSON TYPES POINT, LINE-STRING, POLYGON,

    MULTI-POINT, MULTILINE-STRING, MULTI-POLYGON, GEOMETRICAL COLLECTION
  37. AND MUCH MORE CORS / CACHE CONTROL / OPLOG /

    SOFT DELETES LOGGING / CUSTOM ID FIELDS / JSONP / MULTI-DB / AGGREGATION / ETC.
  38. SECURITY AT A GLANCE • global authentication • endpoint authentication

    • public enpoints and methods • role based access control • user restricted resource access
  39. 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>
  40. FLASK AT YOUR FINGERTIPS from eve import Eve app =

    Eve() # add a regular Flask endpoint @app.route('/hello') def hello_world(): return 'Hello World!' if __name__ == '__main__': app.run()
  41. FLASK AT YOUR FINGERTIPS from eve import Eve from eve.auth

    import requires_auth app = Eve() # add Eve auth to Flask endpoint @app.route('/hello') @requires_auth('resource') def hello_world(): return 'Hello World!' if __name__ == '__main__': app.run()
  42. {137: <you name here>} Bryan Cattle Christoph Witzany 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