forked from boblefrag/python-rest-api-framework
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added the formaters on Views, some refactoring, in the process of wri…
…ting the tutorial.
- Loading branch information
Yohann Gabory
committed
Oct 15, 2013
1 parent
9b8c044
commit 076dbe9
Showing
36 changed files
with
1,771 additions
and
436 deletions.
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
Adding validators to your DataStore | ||
=================================== | ||
|
||
In this exemple, you want to check that a user with the same last_name | ||
and same first_name does not exist in your datastore before creating a | ||
new user. | ||
|
||
For this you can use UniqueTogether: | ||
|
||
UniqueTogether | ||
-------------- | ||
|
||
Change your UserEndPoint to get: | ||
|
||
.. code-block:: python | ||
|
||
from rest_api_framework.datastore.validators import UniqueTogether | ||
|
||
class UserEndPoint(Controller): | ||
ressource = { | ||
"ressource_name": "users", | ||
"ressource": {"name": "adress_book.db", "table": "users"}, | ||
"model": UserModel, | ||
"datastore": SQLiteDataStore, | ||
"options":{"validators": [UniqueTogether("first_name", "last_name")]} | ||
} | ||
|
||
controller = { | ||
"list_verbs": ["GET", "POST"], | ||
"unique_verbs": ["GET", "PUT", "DElETE"] | ||
} | ||
|
||
view = {"response_class": JsonResponse} | ||
|
||
each of ressource, controller and views can have various options to | ||
add new functionality to them. The "validators" option of ressource | ||
enable some datastore based validators. As you can see, validators are | ||
a list. This meen that you can add many validators for a single datastore. | ||
|
||
UniqueTogether will ensure that a user with first_name: John and | ||
last_name: Doe cannot be created. | ||
|
||
Let's try: | ||
|
||
.. code-block:: python | ||
|
||
curl -i -H "Content-type: application/json" -X POST -d '{"first_name": "John", "last_name": "Doe"}' http://localhost:5000/users/ | ||
HTTP/1.0 400 BAD REQUEST | ||
Content-Type: application/json | ||
Content-Length: 57 | ||
Server: Werkzeug/0.8.3 Python/2.7.2 | ||
Date: Mon, 14 Oct 2013 17:13:41 GMT | ||
|
||
{"error": "first_name,last_name must be unique together"} | ||
|
||
Next: :doc:`representing_data` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
First Step Building a user endpoint | ||
=================================== | ||
|
||
For this project we need users. Users will be helpfull for our adress | ||
book and for our authentication process. | ||
|
||
Users will be define with at least a first name and a last name. We | ||
also need an unique identifier to retreive the user. | ||
|
||
Define a model | ||
~~~~~~~~~~~~~~ | ||
|
||
.. code-block:: python | ||
|
||
from rest_api_framework import models | ||
|
||
class UserModel(models.Model): | ||
|
||
fields = [models.StringField(name="first_name", required=True), | ||
models.StringField(name="last_name", required=True), | ||
models.PkField(name="id", required=True) | ||
] | ||
|
||
The use of required_true will ensure that a user without this field | ||
cannot be created | ||
|
||
Chose a DataStore | ||
~~~~~~~~~~~~~~~~~ | ||
|
||
We also need a datastore to get a place where we can save our | ||
users. For instance we will use a sqlite3 database. The | ||
SQLiteDataStore is what we need | ||
|
||
.. code-block:: python | ||
|
||
from rest_api_framework.datastore import SQLiteDataStore | ||
|
||
Chose a view | ||
~~~~~~~~~~~~ | ||
|
||
We want results to be rendered as Json. We use the JsonResponse view | ||
for that: | ||
|
||
.. code-block:: python | ||
|
||
from rest_api_framework.views import JsonResponse | ||
|
||
Create The user endpoint | ||
~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
To create an endpoint, we need a controller. This will manage our | ||
endpoint in a RESTFUL fashion. | ||
|
||
.. code-block:: python | ||
|
||
from rest_api_framework.controllers import Controller | ||
|
||
class UserEndPoint(Controller): | ||
ressource = { | ||
"ressource_name": "users", | ||
"ressource": {"name": "adress_book.db", "table": "users"}, | ||
"model": UserModel, | ||
"datastore": SQLiteDataStore | ||
} | ||
|
||
controller = { | ||
"list_verbs": ["GET", "POST"], | ||
"unique_verbs": ["GET", "PUT", "DElETE"] | ||
} | ||
|
||
view = {"response_class": JsonResponse} | ||
|
||
then we must run our application: | ||
|
||
.. code-block:: python | ||
|
||
if __name__ == '__main__': | ||
from werkzeug.serving import run_simple | ||
from rest_api_framework.controllers import WSGIDispatcher | ||
app = WSGIDispatcher([UserEndPoint]) | ||
run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True) | ||
|
||
Summary | ||
~~~~~~~ | ||
|
||
So far, all of the code should look like this: | ||
|
||
.. code-block:: python | ||
|
||
from rest_api_framework import models | ||
from rest_api_framework.datastore import SQLiteDataStore | ||
from rest_api_framework.views import JsonResponse | ||
from rest_api_framework.controllers import Controller | ||
|
||
|
||
class UserModel(models.Model): | ||
|
||
fields = [models.StringField(name="first_name", required=True), | ||
models.StringField(name="last_name", required=True), | ||
models.PkField(name="id", required=True) | ||
] | ||
|
||
|
||
class UserEndPoint(Controller): | ||
ressource = { | ||
"ressource_name": "users", | ||
"ressource": {"name": "adress_book.db", "table": "users"}, | ||
"model": UserModel, | ||
"datastore": SQLiteDataStore | ||
} | ||
|
||
controller = { | ||
"list_verbs": ["GET", "POST"], | ||
"unique_verbs": ["GET", "PUT", "DElETE"] | ||
} | ||
|
||
view = {"response_class": JsonResponse} | ||
|
||
if __name__ == '__main__': | ||
from werkzeug.serving import run_simple | ||
from rest_api_framework.controllers import WSGIDispatcher | ||
app = WSGIDispatcher([UserEndPoint]) | ||
run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True) | ||
|
||
Next: :doc:`using_user_endpoint` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Linking ressource together | ||
========================== | ||
|
||
Address Book need to link address to user as you can have multiple | ||
users with a single address (a familliy for example) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
Show data to users | ||
================== | ||
|
||
The view you have used so far just added a ressource_uri. But preserve | ||
the id attribut. As id is an internal representation of the data you | ||
may wich to remove it. | ||
|
||
Define a formater function | ||
-------------------------- | ||
|
||
To do so you'll have to write a simple function to plug on the | ||
view. This function is a formater. When the View instanciate the | ||
formater, it give you access to the response object and the object to | ||
be rendered. | ||
|
||
Because you want to remove the id of the reprensentaion of your | ||
ressource, you can write: | ||
|
||
.. code-block:: python | ||
|
||
def remove_id(response, obj): | ||
obj.pop("id") | ||
return obj | ||
|
||
and change the view part of your UserEndPoint as follow: | ||
|
||
.. code-block:: python | ||
|
||
view = {"response_class": JsonResponse, | ||
"options": {"formaters": ["add_ressource_uri", | ||
remove_id]}} | ||
|
||
add_ressource_uri is the default formatter for this View. You dont | ||
need to remove it for now. But if you try, then it will work as | ||
expected. The ressource_uri field will be removed. | ||
|
||
The idea behind Python REST API Framework is to always get out of | ||
your way. | ||
|
||
You can check that it work as expected: | ||
|
||
.. code-block:: bash | ||
|
||
curl -i "http://localhost:5000/users/1/" | ||
HTTP/1.0 200 OK | ||
Content-Type: application/json | ||
Content-Length: 80 | ||
Server: Werkzeug/0.8.3 Python/2.7.2 | ||
Date: Mon, 14 Oct 2013 23:41:55 GMT | ||
|
||
{"first_name": "Cap tain", "last_name": "America", | ||
"ressource_uri": "/users/1/"} | ||
|
||
Make things generics | ||
-------------------- | ||
|
||
This implementation work on your endpoint because you each object has | ||
an id. But, if later you create another endpoint with ressources | ||
lacking the "id" key, you'll have to re-write your function. | ||
|
||
Instead, you can take advantage of the response wich is part of the | ||
parameters of your function. | ||
|
||
response object carry the attribut model who define your ressources | ||
fields. You can then get the name of the Pk field used with this | ||
ressource with: | ||
|
||
.. code-block:: python | ||
|
||
response.model.pk_field.name | ||
|
||
Your code then become: | ||
|
||
.. code-block:: python | ||
|
||
def remove_id(response, obj): | ||
obj.pop(response.model.pk_field.name) | ||
return obj | ||
|
||
And reuse this formatter as long as you need | ||
|
||
Next :doc:`related_ressources` |
Oops, something went wrong.