SlideShare a Scribd company logo
RESTful Web API
                           With Python, Flask and Mongo




                                Nicola Iarocci
mercoledì 4 luglio 2012
Good Morning.



mercoledì 4 luglio 2012
@nicolaiarocci



mercoledì 4 luglio 2012
Full Disclosure



mercoledì 4 luglio 2012
I’m a .NET guy
                          20 years in the Microsoft ecosystem
                                       Scary, Yes.




mercoledì 4 luglio 2012
mercoledì 4 luglio 2012
Still with me?
                               Great.




mercoledì 4 luglio 2012
gestionaleamica.com
                              invoicing & accounting




mercoledì 4 luglio 2012
Your Typical Old School
                              Desktop App...




                   ... now going web & mobile
mercoledì 4 luglio 2012
Enter Python
                            Flask and Mongo




mercoledì 4 luglio 2012
REST
                          So What Is REST All About?




mercoledì 4 luglio 2012
REST is
                          not a standard



mercoledì 4 luglio 2012
REST is
                          not a protocol



mercoledì 4 luglio 2012
REST is an
                          architectural style
                            for networked
                             applications



mercoledì 4 luglio 2012
REST
                              defines a set of
                             simple principles
                          loosely followed by most API implementations




mercoledì 4 luglio 2012
#1
                                  resource
                          the source of a specific information




mercoledì 4 luglio 2012
A web page is not a
                              resource
                           rather, the representation of a resource




mercoledì 4 luglio 2012
#2
                                global
                          permanent identifier
                            every resource is uniquely identified
                                    (think a HTTP URI)




mercoledì 4 luglio 2012
#3
                            standard interface
                          used to exchange representations of resources
                                    (think the HTTP protocol)




mercoledì 4 luglio 2012
#4
                            set of constraints
                          separation of concerns, stateless, cacheability,
                               layered system, uniform interface...


                                                              we’ ll get to
                                                               the se later

mercoledì 4 luglio 2012
The World Wide Web
                        is built on REST
                          and it is meant to be consumed by humans




mercoledì 4 luglio 2012
RESTful Web APIs
                          are built on REST
                          and are meant to be consumed by machines




mercoledì 4 luglio 2012
Beginners Reading




                          How I Explained
                          REST to My Wife
                                    by Ryan Tomayko
                           http://tomayko.com/writings/rest-to-my-wife




mercoledì 4 luglio 2012
The Real Stuff




                   Representational State
                     Transfer (REST)
                                           by Roy Thomas Fielding
                          http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm




mercoledì 4 luglio 2012
RESTful Web API
                               Nuts & Bolts




mercoledì 4 luglio 2012
The Tools
                          or why I picked Flask and Mongo




mercoledì 4 luglio 2012
Flask
                          web development, one drop at a time




mercoledì 4 luglio 2012
Simple & Elegant
                          from flask import Flask
                          app = Flask(__name__)

                          @app.route("/")
                          def hello():
                              return "Hello World!"

                          if __name__ == "__main__":
                              app.run(debug=True)



mercoledì 4 luglio 2012
RESTful
                          request dispacthing
                          @app.route('/user/<username>')
                          def show_user_profile(username):
                              return 'User %s' % username

                          @app.route('/post/<int:post_id>')
                          def show_post(post_id):
                              return 'Post %d' % post_id




mercoledì 4 luglio 2012
Built-in development
                           server & debugger
                            from flask import Flask
                            app = Flask(__name__)

                            @app.route("/")
                            def hello():
                                return "Hello World!"

                            if __name__ == "__main__":
                                app.run(debug=True)



mercoledì 4 luglio 2012
Explicit & passable
                          application objects
                           from flask import Flask
                           app = Flask(__name__)

                           @app.route("/")
                           def hello():
                               return "Hello World!"

                           if __name__ == "__main__":
                               app.run(debug=True)


mercoledì 4 luglio 2012
100% WSGI Compliant
          e.g., response objects are WSGI applications themselves

                          from flask import Flask
                          app = Flask(__name__)

                          @app.route("/")
                          def hello():
                              return "Hello World!"

                          if __name__ == "__main__":
                              app.run(debug=True)



mercoledì 4 luglio 2012
Minimal Footprint
                            Only 800 lines of source code




mercoledì 4 luglio 2012
Heavily Tested
                             1500 lines of tests




mercoledì 4 luglio 2012
Unittesting Support
                            one day I will make good use of it




mercoledì 4 luglio 2012
Bring Your Own
                              Batteries
                             we aim for flexibility




mercoledì 4 luglio 2012
No built-in ORM
                    we want to be as close to the bare metal as possible




mercoledì 4 luglio 2012
No form validation
                          we don’t need no freaking form validation




mercoledì 4 luglio 2012
No data validation
                          Python offers great tools to manipulate JSON,
                               we can tinker something ourselves




mercoledì 4 luglio 2012
Layered API
                          built on Werkzeug, Jinja2, WSGI




mercoledì 4 luglio 2012
Built by the Pros
                          The Pocoo Team did Werkzeug, Jinja2, Sphinx,
                                  Pygments, and much more




mercoledì 4 luglio 2012
Excellent
                             Documentation
                          Over 200 pages, lots of examples and howtos




mercoledì 4 luglio 2012
Active Community
                          Widely adopted, extensions for everything




mercoledì 4 luglio 2012
“Flask is a sharp tool
                           for building sharp
                                services”
                                 Kenneth Reitz,
                                 DjangoCon 2012




mercoledì 4 luglio 2012
MongoDB
                           scalable, high-performance,
                          open source NoSQL database




mercoledì 4 luglio 2012
Similarity with
                             RDBMS
          made NoSQL easy to grasp (even for a dumbhead like me)




mercoledì 4 luglio 2012
Terminology
                          RDBMS           Mongo
                          Database        Database

                           Table         Collection

                          Rows(s)     JSON Document

                           Index           Index

                            Join     Embedding & Linking


mercoledì 4 luglio 2012
JSON-style data store
                          true selling point for me




mercoledì 4 luglio 2012
JSON & RESTful API
                                              GET


                          Client                                 Mongo

                          JSON                                    JSON
                accepted media type                                   (BSON)




                              maybe we can push directly to client?



mercoledì 4 luglio 2012
JSON & RESTful API
                                           GET


                          Client          API               Mongo

                          JSON         JSON/dict            JSON
                accepted media type   maps to python dict   (BSON)




                                          almost.



mercoledì 4 luglio 2012
JSON & RESTful API
                                         POST


                          Client         API               Mongo

                          JSON        JSON/dict            JSON
                                     maps to python dict
                           objects                         (BSON)
                                      (validation layer)




              also works when posting (adding) items to the database



mercoledì 4 luglio 2012
What about Queries?
       Queries in MongoDB are represented as JSON-style objects


                // select * from things where x=3 and y="foo"
                db.things.find({x: 3, y: "foo”});



mercoledì 4 luglio 2012
JSON & RESTful API
                                          FILTERING & SORTING


                                         ?where={x: 3, y: "foo”}


                          Client                  API              Mongo
                                                (very) thin
                          native                  parsing          JSON
                          Mongo                 & validation       (BSON)
                          query syntax
                                                   layer


mercoledì 4 luglio 2012
JSON
                          all along the pipeline
                  mapping to and from the database feels more natural




mercoledì 4 luglio 2012
Schema-less
     dynamic objects allow for a painless evolution of our schema
         (because yes, a schema exists at any point in time)




mercoledì 4 luglio 2012
ORM
                          Where we’re going we don’t need ORMs.




mercoledì 4 luglio 2012
PyMongo
                                    official Python driver
                          all we need to interact with the database




mercoledì 4 luglio 2012
Also in MongoDB

                     • 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/




mercoledì 4 luglio 2012
Shameless Plug




                                Il Piccolo
                            Libro di MongoDB
                                               by Karl Seguin, traduzione di
                                                   Nicola Iarocci
                          http://nicolaiarocci.com/il-piccolo-libro-di-mongodb-edizione-italiana/




mercoledì 4 luglio 2012
MongoDB Interactive
                         Tutorial
                          http://tutorial.mongly.com/tutorial/index




mercoledì 4 luglio 2012
RESTful Web APIs
                       are really just
                  collection of resources
                          accesible through to a uniform interface




mercoledì 4 luglio 2012
#1
                           each resource is
                            identified by a
                          persistent identifier
                 We need to properly implement Request Dispatching




mercoledì 4 luglio 2012
Collections
                           API’s entry point + plural nouns
                          http://api.example.com/v1/contacts




mercoledì 4 luglio 2012
Collections
                           Flask URL dispatcher allows for variables


                          @app.route('/<collection>')
                          def collection(collection):
                              if collection in DOMAIN.keys():
                                  (...)
                              abort(404)


                                   api.example.com/contacts
                                   api.example.com/invoices
                                             etc.




mercoledì 4 luglio 2012
Collections
                           Flask URL dispatcher allows for variables


                          @app.route('/<collection>')
                          def collection(collection):
                              if collection in DOMAIN.keys():
                                  (...)
                              abort(404)



                                           validation
                                           dictonary




mercoledì 4 luglio 2012
Collections
                           Flask URL dispatcher allows for variables


                          @app.route('/<collection>')
                          def collection(collection):
                              if collection in DOMAIN.keys():
                                  (...)
                              abort(404)



                                         we don’t know
                                        this collection,
                                          return a 404




mercoledì 4 luglio 2012
RegEx
                          by design, collection URLs are plural nouns


              @app.route('/<regex("[w]*[Ss]"):collection>')
              def collection(collection):
                  if collection in DOMAIN.keys():
                      (...)
                  abort(404)


                                  regular expressions can be
                                   used to better narrow a
                                      variable part URL.
                                          However...



mercoledì 4 luglio 2012
RegEx
                          We need to build our own Custom Converter


         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


                                  subclass BaseConverter and
                                  pass the new converter to
                                          the url_map




mercoledì 4 luglio 2012
Document
                   Documents are identified by ObjectID
       http://api.example.com/v1/contacts/4f46445fc88e201858000000

                           And eventually by an alternative lookup value
                          http://api.example.com/v1/contacts/CUST12345



mercoledì 4 luglio 2012
Document

    @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):
        (...)



                           URL dispatcher handles multiple variables

                          http://api.example.com/v1/contacts/CUST12345




mercoledì 4 luglio 2012
Document

    @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):
        (...)



                    and of course it also handles multiple RegEx variables

             http://api.example.com/v1/contacts/4f46445fc88e201858000000




mercoledì 4 luglio 2012
Document

    @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):
        (...)



                          Different URLs can be dispatched to
                          the same function just by piling up
                                @app.route decorators.




mercoledì 4 luglio 2012
#2
                      representation of
                  resources via media types
                     JSON, XML or any other valid internet media type



                                         dep ends on the
                                         reque st and not
                                           the identifier
mercoledì 4 luglio 2012
Accepted Media Types
                          mapping supported media types to
                          corresponding renderer functions

    mime_types = {'json_renderer': ('application/json',),
                  'xml_renderer': ('application/xml', 'text/xml',
                                   'application/x-xml',)}




                               JSON rendering function




mercoledì 4 luglio 2012
Accepted Media Types
                          mapping supported media types to
                          corresponding renderer functions

    mime_types = {'json_renderer': ('application/json',),
                  'xml_renderer': ('application/xml', 'text/xml',
                                   'application/x-xml',)}




                                corresponding JSON
                                internet media type




mercoledì 4 luglio 2012
Accepted Media Types
                          mapping supported media types to
                          corresponding renderer functions

    mime_types = {'json_renderer': ('application/json',),
                  'xml_renderer': ('application/xml', 'text/xml',
                                   'application/x-xml',)}




                               XML rendering function




mercoledì 4 luglio 2012
Accepted Media Types
                          mapping supported media types to
                          corresponding renderer functions

    mime_types = {'json_renderer': ('application/json',),
                  'xml_renderer': ('application/xml', 'text/xml',
                                   'application/x-xml',)}




                                 corresponding XML
                                internet media types




mercoledì 4 luglio 2012
JSON Render
                          datetimes and ObjectIDs call for further tinkering
         class APIEncoder(json.JSONEncoder):
             def default(self, obj):
                 if isinstance(obj, datetime.datetime):
                     return date_to_str(obj)
                 elif isinstance(obj, ObjectId):
                     return str(obj)
                 return json.JSONEncoder.default(self, obj)


         def json_renderer(**data):
             return json.dumps(data, cls=APIEncoder)

                                     renderer function mapped to
                                         the appication/json
                                              media type

mercoledì 4 luglio 2012
JSON Render
                          datetimes and ObjectIDs call for further tinkering
         class APIEncoder(json.JSONEncoder):
             def default(self, obj):
                 if isinstance(obj, datetime.datetime):
                     return date_to_str(obj)
                 elif isinstance(obj, ObjectId):
                     return str(obj)
                 return json.JSONEncoder.default(self, obj)


         def json_renderer(**data):
             return json.dumps(data, cls=APIEncoder)

                                      standard json encoding is
                                        not enough, we need a
                                       specialized JSONEncoder

mercoledì 4 luglio 2012
JSON Render
                          datetimes and ObjectIDs call for further tinkering
         class APIEncoder(json.JSONEncoder):
             def default(self, obj):
                 if isinstance(obj, datetime.datetime):
                     return date_to_str(obj)
                 elif isinstance(obj, ObjectId):
                     return str(obj)
                 return json.JSONEncoder.default(self, obj)


         def json_renderer(**data):
             return json.dumps(data, cls=APIEncoder)

                               Python datetimes are encoded as RFC 1123
                               strings: “Wed, 06 Jun 2012 14:19:53 UTC”

mercoledì 4 luglio 2012
JSON Render
                          datetimes and ObjectIDs call for further tinkering
         class APIEncoder(json.JSONEncoder):
             def default(self, obj):
                 if isinstance(obj, datetime.datetime):
                     return date_to_str(obj)
                 elif isinstance(obj, ObjectId):
                     return str(obj)
                 return json.JSONEncoder.default(self, obj)


         def json_renderer(**data):
             return json.dumps(data, cls=APIEncoder)

                               Mongo ObjectId data types are encoded as
                                 strings: “4f46445fc88e201858000000”

mercoledì 4 luglio 2012
JSON Render
                          datetimes and ObjectIDs call for further tinkering
         class APIEncoder(json.JSONEncoder):
             def default(self, obj):
                 if isinstance(obj, datetime.datetime):
                     return date_to_str(obj)
                 elif isinstance(obj, ObjectId):
                     return str(obj)
                 return json.JSONEncoder.default(self, obj)


         def json_renderer(**data):
             return json.dumps(data, cls=APIEncoder)

                                       we let json/simplejson
                                     handle the other data types

mercoledì 4 luglio 2012
Rendering
                Render to JSON or XML and get WSGI response object

              def prep_response(dct, status=200):
                  mime, render = get_best_mime()
                  rendered = globals()[render](**dct)
                  resp = make_response(rendered, status)
                  resp.mimetype = mime
                  return resp


                                best match between
                               request Accept header
                                  and media types
                                 supported by the
                                      service

mercoledì 4 luglio 2012
Rendering
                Render to JSON or XML and get WSGI response object

              def prep_response(dct, status=200):
                  mime, render = get_best_mime()
                  rendered = globals()[render](**dct)
                  resp = make_response(rendered, status)
                  resp.mimetype = mime
                  return resp



                               call the appropriate
                               render function and
                               retrieve the encoded
                                    JSON or XML


mercoledì 4 luglio 2012
Rendering
                Render to JSON or XML and get WSGI response object

              def prep_response(dct, status=200):
                  mime, render = get_best_mime()
                  rendered = globals()[render](**dct)
                  resp = make_response(rendered, status)
                  resp.mimetype = mime
                  return resp



                              flask’s make_response()
                              returns a WSGI response
                              object wich we can use
                                 to attach headers


mercoledì 4 luglio 2012
Rendering
                Render to JSON or XML and get WSGI response object

              def prep_response(dct, status=200):
                  mime, render = get_best_mime()
                  rendered = globals()[render](**dct)
                  resp = make_response(rendered, status)
                  resp.mimetype = mime
                  return resp



                              and finally, we set the
                               appropriate mime type
                              in the response header



mercoledì 4 luglio 2012
Flask-MimeRender
           “Python module for RESTful resource representation using
             MIME Media-Types and the Flask Microframework”




                            !"!#"$%&'((#)('%*+,",-.-$/-.
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




mercoledì 4 luglio 2012
Flask-MimeRender
                          then you just decorate your end-point function

  @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!'}



mercoledì 4 luglio 2012
Flask-MimeRender
                                Requests

    $ curl -H "Accept: application/html" example.com/
    <html><body>Hello, World!</body></html>

    $ curl -H "Accept: application/xml" example.com/
    <message>Hello, World!</message>

    $ curl -H "Accept: application/json" example.com/
    {'message':'Hello, World!'}

    $ curl -H "Accept: text/plain" example.com/
    Hello, World!


mercoledì 4 luglio 2012
#3
                   resource manipulation
                    through HTTP verbs
                          “GET, POST, PUT, DELETE and all that mess”




mercoledì 4 luglio 2012
HTTP Methods
                          Verbs are handled along with URL routing
   @app.route('/<collection>', methods=['GET', 'POST'])
   def collection(collection):
       if collection in DOMAIN.keys():
           if request.method == 'GET':
               return get_collection(collection)
           elif request.method == 'POST':
               return post(collection)
       abort(404)


                                    accepted HTTP verbs
                                     a PUT will throw a
                                  405 Command Not Allowed


mercoledì 4 luglio 2012
HTTP Methods
                          Verbs are handled along with URL routing
   @app.route('/<collection>', methods=['GET', 'POST'])
   def collection(collection):
       if collection in DOMAIN.keys():
           if request.method == 'GET':
               return get_collection(collection)
           elif request.method == 'POST':
               return post(collection)
       abort(404)


                                 the global request object
                                provides access to clients’
                                      request headers


mercoledì 4 luglio 2012
HTTP Methods
                          Verbs are handled along with URL routing
   @app.route('/<collection>', methods=['GET', 'POST'])
   def collection(collection):
       if collection in DOMAIN.keys():
           if request.method == 'GET':
               return get_collection(collection)
           elif request.method == 'POST':
               return post(collection)
       abort(404)



                                we respond to a GET request
                                for a ‘collection’ resource


mercoledì 4 luglio 2012
HTTP Methods
                          Verbs are handled along with URL routing
   @app.route('/<collection>', methods=['GET', 'POST'])
   def collection(collection):
       if collection in DOMAIN.keys():
           if request.method == 'GET':
               return get_collection(collection)
           elif request.method == 'POST':
               return post(collection)
       abort(404)


                                and here we respond to a POST
                                   request. Handling HTTP
                                      methods is easy!


mercoledì 4 luglio 2012
CRUD via REST
                          Acttion   HTTP Verb   Context
                                                Collection/
                           Get        GET
                                                Document
                          Create      POST      Collection

                          Update     PATCH*     Document

                          Delete     DELETE     Document
                                                        * WTF?


mercoledì 4 luglio 2012
GET
                      Retrieve Multiple Documents (accepting Queries)
               http://api.example.com/v1/contacts?where={“age”: {“$gt”: 20}}




mercoledì 4 luglio 2012
Collection GET
               http://api.example.com/v1/contacts?where={“age”: {“$gt”: 20}}

 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        request.args returns the
           return prep_response(response)            original URI’s query
                                                  definition, in our example:
                                                  where = {“age”: {“$gt”: 20}}


mercoledì 4 luglio 2012
Collection GET
               http://api.example.com/v1/contacts?where={“age”: {“$gt”: 20}}

 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.


mercoledì 4 luglio 2012
Collection GET
               http://api.example.com/v1/contacts?where={“age”: {“$gt”: 20}}

 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


mercoledì 4 luglio 2012
Collection GET
               http://api.example.com/v1/contacts?where={“age”: {“$gt”: 20}}

 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


mercoledì 4 luglio 2012
Collection GET
               http://api.example.com/v1/contacts?where={“age”: {“$gt”: 20}}

 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


mercoledì 4 luglio 2012
Interlude
                          On encoding JSON dates




mercoledì 4 luglio 2012
On encoding
                                 JSON dates
                     • We don’t want to force metadata into
                          JSON representation:
                          (“updated”: “$date: Thu 1, ..”)

                     • Likewise, epochs are not an option
                     • We are aiming for a broad solution not
                          relying on the knoweldge of the current
                          domain

mercoledì 4 luglio 2012
uy
    e g ind
  th eh is
    b ed
       R
                          Because, you know




mercoledì 4 luglio 2012
Parsing JSON dates
                                    this is what I came out with

           >>> source = '{"updated": "Thu, 1 Mar 2012 10:00:49 UTC"}'
           >>> dct = json.loads(source, object_hook=datetime_parser)
           >>> dct
           {u'updated': datetime.datetime(2012, 3, 1, 10, 0, 49)}


           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?)


mercoledì 4 luglio 2012
Parsing JSON dates
                                    this is what I came out with

           >>> source = '{"updated": "Thu, 1 Mar 2012 10:00:49 UTC"}'
           >>> dct = json.loads(source, object_hook=datetime_parser)
           >>> dct
           {u'updated': datetime.datetime(2012, 3, 1, 10, 0, 49)}


           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

mercoledì 4 luglio 2012
Parsing JSON dates
                                    this is what I came out with

           >>> source = '{"updated": "Thu, 1 Mar 2012 10:00:49 UTC"}'
           >>> dct = json.loads(source, object_hook=datetime_parser)
           >>> dct
           {u'updated': datetime.datetime(2012, 3, 1, 10, 0, 49)}


           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


mercoledì 4 luglio 2012
Parsing JSON dates
                                    this is what I came out with

           >>> source = '{"updated": "Thu, 1 Mar 2012 10:00:49 UTC"}'
           >>> dct = json.loads(source, object_hook=datetime_parser)
           >>> dct
           {u'updated': datetime.datetime(2012, 3, 1, 10, 0, 49)}


           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)...


mercoledì 4 luglio 2012
Parsing JSON dates
                                    this is what I came out with

           >>> source = '{"updated": "Thu, 1 Mar 2012 10:00:49 UTC"}'
           >>> dct = json.loads(source, object_hook=datetime_parser)
           >>> dct
           {u'updated': datetime.datetime(2012, 3, 1, 10, 0, 49)}


           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

                                  ...are converted to datetime
                                             values


mercoledì 4 luglio 2012
Parsing JSON dates
                                    this is what I came out with

           >>> source = '{"updated": "Thu, 1 Mar 2012 10:00:49 UTC"}'
           >>> dct = json.loads(source, object_hook=datetime_parser)
           >>> dct
           {u'updated': datetime.datetime(2012, 3, 1, 10, 0, 49)}


           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


mercoledì 4 luglio 2012
PATCH
                          Editing a Resource




mercoledì 4 luglio 2012
Why not PUT?
                     •    PUT means resource creation or replacement at
                          a given URL
                     •    PUT does not allow for partial updates of a
                          resource
                     •    99% of the time we are updating just one or two
                          fields
                     •    We don’t want to send complete representations
                          of the document we are updating
                     •    Mongo allows for atomic updates and we want
                          to take advantage of that
mercoledì 4 luglio 2012
‘atomic’ PUT updates
                   are ok when each field
                     is itself a resource
                  http://api.example.com/v1/contacts/<id>/address




mercoledì 4 luglio 2012
Enter PATCH
        “This specification defines the new method, PATCH, which
           is used to apply partial modifications to a resource.”

                                RFC5789



mercoledì 4 luglio 2012
PATCH
                     •    send a “patch document” with just the
                          changes to be applied to the document
                     •    saves bandwidth and reduces traffic
                     •    it’s been around since 1995
                     •    it is a RFC Proposed Standard
                     •    Widely adopted (will replace PUT in Rails 4.0)
                     •    clients not supporting it can fallback to POST
                          with ‘X-HTTP-Method-Override: PATCH’
                          header tag
mercoledì 4 luglio 2012
PATCHing
  def patch_document(collection, original):
      docs = parse_request(request.form)
      if len(docs) > 1:
          abort(400)                               request.form returns a dict
                                                     with request form data.
           key, value = docs.popitem()

           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)

mercoledì 4 luglio 2012
PATCHing
  def patch_document(collection, original):
      docs = parse_request(request.form)
      if len(docs) > 1:
          abort(400)                                we aren’t going to accept
                                                   more than one document here
           key, value = docs.popitem()

           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)

mercoledì 4 luglio 2012
PATCHing
  def patch_document(collection, original):
      docs = parse_request(request.form)
      if len(docs) > 1:                              retrieve the original
          abort(400)
                                                  document ID, will be used by
           key, value = docs.popitem()
                                                       the update command

           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)

mercoledì 4 luglio 2012
PATCHing
  def patch_document(collection, original):
      docs = parse_request(request.form)
      if len(docs) > 1:
          abort(400)
                                                      validate the updates
           key, value = docs.popitem()

           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)

mercoledì 4 luglio 2012
PATCHing
  def patch_document(collection, original):
      docs = parse_request(request.form)
      if len(docs) > 1:
          abort(400)                                add validation results to
                                                     the response dictionary
           key, value = docs.popitem()

           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)

mercoledì 4 luglio 2012
PATCHing
  def patch_document(collection, original):
      docs = parse_request(request.form)
                                                       $set accepts a dict
      if len(docs) > 1:
          abort(400)
                                                   with the updates for the db
                                                     eg: {“active”: False}.
           key, value = docs.popitem()

           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)

mercoledì 4 luglio 2012
PATCHing
  def patch_document(collection, original):
      docs = parse_request(request.form)             mongo update() method
      if len(docs) > 1:
                                                     commits updates to the
          abort(400)
                                                     database. Updates are
           key, value = docs.popitem()                       atomic.

           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)

mercoledì 4 luglio 2012
PATCHing
  def patch_document(collection, original):
      docs = parse_request(request.form)
      if len(docs) > 1:                           udpate() takes the unique Id
          abort(400)                                 of the document andthe
                                                    update expression ($set)
           key, value = docs.popitem()

           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)

mercoledì 4 luglio 2012
PATCHing
  def patch_document(collection, original):
      docs = parse_request(request.form)
      if len(docs) > 1:                              as always, our response
          abort(400)                               dictionary is returned with
                                                         proper encding
           key, value = docs.popitem()

           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)

mercoledì 4 luglio 2012
POST
                          Creating Resources




mercoledì 4 luglio 2012
POSTing
       def post(collection):
           docs = parse_request(request.form)
           response = {}
           for key, item in docs.items():
               response_item = {}
               validate(item, collection)
               if item['validation']['response'] != VALIDATION_ERROR:
                   document = item['doc']
                   response_item[ID_FIELD] = db(collection).insert(document)
                   response_item['link'] = get_document_link(collection,
                                                     response_item[ID_FIELD])
               response_item['validation'] = item['validation']
               response[key] = response_item
           return {'response': response}


                                               we accept multiple documents
                                                   (remember, we are at
                                                  collection level here)



mercoledì 4 luglio 2012
POSTing
       def post(collection):
           docs = parse_request(request.form)
           response = {}
           for key, item in docs.items():
               response_item = {}
               validate(item, collection)
               if item['validation']['response'] != VALIDATION_ERROR:
                   document = item['doc']
                   response_item[ID_FIELD] = db(collection).insert(document)
                   response_item['link'] = get_document_link(collection,
                                                     response_item[ID_FIELD])
               response_item['validation'] = item['validation']
               response[key] = response_item
           return {'response': response}


                                                   we loop through the
                                                 documents to be inserted



mercoledì 4 luglio 2012
POSTing
       def post(collection):
           docs = parse_request(request.form)
           response = {}
           for key, item in docs.items():
               response_item = {}
               validate(item, collection)
               if item['validation']['response'] != VALIDATION_ERROR:
                   document = item['doc']
                   response_item[ID_FIELD] = db(collection).insert(document)
                   response_item['link'] = get_document_link(collection,
                                                     response_item[ID_FIELD])
               response_item['validation'] = item['validation']
               response[key] = response_item
           return {'response': response}


                                                perform validation on the
                                                        document



mercoledì 4 luglio 2012
POSTing
       def post(collection):
           docs = parse_request(request.form)
           response = {}
           for key, item in docs.items():
               response_item = {}
               validate(item, collection)
               if item['validation']['response'] != VALIDATION_ERROR:
                   document = item['doc']
                   response_item[ID_FIELD] = db(collection).insert(document)
                   response_item['link'] = get_document_link(collection,
                                                     response_item[ID_FIELD])
               response_item['validation'] = item['validation']
               response[key] = response_item
           return {'response': response}
                                                push document and get its
                                                ObjectId back from Mongo.
                                               like other CRUD operations,
                                                 inserting is trivial in
                                                          mongo.


mercoledì 4 luglio 2012
POSTing
       def post(collection):
           docs = parse_request(request.form)
           response = {}
           for key, item in docs.items():
               response_item = {}
               validate(item, collection)
               if item['validation']['response'] != VALIDATION_ERROR:
                   document = item['doc']
                   response_item[ID_FIELD] = db(collection).insert(document)
                   response_item['link'] = get_document_link(collection,
                                                     response_item[ID_FIELD])
               response_item['validation'] = item['validation']
               response[key] = response_item
           return {'response': response}


                                                  a direct link to the
                                               resource we just created is
                                                  added to the response



mercoledì 4 luglio 2012
POSTing
       def post(collection):
           docs = parse_request(request.form)
           response = {}
           for key, item in docs.items():
               response_item = {}
               validate(item, collection)
               if item['validation']['response'] != VALIDATION_ERROR:
                   document = item['doc']
                   response_item[ID_FIELD] = db(collection).insert(document)
                   response_item['link'] = get_document_link(collection,
                                                     response_item[ID_FIELD])
               response_item['validation'] = item['validation']
               response[key] = response_item
           return {'response': response}

                                               validation result is always
                                               returned to the client, even
                                                 if the doc has not been
                                                         inserted


mercoledì 4 luglio 2012
POSTing
       def post(collection):
           docs = parse_request(request.form)
           response = {}
           for key, item in docs.items():
               response_item = {}
               validate(item, collection)
               if item['validation']['response'] != VALIDATION_ERROR:
                   document = item['doc']
                   response_item[ID_FIELD] = db(collection).insert(document)
                   response_item['link'] = get_document_link(collection,
                                                     response_item[ID_FIELD])
               response_item['validation'] = item['validation']
               response[key] = response_item
           return {'response': response}


                                               standard response enconding
                                                         applied



mercoledì 4 luglio 2012
Data Validation
                          We still need to validate incoming data




mercoledì 4 luglio 2012
Data Validation
                     DOMAIN = {}
                     DOMAIN['contacts'] = {
                                              'secondary_id': 'name',
                                              'fields': {
                                                  'name': {
                                                      'data_type': 'string',
                                                      'required': True,
                                                      'unique': True,
                                                      'max_length': 120,
                                                      'min_length': 1
                                                  },



                                   DOMAIN is a Python dict
                                  containing our validation
                                  rules and schema structure



mercoledì 4 luglio 2012
Data Validation
                     DOMAIN = {}
                     DOMAIN['contacts'] = {
                                              'secondary_id': 'name',
                                              'fields': {
                                                  'name': {
                                                      'data_type': 'string',
                                                      'required': True,
                                                      'unique': True,
                                                      'max_length': 120,
                                                      'min_length': 1
                                                  },



                                  every resource (collection)
                                  maintained by the API has a
                                         key in DOMAIN



mercoledì 4 luglio 2012
Data Validation
                     DOMAIN = {}
                     DOMAIN['contacts'] = {
                                              'secondary_id': 'name',
                                              'fields': {
                                                  'name': {
                                                      'data_type': 'string',
                                                      'required': True,
                                                      'unique': True,
                                                      'max_length': 120,
                                                      'min_length': 1
                                                  },


                                 if the resource allows for a
                                  secondary lookup field, we
                                        define it here



mercoledì 4 luglio 2012
Data Validation
                     DOMAIN = {}
                     DOMAIN['contacts'] = {
                                              'secondary_id': 'name',
                                              'fields': {
                                                  'name': {
                                                      'data_type': 'string',
                                                      'required': True,
                                                      'unique': True,
                                                      'max_length': 120,
                                                      'min_length': 1
                                                  },



                                    known fields go in the
                                          fields dict



mercoledì 4 luglio 2012
Data Validation
                     DOMAIN = {}
                     DOMAIN['contacts'] = {
                                              'secondary_id': 'name',
                                              'fields': {
                                                  'name': {
                                                      'data_type': 'string',
                                                      'required': True,
                                                      'unique': True,
                                                      'max_length': 120,
                                                      'min_length': 1
                                                  },



                                 validation rules for ‘name’
                                 field. data_type is mostly
                                 needed to process datetimes
                                     and currency values


mercoledì 4 luglio 2012
Data Validation
                          (...)
                          'iban': {
                              'data_type': 'string',
                              'custom_validation': {
                                  'module': 'customvalidation',
                                  'function': 'validate_iban'
                              }
                          }
                          (...)




                                  we can define custom
                                validation functions when
                                     the need arises


mercoledì 4 luglio 2012
Data Validation
                          (...)
                          'contact_type': {
                                      'data_type': 'array',
                                      'allowed_values': [
                                          'client',
                                          'agent',
                                          'supplier',
                                          'area manager',
                                          'vector'
                                       ]
                          }
                          (...)


                              or we can define our own
                                custom data types...



mercoledì 4 luglio 2012
Data Validation
                          (...)
                          'contact_type': {
                                      'data_type': 'array',
                                      'allowed_values': [
                                          'client',
                                          'agent',
                                          'supplier',
                                          'area manager',
                                          'vector'
                                       ]
                          }
                          (...)

                             ... like the array, which
                             allows us to define a list
                             of accepted values for the
                                        field


mercoledì 4 luglio 2012
I will spare you the
                          validation function
                              It’s pretty simple really




mercoledì 4 luglio 2012
Hey but!
                          You’re building
                          your own ORM!
               Just a thin validation layer on which I have total control


                                        AKA
                                      So What?

mercoledì 4 luglio 2012
#4
                             Caching and
                          concurrency control
                          resource representation describes how
                     when and if it can be used, discarded or re-fetched




mercoledì 4 luglio 2012
Driving conditional
                               requests
                     Servers use Last-Modified and ETag response
                            headers to drive conditional requests




mercoledì 4 luglio 2012
Last-Modified
                    Generally considered a weak validator since it has a
                                  one-second resolution
                           “Wed, 06 Jun 2012 14:19:53 UTC”




mercoledì 4 luglio 2012
ETag
                  Entity Tag is a strong validator since its value can be
                       changed every time the server modifies the
                                      representation
                          7a9f477cde424cf93a7db20b69e05f7b680b7f08



mercoledì 4 luglio 2012
On ETags
                     • Clients should be able to use ETag to
                          compare representations of a resouce
                     • An ETag is supposed to be like an
                          object’s hash code.
                     • Actually, some web frameworks and a lot
                          of implementations do just that
                     • ETag computed on an entire
                          representation of the resource may
                          become a performance bottleneck
mercoledì 4 luglio 2012
Last-Modified
                                   or ETag?
                          You can use either or both. Consider the types of
                           client consuming your service. Hint: use both.




mercoledì 4 luglio 2012
Validating cached
                           representations
                 Clients use If-Modified-Since and If-None-Match
                   in request headers for validating cached representations




mercoledì 4 luglio 2012
If-Mod-Since & ETag
        def get_document(collection, object_id=None, lookup=None):
            response = {}
            document = find_document(collection, object_id, lookup)
            if document:
                etag = get_etag(document)
                header_etag = request.headers.get('If-None-Match')
                if header_etag and header_etag == etag:
                    return prep_response(dict(), status=304)

                          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

mercoledì 4 luglio 2012
If-Mod-Since & ETag
        def get_document(collection, object_id=None, lookup=None):
            response = {}
            document = find_document(collection, object_id, lookup)
            if document:
                etag = get_etag(document)
                header_etag = request.headers.get('If-None-Match')
                if header_etag and header_etag == etag:
                    return prep_response(dict(), status=304)

                          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 ETag for the current
                                                       = document
                      return prep_response(response, last_modified, etag)
                                                       representation. We test ETag
                  abort(404)
                                                           first, as it is a stronger
                                                                    validator

mercoledì 4 luglio 2012
If-Mod-Since & ETag
        def get_document(collection, object_id=None, lookup=None):
            response = {}
            document = find_document(collection, object_id, lookup)
            if document:
                etag = get_etag(document)
                header_etag = request.headers.get('If-None-Match')
                if header_etag and header_etag == etag:
                    return prep_response(dict(), status=304)

                          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

mercoledì 4 luglio 2012
If-Mod-Since & ETag
        def get_document(collection, object_id=None, lookup=None):
            response = {}
            document = find_document(collection, object_id, lookup)
            if document:
                etag = get_etag(document)
                header_etag = request.headers.get('If-None-Match')
                if header_etag and header_etag == etag:
                    return prep_response(dict(), status=304)

                          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

mercoledì 4 luglio 2012
If-Mod-Since & ETag
        def get_document(collection, object_id=None, lookup=None):
            response = {}
            document = find_document(collection, object_id, lookup)
            if document:
                etag = get_etag(document)
                header_etag = request.headers.get('If-None-Match')
                if header_etag and header_etag == etag:
                    return prep_response(dict(), status=304)

                          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, if the resource
                                                         likewise, etag)
                  abort(404)                           has not been modified since
                                                                If-Modifed-Since,
                                                             return 304 Not Modified

mercoledì 4 luglio 2012
Concurrency control
                  Clients use If-Unmodified-Since and If-Match in
                   request headers as preconditions for concurrency control




mercoledì 4 luglio 2012
Concurrency control
                          Create/Update/Delete are controlled by ETag
     def edit_document(collection, object_id, method):
         document = find_document(collection, object_id)
         if document:
             header_etag = request.headers.get('If-Match')
             if header_etag is None:
                 return prep_response('If-Match missing from request header',
                                       status=403)
             if header_etag != get_etag(document[LAST_UPDATED]):
                 # Precondition failed
                 abort(412)
             else:
                 if method in ('PATCH', 'POST'):
                      return patch_document(collection, document)
                 elif method == 'DELETE':
                      return delete_document(collection, object_id)
         else:
             abort(404)                          retrieve client’s If-Match
                                                   ETag from the request header

mercoledì 4 luglio 2012
Concurrency control
                          Create/Update/Delete are controlled by ETag
     def edit_document(collection, object_id, method):
         document = find_document(collection, object_id)
         if document:
             header_etag = request.headers.get('If-Match')
             if header_etag is None:
                 return prep_response('If-Match missing from request header',
                                       status=403)
             if header_etag != get_etag(document[LAST_UPDATED]):
                 # Precondition failed
                 abort(412)
             else:
                 if method in ('PATCH', 'POST'):
                      return patch_document(collection, document)
                 elif method == 'DELETE':
                      return delete_document(collection, object_id)
         else:
             abort(404)                         editing is forbidden if ETag
                                                        is not provided

mercoledì 4 luglio 2012
Concurrency control
                          Create/Update/Delete are controlled by ETag
     def edit_document(collection, object_id, method):
         document = find_document(collection, object_id)
         if document:
             header_etag = request.headers.get('If-Match')
             if header_etag is None:
                 return prep_response('If-Match missing from request header',
                                       status=403)
             if header_etag != get_etag(document[LAST_UPDATED]):
                 # Precondition failed
                 abort(412)
             else:
                 if method in ('PATCH', 'POST'):
                      return patch_document(collection, document)
                 elif method == 'DELETE':
                      return delete_document(collection, object_id)
         else:                                        client and server
             abort(404)
                                                   representations don’t match.
                                                       Precondition failed.

mercoledì 4 luglio 2012
Concurrency control
                          Create/Update/Delete are controlled by ETag
     def edit_document(collection, object_id, method):
         document = find_document(collection, object_id)
         if document:
             header_etag = request.headers.get('If-Match')
             if header_etag is None:                  client and server
                                                   representation match,
                 return prep_response('If-Match missing from request header',
                                       status=403) go ahead with the edit
             if header_etag != get_etag(document[LAST_UPDATED]):
                 # Precondition failed
                 abort(412)
             else:
                 if method in ('PATCH', 'POST'):
                      return patch_document(collection, document)
                 elif method == 'DELETE':
                      return delete_document(collection, object_id)
         else:
             abort(404)



mercoledì 4 luglio 2012
Sending cache &
                  concurrency directives
                     back to clients



mercoledì 4 luglio 2012
Cache & Concurrency
 def prep_response(dct, last_modified=None, etag=None, status=200):
     (...)
     resp.headers.add('Cache-Control',
                      'max-age=%s,must-revalidate' & 30)
     resp.expires = time.time() + 30
     if etag:
         resp.headers.add('ETag', etag)
     if last_modified:
         resp.headers.add('Last-Modified', date_to_str(last_modified))
     return resp


                          encodes ‘dct’ according
                           to client’s accepted
                              MIME Data-Type
                             (click here see that slide)




mercoledì 4 luglio 2012
Cache & Concurrency
 def prep_response(dct, last_modified=None, etag=None, status=200):
     (...)
     resp.headers.add('Cache-Control',
                       'max-age=%s,must-revalidate' & 30)
     resp.expires = time.time() + 30
     if etag:
         resp.headers.add('ETag', etag)
     if last_modified:
         resp.headers.add('Last-Modified', date_to_str(last_modified))
     return resp



                          Cache-Control, a directive
                          for HTTP/1.1 clients (and
                               later) -RFC2616



mercoledì 4 luglio 2012
Cache & Concurrency
 def prep_response(dct, last_modified=None, etag=None, status=200):
     (...)
     resp.headers.add('Cache-Control',
                       'max-age=%s,must-revalidate' & 30)
     resp.expires = time.time() + 30
     if etag:
         resp.headers.add('ETag', etag)
     if last_modified:
         resp.headers.add('Last-Modified', date_to_str(last_modified))
     return resp



                          Expires, a directive for
                              HTTP/1.0 clients



mercoledì 4 luglio 2012
Cache & Concurrency
 def prep_response(dct, last_modified=None, etag=None, status=200):
     (...)
     resp.headers.add('Cache-Control',
                       'max-age=%s,must-revalidate' & 30)
     resp.expires = time.time() + 30
     if etag:
         resp.headers.add('ETag', etag)
     if last_modified:
         resp.headers.add('Last-Modified', date_to_str(last_modified))
     return resp


                          ETag. Notice that we don’t
                          compute it on the rendered
                          representation, this is by
                                    design.


mercoledì 4 luglio 2012
Cache & Concurrency
 def prep_response(dct, last_modified=None, etag=None, status=200):
     (...)
     resp.headers.add('Cache-Control',
                       'max-age=%s,must-revalidate' & 30)
     resp.expires = time.time() + 30
     if etag:
         resp.headers.add('ETag', etag)
     if last_modified:
         resp.headers.add('Last-Modified', date_to_str(last_modified))
     return resp



                           And finally, we add the
                          Last-Modified header tag.



mercoledì 4 luglio 2012
Cache & Concurrency
 def prep_response(dct, last_modified=None, etag=None, status=200):
     (...)
     resp.headers.add('Cache-Control',
                       'max-age=%s,must-revalidate' & 30)
     resp.expires = time.time() + 30
     if etag:
         resp.headers.add('ETag', etag)
     if last_modified:
         resp.headers.add('Last-Modified', date_to_str(last_modified))
     return resp



                          the response object is now
                           complete and ready to be
                            returned to the client



mercoledì 4 luglio 2012
that
                                                                 ’s o
                                                             long ne
                                                            acro ass
                                                                nym

                                      #5
                                    HATEOAS
                          “Hypertext As The Engine Of Application State”




mercoledì 4 luglio 2012
HATEOAS
                                in a Nutshell
                     •    clients interact entirely through hypermedia
                          provided dynamically by the server
                     •    clients need no prior knowledge about how
                          to interact with the server
                     •    clients access an application through a
                          single well known URL (the entry point)
                     •    All future actions the clients may take are
                          discovered within resource representations
                          returned from the server
mercoledì 4 luglio 2012
It’s all about Links
           resource representation includes links to related resources




mercoledì 4 luglio 2012
Collection
 {
                          Representation
  "links":[
     "<link rel='parent' title='home' href='http://api.example.com/' />",
     "<link rel='collection' title='contacts'
               href='http://api.example.com/Contacts' />",
     "<link rel='next' title='next page' 
               href='http://api.example.com/Contacts?page=2' />"
    ],
    "contacts":[
       {
          "updated":"Wed, 06 Jun 2012 14:19:53 UTC",
          "name":"Jon Doe",
          "age": 27,
          "etag":"7a9f477cde424cf93a7db20b69e05f7b680b7f08",
          "link":"<link rel='self' title='Contact'
                                                    every resource
               href='http://api.example.com/Contacts/
               4f46445fc88e201858000000' />", representation provides a
          "_id":"4f46445fc88e201858000000",       links section with
       },                                       navigational info for
     ]                                                  clients
 }
mercoledì 4 luglio 2012
Collection
 {
                          Representation
  "links":[
     "<link rel='parent' title='home' href='http://api.example.com/' />",
     "<link rel='collection' title='contacts'
               href='http://api.example.com/Contacts' />",
     "<link rel='next' title='next page' 
               href='http://api.example.com/Contacts?page=2' />"
    ],
    "contacts":[
       {
          "updated":"Wed, 06 Jun 2012 14:19:53 UTC",
          "name":"Jon Doe",
          "age": 27,
          "etag":"7a9f477cde424cf93a7db20b69e05f7b680b7f08",
          "link":"<link rel='self' title='Contact'
               href='http://api.example.com/Contacts/ attribute provides
                                              the rel
               4f46445fc88e201858000000' />",
                                             the relationship between the
          "_id":"4f46445fc88e201858000000",
       },                                     linked resource and the one
     ]                                           currently represented
 }
mercoledì 4 luglio 2012
Collection
 {
                          Representation
  "links":[
     "<link rel='parent' title='home' href='http://api.example.com/' />",
     "<link rel='collection' title='contacts'
               href='http://api.example.com/Contacts' />",
     "<link rel='next' title='next page' 
               href='http://api.example.com/Contacts?page=2' />"
    ],
    "contacts":[
       {
          "updated":"Wed, 06 Jun 2012 14:19:53 UTC",
          "name":"Jon Doe",
          "age": 27,
          "etag":"7a9f477cde424cf93a7db20b69e05f7b680b7f08",
          "link":"<link rel='self' title='Contact'
                                             the title attribute provides
               href='http://api.example.com/Contacts/
               4f46445fc88e201858000000' />", a tag (or description) for
          "_id":"4f46445fc88e201858000000", the linked resource. Could
       },                                     be used as a caption for a
     ]                                              client button.
 }
mercoledì 4 luglio 2012
Collection
 {
                          Representation
  "links":[
     "<link rel='parent' title='home' href='http://api.example.com/' />",
     "<link rel='collection' title='contacts'
               href='http://api.example.com/Contacts' />",
     "<link rel='next' title='next page' 
               href='http://api.example.com/Contacts?page=2' />"
    ],
    "contacts":[
       {
          "updated":"Wed, 06 Jun 2012 14:19:53 UTC",
          "name":"Jon Doe",
          "age": 27,
          "etag":"7a9f477cde424cf93a7db20b69e05f7b680b7f08",
          "link":"<link rel='self' title='Contact'
               href='http://api.example.com/Contacts/ attribute provides
                                              the href
               4f46445fc88e201858000000' />",
                                               and absolute path to the
          "_id":"4f46445fc88e201858000000",
       },                                      resource (the “permanent
     ]                                        identifier” per REST def.)
 }
mercoledì 4 luglio 2012
Collection
 {
                          Representation
  "links":[
     "<link rel='parent' title='home' href='http://api.example.com/' />",
     "<link rel='collection' title='contacts'    every resource listed
               href='http://api.example.com/Contacts' />",
                                              exposes its own link, which
     "<link rel='next' title='next page' 
                                               will allow the client to
               href='http://api.example.com/Contacts?page=2' />"
    ],                                        perform PATCH, DELETE etc.
    "contacts":[                                    on the resource
       {
          "updated":"Wed, 06 Jun 2012 14:19:53 UTC",
          "name":"Jon Doe",
          "age": 27,
          "etag":"7a9f477cde424cf93a7db20b69e05f7b680b7f08",
          "link":"<link rel='self' title='Contact'
               href='http://api.example.com/Contacts/
               4f46445fc88e201858000000' />",
          "_id":"4f46445fc88e201858000000",
       },
     ]
 }
mercoledì 4 luglio 2012
Collection
 {
                          Representation
  "links":[
                                                  while we are here,
     "<link rel='parent' title='home' href='http://api.example.com/' />",
     "<link rel='collection' title='contacts' notice how every resource
               href='http://api.example.com/Contacts' />", its own etag,
                                              also exposes
     "<link rel='next' title='next page'          last-modified date.
               href='http://api.example.com/Contacts?page=2' />"
    ],
    "contacts":[
       {
          "updated":"Wed, 06 Jun 2012 14:19:53 UTC",
          "name":"Jon Doe",
          "age": 27,
          "etag":"7a9f477cde424cf93a7db20b69e05f7b680b7f08",
          "link":"<link rel='self' title='Contact'
               href='http://api.example.com/Contacts/
               4f46445fc88e201858000000' />",
          "_id":"4f46445fc88e201858000000",
       },
     ]
 }
mercoledì 4 luglio 2012
HATEOAS
                          The API entry point (the homepage)

 @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

                                              the API homepage responds to
                                               GET requests and provides
                                                 links to its top level
                                                resources to the clients

mercoledì 4 luglio 2012
Developing RESTful Web APIs with Python, Flask and MongoDB
Developing RESTful Web APIs with Python, Flask and MongoDB
Developing RESTful Web APIs with Python, Flask and MongoDB
Developing RESTful Web APIs with Python, Flask and MongoDB
Developing RESTful Web APIs with Python, Flask and MongoDB
Developing RESTful Web APIs with Python, Flask and MongoDB
Developing RESTful Web APIs with Python, Flask and MongoDB
Developing RESTful Web APIs with Python, Flask and MongoDB
Developing RESTful Web APIs with Python, Flask and MongoDB
Developing RESTful Web APIs with Python, Flask and MongoDB
Developing RESTful Web APIs with Python, Flask and MongoDB
Developing RESTful Web APIs with Python, Flask and MongoDB
Developing RESTful Web APIs with Python, Flask and MongoDB
Developing RESTful Web APIs with Python, Flask and MongoDB
Developing RESTful Web APIs with Python, Flask and MongoDB

More Related Content

What's hot

Introduction to React JS
Introduction to React JSIntroduction to React JS
Introduction to React JS
Arno Lordkronos
 
Spring Boot and REST API
Spring Boot and REST APISpring Boot and REST API
Spring Boot and REST API
07.pallav
 
Spring mvc
Spring mvcSpring mvc
Spring mvc
Lhouceine OUHAMZA
 
Reactive Java Microservices with Spring Boot and JHipster - Spring I/O 2022
Reactive Java Microservices with Spring Boot and JHipster - Spring I/O 2022Reactive Java Microservices with Spring Boot and JHipster - Spring I/O 2022
Reactive Java Microservices with Spring Boot and JHipster - Spring I/O 2022
Matt Raible
 
Presentation Spring, Spring MVC
Presentation Spring, Spring MVCPresentation Spring, Spring MVC
Presentation Spring, Spring MVCNathaniel Richand
 
Express js
Express jsExpress js
Express js
Manav Prasad
 
Building a High-Performance Distributed Task Queue on MongoDB
Building a High-Performance Distributed Task Queue on MongoDBBuilding a High-Performance Distributed Task Queue on MongoDB
Building a High-Performance Distributed Task Queue on MongoDB
MongoDB
 
Defending against Java Deserialization Vulnerabilities
 Defending against Java Deserialization Vulnerabilities Defending against Java Deserialization Vulnerabilities
Defending against Java Deserialization Vulnerabilities
Luca Carettoni
 
API for Beginners
API for BeginnersAPI for Beginners
API for Beginners
Sébastien Saunier
 
[오픈소스컨설팅] 스카우터 사용자 가이드 2020
[오픈소스컨설팅] 스카우터 사용자 가이드 2020[오픈소스컨설팅] 스카우터 사용자 가이드 2020
[오픈소스컨설팅] 스카우터 사용자 가이드 2020
Ji-Woong Choi
 
주니어의 쿠버네티스 생태계에서 살아남기
주니어의 쿠버네티스 생태계에서 살아남기주니어의 쿠버네티스 생태계에서 살아남기
주니어의 쿠버네티스 생태계에서 살아남기
InfraEngineer
 
Ionic Framework
Ionic FrameworkIonic Framework
Ionic Framework
Thinh VoXuan
 
Introduction to Spring Framework
Introduction to Spring FrameworkIntroduction to Spring Framework
Introduction to Spring Framework
Serhat Can
 
당근마켓에서 IaC경험
당근마켓에서 IaC경험당근마켓에서 IaC경험
당근마켓에서 IaC경험
용진 조
 
Angular & RXJS: examples and use cases
Angular & RXJS: examples and use casesAngular & RXJS: examples and use cases
Angular & RXJS: examples and use cases
Fabio Biondi
 
Python RESTful webservices with Python: Flask and Django solutions
Python RESTful webservices with Python: Flask and Django solutionsPython RESTful webservices with Python: Flask and Django solutions
Python RESTful webservices with Python: Flask and Django solutions
Solution4Future
 
Introduction to Node js
Introduction to Node jsIntroduction to Node js
Introduction to Node js
Akshay Mathur
 
Introduction à React
Introduction à ReactIntroduction à React
Introduction à React
Thibault Martinez
 
Clean Architecture Applications in Python
Clean Architecture Applications in PythonClean Architecture Applications in Python
Clean Architecture Applications in Python
Subhash Bhushan
 
Content Management With Apache Jackrabbit
Content Management With Apache JackrabbitContent Management With Apache Jackrabbit
Content Management With Apache Jackrabbit
Jukka Zitting
 

What's hot (20)

Introduction to React JS
Introduction to React JSIntroduction to React JS
Introduction to React JS
 
Spring Boot and REST API
Spring Boot and REST APISpring Boot and REST API
Spring Boot and REST API
 
Spring mvc
Spring mvcSpring mvc
Spring mvc
 
Reactive Java Microservices with Spring Boot and JHipster - Spring I/O 2022
Reactive Java Microservices with Spring Boot and JHipster - Spring I/O 2022Reactive Java Microservices with Spring Boot and JHipster - Spring I/O 2022
Reactive Java Microservices with Spring Boot and JHipster - Spring I/O 2022
 
Presentation Spring, Spring MVC
Presentation Spring, Spring MVCPresentation Spring, Spring MVC
Presentation Spring, Spring MVC
 
Express js
Express jsExpress js
Express js
 
Building a High-Performance Distributed Task Queue on MongoDB
Building a High-Performance Distributed Task Queue on MongoDBBuilding a High-Performance Distributed Task Queue on MongoDB
Building a High-Performance Distributed Task Queue on MongoDB
 
Defending against Java Deserialization Vulnerabilities
 Defending against Java Deserialization Vulnerabilities Defending against Java Deserialization Vulnerabilities
Defending against Java Deserialization Vulnerabilities
 
API for Beginners
API for BeginnersAPI for Beginners
API for Beginners
 
[오픈소스컨설팅] 스카우터 사용자 가이드 2020
[오픈소스컨설팅] 스카우터 사용자 가이드 2020[오픈소스컨설팅] 스카우터 사용자 가이드 2020
[오픈소스컨설팅] 스카우터 사용자 가이드 2020
 
주니어의 쿠버네티스 생태계에서 살아남기
주니어의 쿠버네티스 생태계에서 살아남기주니어의 쿠버네티스 생태계에서 살아남기
주니어의 쿠버네티스 생태계에서 살아남기
 
Ionic Framework
Ionic FrameworkIonic Framework
Ionic Framework
 
Introduction to Spring Framework
Introduction to Spring FrameworkIntroduction to Spring Framework
Introduction to Spring Framework
 
당근마켓에서 IaC경험
당근마켓에서 IaC경험당근마켓에서 IaC경험
당근마켓에서 IaC경험
 
Angular & RXJS: examples and use cases
Angular & RXJS: examples and use casesAngular & RXJS: examples and use cases
Angular & RXJS: examples and use cases
 
Python RESTful webservices with Python: Flask and Django solutions
Python RESTful webservices with Python: Flask and Django solutionsPython RESTful webservices with Python: Flask and Django solutions
Python RESTful webservices with Python: Flask and Django solutions
 
Introduction to Node js
Introduction to Node jsIntroduction to Node js
Introduction to Node js
 
Introduction à React
Introduction à ReactIntroduction à React
Introduction à React
 
Clean Architecture Applications in Python
Clean Architecture Applications in PythonClean Architecture Applications in Python
Clean Architecture Applications in Python
 
Content Management With Apache Jackrabbit
Content Management With Apache JackrabbitContent Management With Apache Jackrabbit
Content Management With Apache Jackrabbit
 

Viewers also liked

Building Automated REST APIs with Python
Building Automated REST APIs with PythonBuilding Automated REST APIs with Python
Building Automated REST APIs with Python
Jeff Knupp
 
Environmental Impact Assessment (EIA) report on Rampal 1320MW coal-based powe...
Environmental Impact Assessment (EIA) report on Rampal 1320MW coal-based powe...Environmental Impact Assessment (EIA) report on Rampal 1320MW coal-based powe...
Environmental Impact Assessment (EIA) report on Rampal 1320MW coal-based powe...
Probir Bidhan
 
Allah names , meanings and benefits
Allah names , meanings and benefitsAllah names , meanings and benefits
Allah names , meanings and benefits
Yasir Kamran
 
BUSINESS QUIZ -Round 1
 BUSINESS QUIZ -Round 1 BUSINESS QUIZ -Round 1
BUSINESS QUIZ -Round 1
pradeep acharya
 
DMBOK 2.0 and other frameworks including TOGAF & COBIT - keynote from DAMA Au...
DMBOK 2.0 and other frameworks including TOGAF & COBIT - keynote from DAMA Au...DMBOK 2.0 and other frameworks including TOGAF & COBIT - keynote from DAMA Au...
DMBOK 2.0 and other frameworks including TOGAF & COBIT - keynote from DAMA Au...
Christopher Bradley
 
Drug scenario in the philippines
Drug scenario in the philippinesDrug scenario in the philippines
Drug scenario in the philippines
Al-lyn Vocal
 
Operating Systems - File Management
Operating Systems -  File ManagementOperating Systems -  File Management
Operating Systems - File Management
Damian T. Gordon
 
Mfl Activities
Mfl ActivitiesMfl Activities
Mfl Activities
Dannielle Warren
 
5000 Sat Words With Definitions
5000 Sat Words With Definitions5000 Sat Words With Definitions
5000 Sat Words With Definitions
Brent Daigle, Ph.D.
 
61 Beautiful & Inspirational Timeline Cover on Facebook
61 Beautiful & Inspirational Timeline Cover on Facebook61 Beautiful & Inspirational Timeline Cover on Facebook
61 Beautiful & Inspirational Timeline Cover on Facebook
Consonaute
 
Engaging Learners with Technology
Engaging Learners with TechnologyEngaging Learners with Technology
Engaging Learners with Technology
Dean Shareski
 
Fintech and Transformation of the Financial Services Industry
Fintech and Transformation of the Financial Services IndustryFintech and Transformation of the Financial Services Industry
Fintech and Transformation of the Financial Services Industry
Robin Teigland
 
Mri brain anatomy Dr Muhammad Bin Zulfiqar
Mri brain anatomy Dr Muhammad Bin ZulfiqarMri brain anatomy Dr Muhammad Bin Zulfiqar
Mri brain anatomy Dr Muhammad Bin Zulfiqar
Dr. Muhammad Bin Zulfiqar
 
4. heredity and evolution
4. heredity and evolution4. heredity and evolution
4. heredity and evolution
Abhay Goyal
 
150 Tips Tricks and Ideas for Personal Branding
150 Tips Tricks and Ideas for Personal Branding150 Tips Tricks and Ideas for Personal Branding
150 Tips Tricks and Ideas for Personal Branding
Kyle Lacy
 
Thai tech startup ecosystem report 2017
Thai tech startup ecosystem report 2017Thai tech startup ecosystem report 2017
Thai tech startup ecosystem report 2017
Techsauce Media
 
What I Carry: 10 Tools for Success
What I Carry: 10 Tools for SuccessWhat I Carry: 10 Tools for Success
What I Carry: 10 Tools for Success
Jonathon Colman
 
Deep Learning through Examples
Deep Learning through ExamplesDeep Learning through Examples
Deep Learning through Examples
Sri Ambati
 
Circuit Analysis – DC Circuits
Circuit Analysis – DC CircuitsCircuit Analysis – DC Circuits
Circuit Analysis – DC Circuits
Vesa Linja-aho
 
Understanding text-structure-powerpoint
Understanding text-structure-powerpointUnderstanding text-structure-powerpoint
Understanding text-structure-powerpoint
aelowans
 

Viewers also liked (20)

Building Automated REST APIs with Python
Building Automated REST APIs with PythonBuilding Automated REST APIs with Python
Building Automated REST APIs with Python
 
Environmental Impact Assessment (EIA) report on Rampal 1320MW coal-based powe...
Environmental Impact Assessment (EIA) report on Rampal 1320MW coal-based powe...Environmental Impact Assessment (EIA) report on Rampal 1320MW coal-based powe...
Environmental Impact Assessment (EIA) report on Rampal 1320MW coal-based powe...
 
Allah names , meanings and benefits
Allah names , meanings and benefitsAllah names , meanings and benefits
Allah names , meanings and benefits
 
BUSINESS QUIZ -Round 1
 BUSINESS QUIZ -Round 1 BUSINESS QUIZ -Round 1
BUSINESS QUIZ -Round 1
 
DMBOK 2.0 and other frameworks including TOGAF & COBIT - keynote from DAMA Au...
DMBOK 2.0 and other frameworks including TOGAF & COBIT - keynote from DAMA Au...DMBOK 2.0 and other frameworks including TOGAF & COBIT - keynote from DAMA Au...
DMBOK 2.0 and other frameworks including TOGAF & COBIT - keynote from DAMA Au...
 
Drug scenario in the philippines
Drug scenario in the philippinesDrug scenario in the philippines
Drug scenario in the philippines
 
Operating Systems - File Management
Operating Systems -  File ManagementOperating Systems -  File Management
Operating Systems - File Management
 
Mfl Activities
Mfl ActivitiesMfl Activities
Mfl Activities
 
5000 Sat Words With Definitions
5000 Sat Words With Definitions5000 Sat Words With Definitions
5000 Sat Words With Definitions
 
61 Beautiful & Inspirational Timeline Cover on Facebook
61 Beautiful & Inspirational Timeline Cover on Facebook61 Beautiful & Inspirational Timeline Cover on Facebook
61 Beautiful & Inspirational Timeline Cover on Facebook
 
Engaging Learners with Technology
Engaging Learners with TechnologyEngaging Learners with Technology
Engaging Learners with Technology
 
Fintech and Transformation of the Financial Services Industry
Fintech and Transformation of the Financial Services IndustryFintech and Transformation of the Financial Services Industry
Fintech and Transformation of the Financial Services Industry
 
Mri brain anatomy Dr Muhammad Bin Zulfiqar
Mri brain anatomy Dr Muhammad Bin ZulfiqarMri brain anatomy Dr Muhammad Bin Zulfiqar
Mri brain anatomy Dr Muhammad Bin Zulfiqar
 
4. heredity and evolution
4. heredity and evolution4. heredity and evolution
4. heredity and evolution
 
150 Tips Tricks and Ideas for Personal Branding
150 Tips Tricks and Ideas for Personal Branding150 Tips Tricks and Ideas for Personal Branding
150 Tips Tricks and Ideas for Personal Branding
 
Thai tech startup ecosystem report 2017
Thai tech startup ecosystem report 2017Thai tech startup ecosystem report 2017
Thai tech startup ecosystem report 2017
 
What I Carry: 10 Tools for Success
What I Carry: 10 Tools for SuccessWhat I Carry: 10 Tools for Success
What I Carry: 10 Tools for Success
 
Deep Learning through Examples
Deep Learning through ExamplesDeep Learning through Examples
Deep Learning through Examples
 
Circuit Analysis – DC Circuits
Circuit Analysis – DC CircuitsCircuit Analysis – DC Circuits
Circuit Analysis – DC Circuits
 
Understanding text-structure-powerpoint
Understanding text-structure-powerpointUnderstanding text-structure-powerpoint
Understanding text-structure-powerpoint
 

Similar to Developing RESTful Web APIs with Python, Flask and MongoDB

Games for the Masses - Wie DevOps die Entwicklung von Architektur verändert (...
Games for the Masses - Wie DevOps die Entwicklung von Architektur verändert (...Games for the Masses - Wie DevOps die Entwicklung von Architektur verändert (...
Games for the Masses - Wie DevOps die Entwicklung von Architektur verändert (...
Wooga
 
Web Page Test - Beyond the Basics
Web Page Test - Beyond the BasicsWeb Page Test - Beyond the Basics
Web Page Test - Beyond the Basics
Andy Davies
 
Improving Front End Performance
Improving Front End PerformanceImproving Front End Performance
Improving Front End Performance
Joseph Scott
 
Fluentd loves MongoDB, at MongoDB SV User Group, July 17, 2012
Fluentd loves MongoDB, at MongoDB SV User Group, July 17, 2012Fluentd loves MongoDB, at MongoDB SV User Group, July 17, 2012
Fluentd loves MongoDB, at MongoDB SV User Group, July 17, 2012
Treasure Data, Inc.
 
Multilingual solutions florian loretan
Multilingual solutions florian loretanMultilingual solutions florian loretan
Multilingual solutions florian loretan
drupalconf
 
2012 HK F/OSS Community News & Events
2012 HK F/OSS Community News & Events2012 HK F/OSS Community News & Events
2012 HK F/OSS Community News & Events
Sammy Fung
 
Análisis de ataques APT
Análisis de ataques APT Análisis de ataques APT
Análisis de ataques APT
linenoise
 
The Future of Apache CloudStack (Not So Cloudy) (Collab 2012)
The Future of Apache CloudStack (Not So Cloudy) (Collab 2012)The Future of Apache CloudStack (Not So Cloudy) (Collab 2012)
The Future of Apache CloudStack (Not So Cloudy) (Collab 2012)
Chiradeep Vittal
 
Writing jQuery that doesn't suck - London jQuery
Writing jQuery that doesn't suck - London jQueryWriting jQuery that doesn't suck - London jQuery
Writing jQuery that doesn't suck - London jQuery
Ross Bruniges
 
Games for the Masses (QCon London 2012)
Games for the Masses (QCon London 2012)Games for the Masses (QCon London 2012)
Games for the Masses (QCon London 2012)
Wooga
 
Space proposal
Space proposalSpace proposal
Space proposal
Evgeny Bogdanov
 
A Dive Into Clojure
A Dive Into ClojureA Dive Into Clojure
A Dive Into Clojure
Carlo Sciolla
 
Symfony2 y MongoDB - deSymfony 2012
Symfony2 y MongoDB - deSymfony 2012Symfony2 y MongoDB - deSymfony 2012
Symfony2 y MongoDB - deSymfony 2012
Pablo Godel
 
Learning sparql 2012 12
Learning sparql 2012 12Learning sparql 2012 12
Learning sparql 2012 12
Jerven Bolleman
 
Como escalar aplicações PHP
Como escalar aplicações PHPComo escalar aplicações PHP
Como escalar aplicações PHP
Augusto Pascutti
 
EDF2012 Chris Taggart - How the biggest Open Database of Companies was built
EDF2012   Chris Taggart - How the biggest Open Database of Companies was builtEDF2012   Chris Taggart - How the biggest Open Database of Companies was built
EDF2012 Chris Taggart - How the biggest Open Database of Companies was built
European Data Forum
 
Drupalcamp Tallinn - Drupal 8
Drupalcamp Tallinn - Drupal 8Drupalcamp Tallinn - Drupal 8
Drupalcamp Tallinn - Drupal 8
drupalcampest
 
Beginning Android Development
Beginning Android DevelopmentBeginning Android Development
Beginning Android Development
José Ferreiro
 
Jenkins Evolutions
Jenkins EvolutionsJenkins Evolutions
Jenkins Evolutions
Toomas Römer
 
You rang, M’LOD? Google Refine in the world of LOD
You rang, M’LOD? Google Refine in the world of LODYou rang, M’LOD? Google Refine in the world of LOD
You rang, M’LOD? Google Refine in the world of LOD
Mateja Verlic
 

Similar to Developing RESTful Web APIs with Python, Flask and MongoDB (20)

Games for the Masses - Wie DevOps die Entwicklung von Architektur verändert (...
Games for the Masses - Wie DevOps die Entwicklung von Architektur verändert (...Games for the Masses - Wie DevOps die Entwicklung von Architektur verändert (...
Games for the Masses - Wie DevOps die Entwicklung von Architektur verändert (...
 
Web Page Test - Beyond the Basics
Web Page Test - Beyond the BasicsWeb Page Test - Beyond the Basics
Web Page Test - Beyond the Basics
 
Improving Front End Performance
Improving Front End PerformanceImproving Front End Performance
Improving Front End Performance
 
Fluentd loves MongoDB, at MongoDB SV User Group, July 17, 2012
Fluentd loves MongoDB, at MongoDB SV User Group, July 17, 2012Fluentd loves MongoDB, at MongoDB SV User Group, July 17, 2012
Fluentd loves MongoDB, at MongoDB SV User Group, July 17, 2012
 
Multilingual solutions florian loretan
Multilingual solutions florian loretanMultilingual solutions florian loretan
Multilingual solutions florian loretan
 
2012 HK F/OSS Community News & Events
2012 HK F/OSS Community News & Events2012 HK F/OSS Community News & Events
2012 HK F/OSS Community News & Events
 
Análisis de ataques APT
Análisis de ataques APT Análisis de ataques APT
Análisis de ataques APT
 
The Future of Apache CloudStack (Not So Cloudy) (Collab 2012)
The Future of Apache CloudStack (Not So Cloudy) (Collab 2012)The Future of Apache CloudStack (Not So Cloudy) (Collab 2012)
The Future of Apache CloudStack (Not So Cloudy) (Collab 2012)
 
Writing jQuery that doesn't suck - London jQuery
Writing jQuery that doesn't suck - London jQueryWriting jQuery that doesn't suck - London jQuery
Writing jQuery that doesn't suck - London jQuery
 
Games for the Masses (QCon London 2012)
Games for the Masses (QCon London 2012)Games for the Masses (QCon London 2012)
Games for the Masses (QCon London 2012)
 
Space proposal
Space proposalSpace proposal
Space proposal
 
A Dive Into Clojure
A Dive Into ClojureA Dive Into Clojure
A Dive Into Clojure
 
Symfony2 y MongoDB - deSymfony 2012
Symfony2 y MongoDB - deSymfony 2012Symfony2 y MongoDB - deSymfony 2012
Symfony2 y MongoDB - deSymfony 2012
 
Learning sparql 2012 12
Learning sparql 2012 12Learning sparql 2012 12
Learning sparql 2012 12
 
Como escalar aplicações PHP
Como escalar aplicações PHPComo escalar aplicações PHP
Como escalar aplicações PHP
 
EDF2012 Chris Taggart - How the biggest Open Database of Companies was built
EDF2012   Chris Taggart - How the biggest Open Database of Companies was builtEDF2012   Chris Taggart - How the biggest Open Database of Companies was built
EDF2012 Chris Taggart - How the biggest Open Database of Companies was built
 
Drupalcamp Tallinn - Drupal 8
Drupalcamp Tallinn - Drupal 8Drupalcamp Tallinn - Drupal 8
Drupalcamp Tallinn - Drupal 8
 
Beginning Android Development
Beginning Android DevelopmentBeginning Android Development
Beginning Android Development
 
Jenkins Evolutions
Jenkins EvolutionsJenkins Evolutions
Jenkins Evolutions
 
You rang, M’LOD? Google Refine in the world of LOD
You rang, M’LOD? Google Refine in the world of LODYou rang, M’LOD? Google Refine in the world of LOD
You rang, M’LOD? Google Refine in the world of LOD
 

More from Nicola Iarocci

Eve - REST API for Humans™
Eve - REST API for Humans™Eve - REST API for Humans™
Eve - REST API for Humans™
Nicola Iarocci
 
CoderDojo Romagna
CoderDojo RomagnaCoderDojo Romagna
CoderDojo Romagna
Nicola Iarocci
 
We Are All Remote Workers
We Are All Remote WorkersWe Are All Remote Workers
We Are All Remote Workers
Nicola Iarocci
 
RESTful Web API and MongoDB go for a pic nic
RESTful Web API and MongoDB go for a pic nicRESTful Web API and MongoDB go for a pic nic
RESTful Web API and MongoDB go for a pic nic
Nicola Iarocci
 
Online / Offline
Online / OfflineOnline / Offline
Online / Offline
Nicola Iarocci
 
Personal Branding per Studenti (e non solo)
Personal Branding per Studenti (e non solo)Personal Branding per Studenti (e non solo)
Personal Branding per Studenti (e non solo)
Nicola Iarocci
 
Identità Reale, Virtuale e Digitale
Identità Reale, Virtuale e DigitaleIdentità Reale, Virtuale e Digitale
Identità Reale, Virtuale e Digitale
Nicola Iarocci
 
Fuga dalla Comfort Zone
Fuga dalla Comfort ZoneFuga dalla Comfort Zone
Fuga dalla Comfort Zone
Nicola Iarocci
 
Quattro passi tra le nuvole (e non scordate il paracadute)
Quattro passi tra le nuvole (e non scordate il paracadute)Quattro passi tra le nuvole (e non scordate il paracadute)
Quattro passi tra le nuvole (e non scordate il paracadute)
Nicola Iarocci
 

More from Nicola Iarocci (9)

Eve - REST API for Humans™
Eve - REST API for Humans™Eve - REST API for Humans™
Eve - REST API for Humans™
 
CoderDojo Romagna
CoderDojo RomagnaCoderDojo Romagna
CoderDojo Romagna
 
We Are All Remote Workers
We Are All Remote WorkersWe Are All Remote Workers
We Are All Remote Workers
 
RESTful Web API and MongoDB go for a pic nic
RESTful Web API and MongoDB go for a pic nicRESTful Web API and MongoDB go for a pic nic
RESTful Web API and MongoDB go for a pic nic
 
Online / Offline
Online / OfflineOnline / Offline
Online / Offline
 
Personal Branding per Studenti (e non solo)
Personal Branding per Studenti (e non solo)Personal Branding per Studenti (e non solo)
Personal Branding per Studenti (e non solo)
 
Identità Reale, Virtuale e Digitale
Identità Reale, Virtuale e DigitaleIdentità Reale, Virtuale e Digitale
Identità Reale, Virtuale e Digitale
 
Fuga dalla Comfort Zone
Fuga dalla Comfort ZoneFuga dalla Comfort Zone
Fuga dalla Comfort Zone
 
Quattro passi tra le nuvole (e non scordate il paracadute)
Quattro passi tra le nuvole (e non scordate il paracadute)Quattro passi tra le nuvole (e non scordate il paracadute)
Quattro passi tra le nuvole (e non scordate il paracadute)
 

Recently uploaded

GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)
Javier Junquera
 
What is an RPA CoE? Session 1 – CoE Vision
What is an RPA CoE?  Session 1 – CoE VisionWhat is an RPA CoE?  Session 1 – CoE Vision
What is an RPA CoE? Session 1 – CoE Vision
DianaGray10
 
Choosing The Best AWS Service For Your Website + API.pptx
Choosing The Best AWS Service For Your Website + API.pptxChoosing The Best AWS Service For Your Website + API.pptx
Choosing The Best AWS Service For Your Website + API.pptx
Brandon Minnick, MBA
 
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge GraphGraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
Neo4j
 
Apps Break Data
Apps Break DataApps Break Data
Apps Break Data
Ivo Velitchkov
 
“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...
“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...
“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...
Edge AI and Vision Alliance
 
The Microsoft 365 Migration Tutorial For Beginner.pptx
The Microsoft 365 Migration Tutorial For Beginner.pptxThe Microsoft 365 Migration Tutorial For Beginner.pptx
The Microsoft 365 Migration Tutorial For Beginner.pptx
operationspcvita
 
June Patch Tuesday
June Patch TuesdayJune Patch Tuesday
June Patch Tuesday
Ivanti
 
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
Jason Yip
 
Leveraging the Graph for Clinical Trials and Standards
Leveraging the Graph for Clinical Trials and StandardsLeveraging the Graph for Clinical Trials and Standards
Leveraging the Graph for Clinical Trials and Standards
Neo4j
 
Crafting Excellence: A Comprehensive Guide to iOS Mobile App Development Serv...
Crafting Excellence: A Comprehensive Guide to iOS Mobile App Development Serv...Crafting Excellence: A Comprehensive Guide to iOS Mobile App Development Serv...
Crafting Excellence: A Comprehensive Guide to iOS Mobile App Development Serv...
Pitangent Analytics & Technology Solutions Pvt. Ltd
 
AppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSFAppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSF
Ajin Abraham
 
9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...
9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...
9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...
saastr
 
Energy Efficient Video Encoding for Cloud and Edge Computing Instances
Energy Efficient Video Encoding for Cloud and Edge Computing InstancesEnergy Efficient Video Encoding for Cloud and Edge Computing Instances
Energy Efficient Video Encoding for Cloud and Edge Computing Instances
Alpen-Adria-Universität
 
zkStudyClub - LatticeFold: A Lattice-based Folding Scheme and its Application...
zkStudyClub - LatticeFold: A Lattice-based Folding Scheme and its Application...zkStudyClub - LatticeFold: A Lattice-based Folding Scheme and its Application...
zkStudyClub - LatticeFold: A Lattice-based Folding Scheme and its Application...
Alex Pruden
 
HCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAUHCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAU
panagenda
 
Harnessing the Power of NLP and Knowledge Graphs for Opioid Research
Harnessing the Power of NLP and Knowledge Graphs for Opioid ResearchHarnessing the Power of NLP and Knowledge Graphs for Opioid Research
Harnessing the Power of NLP and Knowledge Graphs for Opioid Research
Neo4j
 
Programming Foundation Models with DSPy - Meetup Slides
Programming Foundation Models with DSPy - Meetup SlidesProgramming Foundation Models with DSPy - Meetup Slides
Programming Foundation Models with DSPy - Meetup Slides
Zilliz
 
Skybuffer SAM4U tool for SAP license adoption
Skybuffer SAM4U tool for SAP license adoptionSkybuffer SAM4U tool for SAP license adoption
Skybuffer SAM4U tool for SAP license adoption
Tatiana Kojar
 
Dandelion Hashtable: beyond billion requests per second on a commodity server
Dandelion Hashtable: beyond billion requests per second on a commodity serverDandelion Hashtable: beyond billion requests per second on a commodity server
Dandelion Hashtable: beyond billion requests per second on a commodity server
Antonios Katsarakis
 

Recently uploaded (20)

GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)
 
What is an RPA CoE? Session 1 – CoE Vision
What is an RPA CoE?  Session 1 – CoE VisionWhat is an RPA CoE?  Session 1 – CoE Vision
What is an RPA CoE? Session 1 – CoE Vision
 
Choosing The Best AWS Service For Your Website + API.pptx
Choosing The Best AWS Service For Your Website + API.pptxChoosing The Best AWS Service For Your Website + API.pptx
Choosing The Best AWS Service For Your Website + API.pptx
 
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge GraphGraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
 
Apps Break Data
Apps Break DataApps Break Data
Apps Break Data
 
“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...
“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...
“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...
 
The Microsoft 365 Migration Tutorial For Beginner.pptx
The Microsoft 365 Migration Tutorial For Beginner.pptxThe Microsoft 365 Migration Tutorial For Beginner.pptx
The Microsoft 365 Migration Tutorial For Beginner.pptx
 
June Patch Tuesday
June Patch TuesdayJune Patch Tuesday
June Patch Tuesday
 
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
 
Leveraging the Graph for Clinical Trials and Standards
Leveraging the Graph for Clinical Trials and StandardsLeveraging the Graph for Clinical Trials and Standards
Leveraging the Graph for Clinical Trials and Standards
 
Crafting Excellence: A Comprehensive Guide to iOS Mobile App Development Serv...
Crafting Excellence: A Comprehensive Guide to iOS Mobile App Development Serv...Crafting Excellence: A Comprehensive Guide to iOS Mobile App Development Serv...
Crafting Excellence: A Comprehensive Guide to iOS Mobile App Development Serv...
 
AppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSFAppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSF
 
9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...
9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...
9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...
 
Energy Efficient Video Encoding for Cloud and Edge Computing Instances
Energy Efficient Video Encoding for Cloud and Edge Computing InstancesEnergy Efficient Video Encoding for Cloud and Edge Computing Instances
Energy Efficient Video Encoding for Cloud and Edge Computing Instances
 
zkStudyClub - LatticeFold: A Lattice-based Folding Scheme and its Application...
zkStudyClub - LatticeFold: A Lattice-based Folding Scheme and its Application...zkStudyClub - LatticeFold: A Lattice-based Folding Scheme and its Application...
zkStudyClub - LatticeFold: A Lattice-based Folding Scheme and its Application...
 
HCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAUHCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAU
 
Harnessing the Power of NLP and Knowledge Graphs for Opioid Research
Harnessing the Power of NLP and Knowledge Graphs for Opioid ResearchHarnessing the Power of NLP and Knowledge Graphs for Opioid Research
Harnessing the Power of NLP and Knowledge Graphs for Opioid Research
 
Programming Foundation Models with DSPy - Meetup Slides
Programming Foundation Models with DSPy - Meetup SlidesProgramming Foundation Models with DSPy - Meetup Slides
Programming Foundation Models with DSPy - Meetup Slides
 
Skybuffer SAM4U tool for SAP license adoption
Skybuffer SAM4U tool for SAP license adoptionSkybuffer SAM4U tool for SAP license adoption
Skybuffer SAM4U tool for SAP license adoption
 
Dandelion Hashtable: beyond billion requests per second on a commodity server
Dandelion Hashtable: beyond billion requests per second on a commodity serverDandelion Hashtable: beyond billion requests per second on a commodity server
Dandelion Hashtable: beyond billion requests per second on a commodity server
 

Developing RESTful Web APIs with Python, Flask and MongoDB

  • 1. RESTful Web API With Python, Flask and Mongo Nicola Iarocci mercoledì 4 luglio 2012
  • 5. I’m a .NET guy 20 years in the Microsoft ecosystem Scary, Yes. mercoledì 4 luglio 2012
  • 7. Still with me? Great. mercoledì 4 luglio 2012
  • 8. gestionaleamica.com invoicing & accounting mercoledì 4 luglio 2012
  • 9. Your Typical Old School Desktop App... ... now going web & mobile mercoledì 4 luglio 2012
  • 10. Enter Python Flask and Mongo mercoledì 4 luglio 2012
  • 11. REST So What Is REST All About? mercoledì 4 luglio 2012
  • 12. REST is not a standard mercoledì 4 luglio 2012
  • 13. REST is not a protocol mercoledì 4 luglio 2012
  • 14. REST is an architectural style for networked applications mercoledì 4 luglio 2012
  • 15. REST defines a set of simple principles loosely followed by most API implementations mercoledì 4 luglio 2012
  • 16. #1 resource the source of a specific information mercoledì 4 luglio 2012
  • 17. A web page is not a resource rather, the representation of a resource mercoledì 4 luglio 2012
  • 18. #2 global permanent identifier every resource is uniquely identified (think a HTTP URI) mercoledì 4 luglio 2012
  • 19. #3 standard interface used to exchange representations of resources (think the HTTP protocol) mercoledì 4 luglio 2012
  • 20. #4 set of constraints separation of concerns, stateless, cacheability, layered system, uniform interface... we’ ll get to the se later mercoledì 4 luglio 2012
  • 21. The World Wide Web is built on REST and it is meant to be consumed by humans mercoledì 4 luglio 2012
  • 22. RESTful Web APIs are built on REST and are meant to be consumed by machines mercoledì 4 luglio 2012
  • 23. Beginners Reading How I Explained REST to My Wife by Ryan Tomayko http://tomayko.com/writings/rest-to-my-wife mercoledì 4 luglio 2012
  • 24. The Real Stuff Representational State Transfer (REST) by Roy Thomas Fielding http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm mercoledì 4 luglio 2012
  • 25. RESTful Web API Nuts & Bolts mercoledì 4 luglio 2012
  • 26. The Tools or why I picked Flask and Mongo mercoledì 4 luglio 2012
  • 27. Flask web development, one drop at a time mercoledì 4 luglio 2012
  • 28. Simple & Elegant from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run(debug=True) mercoledì 4 luglio 2012
  • 29. RESTful request dispacthing @app.route('/user/<username>') def show_user_profile(username): return 'User %s' % username @app.route('/post/<int:post_id>') def show_post(post_id): return 'Post %d' % post_id mercoledì 4 luglio 2012
  • 30. Built-in development server & debugger from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run(debug=True) mercoledì 4 luglio 2012
  • 31. Explicit & passable application objects from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run(debug=True) mercoledì 4 luglio 2012
  • 32. 100% WSGI Compliant e.g., response objects are WSGI applications themselves from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run(debug=True) mercoledì 4 luglio 2012
  • 33. Minimal Footprint Only 800 lines of source code mercoledì 4 luglio 2012
  • 34. Heavily Tested 1500 lines of tests mercoledì 4 luglio 2012
  • 35. Unittesting Support one day I will make good use of it mercoledì 4 luglio 2012
  • 36. Bring Your Own Batteries we aim for flexibility mercoledì 4 luglio 2012
  • 37. No built-in ORM we want to be as close to the bare metal as possible mercoledì 4 luglio 2012
  • 38. No form validation we don’t need no freaking form validation mercoledì 4 luglio 2012
  • 39. No data validation Python offers great tools to manipulate JSON, we can tinker something ourselves mercoledì 4 luglio 2012
  • 40. Layered API built on Werkzeug, Jinja2, WSGI mercoledì 4 luglio 2012
  • 41. Built by the Pros The Pocoo Team did Werkzeug, Jinja2, Sphinx, Pygments, and much more mercoledì 4 luglio 2012
  • 42. Excellent Documentation Over 200 pages, lots of examples and howtos mercoledì 4 luglio 2012
  • 43. Active Community Widely adopted, extensions for everything mercoledì 4 luglio 2012
  • 44. “Flask is a sharp tool for building sharp services” Kenneth Reitz, DjangoCon 2012 mercoledì 4 luglio 2012
  • 45. MongoDB scalable, high-performance, open source NoSQL database mercoledì 4 luglio 2012
  • 46. Similarity with RDBMS made NoSQL easy to grasp (even for a dumbhead like me) mercoledì 4 luglio 2012
  • 47. Terminology RDBMS Mongo Database Database Table Collection Rows(s) JSON Document Index Index Join Embedding & Linking mercoledì 4 luglio 2012
  • 48. JSON-style data store true selling point for me mercoledì 4 luglio 2012
  • 49. JSON & RESTful API GET Client Mongo JSON JSON accepted media type (BSON) maybe we can push directly to client? mercoledì 4 luglio 2012
  • 50. JSON & RESTful API GET Client API Mongo JSON JSON/dict JSON accepted media type maps to python dict (BSON) almost. mercoledì 4 luglio 2012
  • 51. JSON & RESTful API POST Client API Mongo JSON JSON/dict JSON maps to python dict objects (BSON) (validation layer) also works when posting (adding) items to the database mercoledì 4 luglio 2012
  • 52. What about Queries? Queries in MongoDB are represented as JSON-style objects // select * from things where x=3 and y="foo" db.things.find({x: 3, y: "foo”}); mercoledì 4 luglio 2012
  • 53. JSON & RESTful API FILTERING & SORTING ?where={x: 3, y: "foo”} Client API Mongo (very) thin native parsing JSON Mongo & validation (BSON) query syntax layer mercoledì 4 luglio 2012
  • 54. JSON all along the pipeline mapping to and from the database feels more natural mercoledì 4 luglio 2012
  • 55. Schema-less dynamic objects allow for a painless evolution of our schema (because yes, a schema exists at any point in time) mercoledì 4 luglio 2012
  • 56. ORM Where we’re going we don’t need ORMs. mercoledì 4 luglio 2012
  • 57. PyMongo official Python driver all we need to interact with the database mercoledì 4 luglio 2012
  • 58. Also in MongoDB • setup is a breeze • lightweight • fast inserts, updates and queries • excellent documentation • great support by 10gen • great community mercoledì 4 luglio 2012
  • 59. A Great Introduction To MongoDB The Little MongoDB Book by Karl Seguin http://openmymind.net/2011/3/28/The-Little-MongoDB-Book/ mercoledì 4 luglio 2012
  • 60. Shameless Plug Il Piccolo Libro di MongoDB by Karl Seguin, traduzione di Nicola Iarocci http://nicolaiarocci.com/il-piccolo-libro-di-mongodb-edizione-italiana/ mercoledì 4 luglio 2012
  • 61. MongoDB Interactive Tutorial http://tutorial.mongly.com/tutorial/index mercoledì 4 luglio 2012
  • 62. RESTful Web APIs are really just collection of resources accesible through to a uniform interface mercoledì 4 luglio 2012
  • 63. #1 each resource is identified by a persistent identifier We need to properly implement Request Dispatching mercoledì 4 luglio 2012
  • 64. Collections API’s entry point + plural nouns http://api.example.com/v1/contacts mercoledì 4 luglio 2012
  • 65. Collections Flask URL dispatcher allows for variables @app.route('/<collection>') def collection(collection): if collection in DOMAIN.keys(): (...) abort(404) api.example.com/contacts api.example.com/invoices etc. mercoledì 4 luglio 2012
  • 66. Collections Flask URL dispatcher allows for variables @app.route('/<collection>') def collection(collection): if collection in DOMAIN.keys(): (...) abort(404) validation dictonary mercoledì 4 luglio 2012
  • 67. Collections Flask URL dispatcher allows for variables @app.route('/<collection>') def collection(collection): if collection in DOMAIN.keys(): (...) abort(404) we don’t know this collection, return a 404 mercoledì 4 luglio 2012
  • 68. RegEx by design, collection URLs are plural nouns @app.route('/<regex("[w]*[Ss]"):collection>') def collection(collection): if collection in DOMAIN.keys(): (...) abort(404) regular expressions can be used to better narrow a variable part URL. However... mercoledì 4 luglio 2012
  • 69. RegEx We need to build our own Custom Converter 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 subclass BaseConverter and pass the new converter to the url_map mercoledì 4 luglio 2012
  • 70. Document Documents are identified by ObjectID http://api.example.com/v1/contacts/4f46445fc88e201858000000 And eventually by an alternative lookup value http://api.example.com/v1/contacts/CUST12345 mercoledì 4 luglio 2012
  • 71. Document @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): (...) URL dispatcher handles multiple variables http://api.example.com/v1/contacts/CUST12345 mercoledì 4 luglio 2012
  • 72. Document @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): (...) and of course it also handles multiple RegEx variables http://api.example.com/v1/contacts/4f46445fc88e201858000000 mercoledì 4 luglio 2012
  • 73. Document @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): (...) Different URLs can be dispatched to the same function just by piling up @app.route decorators. mercoledì 4 luglio 2012
  • 74. #2 representation of resources via media types JSON, XML or any other valid internet media type dep ends on the reque st and not the identifier mercoledì 4 luglio 2012
  • 75. Accepted Media Types mapping supported media types to corresponding renderer functions mime_types = {'json_renderer': ('application/json',), 'xml_renderer': ('application/xml', 'text/xml', 'application/x-xml',)} JSON rendering function mercoledì 4 luglio 2012
  • 76. Accepted Media Types mapping supported media types to corresponding renderer functions mime_types = {'json_renderer': ('application/json',), 'xml_renderer': ('application/xml', 'text/xml', 'application/x-xml',)} corresponding JSON internet media type mercoledì 4 luglio 2012
  • 77. Accepted Media Types mapping supported media types to corresponding renderer functions mime_types = {'json_renderer': ('application/json',), 'xml_renderer': ('application/xml', 'text/xml', 'application/x-xml',)} XML rendering function mercoledì 4 luglio 2012
  • 78. Accepted Media Types mapping supported media types to corresponding renderer functions mime_types = {'json_renderer': ('application/json',), 'xml_renderer': ('application/xml', 'text/xml', 'application/x-xml',)} corresponding XML internet media types mercoledì 4 luglio 2012
  • 79. JSON Render datetimes and ObjectIDs call for further tinkering class APIEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime.datetime): return date_to_str(obj) elif isinstance(obj, ObjectId): return str(obj) return json.JSONEncoder.default(self, obj) def json_renderer(**data): return json.dumps(data, cls=APIEncoder) renderer function mapped to the appication/json media type mercoledì 4 luglio 2012
  • 80. JSON Render datetimes and ObjectIDs call for further tinkering class APIEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime.datetime): return date_to_str(obj) elif isinstance(obj, ObjectId): return str(obj) return json.JSONEncoder.default(self, obj) def json_renderer(**data): return json.dumps(data, cls=APIEncoder) standard json encoding is not enough, we need a specialized JSONEncoder mercoledì 4 luglio 2012
  • 81. JSON Render datetimes and ObjectIDs call for further tinkering class APIEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime.datetime): return date_to_str(obj) elif isinstance(obj, ObjectId): return str(obj) return json.JSONEncoder.default(self, obj) def json_renderer(**data): return json.dumps(data, cls=APIEncoder) Python datetimes are encoded as RFC 1123 strings: “Wed, 06 Jun 2012 14:19:53 UTC” mercoledì 4 luglio 2012
  • 82. JSON Render datetimes and ObjectIDs call for further tinkering class APIEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime.datetime): return date_to_str(obj) elif isinstance(obj, ObjectId): return str(obj) return json.JSONEncoder.default(self, obj) def json_renderer(**data): return json.dumps(data, cls=APIEncoder) Mongo ObjectId data types are encoded as strings: “4f46445fc88e201858000000” mercoledì 4 luglio 2012
  • 83. JSON Render datetimes and ObjectIDs call for further tinkering class APIEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime.datetime): return date_to_str(obj) elif isinstance(obj, ObjectId): return str(obj) return json.JSONEncoder.default(self, obj) def json_renderer(**data): return json.dumps(data, cls=APIEncoder) we let json/simplejson handle the other data types mercoledì 4 luglio 2012
  • 84. Rendering Render to JSON or XML and get WSGI response object def prep_response(dct, status=200): mime, render = get_best_mime() rendered = globals()[render](**dct) resp = make_response(rendered, status) resp.mimetype = mime return resp best match between request Accept header and media types supported by the service mercoledì 4 luglio 2012
  • 85. Rendering Render to JSON or XML and get WSGI response object def prep_response(dct, status=200): mime, render = get_best_mime() rendered = globals()[render](**dct) resp = make_response(rendered, status) resp.mimetype = mime return resp call the appropriate render function and retrieve the encoded JSON or XML mercoledì 4 luglio 2012
  • 86. Rendering Render to JSON or XML and get WSGI response object def prep_response(dct, status=200): mime, render = get_best_mime() rendered = globals()[render](**dct) resp = make_response(rendered, status) resp.mimetype = mime return resp flask’s make_response() returns a WSGI response object wich we can use to attach headers mercoledì 4 luglio 2012
  • 87. Rendering Render to JSON or XML and get WSGI response object def prep_response(dct, status=200): mime, render = get_best_mime() rendered = globals()[render](**dct) resp = make_response(rendered, status) resp.mimetype = mime return resp and finally, we set the appropriate mime type in the response header mercoledì 4 luglio 2012
  • 88. Flask-MimeRender “Python module for RESTful resource representation using MIME Media-Types and the Flask Microframework” !"!#"$%&'((#)('%*+,",-.-$/-. mercoledì 4 luglio 2012
  • 89. 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 mercoledì 4 luglio 2012
  • 90. Flask-MimeRender then you just decorate your end-point function @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!'} mercoledì 4 luglio 2012
  • 91. Flask-MimeRender Requests $ curl -H "Accept: application/html" example.com/ <html><body>Hello, World!</body></html> $ curl -H "Accept: application/xml" example.com/ <message>Hello, World!</message> $ curl -H "Accept: application/json" example.com/ {'message':'Hello, World!'} $ curl -H "Accept: text/plain" example.com/ Hello, World! mercoledì 4 luglio 2012
  • 92. #3 resource manipulation through HTTP verbs “GET, POST, PUT, DELETE and all that mess” mercoledì 4 luglio 2012
  • 93. HTTP Methods Verbs are handled along with URL routing @app.route('/<collection>', methods=['GET', 'POST']) def collection(collection): if collection in DOMAIN.keys(): if request.method == 'GET': return get_collection(collection) elif request.method == 'POST': return post(collection) abort(404) accepted HTTP verbs a PUT will throw a 405 Command Not Allowed mercoledì 4 luglio 2012
  • 94. HTTP Methods Verbs are handled along with URL routing @app.route('/<collection>', methods=['GET', 'POST']) def collection(collection): if collection in DOMAIN.keys(): if request.method == 'GET': return get_collection(collection) elif request.method == 'POST': return post(collection) abort(404) the global request object provides access to clients’ request headers mercoledì 4 luglio 2012
  • 95. HTTP Methods Verbs are handled along with URL routing @app.route('/<collection>', methods=['GET', 'POST']) def collection(collection): if collection in DOMAIN.keys(): if request.method == 'GET': return get_collection(collection) elif request.method == 'POST': return post(collection) abort(404) we respond to a GET request for a ‘collection’ resource mercoledì 4 luglio 2012
  • 96. HTTP Methods Verbs are handled along with URL routing @app.route('/<collection>', methods=['GET', 'POST']) def collection(collection): if collection in DOMAIN.keys(): if request.method == 'GET': return get_collection(collection) elif request.method == 'POST': return post(collection) abort(404) and here we respond to a POST request. Handling HTTP methods is easy! mercoledì 4 luglio 2012
  • 97. CRUD via REST Acttion HTTP Verb Context Collection/ Get GET Document Create POST Collection Update PATCH* Document Delete DELETE Document * WTF? mercoledì 4 luglio 2012
  • 98. GET Retrieve Multiple Documents (accepting Queries) http://api.example.com/v1/contacts?where={“age”: {“$gt”: 20}} mercoledì 4 luglio 2012
  • 99. Collection GET http://api.example.com/v1/contacts?where={“age”: {“$gt”: 20}} 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 request.args returns the return prep_response(response) original URI’s query definition, in our example: where = {“age”: {“$gt”: 20}} mercoledì 4 luglio 2012
  • 100. Collection GET http://api.example.com/v1/contacts?where={“age”: {“$gt”: 20}} 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. mercoledì 4 luglio 2012
  • 101. Collection GET http://api.example.com/v1/contacts?where={“age”: {“$gt”: 20}} 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 mercoledì 4 luglio 2012
  • 102. Collection GET http://api.example.com/v1/contacts?where={“age”: {“$gt”: 20}} 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 mercoledì 4 luglio 2012
  • 103. Collection GET http://api.example.com/v1/contacts?where={“age”: {“$gt”: 20}} 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 mercoledì 4 luglio 2012
  • 104. Interlude On encoding JSON dates mercoledì 4 luglio 2012
  • 105. On encoding JSON dates • We don’t want to force metadata into JSON representation: (“updated”: “$date: Thu 1, ..”) • Likewise, epochs are not an option • We are aiming for a broad solution not relying on the knoweldge of the current domain mercoledì 4 luglio 2012
  • 106. uy e g ind th eh is b ed R Because, you know mercoledì 4 luglio 2012
  • 107. Parsing JSON dates this is what I came out with >>> source = '{"updated": "Thu, 1 Mar 2012 10:00:49 UTC"}' >>> dct = json.loads(source, object_hook=datetime_parser) >>> dct {u'updated': datetime.datetime(2012, 3, 1, 10, 0, 49)} 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?) mercoledì 4 luglio 2012
  • 108. Parsing JSON dates this is what I came out with >>> source = '{"updated": "Thu, 1 Mar 2012 10:00:49 UTC"}' >>> dct = json.loads(source, object_hook=datetime_parser) >>> dct {u'updated': datetime.datetime(2012, 3, 1, 10, 0, 49)} 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 mercoledì 4 luglio 2012
  • 109. Parsing JSON dates this is what I came out with >>> source = '{"updated": "Thu, 1 Mar 2012 10:00:49 UTC"}' >>> dct = json.loads(source, object_hook=datetime_parser) >>> dct {u'updated': datetime.datetime(2012, 3, 1, 10, 0, 49)} 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 mercoledì 4 luglio 2012
  • 110. Parsing JSON dates this is what I came out with >>> source = '{"updated": "Thu, 1 Mar 2012 10:00:49 UTC"}' >>> dct = json.loads(source, object_hook=datetime_parser) >>> dct {u'updated': datetime.datetime(2012, 3, 1, 10, 0, 49)} 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)... mercoledì 4 luglio 2012
  • 111. Parsing JSON dates this is what I came out with >>> source = '{"updated": "Thu, 1 Mar 2012 10:00:49 UTC"}' >>> dct = json.loads(source, object_hook=datetime_parser) >>> dct {u'updated': datetime.datetime(2012, 3, 1, 10, 0, 49)} 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 ...are converted to datetime values mercoledì 4 luglio 2012
  • 112. Parsing JSON dates this is what I came out with >>> source = '{"updated": "Thu, 1 Mar 2012 10:00:49 UTC"}' >>> dct = json.loads(source, object_hook=datetime_parser) >>> dct {u'updated': datetime.datetime(2012, 3, 1, 10, 0, 49)} 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 mercoledì 4 luglio 2012
  • 113. PATCH Editing a Resource mercoledì 4 luglio 2012
  • 114. Why not PUT? • PUT means resource creation or replacement at a given URL • PUT does not allow for partial updates of a resource • 99% of the time we are updating just one or two fields • We don’t want to send complete representations of the document we are updating • Mongo allows for atomic updates and we want to take advantage of that mercoledì 4 luglio 2012
  • 115. ‘atomic’ PUT updates are ok when each field is itself a resource http://api.example.com/v1/contacts/<id>/address mercoledì 4 luglio 2012
  • 116. Enter PATCH “This specification defines the new method, PATCH, which is used to apply partial modifications to a resource.” RFC5789 mercoledì 4 luglio 2012
  • 117. PATCH • send a “patch document” with just the changes to be applied to the document • saves bandwidth and reduces traffic • it’s been around since 1995 • it is a RFC Proposed Standard • Widely adopted (will replace PUT in Rails 4.0) • clients not supporting it can fallback to POST with ‘X-HTTP-Method-Override: PATCH’ header tag mercoledì 4 luglio 2012
  • 118. PATCHing def patch_document(collection, original): docs = parse_request(request.form) if len(docs) > 1: abort(400) request.form returns a dict with request form data. key, value = docs.popitem() 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) mercoledì 4 luglio 2012
  • 119. PATCHing def patch_document(collection, original): docs = parse_request(request.form) if len(docs) > 1: abort(400) we aren’t going to accept more than one document here key, value = docs.popitem() 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) mercoledì 4 luglio 2012
  • 120. PATCHing def patch_document(collection, original): docs = parse_request(request.form) if len(docs) > 1: retrieve the original abort(400) document ID, will be used by key, value = docs.popitem() the update command 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) mercoledì 4 luglio 2012
  • 121. PATCHing def patch_document(collection, original): docs = parse_request(request.form) if len(docs) > 1: abort(400) validate the updates key, value = docs.popitem() 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) mercoledì 4 luglio 2012
  • 122. PATCHing def patch_document(collection, original): docs = parse_request(request.form) if len(docs) > 1: abort(400) add validation results to the response dictionary key, value = docs.popitem() 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) mercoledì 4 luglio 2012
  • 123. PATCHing def patch_document(collection, original): docs = parse_request(request.form) $set accepts a dict if len(docs) > 1: abort(400) with the updates for the db eg: {“active”: False}. key, value = docs.popitem() 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) mercoledì 4 luglio 2012
  • 124. PATCHing def patch_document(collection, original): docs = parse_request(request.form) mongo update() method if len(docs) > 1: commits updates to the abort(400) database. Updates are key, value = docs.popitem() atomic. 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) mercoledì 4 luglio 2012
  • 125. PATCHing def patch_document(collection, original): docs = parse_request(request.form) if len(docs) > 1: udpate() takes the unique Id abort(400) of the document andthe update expression ($set) key, value = docs.popitem() 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) mercoledì 4 luglio 2012
  • 126. PATCHing def patch_document(collection, original): docs = parse_request(request.form) if len(docs) > 1: as always, our response abort(400) dictionary is returned with proper encding key, value = docs.popitem() 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) mercoledì 4 luglio 2012
  • 127. POST Creating Resources mercoledì 4 luglio 2012
  • 128. POSTing def post(collection): docs = parse_request(request.form) response = {} for key, item in docs.items(): response_item = {} validate(item, collection) if item['validation']['response'] != VALIDATION_ERROR: document = item['doc'] response_item[ID_FIELD] = db(collection).insert(document) response_item['link'] = get_document_link(collection, response_item[ID_FIELD]) response_item['validation'] = item['validation'] response[key] = response_item return {'response': response} we accept multiple documents (remember, we are at collection level here) mercoledì 4 luglio 2012
  • 129. POSTing def post(collection): docs = parse_request(request.form) response = {} for key, item in docs.items(): response_item = {} validate(item, collection) if item['validation']['response'] != VALIDATION_ERROR: document = item['doc'] response_item[ID_FIELD] = db(collection).insert(document) response_item['link'] = get_document_link(collection, response_item[ID_FIELD]) response_item['validation'] = item['validation'] response[key] = response_item return {'response': response} we loop through the documents to be inserted mercoledì 4 luglio 2012
  • 130. POSTing def post(collection): docs = parse_request(request.form) response = {} for key, item in docs.items(): response_item = {} validate(item, collection) if item['validation']['response'] != VALIDATION_ERROR: document = item['doc'] response_item[ID_FIELD] = db(collection).insert(document) response_item['link'] = get_document_link(collection, response_item[ID_FIELD]) response_item['validation'] = item['validation'] response[key] = response_item return {'response': response} perform validation on the document mercoledì 4 luglio 2012
  • 131. POSTing def post(collection): docs = parse_request(request.form) response = {} for key, item in docs.items(): response_item = {} validate(item, collection) if item['validation']['response'] != VALIDATION_ERROR: document = item['doc'] response_item[ID_FIELD] = db(collection).insert(document) response_item['link'] = get_document_link(collection, response_item[ID_FIELD]) response_item['validation'] = item['validation'] response[key] = response_item return {'response': response} push document and get its ObjectId back from Mongo. like other CRUD operations, inserting is trivial in mongo. mercoledì 4 luglio 2012
  • 132. POSTing def post(collection): docs = parse_request(request.form) response = {} for key, item in docs.items(): response_item = {} validate(item, collection) if item['validation']['response'] != VALIDATION_ERROR: document = item['doc'] response_item[ID_FIELD] = db(collection).insert(document) response_item['link'] = get_document_link(collection, response_item[ID_FIELD]) response_item['validation'] = item['validation'] response[key] = response_item return {'response': response} a direct link to the resource we just created is added to the response mercoledì 4 luglio 2012
  • 133. POSTing def post(collection): docs = parse_request(request.form) response = {} for key, item in docs.items(): response_item = {} validate(item, collection) if item['validation']['response'] != VALIDATION_ERROR: document = item['doc'] response_item[ID_FIELD] = db(collection).insert(document) response_item['link'] = get_document_link(collection, response_item[ID_FIELD]) response_item['validation'] = item['validation'] response[key] = response_item return {'response': response} validation result is always returned to the client, even if the doc has not been inserted mercoledì 4 luglio 2012
  • 134. POSTing def post(collection): docs = parse_request(request.form) response = {} for key, item in docs.items(): response_item = {} validate(item, collection) if item['validation']['response'] != VALIDATION_ERROR: document = item['doc'] response_item[ID_FIELD] = db(collection).insert(document) response_item['link'] = get_document_link(collection, response_item[ID_FIELD]) response_item['validation'] = item['validation'] response[key] = response_item return {'response': response} standard response enconding applied mercoledì 4 luglio 2012
  • 135. Data Validation We still need to validate incoming data mercoledì 4 luglio 2012
  • 136. Data Validation DOMAIN = {} DOMAIN['contacts'] = { 'secondary_id': 'name', 'fields': { 'name': { 'data_type': 'string', 'required': True, 'unique': True, 'max_length': 120, 'min_length': 1 }, DOMAIN is a Python dict containing our validation rules and schema structure mercoledì 4 luglio 2012
  • 137. Data Validation DOMAIN = {} DOMAIN['contacts'] = { 'secondary_id': 'name', 'fields': { 'name': { 'data_type': 'string', 'required': True, 'unique': True, 'max_length': 120, 'min_length': 1 }, every resource (collection) maintained by the API has a key in DOMAIN mercoledì 4 luglio 2012
  • 138. Data Validation DOMAIN = {} DOMAIN['contacts'] = { 'secondary_id': 'name', 'fields': { 'name': { 'data_type': 'string', 'required': True, 'unique': True, 'max_length': 120, 'min_length': 1 }, if the resource allows for a secondary lookup field, we define it here mercoledì 4 luglio 2012
  • 139. Data Validation DOMAIN = {} DOMAIN['contacts'] = { 'secondary_id': 'name', 'fields': { 'name': { 'data_type': 'string', 'required': True, 'unique': True, 'max_length': 120, 'min_length': 1 }, known fields go in the fields dict mercoledì 4 luglio 2012
  • 140. Data Validation DOMAIN = {} DOMAIN['contacts'] = { 'secondary_id': 'name', 'fields': { 'name': { 'data_type': 'string', 'required': True, 'unique': True, 'max_length': 120, 'min_length': 1 }, validation rules for ‘name’ field. data_type is mostly needed to process datetimes and currency values mercoledì 4 luglio 2012
  • 141. Data Validation (...) 'iban': { 'data_type': 'string', 'custom_validation': { 'module': 'customvalidation', 'function': 'validate_iban' } } (...) we can define custom validation functions when the need arises mercoledì 4 luglio 2012
  • 142. Data Validation (...) 'contact_type': { 'data_type': 'array', 'allowed_values': [ 'client', 'agent', 'supplier', 'area manager', 'vector' ] } (...) or we can define our own custom data types... mercoledì 4 luglio 2012
  • 143. Data Validation (...) 'contact_type': { 'data_type': 'array', 'allowed_values': [ 'client', 'agent', 'supplier', 'area manager', 'vector' ] } (...) ... like the array, which allows us to define a list of accepted values for the field mercoledì 4 luglio 2012
  • 144. I will spare you the validation function It’s pretty simple really mercoledì 4 luglio 2012
  • 145. Hey but! You’re building your own ORM! Just a thin validation layer on which I have total control AKA So What? mercoledì 4 luglio 2012
  • 146. #4 Caching and concurrency control resource representation describes how when and if it can be used, discarded or re-fetched mercoledì 4 luglio 2012
  • 147. Driving conditional requests Servers use Last-Modified and ETag response headers to drive conditional requests mercoledì 4 luglio 2012
  • 148. Last-Modified Generally considered a weak validator since it has a one-second resolution “Wed, 06 Jun 2012 14:19:53 UTC” mercoledì 4 luglio 2012
  • 149. ETag Entity Tag is a strong validator since its value can be changed every time the server modifies the representation 7a9f477cde424cf93a7db20b69e05f7b680b7f08 mercoledì 4 luglio 2012
  • 150. On ETags • Clients should be able to use ETag to compare representations of a resouce • An ETag is supposed to be like an object’s hash code. • Actually, some web frameworks and a lot of implementations do just that • ETag computed on an entire representation of the resource may become a performance bottleneck mercoledì 4 luglio 2012
  • 151. Last-Modified or ETag? You can use either or both. Consider the types of client consuming your service. Hint: use both. mercoledì 4 luglio 2012
  • 152. Validating cached representations Clients use If-Modified-Since and If-None-Match in request headers for validating cached representations mercoledì 4 luglio 2012
  • 153. If-Mod-Since & ETag def get_document(collection, object_id=None, lookup=None): response = {} document = find_document(collection, object_id, lookup) if document: etag = get_etag(document) header_etag = request.headers.get('If-None-Match') if header_etag and header_etag == etag: return prep_response(dict(), status=304) 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 mercoledì 4 luglio 2012
  • 154. If-Mod-Since & ETag def get_document(collection, object_id=None, lookup=None): response = {} document = find_document(collection, object_id, lookup) if document: etag = get_etag(document) header_etag = request.headers.get('If-None-Match') if header_etag and header_etag == etag: return prep_response(dict(), status=304) 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 ETag for the current = document return prep_response(response, last_modified, etag) representation. We test ETag abort(404) first, as it is a stronger validator mercoledì 4 luglio 2012
  • 155. If-Mod-Since & ETag def get_document(collection, object_id=None, lookup=None): response = {} document = find_document(collection, object_id, lookup) if document: etag = get_etag(document) header_etag = request.headers.get('If-None-Match') if header_etag and header_etag == etag: return prep_response(dict(), status=304) 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 mercoledì 4 luglio 2012
  • 156. If-Mod-Since & ETag def get_document(collection, object_id=None, lookup=None): response = {} document = find_document(collection, object_id, lookup) if document: etag = get_etag(document) header_etag = request.headers.get('If-None-Match') if header_etag and header_etag == etag: return prep_response(dict(), status=304) 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 mercoledì 4 luglio 2012
  • 157. If-Mod-Since & ETag def get_document(collection, object_id=None, lookup=None): response = {} document = find_document(collection, object_id, lookup) if document: etag = get_etag(document) header_etag = request.headers.get('If-None-Match') if header_etag and header_etag == etag: return prep_response(dict(), status=304) 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, if the resource likewise, etag) abort(404) has not been modified since If-Modifed-Since, return 304 Not Modified mercoledì 4 luglio 2012
  • 158. Concurrency control Clients use If-Unmodified-Since and If-Match in request headers as preconditions for concurrency control mercoledì 4 luglio 2012
  • 159. Concurrency control Create/Update/Delete are controlled by ETag def edit_document(collection, object_id, method): document = find_document(collection, object_id) if document: header_etag = request.headers.get('If-Match') if header_etag is None: return prep_response('If-Match missing from request header', status=403) if header_etag != get_etag(document[LAST_UPDATED]): # Precondition failed abort(412) else: if method in ('PATCH', 'POST'): return patch_document(collection, document) elif method == 'DELETE': return delete_document(collection, object_id) else: abort(404) retrieve client’s If-Match ETag from the request header mercoledì 4 luglio 2012
  • 160. Concurrency control Create/Update/Delete are controlled by ETag def edit_document(collection, object_id, method): document = find_document(collection, object_id) if document: header_etag = request.headers.get('If-Match') if header_etag is None: return prep_response('If-Match missing from request header', status=403) if header_etag != get_etag(document[LAST_UPDATED]): # Precondition failed abort(412) else: if method in ('PATCH', 'POST'): return patch_document(collection, document) elif method == 'DELETE': return delete_document(collection, object_id) else: abort(404) editing is forbidden if ETag is not provided mercoledì 4 luglio 2012
  • 161. Concurrency control Create/Update/Delete are controlled by ETag def edit_document(collection, object_id, method): document = find_document(collection, object_id) if document: header_etag = request.headers.get('If-Match') if header_etag is None: return prep_response('If-Match missing from request header', status=403) if header_etag != get_etag(document[LAST_UPDATED]): # Precondition failed abort(412) else: if method in ('PATCH', 'POST'): return patch_document(collection, document) elif method == 'DELETE': return delete_document(collection, object_id) else: client and server abort(404) representations don’t match. Precondition failed. mercoledì 4 luglio 2012
  • 162. Concurrency control Create/Update/Delete are controlled by ETag def edit_document(collection, object_id, method): document = find_document(collection, object_id) if document: header_etag = request.headers.get('If-Match') if header_etag is None: client and server representation match, return prep_response('If-Match missing from request header', status=403) go ahead with the edit if header_etag != get_etag(document[LAST_UPDATED]): # Precondition failed abort(412) else: if method in ('PATCH', 'POST'): return patch_document(collection, document) elif method == 'DELETE': return delete_document(collection, object_id) else: abort(404) mercoledì 4 luglio 2012
  • 163. Sending cache & concurrency directives back to clients mercoledì 4 luglio 2012
  • 164. Cache & Concurrency def prep_response(dct, last_modified=None, etag=None, status=200): (...) resp.headers.add('Cache-Control', 'max-age=%s,must-revalidate' & 30) resp.expires = time.time() + 30 if etag: resp.headers.add('ETag', etag) if last_modified: resp.headers.add('Last-Modified', date_to_str(last_modified)) return resp encodes ‘dct’ according to client’s accepted MIME Data-Type (click here see that slide) mercoledì 4 luglio 2012
  • 165. Cache & Concurrency def prep_response(dct, last_modified=None, etag=None, status=200): (...) resp.headers.add('Cache-Control', 'max-age=%s,must-revalidate' & 30) resp.expires = time.time() + 30 if etag: resp.headers.add('ETag', etag) if last_modified: resp.headers.add('Last-Modified', date_to_str(last_modified)) return resp Cache-Control, a directive for HTTP/1.1 clients (and later) -RFC2616 mercoledì 4 luglio 2012
  • 166. Cache & Concurrency def prep_response(dct, last_modified=None, etag=None, status=200): (...) resp.headers.add('Cache-Control', 'max-age=%s,must-revalidate' & 30) resp.expires = time.time() + 30 if etag: resp.headers.add('ETag', etag) if last_modified: resp.headers.add('Last-Modified', date_to_str(last_modified)) return resp Expires, a directive for HTTP/1.0 clients mercoledì 4 luglio 2012
  • 167. Cache & Concurrency def prep_response(dct, last_modified=None, etag=None, status=200): (...) resp.headers.add('Cache-Control', 'max-age=%s,must-revalidate' & 30) resp.expires = time.time() + 30 if etag: resp.headers.add('ETag', etag) if last_modified: resp.headers.add('Last-Modified', date_to_str(last_modified)) return resp ETag. Notice that we don’t compute it on the rendered representation, this is by design. mercoledì 4 luglio 2012
  • 168. Cache & Concurrency def prep_response(dct, last_modified=None, etag=None, status=200): (...) resp.headers.add('Cache-Control', 'max-age=%s,must-revalidate' & 30) resp.expires = time.time() + 30 if etag: resp.headers.add('ETag', etag) if last_modified: resp.headers.add('Last-Modified', date_to_str(last_modified)) return resp And finally, we add the Last-Modified header tag. mercoledì 4 luglio 2012
  • 169. Cache & Concurrency def prep_response(dct, last_modified=None, etag=None, status=200): (...) resp.headers.add('Cache-Control', 'max-age=%s,must-revalidate' & 30) resp.expires = time.time() + 30 if etag: resp.headers.add('ETag', etag) if last_modified: resp.headers.add('Last-Modified', date_to_str(last_modified)) return resp the response object is now complete and ready to be returned to the client mercoledì 4 luglio 2012
  • 170. that ’s o long ne acro ass nym #5 HATEOAS “Hypertext As The Engine Of Application State” mercoledì 4 luglio 2012
  • 171. HATEOAS in a Nutshell • clients interact entirely through hypermedia provided dynamically by the server • clients need no prior knowledge about how to interact with the server • clients access an application through a single well known URL (the entry point) • All future actions the clients may take are discovered within resource representations returned from the server mercoledì 4 luglio 2012
  • 172. It’s all about Links resource representation includes links to related resources mercoledì 4 luglio 2012
  • 173. Collection { Representation  "links":[     "<link rel='parent' title='home' href='http://api.example.com/' />",     "<link rel='collection' title='contacts' href='http://api.example.com/Contacts' />",     "<link rel='next' title='next page'  href='http://api.example.com/Contacts?page=2' />"    ],    "contacts":[       {          "updated":"Wed, 06 Jun 2012 14:19:53 UTC",          "name":"Jon Doe",          "age": 27,          "etag":"7a9f477cde424cf93a7db20b69e05f7b680b7f08",          "link":"<link rel='self' title='Contact' every resource href='http://api.example.com/Contacts/ 4f46445fc88e201858000000' />", representation provides a          "_id":"4f46445fc88e201858000000", links section with       }, navigational info for ] clients } mercoledì 4 luglio 2012
  • 174. Collection { Representation  "links":[     "<link rel='parent' title='home' href='http://api.example.com/' />",     "<link rel='collection' title='contacts' href='http://api.example.com/Contacts' />",     "<link rel='next' title='next page'  href='http://api.example.com/Contacts?page=2' />"    ],    "contacts":[       {          "updated":"Wed, 06 Jun 2012 14:19:53 UTC",          "name":"Jon Doe",          "age": 27,          "etag":"7a9f477cde424cf93a7db20b69e05f7b680b7f08",          "link":"<link rel='self' title='Contact' href='http://api.example.com/Contacts/ attribute provides the rel 4f46445fc88e201858000000' />", the relationship between the          "_id":"4f46445fc88e201858000000",       }, linked resource and the one ] currently represented } mercoledì 4 luglio 2012
  • 175. Collection { Representation  "links":[     "<link rel='parent' title='home' href='http://api.example.com/' />",     "<link rel='collection' title='contacts' href='http://api.example.com/Contacts' />",     "<link rel='next' title='next page'  href='http://api.example.com/Contacts?page=2' />"    ],    "contacts":[       {          "updated":"Wed, 06 Jun 2012 14:19:53 UTC",          "name":"Jon Doe",          "age": 27,          "etag":"7a9f477cde424cf93a7db20b69e05f7b680b7f08",          "link":"<link rel='self' title='Contact' the title attribute provides href='http://api.example.com/Contacts/ 4f46445fc88e201858000000' />", a tag (or description) for          "_id":"4f46445fc88e201858000000", the linked resource. Could       }, be used as a caption for a ] client button. } mercoledì 4 luglio 2012
  • 176. Collection { Representation  "links":[     "<link rel='parent' title='home' href='http://api.example.com/' />",     "<link rel='collection' title='contacts' href='http://api.example.com/Contacts' />",     "<link rel='next' title='next page'  href='http://api.example.com/Contacts?page=2' />"    ],    "contacts":[       {          "updated":"Wed, 06 Jun 2012 14:19:53 UTC",          "name":"Jon Doe",          "age": 27,          "etag":"7a9f477cde424cf93a7db20b69e05f7b680b7f08",          "link":"<link rel='self' title='Contact' href='http://api.example.com/Contacts/ attribute provides the href 4f46445fc88e201858000000' />", and absolute path to the          "_id":"4f46445fc88e201858000000",       }, resource (the “permanent ] identifier” per REST def.) } mercoledì 4 luglio 2012
  • 177. Collection { Representation  "links":[     "<link rel='parent' title='home' href='http://api.example.com/' />",     "<link rel='collection' title='contacts' every resource listed href='http://api.example.com/Contacts' />", exposes its own link, which     "<link rel='next' title='next page'  will allow the client to href='http://api.example.com/Contacts?page=2' />"    ], perform PATCH, DELETE etc.    "contacts":[ on the resource       {          "updated":"Wed, 06 Jun 2012 14:19:53 UTC",          "name":"Jon Doe",          "age": 27,          "etag":"7a9f477cde424cf93a7db20b69e05f7b680b7f08",          "link":"<link rel='self' title='Contact' href='http://api.example.com/Contacts/ 4f46445fc88e201858000000' />",          "_id":"4f46445fc88e201858000000",       }, ] } mercoledì 4 luglio 2012
  • 178. Collection { Representation  "links":[ while we are here,     "<link rel='parent' title='home' href='http://api.example.com/' />",     "<link rel='collection' title='contacts' notice how every resource href='http://api.example.com/Contacts' />", its own etag, also exposes     "<link rel='next' title='next page'  last-modified date. href='http://api.example.com/Contacts?page=2' />"    ],    "contacts":[       {          "updated":"Wed, 06 Jun 2012 14:19:53 UTC",          "name":"Jon Doe",          "age": 27,          "etag":"7a9f477cde424cf93a7db20b69e05f7b680b7f08",          "link":"<link rel='self' title='Contact' href='http://api.example.com/Contacts/ 4f46445fc88e201858000000' />",          "_id":"4f46445fc88e201858000000",       }, ] } mercoledì 4 luglio 2012
  • 179. HATEOAS The API entry point (the homepage) @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 the API homepage responds to GET requests and provides links to its top level resources to the clients mercoledì 4 luglio 2012