In the time since I started writing this series I've learned a lot about REST and am continuing to learn more. While there is nothing intentionally incorrect in these blog posting, after reading Roy Fielding's thesis I'm not satisfied with the coupling of the technologies in this example, and believe that Dr. Fielding's work describes a simpler and much more elegant solution. Use these blogs with that in mind and I will be back when I have an example of REST with Python that I feel best represents Dr. Fielding's idea.
In my last post I digressed into talking about Unicode, Unicode was something that was on my mind, and had thought about blogging on the topic for a while but had not taken the time. So, I decided to take the occasion and write about Unicode while it was on my mind, now that I'm ready to start writing about statelessness in REST I can implement the project application to be Unicode aware and add a little more value to my examples.
In the example project I started previously I've decided to use Mongo DB and have integrated a Mongo DB database connection into the application. To use the project you will need to install Mongo DB and can get the example project's source code at here. Because of its native use of the Python dict type, Mongo will be a great match for the use of JSON as our platform independent data format, there will be no need to write encoders for our model objects, instead we will deal with dict instances and that is all. I've also added Mako templates, this dependency is part of the Pyramid installation and won't require you to an additional install. You can checkout the Mako if you are unfamiliar with this template engine.
Now in my Unicode examples from my previous post I was using SQLAlchemy to define a model and persist data to a SQLite database instance, with MongoDB I am not defining a model but I will be dealing with text data and in general the same principles will apply. My documents in MongoDB parlance will need to keep track of their original encoding, and MongoDB's implementation in Python uses unicode object by default. Lets see how pymongo measures up.
Python 2.7.2 (default, Oct 11 2012, 20:14:37)
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pymongo import MongoClient
>>> client = MongoClient('localhost', 27017)
>>> database = client.unicode_test
>>> incoming_utf8_text = "Non c'è giornale sul tavolo. Dov'è il giornale?"
>>> unicode_text = incoming_utf8_text.decode('utf_8')
>>> database.messages.insert({"text":unicode_text})
ObjectId('51bb15584b14f24d57c76444')
>>> messages = database.messages.find_one()
>>> messages
{u'text': u"Non c'\xe8 giornale sul tavolo. Dov'\xe8 il giornale?", u'_id': ObjectId('51bb15584b14f24d57c76444')}
>>> print(messages["text"])
Non c'è giornale sul tavolo. Dov'è il giornale?
>>>
Bellissimo! The only issue was with my document, there was no attribute to keep track of the encoding used to decode the string object to a unicode object, but I will correct that oversight in my implementation for the web app.
Our RESTful URLs should be path like and incorporate information about the hierarchy in the resource types our service API provides access too. For this example, a contrived publishing application that allows the user to manage book types, categorized by author, and allowing the user to add and manage chapters and pages to books. The API to service should also be stateless, the service will not need anymore information about the state of the client than what is provided by the client in the request, to fulfill the request. A key reason for this is scalability; the service will not have to request additional information from the client to process a request, alternately the service will not need to maintain and synchronize information about client between or during request processing, like the previous page viewed, or the order of pages in the application.
With this simple set of resources we can plan out all of your URLs for our service.
/author #create author
/author/{id} #update/view author
/author/book #create book
/author/book/{id} #update/view book
/author/book/chapter #create chapter
/author/book/chapter/{id} #update/view book
/author/book/chapter/page #create page
/author/book/chapter/page/{id} #update/view page
With URLs following this format as long as the resource exists the URL will be valid and these URLs can be used to bookmark. After routing those URLs to the view handlers, I stubbed out the view handlers in views.py, the URL configuration looks like.
config.add_handler('create_author','/authors',handler=AuthorCreate,action='read')
config.add_handler('update_author','/authors/{id}',handler=Author,action='read')
config.add_handler('create_book','/authors/books',handler=BookCreate,action='read')
config.add_handler('update_book','/authors/books/{id}',handler=Book,action='read')
config.add_handler('create_chapter','/authors/books/chapters',handler=ChapterCreate,action='read')
config.add_handler('update_chapter','/authors/books/chapters/{id}',handler=Chapter,action='read')
config.add_handler('create_page','/authors/books/chapters/pages',handler=PageCreate,action='read')
config.add_handler('update_page','/authors/books/chapters/pages{id}',handler=Page,action='read')
The corresponding view handler method for /author/{id} GET request looks like.
class Author(BaseHandler):
@action(name='read',request_method="GET",renderer="json")
def get(self):
document_id = self.request.matchdict['id']
try:
author = Database.authors.find(dict(_id=ObjectId(document_id)))
except InvalidId, e: raise HTTPNotFound(self.error_message_404 % document_id)
if author: self.request.response.body = dumps(author)
else: raise HTTPNotFound(self.error_message_404 % document_id)
return self.request.response
This was a very simple example, in part two I will provide a more complex example of retrieving the next page or chapter with the service API requiring the client to provide the necessary information to determine which page is displayed next.
No comments:
Post a Comment