diff --git a/docs/build/.doctrees/environment.pickle b/docs/build/.doctrees/environment.pickle index 2459568..e2f15c6 100644 Binary files a/docs/build/.doctrees/environment.pickle and b/docs/build/.doctrees/environment.pickle differ diff --git a/docs/build/.doctrees/index.doctree b/docs/build/.doctrees/index.doctree index 4774476..f8b0da6 100644 Binary files a/docs/build/.doctrees/index.doctree and b/docs/build/.doctrees/index.doctree differ diff --git a/docs/build/.doctrees/introduction.doctree b/docs/build/.doctrees/introduction.doctree index 0b8f096..d0edc5e 100644 Binary files a/docs/build/.doctrees/introduction.doctree and b/docs/build/.doctrees/introduction.doctree differ diff --git a/docs/build/.doctrees/references.doctree b/docs/build/.doctrees/references.doctree index eb204d1..0d236e7 100644 Binary files a/docs/build/.doctrees/references.doctree and b/docs/build/.doctrees/references.doctree differ diff --git a/docs/build/.doctrees/representing_data.doctree b/docs/build/.doctrees/representing_data.doctree index 1ae60d1..838d2ce 100644 Binary files a/docs/build/.doctrees/representing_data.doctree and b/docs/build/.doctrees/representing_data.doctree differ diff --git a/docs/build/.doctrees/tutorial.doctree b/docs/build/.doctrees/tutorial.doctree index 3914f7b..6883200 100644 Binary files a/docs/build/.doctrees/tutorial.doctree and b/docs/build/.doctrees/tutorial.doctree differ diff --git a/docs/build/.doctrees/using_user_endpoint.doctree b/docs/build/.doctrees/using_user_endpoint.doctree index 1d9e928..1db6be0 100644 Binary files a/docs/build/.doctrees/using_user_endpoint.doctree and b/docs/build/.doctrees/using_user_endpoint.doctree differ diff --git a/docs/build/.doctrees/work_with_pagination.doctree b/docs/build/.doctrees/work_with_pagination.doctree new file mode 100644 index 0000000..c62f838 Binary files /dev/null and b/docs/build/.doctrees/work_with_pagination.doctree differ diff --git a/docs/build/_sources/index.txt b/docs/build/_sources/index.txt index d4c2c38..a16039d 100644 --- a/docs/build/_sources/index.txt +++ b/docs/build/_sources/index.txt @@ -3,8 +3,15 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to Python Rest Api Framework's documentation! -===================================================== +Python Rest Api Framework's documentation +========================================= + +Python REST API framework is a set of utilities based on werkzeug to +easily build Restful API with a MVC pattern. Main features includes: +Pagination, Authentication, Authorization, Filters, Partials Response, +Error handling, data validators, data formaters... +and more... + Contents: @@ -13,12 +20,12 @@ Contents: introduction tutorial - datastore - controller - pagination - authentication - multiple_endpoint - references +.. datastore +.. controller +.. pagination +.. authentication +.. multiple_endpoint +.. references Indices and tables ================== @@ -27,3 +34,62 @@ Indices and tables * :ref:`modindex` * :ref:`search` + + + +A Full working example +---------------------- + +.. 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 + from rest_api_framework.datastore.validators import UniqueTogether + from rest_api_framework.pagination import Pagination + + + class UserModel(models.Model): + """ + Define how to handle and validate your data. + """ + fields = [models.StringField(name="first_name", required=True), + models.StringField(name="last_name", required=True), + models.PkField(name="id", required=True) + ] + + + def remove_id(response, obj): + """ + Do not show the id in the response. + """ + obj.pop(response.model.pk_field.name) + return obj + + + 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"], + "options": {"pagination": Pagination(20)} + } + + view = {"response_class": JsonResponse, + "options": {"formaters": ["add_ressource_uri", remove_id]}} + + + 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) diff --git a/docs/build/_sources/introduction.txt b/docs/build/_sources/introduction.txt index c3f3823..90ce8cb 100644 --- a/docs/build/_sources/introduction.txt +++ b/docs/build/_sources/introduction.txt @@ -346,5 +346,5 @@ is the same as with the PythonListDataStore. Where to go from here --------------------- -* :doc:`Authentication and Authorization ` -* :doc:`multiple_endpoint` +.. * :doc:`Authentication and Authorization ` +.. * :doc:`multiple_endpoint` diff --git a/docs/build/_sources/representing_data.txt b/docs/build/_sources/representing_data.txt index bb030d6..da59b3d 100644 --- a/docs/build/_sources/representing_data.txt +++ b/docs/build/_sources/representing_data.txt @@ -48,7 +48,7 @@ You can check that it work as expected: 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", + {"first_name": "Captain", "last_name": "America", "ressource_uri": "/users/1/"} Make things generics @@ -77,6 +77,11 @@ Your code then become: obj.pop(response.model.pk_field.name) return obj -And reuse this formatter as long as you need +And reuse this formatter as long as you need. -Next :doc:`related_ressources` +Formaters are here to help you build clean and meaningful ressources +representations. It should hide internal representation of your +ressources and return all of the fields needed to manipulate and +represent your data. + +Next :doc:`work_with_pagination` diff --git a/docs/build/_sources/tutorial.txt b/docs/build/_sources/tutorial.txt index 6abcfa8..ccaefd5 100644 --- a/docs/build/_sources/tutorial.txt +++ b/docs/build/_sources/tutorial.txt @@ -8,4 +8,5 @@ Tutorial building an adressebook API using_user_endpoint adding_validator_datastore representing_data + work_with_pagination related_ressources diff --git a/docs/build/_sources/using_user_endpoint.txt b/docs/build/_sources/using_user_endpoint.txt index 28119fe..5994155 100644 --- a/docs/build/_sources/using_user_endpoint.txt +++ b/docs/build/_sources/using_user_endpoint.txt @@ -9,9 +9,11 @@ First you can check that your endpoint is up HTTP/1.0 200 OK Content-Type: application/json - Content-Length: 2 + Content-Length: 44 Server: Werkzeug/0.8.3 Python/2.7.2 - Date: Mon, 14 Oct 2013 12:52:22 GMT + Date: Tue, 15 Oct 2013 11:13:44 GMT + + {"meta": {"filters": {}}, "object_list": []} Your endpoint is responding but does not have any data. Let's add some: @@ -59,14 +61,14 @@ The list of users is also updated: .. code-block:: bash - curl -i "http://localhost:5000/users/1/" + curl -i "http://localhost:5000/users/" HTTP/1.0 200 OK Content-Type: application/json Content-Length: 83 Server: Werkzeug/0.8.3 Python/2.7.2 Date: Mon, 14 Oct 2013 17:03:00 GMT - [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}] + {"meta": {"filters": {}}, "object_list": [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]} Delete a user ------------- @@ -94,8 +96,35 @@ and now delete it: Server: Werkzeug/0.8.3 Python/2.7.2 Date: Mon, 14 Oct 2013 20:41:46 GMT +You can check that the user no longer exists: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/2/" + HTTP/1.0 404 NOT FOUND + Content-Type: application/json + Connection: close + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 11:16:33 GMT + + {"error": "
The requested URL was not found on the server.
If you entered the URL manually please check your spelling and try again.
"} + +And the list is also updated: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 125 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 11:17:46 GMT + + {"meta": {"filters": {}}, "object_list": [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]} + + Update a User -============= +------------- Let's go another time to the creation process: @@ -114,24 +143,63 @@ America. Let's update this user: .. code-block:: bash - curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Captain", "last_name": "America"}' http://localhost:5000/users/3/ + curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Capitain", "last_name": "America"}' http://localhost:5000/users/3/ HTTP/1.0 200 OK Content-Type: application/json Content-Length: 58 Server: Werkzeug/0.8.3 Python/2.7.2 Date: Mon, 14 Oct 2013 20:57:47 GMT -Partial update is also possible: + {"first_name": "Capitain", "last_name": "America", "id": 3, "ressource_uri": "/users/3/"} + +Argh! Thats a typo. the fist name is "Captain", not "Capitain". Let's +correct this: .. code-block:: bash - curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Cap tain"}' http://localhost:5000/users/3/ + curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Captain"}' http://localhost:5000/users/3/ HTTP/1.0 200 OK Content-Type: application/json Content-Length: 59 Server: Werkzeug/0.8.3 Python/2.7.2 Date: Mon, 14 Oct 2013 21:08:04 GMT + {"first_name": "Captain", "last_name": "America", "id": 3, "ressource_uri": "/users/3/"} + + +Filtering +--------- + +Ressources can be filtered easily using parameters: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/?last_name=America" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 236 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 12:07:21 GMT + + {"meta": {"filters": {"last_name": "America"}}, "object_list": + [{"first_name": "Joe", "last_name": "America", "id": 1, + "ressource_uri": "/users/1/"}, {"first_name": "Bob", "last_name": + "America", "id": 3, "ressource_uri": "/users/3/"}] + +Multiple filters are allowed: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/?last_name=America&first_name=Joe" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 171 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 12:09:32 GMT + + {"meta": {"filters": {"first_name": "Joe", "last_name": "America"}}, + "object_list": [{"first_name": "Joe", "last_name": "America", "id": 1, + "ressource_uri": "/users/1/"}]} Error handling -------------- diff --git a/docs/build/_sources/work_with_pagination.txt b/docs/build/_sources/work_with_pagination.txt new file mode 100644 index 0000000..eebe89a --- /dev/null +++ b/docs/build/_sources/work_with_pagination.txt @@ -0,0 +1,215 @@ +Working with Pagination +======================= + +Creating fixtures +----------------- + +When your address book will be full of entry, you will need to add a +pagination on your API. As it is a common need, REST API Framework +implement a very easy way of doing so. + +Before you can play with the pagination process, you will need to +create more data. You can create those records the way you want: + +* direct insert into the database + +.. code-block:: bash + + sqlite3 adress_book.db + INSERT INTO users VALUES ("Nick", "Furry", 6); + +* using the datastore directly + +.. code-block:: python + + store = SQLiteDataStore({"name": "adress_book.db", "table": "users"}, UserModel) + store.create({"first_name": "Nick", "last_name": "Furry"}) + +* using your API + +.. code-block:: python + + curl -i -H "Content-type: application/json" -X POST -d '{"first_name": "Nick", "last_name": "Furry"}' http://localhost:5000/users/ + +each on of those methods have advantages and disavantages but they all +make the work done. For this example, I propose to use the well know +requests package with a script to create a bunch of random records: + +For this to work you need to install resquests : http://docs.python-requests.org/en/latest/user/install/#install + +.. code-block:: python + + import json + import requests + import random + import string + + def get_random(): + return ''.join( + random.choice( + string.ascii_letters) for x in range( + int(random.random() * 20) + ) + ) + + for i in range(200): + requests.post("http://localhost:5000/users/", data=json.dumps({"first_name": get_random(), "last_name": get_random()})) + +Pagination +---------- + +Now your datastore is filled with more than 200 records, it's time to +paginate. To do so import Pagination and change the controller part of +your app. + +.. code-block:: python + + from rest_api_framework.pagination import Pagination + + 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"], + "options": {"pagination": Pagination(20)} + } + + view = {"response_class": JsonResponse, + "options": {"formaters": ["add_ressource_uri", remove_id]}} + +and try your new pagination: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 1811 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 11:32:55 GMT + + {"meta": {"count": 20, "total_count": 802, "next": "?offset=20", + "filters": {}, "offset": 0, "previous": "null"}, "object_list": + [{"first_name": "Captain", "last_name": "America", + "ressource_uri": "/users/1/"}, {"first_name": "Captain", + "last_name": "America", "ressource_uri": "/users/3/"}, + {"first_name": "John", "last_name": "Doe", "ressource_uri": + "/users/4/"}, {"first_name": "arRFOSYZT", "last_name": "", + "ressource_uri": "/users/5/"}, {"first_name": "iUJsYORMuYeMUDy", + "last_name": "TqFpmcBQD", "ressource_uri": "/users/6/"}, + {"first_name": "EU", "last_name": "FMSAbcUJBSBDPaF", + "ressource_uri": "/users/7/"}, {"first_name": "mWAwamrMQARXW", + "last_name": "yMNpEnYOPzY", "ressource_uri": "/users/8/"}, + {"first_name": "y", "last_name": "yNiKP", "ressource_uri": + "/users/9/"}, {"first_name": "s", "last_name": "TRT", + "ressource_uri": "/users/10/"}, {"first_name": "", "last_name": + "zFUaBd", "ressource_uri": "/users/11/"}, {"first_name": "WA", + "last_name": "priJ", "ressource_uri": "/users/12/"}, + {"first_name": "XvpLttDqFmR", "last_name": "liU", "ressource_uri": + "/users/13/"}, {"first_name": "ZhJqTgYoEUzmcN", "last_name": + "KKDqHJwJMxPSaTX", "ressource_uri": "/users/14/"}, {"first_name": + "qvUxiKIATdKdkC", "last_name": "wIVzfDlKCkjkHIaC", + "ressource_uri": "/users/15/"}, {"first_name": "YSSMHxdDQQsW", + "last_name": "UaKCKgKsgEe", "ressource_uri": "/users/16/"}, + {"first_name": "EKLFTPJLKDINZio", "last_name": "nuilPTzHqattX", + "ressource_uri": "/users/17/"}, {"first_name": "SPcDBtmDIi", + "last_name": "MrytYqElXiIxA", "ressource_uri": "/users/18/"}, + {"first_name": "OHxNppXiYp", "last_name": "AUvUXFRPICsJIB", + "ressource_uri": "/users/19/"}, {"first_name": "WBFGxnoe", + "last_name": "KG", "ressource_uri": "/users/20/"}, {"first_name": + "i", "last_name": "ggLOcKPpMfgvVGtv", "ressource_uri": + "/users/21/"}]} + +Browsering Through Paginated objects +------------------------------------ + +Of course you get 20 records but the most usefull part is the meta +key: + +.. code-block:: json + + {"meta": + {"count": 20, + "total_count": 802, + "next": "?offset=20", + "filters": {}, + "offset": 0, + "previous": "null"} + } + +You can use the "next" key to retreive the 20 next rows: + +.. code-block:: bash + + curl -i "http://localhost:5000/users/?offset=20" + HTTP/1.0 200 OK + Content-Type: application/json + Content-Length: 1849 + Server: Werkzeug/0.8.3 Python/2.7.2 + Date: Tue, 15 Oct 2013 11:38:59 GMT + +.. code-block:: json + + {"meta": {"count": 20, "total_count": 802, "next": "?offset=40", + "filters": {}, "offset": 20, "previous": "?offset=0"}, "object_list": + [And reuse this formatter as long as you need
-Next Linking ressource together
+And reuse this formatter as long as you need.
+Formaters are here to help you build clean and meaningful ressources +representations. It should hide internal representation of your +ressources and return all of the fields needed to manipulate and +represent your data.
+ diff --git a/docs/build/searchindex.js b/docs/build/searchindex.js index 6cf16d2..be2f2e2 100644 --- a/docs/build/searchindex.js +++ b/docs/build/searchindex.js @@ -1 +1 @@ -Search.setIndex({envversion:42,terms:{represent:[3,12,8,9],all:[3,6,7,8,12],code:[6,9],partial:2,list_verb:[3,6,1,4,7],lack:9,steve:2,carfulli:2,follow:[3,9],sould:[12,8],captain:2,row:[12,8],decid:3,typeerror:[],depend:[3,7,8,12],authoris:3,sensit:[],tweet:[3,12,8],show:3,send:[3,12,8],tain:[2,9],decis:3,get_connector:[12,8],islic:[],ressource_list:[],sourc:[12,8],string:8,fals:3,util:3,verb:[3,7,8],mechan:1,objec:[],veri:[3,11,5],hanld:3,retrrreiv:[],list:[3,11,5],last_nam:[2,4,9,6],"try":[2,5,4,9],adress_book:[6,4],uniquetogeth:[],"20th":1,cap:[2,9],naiv:[12,8],rate:[3,7],design:[],pass:8,chek:[12,8],integr:3,compat:12,index:[0,8],what:[],neg:[],abl:[2,1,3,7,8,12],access:[3,5,9],delet:[3,6,1,7],"new":[3,2,4,8,12],method:3,metadata:2,hash:5,behavior:3,gener:[],secondapp:8,here:[],behaviour:[],modif:[],address:[3,10],eventapp:11,becom:9,modifi:3,valu:[3,1,5],search:0,reprensentaion:9,later:9,permit:8,codebas:3,bob:3,explain:2,environn:11,control:[],useful:[3,2],extra:12,appli:2,app:[3,6,8,11],add_ressource_uri:9,from:[],describ:[],fron:[12,8],regist:[],next:[2,4,9,6],everybodi:2,live:3,call:[3,12,8],type:[2,3,4,8,12,9],tell:[3,7],more:[],clever:[],actuali:[3,12,8],under:11,notic:11,"__iter__":[],particular:[3,5],actual:[12,8],must:[2,3,6,5,8,4,12],none:[5,8,12],endpoint:[],alia:[],prepar:[12,8],work:[1,8,5,12,9],uniqu:[2,3,6,7,8,4],firstapp:8,itself:2,obvious:[12,8],whatev:[3,12],learn:3,those:[3,1,12],purpos:5,root:[7,8],def:[5,9],overrid:12,sqlite:[3,12,8],quickstart:[],give:[2,1,3,8,12,9],process:[2,8,6],accept:[],topic:[3,7],want:[2,1,3,6,8,4,12,9],paginated_bi:12,alwai:[1,5,9],cours:2,multipl:[2,10],first_nam:[2,4,9,6],secur:3,anoth:[3,2,5,12,9],write:[11,7,9],how:[],instead:[3,1,9],csv:[3,12],simpl:[],updat:[3,12],map:8,resourc:3,huge:2,max:7,after:[12,8],befor:4,mai:[3,11,12,9],datastor:[],data:[3,5],bind:[],author:[],correspond:3,inform:[3,1,8,12],environ:8,allow:[3,7],callabl:8,talk:11,oper:[12,8],soon:[3,12,8],userendpoint:[6,4,9],paramet:3,render:[3,6,9],fit:3,carri:9,trootl:3,main:[3,8],them:[3,4,8],good:3,"return":[1,3,5,8,12,9],"__getitem__":[],handl:[3,7],auto:[],auth:[5,8],number:[3,12,8],now:[2,5,8,12,9],nor:[],enabl:[3,4,1,8],choic:1,somewher:5,name:[2,3,4,6,5,8,11,12,9],anyth:[3,5,8,12],authent:[],wole:[12,8],easili:[3,5],token:[3,1],each:[2,1,3,4,5,7,8,11,12,9],fulli:[3,5],unique_uri:8,mean:3,compil:3,domain:1,idea:9,unique_verb:[3,6,1,4,7],connect:[5,8,12],our:[3,6,5],meth:[],todo:[],event:11,special:[3,7],out:9,miss:[],newli:[],content:[0,12,4,9,2],rewrit:[],rel:[],sqlitedatastor:3,wich:[3,7,9],to_dict:5,earlier:[],free:[3,12,8],base:[3,1],mime:3,scallabl:3,put:[2,1,3,6,7,4],care:12,reusabl:3,launch:11,could:[2,5,8,12],keep:3,filter:[3,1,8,12],length:[2,4,9],mvc:3,pagin:[3,7],think:12,first:3,feed:3,rang:3,dont:[3,2,9],directli:1,feel:8,onc:[3,12],sqldatastor:12,placehold:5,hook:3,alreadi:3,done:[3,12,8],messag:3,count_kei:[1,8],primari:[12,8],given:5,"long":9,interact:3,least:6,get_list:[12,8],too:[3,5,8,12],statement:[12,8],john:[2,4],"final":12,store:[3,12],schema:[],option:[],wsgiwrapp:8,part:[3,2,9],than:[],serv:[3,6,11],notfound:[5,8,12],keyword:[1,7],provid:[3,2,5,12],remov:[2,1,8,12,9],meen:4,second:[],bee:12,project:6,reus:[3,12,9],ressourc:[],str:[],make_opt:8,fashion:6,apikeyauthent:5,argument:3,have:[2,1,3,4,5,8,10,11,12,9],"__main__":[3,6],need:[2,1,3,6,5,8,10,12,9],check_auth:[5,8],date:[2,4,9],load_url:8,dictionnari:[12,8],self:[5,8,12],client:3,note:[],also:[3,2,12,6],without:[6,8],take:[2,1,3,8,11,12,9],instanci:[1,8,5,12,9],singl:[],even:3,integerfield:[3,12],object:[2,3,5,8,12,9],dialogu:7,plai:[],america:[2,9],"class":[1,3,4,6,5,8,11,12],ressouc:3,don:2,url:[3,7,8],gather:[],flow:3,uri:[2,8],doe:[3,2,4,8,12],ext:1,unique_id:7,clean:3,pattern:3,constrain:[12,8],jsonrespons:[3,6,4,9],apiapp:[3,11],wsgi:8,ressource_uri:[2,9],text:[12,8],syntax:8,fine:5,xml:3,moer:3,onli:[2,1,3,5,7,8,11,12],locat:2,apimodel:[3,12,8],run_simpl:[3,6,11],configur:[3,1],should:[3,6,1,7,5],dict:[3,1,8,5,12],local:3,oct:[2,4,9],variou:4,get:[3,6,1,7,5],stop:[],becaus:[2,1,7,9],get_us:[5,8],cannot:[3,2,12,4,6],other_paramet:[],requir:[3,6,5,8,12],retreiv:[3,2,12,8,6],userapp:11,emb:[],dispatch_request:8,ressoourc:3,yield:[],"public":5,book:[6,10],bad:[2,4],stuff:7,roger:2,common:3,contain:[3,12,8],orign:1,where:[],view:[],respond:2,set:[3,12,8],see:[2,1,4,7,12],mandatori:[],result:[1,3,6,7,8,12],arg:8,close:[12,8],extend:3,databas:[],someth:[2,5,12],unauthoriez:[],behind:9,response_class:[3,6,4,9],between:[3,7,12],"import":[3,6,4,11],insensitv:[],attribut:[1,12,9],accord:[12,8],kei:[5,8,12,9],unauthor:5,itertool:[],extens:3,lazi:[12,8],come:12,famillii:10,protect:5,last:[3,6,12,8],easi:[3,11,12],howev:[2,11],lowercas:[],etc:3,instanc:[3,6,11],grain:5,basestr:[12,8],let:[2,4],com:3,becam:[],"30th":1,can:[2,1,3,4,6,5,8,10,11,12,9],pop:9,loader:8,header:[3,2],dispatch:8,path:[],usermodel:[6,4],wsgi_app:8,backend:[],evalu:[12,8],been:[3,2,12,8],wsgidispatch:[3,6,8,11],json:[3,2,4,9,6],basic:8,validate_field:[12,8],immedi:12,tigh:[],convert:[12,8],ani:[3,2,5],understand:3,togeth:[],required_tru:6,wrapper:[12,8],"case":3,look:[2,3,6,8,11,12],mount:8,properti:3,act:[3,8],formatt:9,defin:[3,1,7],calcul:1,apikei:5,elt:[],abov:1,error:5,"na\u00efv":[],multiple_endpoint:[],pythonlistdatastor:[3,5,8],azerti:5,loos:[12,8],finnali:5,coher:2,advantag:[12,8,9],applic:[2,1,3,6,8,4,9],implment:3,dictonari:3,ratelimit:8,kwarg:[1,8,12],allawi:[],"__init__":[5,8,12],present:[3,8],thei:[3,7,8,5,12],grant:5,perform:5,make:[3,7,8],same:[3,2,4],preserv:9,tutori:[],sqlite3:[3,6,12,8],apiappauth:5,complet:12,http:[2,1,8,9,4],pouet:[],hanl:3,action:[3,5],rais:[2,5,8,12],user:[3,11,5],mani:[3,2,4],outbound:[],respons:[3,2,8,9],implement:[3,7],whole:1,well:[2,12],inherit:[3,1,8,12],pk_field:9,exampl:[],thi:[3,6,1,7,5],fiter:[12,8],apikeyauthor:5,rout:[3,8],usual:3,authmodel:5,construct:8,identifi:[2,3,5,6,7,8,12],just:[2,3,5,8,12,9],dissuss:[],distant:3,expos:3,hint:[],point:[12,8],except:[5,8,12],param:8,exempl:[3,1,4,7],add:[3,2,12,4,11],other:3,els:5,save:[3,6,12,8],adress:6,modul:0,match:[12,8],build:[3,1],real:[2,12,8],indentifi:5,ressource_config:[12,8],format:[3,12],read:[3,7,5],pkfield:[3,6,12,8],mon:[2,4,9],gmt:[2,4,9],password:5,helpful:6,like:[1,3,6,7,11,12],specif:[],integ:[12,8],server:[2,4,9],either:[3,12,8],authorized_method:[],apicontrol:8,twitter:[3,12],creation:[2,12,8],some:[2,1,3,7,8,4,12],back:[12,8],intern:9,use_reload:[3,6,11],peter:2,kawrg:[],insensit:[],definit:[],totali:3,werkzeug:[2,3,6,4,8,11,9],adressebook:[],duplic:2,localhost:[2,4,9],refer:[],machin:3,who:9,run:[3,6],stringfield:[3,6,5,12],autoincr:[12,8],step:[],offset:7,"__name__":[3,6],post:[2,1,3,6,7,8,4],plug:9,about:[3,11,1,12],obj:[12,8,9],memori:[3,12,8],page:[3,0],constructor:11,disabl:3,block:[],subset:[12,8],paginate_bi:[12,8],own:[3,1,8,7,12],"float":[12,8],empti:1,ensur:[2,4,6],chang:[2,3,4,8,12,9],your:[],manag:[3,6,7,8],accordingli:8,wai:[3,5,8,12,9],support:[12,8],use_debugg:[3,6,11],custom:[5,12],start:[3,1,8,12],suit:[3,12],"function":5,properli:[12,8],max_result:8,form:8,tupl:8,link:12,"true":[3,6,5,12,11],count:7,commun:[3,12,8],made:8,possibl:[2,1,8,12,11],"default":9,start_respons:8,displai:3,below:1,limit:[3,1,8,7,12],otherwis:[3,5,8],problem:[2,1],unrel:3,expect:[2,9],datastoreinst:3,featur:7,creat:[],"int":[12,8],request:[2,1,5,7,8,4],"abstract":8,deep:[],repres:[12,8],exist:[3,4,5,8,12],file:[12,8],curl:[2,1,4,9],offset_kei:[1,8],check:[2,3,5,8,4,12,9],herit:8,automaticali:8,when:[3,1,7,9],detail:7,rest_api_framework:[1,3,6,8,11,4,12],field:[],valid:[],test:[3,12,8],remove_id:9,you:[2,1,3,4,5,8,10,11,12,9],ressource_nam:[3,6,7,4],architectur:[],relat:[7,12],finali:3,multimpl:11,developp:11,queri:[12,8],sql:[3,12,8],endoint:11,receiv:[1,8,12],dog:1,descript:[],place:[2,6],time:[2,5,8,12],far:[6,9,11]},objtypes:{"0":"py:module","1":"py:class","2":"py:method"},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"]},filenames:["index","pagination","using_user_endpoint","introduction","adding_validator_datastore","authentication","first_step","controller","references","representing_data","related_ressources","multiple_endpoint","datastore","tutorial"],titles:["Welcome to Python Rest Api Framework’s documentation!","Paginate a ressource","Playing with the newly created endpoint","What is Python REST API Framework","Adding validators to your DataStore","Authentication and Authorization","First Step Building a user endpoint","Controllers","REST API Framework API references","Show data to users","Linking ressource together","Using more than a single endpoint","Datastore","Tutorial building an adressebook API"],objects:{"rest_api_framework.datastore.sql.SQLiteDataStore":{get:[12,2,1,""],get_connector:[12,2,1,""],create:[12,2,1,""],paginate:[12,2,1,""],update:[12,2,1,""],get_list:[12,2,1,""],filter:[12,2,1,""],"delete":[12,2,1,""]},"rest_api_framework.datastore.base.DataStore":{get:[12,2,1,""],create:[12,2,1,""],paginate:[12,2,1,""],update:[12,2,1,""],get_list:[12,2,1,""],filter:[12,2,1,""],validate_fields:[12,2,1,""],validate:[12,2,1,""],"delete":[12,2,1,""]},"rest_api_framework.controllers.ApiController":{index:[8,2,1,""],get:[8,2,1,""],create:[8,2,1,""],paginate:[8,2,1,""],update:[8,2,1,""],get_list:[8,2,1,""],unique_uri:[8,2,1,""],"delete":[8,2,1,""]},"rest_api_framework.datastore.simple.PythonListDataStore":{get:[8,2,1,""],update:[8,2,1,""],get_list:[8,2,1,""]},"rest_api_framework.datastore.sql":{SQLiteDataStore:[12,1,1,""]},"rest_api_framework.datastore.simple":{PythonListDataStore:[8,1,1,""]},"rest_api_framework.controllers":{Controller:[8,1,1,""],WSGIWrapper:[8,1,1,""],ApiController:[8,1,1,""],WSGIDispatcher:[8,1,1,""]},rest_api_framework:{controllers:[8,0,0,"-"]},"rest_api_framework.datastore.base":{DataStore:[12,1,1,""]},"rest_api_framework.controllers.Controller":{load_urls:[8,2,1,""],make_options:[8,2,1,""]},"rest_api_framework.authentication.Authentication":{get_user:[8,2,1,""]},"rest_api_framework.pagination.Pagination":{paginate:[8,2,1,""]},"rest_api_framework.controllers.WSGIWrapper":{dispatch_request:[8,2,1,""],wsgi_app:[8,2,1,""]},"rest_api_framework.pagination":{Pagination:[8,1,1,""]},"rest_api_framework.authentication":{Authentication:[8,1,1,""]}},titleterms:{control:[3,7,8],quickstart:3,creat:[2,12,6],show:9,argument:7,rest:[3,0,8],api:[3,0,8,13],mandatori:7,tabl:0,thing:9,miss:2,your:[12,4],paramet:7,what:3,from:3,describ:12,field:3,databas:3,newli:2,format:9,invalid:2,should:[],avail:12,how:[3,5],make:9,valid:[3,4],summari:6,build:[6,13],indic:0,adressebook:13,sqlitedatastor:8,ressourc:[3,1,12,10],document:0,simpl:[3,8],method:1,singl:11,refer:8,"function":9,model:[3,6,12],option:[3,7],get:2,python:[3,0],max:1,gener:9,first:6,here:3,backend:5,framework:[3,0,8],step:6,base:8,link:10,user:[2,9,6],offset:1,datastor:[3,6,4,8,12],data:[2,9],than:11,welcom:0,count:1,chose:6,endpoint:[2,11,6],handl:2,architectur:3,togeth:10,tutori:13,uniquetogeth:4,author:5,list:2,plai:2,authent:[3,5,8],defin:[6,9],delet:2,exampl:5,pagin:[1,8],updat:2,thi:[],error:2,implement:1,more:11,where:3,other:1,view:[3,6]}}) \ No newline at end of file +Search.setIndex({envversion:42,terms:{represent:[4,12,2,9],all:[4,6,7,2,14,12,9],code:[6,9],partial:0,list_verb:[0,1,4,6,7,8,14],lack:9,steve:3,carfulli:3,follow:[4,14,9],sould:[12,2],captain:[3,14,9],eklftpjlkdinzio:14,row:[12,2,14],decid:4,typeerror:[],depend:[4,7,2,12],authoris:4,sensit:[],tweet:[4,12,2],show:[],readabl:14,send:[4,12,2],tain:[],decis:4,get_connector:[12,2],islic:[],ressource_list:[],sourc:[12,2],string:[2,14],fals:4,util:[4,0],verb:[4,7,2],mechan:1,objec:[],veri:[4,11,5,14],snip:14,mrytyqelxiixa:14,hanld:4,disavantag:14,retrrreiv:[],list:[11,5],last_nam:[0,3,6,8,14,9],"try":[3,5,8,14,9],adress_book:[0,8,14,6],ynikp:14,uniquetogeth:[],"20th":1,pleas:3,cap:[],kkdqhjwjmxpsatx:14,naiv:[12,2],direct:14,famillii:10,second:[],design:[],pass:2,chek:[12,2],iujsyormuyemudi:14,compat:12,index:[0,2],what:[],hide:9,neg:[],abl:[3,1,4,7,2,12],access:[4,5,9],delet:[6,1,7],"new":[3,4,8,14,2,12],ohxnppxiyp:14,method:[],metadata:3,full:14,hash:5,behavior:4,gener:[],secondapp:2,here:[],behaviour:[],modif:[],address:[4,14,10],eventapp:11,becom:9,modifi:4,valu:[4,1,5,14],search:0,auvuxfrpicsjib:14,reprensentaion:9,permit:2,codebas:4,bob:[4,3,14],explain:3,environn:11,control:[],useful:[4,3,14],xvplttdqfmr:14,extra:12,appli:3,app:[0,4,6,2,14,11],prefer:14,add_ressource_uri:[0,14,9],zfuabd:14,instal:14,from:[],describ:[],fron:[12,2],regist:[],next:[3,8,14,9,6],everybodi:3,live:4,call:[4,12,2],typo:3,capitain:3,type:[3,4,8,14,2,12,9],tell:[4,7],more:[],clever:[],actuali:[4,12,2],under:11,notic:11,"__iter__":[],particular:[4,5],actual:[12,2],must:[3,4,6,5,8,2,12],none:[5,2,12],endpoint:[],join:14,alia:[],prepar:[12,2],work:[1,5],uniqu:[3,4,6,7,8,2],firstapp:2,itself:3,obvious:[12,2],whatev:[4,12],learn:4,purpos:5,root:[7,2],def:[0,5,14,9],overrid:12,sqlite:[4,12,2],quickstart:[],give:[3,1,4,2,12,9],process:[3,2,14,6],accept:[],topic:[4,7],want:[3,1,4,6,8,14,2,12,9],paginated_bi:12,alwai:[1,5,9],cours:[3,14],multipl:[3,10],first_nam:[0,3,6,8,14,9],secur:4,anoth:[4,3,5,12,9],object_list:[3,14],ymnpenyopzi:14,write:[11,7,9],how:[],instead:[4,1,9],csv:[4,12],simpl:[],updat:[],map:2,first_id:14,resourc:4,huge:3,max:7,earlier:[],befor:[8,14],mai:[4,11,12,14,9],datastor:[],data:5,bind:[],author:[],correspond:4,inform:[4,1,2,12],environ:2,allow:[4,3,7],enter:3,callabl:2,unique_id:7,oper:[12,2],least:6,help:9,get_random:14,soon:[4,12,2],userendpoint:[0,8,14,9,6],paramet:[],render:[4,6,9],fit:4,gglockppmfgvvgtv:14,carri:9,trootl:4,main:[4,0,2],them:[4,2,8],good:4,"return":[0,1,4,5,2,14,12,9],"__getitem__":[],handl:7,auto:[],spell:3,auth:[5,2],number:[4,12,2],now:[3,5,2,14,12,9],nor:[],enabl:[4,2,1,8],choic:[1,14],somewher:5,name:[0,3,4,6,5,8,14,2,11,12,9],anyth:[4,5,2,12],total_count:14,authent:[],wole:[12,2],easili:[4,0,5,14,3],token:[4,1],each:[3,1,4,5,7,8,14,2,11,12,9],fulli:[4,5],unique_uri:2,mean:4,compil:4,nick:14,domain:1,idea:9,meta:[3,14],unique_verb:[0,1,4,6,7,8,14],connect:[3,5,2,12],fist:3,our:[4,6,5],meth:[],todo:[],event:11,special:[4,7],out:9,miss:[],newli:[],content:[0,3,8,14,12,9],rewrit:[],uakckgksge:14,rel:[],sqlitedatastor:[],wich:[4,7,14,9],correct:3,to_dict:5,after:[12,2],found:3,manipul:9,free:[4,12,2],argh:3,base:1,mime:4,scallabl:4,latest:14,put:[0,1,3,4,6,7,8,14],org:14,care:12,resquest:14,reusabl:4,launch:11,could:[3,5,2,12],keep:4,filter:[1,2,12],length:[3,8,14,9],mvc:[4,0],pagin:7,think:12,first:[],feed:4,rang:[4,14],dont:[4,3,9],directli:[1,14],feel:2,onc:[4,12],sqldatastor:12,placehold:5,hook:4,alreadi:4,done:[4,12,2,14],messag:4,count_kei:[1,2,14],primari:[12,2],given:5,"long":9,script:14,interact:4,wrapper:[12,2],get_list:[12,2],too:[4,5,2,12],statement:[12,2],john:[3,8,14],"final":12,store:[4,12,14],schema:[],option:[],nuilptzhqattx:14,wsgiwrapp:2,part:[4,3,14,9],than:[],ascii_lett:14,serv:[4,0,11,6],notfound:[5,2,12],keyword:[1,7,14],provid:[4,3,5,12],remov:[3,1,2,12,9],meen:8,rate:[4,7],bee:12,project:6,reus:[4,12,9],ressourc:[],str:[],entri:14,make_opt:2,fashion:6,apikeyauthent:5,argument:[],packag:14,have:[3,1,2,4,5,8,14,10,11,12,9],"__main__":[4,0,6],need:[3,1,4,6,5,2,14,10,12,9],"null":14,check_auth:[5,2],date:[3,8,14,9],load_url:2,dictionnari:[12,2],self:[5,2,12],client:4,note:[],also:[4,3,12,6],without:[6,2],take:[3,1,4,2,14,11,12,9],instanci:[1,2,5,12,9],singl:[],even:4,integerfield:[4,12],previou:14,dialogu:7,most:14,plai:[],america:[3,14,9],"class":[0,1,4,6,5,8,14,2,11,12],ressouc:4,don:3,url:[4,3,7,2],doc:14,later:9,flow:4,uri:[3,2],doe:[3,4,8,14,2,12],ext:1,talk:11,clean:[4,9],pattern:[4,0],constrain:[12,2],propos:14,jsonrespons:[0,4,6,8,14,9],apiapp:[4,11],wsgi:2,prij:14,ressource_uri:[3,14,9],text:[12,2],random:14,syntax:2,fine:5,xml:4,moer:4,onli:[3,1,4,5,7,2,11,12],locat:3,apimodel:[4,12,2],run_simpl:[4,0,11,6],configur:[4,1],should:[6,1,7,5],dict:[4,1,2,5,12],local:4,oct:[3,8,14,9],variou:8,get:[6,1,7,5],stop:[],becaus:[3,1,7,9],get_us:[5,2],cannot:[4,3,12,8,6],other_paramet:[],requir:[0,4,6,5,2,12],liu:14,retreiv:[3,4,6,2,14,12],userapp:11,emb:[],dispatch_request:2,ressoourc:4,yield:[],"public":5,book:[6,14,10],bad:[3,8],stuff:7,roger:3,common:[4,14],contain:[4,12,2],orign:1,where:[],view:[],respond:3,set:[4,0,12,2],dump:14,see:[3,1,8,7,12],mandatori:[],result:[1,4,6,7,2,14,12],arg:2,close:[3,12,2],wbfgxnoe:14,extend:4,databas:[],someth:[3,5,12],unauthoriez:[],behind:9,response_class:[0,4,6,8,14,9],between:[4,7,12],"import":[0,4,6,8,14,11],bunch:14,qvuxikiatdkdkc:14,insensitv:[],attribut:[1,12,9],accord:[12,2],kei:[5,2,12,9,14],unauthor:5,itertool:[],extens:4,lazi:[12,2],joe:[3,14],come:12,tue:[3,14],protect:5,last:[4,6,12,2],easi:[4,11,12,14],howev:[3,11],lowercas:[],etc:4,instanc:[4,6,11],grain:5,basestr:[12,2],let:[3,8],com:4,becam:[],"30th":1,mwawamrmqarxw:14,can:[3,1,2,4,6,5,8,14,10,11,12,9],pop:[0,9],loader:2,header:[4,3],dispatch:2,path:[],usermodel:[0,8,14,6],wsgi_app:2,duplic:3,evalu:[12,2],been:[4,3,12,2],wsgidispatch:[4,0,11,2,6],json:[3,4,6,8,14,9],arrfosyzt:14,basic:2,validate_field:[12,2],immedi:12,tigh:[],convert:[12,2],ani:[4,3,5],understand:4,togeth:[],furri:14,required_tru:6,those:[4,1,12,14],"case":4,look:[3,4,6,2,11,12],mount:2,properti:4,act:[4,2],formatt:9,defin:[1,7],calcul:1,apikei:5,elt:[],abov:1,error:5,"na\u00efv":[],multiple_endpoint:[],pythonlistdatastor:[4,5,2],azerti:5,loos:[12,2],finnali:5,coher:3,advantag:[12,2,14,9],applic:[3,1,4,6,8,14,2,9],implment:4,dictonari:4,ratelimit:2,kwarg:[1,2,12],allawi:[],"__init__":[5,2,12],present:[4,2],thei:[4,5,7,2,14,12],grant:5,perform:5,make:7,same:[4,3,8],preserv:9,tutori:[],sqlite3:[4,6,12,2,14],apiappauth:5,complet:12,http:[3,1,8,14,2,9],pouet:[],hanl:4,wivzfdlkckjkhiac:14,action:[4,5],rais:[3,5,2,12],user:[11,5],mani:[4,3,8],outbound:[],respons:[4,0,2,9,3],implement:7,whole:1,well:[3,12,14],inherit:[4,1,2,12],pk_field:[0,9],exampl:[],thi:[6,1,7,5],fiter:[12,2],yssmhxddqqsw:14,apikeyauthor:5,rout:[4,2],usual:4,authmodel:5,construct:2,identifi:[3,4,5,6,7,2,12],just:[3,4,5,2,12,9],object:[5,9],dissuss:[],distant:4,fmsabcujbsbdpaf:14,gather:[],expos:4,hint:[],point:[12,2],except:[5,2,12],param:2,exempl:[4,1,8,7],add:[3,4,8,14,11,12],other:[],els:5,save:[4,6,12,2],adress:6,modul:0,match:[12,2,14],build:1,real:[3,12,2],indentifi:5,ressource_config:[12,2],format:[],read:[4,7,5],pkfield:[4,0,12,2,6],mon:[3,8,9],gmt:[3,8,14,9],password:5,helpful:6,insert:14,like:[1,4,6,7,11,12],specif:[],manual:3,integ:[12,2],server:[3,8,14,9],either:[4,12,2],authorized_method:[],architectur:[],apicontrol:2,twitter:[4,12],creation:[3,12,2],some:[3,1,4,7,8,2,12],back:[12,2],intern:9,use_reload:[4,0,11,6],peter:3,kawrg:[],insensit:[],definit:[],totali:4,werkzeug:[0,3,4,6,8,14,2,11,9],adressebook:[],backend:[],localhost:[3,8,14,9],refer:[],machin:4,who:9,run:[4,6],spcdbtmdii:14,stringfield:[4,0,5,12,6],autoincr:[12,2],step:[],offset:7,"__name__":[4,0,6],post:[0,1,3,4,6,7,8,14,2],plug:9,about:[4,11,1,12],obj:[0,12,2,9],memori:[4,12,2],page:[4,0],integr:4,constructor:11,disabl:4,block:[],subset:[12,2],paginate_bi:[12,2],own:[4,1,2,7,12],"float":[12,2],empti:1,ensur:[3,8,6],chang:[3,4,8,14,2,12,9],your:[],manag:[4,6,7,2],accordingli:2,wai:[4,5,2,14,12,9],support:[12,2],use_debugg:[4,0,11,6],custom:[5,12],start:[4,1,2,12],includ:0,suit:[4,12],"function":5,properli:[12,2],max_result:2,form:2,tupl:2,link:[],"true":[0,4,6,5,11,12],count:7,commun:[4,12,2],made:2,possibl:[11,1,2,12],"default":9,start_respons:2,displai:4,trt:14,record:14,below:1,limit:[1,4,7,2,14,12],otherwis:[4,5,2],problem:[3,1],unrel:4,expect:[3,9],datastoreinst:4,featur:[0,7],creat:[],"int":[12,2,14],tqfpmcbqd:14,"abstract":2,deep:[],repres:[12,2,9],exist:[3,4,5,8,2,12],file:[12,2],request:[3,1,5,7,8,14,2],curl:[3,1,8,14,9],offset_kei:[1,2,14],check:[3,4,5,8,2,12,9],fill:14,again:3,herit:2,know:14,automaticali:2,when:[4,1,7,9,14],detail:7,rest_api_framework:[0,1,4,6,8,14,2,11,12],field:[],valid:[],test:[4,12,2],remove_id:[0,14,9],you:[3,1,2,4,5,8,14,10,11,12,9],ressource_nam:[0,4,6,7,8,14],nice:14,relat:[7,12],finali:4,zhjqtgyoeuzmcn:14,multimpl:11,developp:11,meaning:9,queri:[12,2],sql:[4,12,2],endoint:11,receiv:[1,2,12],longer:3,dog:1,descript:[],place:[3,6],time:[3,5,2,12,14],far:[6,9,11]},objtypes:{"0":"py:module","1":"py:class","2":"py:method"},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"]},filenames:["index","pagination","references","using_user_endpoint","introduction","authentication","first_step","controller","adding_validator_datastore","representing_data","related_ressources","multiple_endpoint","datastore","tutorial","work_with_pagination"],titles:["Python Rest Api Framework’s documentation","Paginate a ressource","REST API Framework API references","Playing with the newly created endpoint","What is Python REST API Framework","Authentication and Authorization","First Step Building a user endpoint","Controllers","Adding validators to your DataStore","Show data to users","Linking ressource together","Using more than a single endpoint","Datastore","Tutorial building an adressebook API","Working with Pagination"],objects:{"rest_api_framework.datastore.sql.SQLiteDataStore":{get:[12,2,1,""],get_connector:[12,2,1,""],create:[12,2,1,""],paginate:[12,2,1,""],update:[12,2,1,""],get_list:[12,2,1,""],filter:[12,2,1,""],"delete":[12,2,1,""]},"rest_api_framework.datastore.base.DataStore":{get:[12,2,1,""],create:[12,2,1,""],paginate:[12,2,1,""],update:[12,2,1,""],get_list:[12,2,1,""],filter:[12,2,1,""],validate_fields:[12,2,1,""],validate:[12,2,1,""],"delete":[12,2,1,""]},"rest_api_framework.controllers.ApiController":{index:[2,2,1,""],get:[2,2,1,""],create:[2,2,1,""],paginate:[2,2,1,""],update:[2,2,1,""],get_list:[2,2,1,""],unique_uri:[2,2,1,""],"delete":[2,2,1,""]},"rest_api_framework.datastore.simple.PythonListDataStore":{get:[2,2,1,""],update:[2,2,1,""],get_list:[2,2,1,""]},"rest_api_framework.datastore.sql":{SQLiteDataStore:[12,1,1,""]},"rest_api_framework.datastore.simple":{PythonListDataStore:[2,1,1,""]},"rest_api_framework.controllers":{Controller:[2,1,1,""],WSGIWrapper:[2,1,1,""],ApiController:[2,1,1,""],WSGIDispatcher:[2,1,1,""]},rest_api_framework:{controllers:[2,0,0,"-"]},"rest_api_framework.datastore.base":{DataStore:[12,1,1,""]},"rest_api_framework.controllers.Controller":{load_urls:[2,2,1,""],make_options:[2,2,1,""]},"rest_api_framework.authentication.Authentication":{get_user:[2,2,1,""]},"rest_api_framework.pagination.Pagination":{paginate:[2,2,1,""]},"rest_api_framework.controllers.WSGIWrapper":{dispatch_request:[2,2,1,""],wsgi_app:[2,2,1,""]},"rest_api_framework.pagination":{Pagination:[2,1,1,""]},"rest_api_framework.authentication":{Authentication:[2,1,1,""]}},titleterms:{show:9,rest:[4,0,2],through:14,paramet:7,how:[4,5],should:[],other:1,format:9,python:[4,0],framework:[4,0,2],list:3,authent:[4,5,2],updat:3,where:4,summari:6,mandatori:7,what:4,databas:4,newli:3,delet:3,adressebook:13,sqlitedatastor:2,method:1,refer:2,full:0,chose:6,gener:9,here:4,step:6,base:2,offset:1,filter:[3,14],thing:9,tutori:13,pagin:[1,2,14],implement:1,view:[4,6],first:6,api:[4,0,2,13],miss:3,your:[12,8],backend:5,from:4,describ:12,avail:12,handl:3,more:11,"function":9,option:[4,7],link:10,togeth:10,than:11,count:1,endpoint:[3,11,6],uniquetogeth:8,work:[0,14],defin:[6,9],ressourc:[4,1,12,10],error:3,fixtur:14,browser:14,control:[4,7,2],quickstart:4,creat:[3,12,14,6],argument:7,indic:0,tabl:0,welcom:[],author:5,make:9,get:3,invalid:3,field:4,valid:[4,8],build:[6,13],document:0,simpl:[4,2],singl:11,architectur:4,max:1,object:14,plai:3,user:[3,9,6],datastor:[4,6,12,8,2],data:[3,9],exampl:[0,5],thi:[],model:[4,6,12]}}) \ No newline at end of file diff --git a/docs/build/tutorial.html b/docs/build/tutorial.html index 66b577a..6838f03 100644 --- a/docs/build/tutorial.html +++ b/docs/build/tutorial.html @@ -68,9 +68,8 @@Your endpoint is responding but does not have any data. Let’s add @@ -102,14 +104,14 @@
The list of users is also updated:
-curl -i "http://localhost:5000/users/1/"
+curl -i "http://localhost:5000/users/"
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 83
Server: Werkzeug/0.8.3 Python/2.7.2
Date: Mon, 14 Oct 2013 17:03:00 GMT
-[{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]
+{"meta": {"filters": {}}, "object_list": [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]}
curl -i "http://localhost:5000/users/2/"
+HTTP/1.0 404 NOT FOUND
+Content-Type: application/json
+Connection: close
+Server: Werkzeug/0.8.3 Python/2.7.2
+Date: Tue, 15 Oct 2013 11:16:33 GMT
+
+{"error": "<p>The requested URL was not found on the server.</p><p>If you entered the URL manually please check your spelling and try again.</p>"}
+
And the list is also updated:
+curl -i "http://localhost:5000/users/"
+HTTP/1.0 200 OK
+Content-Type: application/json
+Content-Length: 125
+Server: Werkzeug/0.8.3 Python/2.7.2
+Date: Tue, 15 Oct 2013 11:17:46 GMT
+
+{"meta": {"filters": {}}, "object_list": [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]}
+
Let’s go another time to the creation process:
curl -i -H "Content-type: application/json" -X POST -d '{"first_name":"Steve", "last_name": "Roger"}' http://localhost:5000/users/
HTTP/1.0 201 CREATED
@@ -151,23 +174,59 @@ Update a Usercurl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Captain", "last_name": "America"}' http://localhost:5000/users/3/
+curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Capitain", "last_name": "America"}' http://localhost:5000/users/3/
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 58
Server: Werkzeug/0.8.3 Python/2.7.2
Date: Mon, 14 Oct 2013 20:57:47 GMT
+
+{"first_name": "Capitain", "last_name": "America", "id": 3, "ressource_uri": "/users/3/"}
-Partial update is also possible:
-curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Cap tain"}' http://localhost:5000/users/3/
+Argh! Thats a typo. the fist name is “Captain”, not “Capitain”. Let’s
+correct this:
+curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Captain"}' http://localhost:5000/users/3/
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 59
Server: Werkzeug/0.8.3 Python/2.7.2
Date: Mon, 14 Oct 2013 21:08:04 GMT
+
+{"first_name": "Captain", "last_name": "America", "id": 3, "ressource_uri": "/users/3/"}
+
+
+Filtering¶
+Ressources can be filtered easily using parameters:
+curl -i "http://localhost:5000/users/?last_name=America"
+HTTP/1.0 200 OK
+Content-Type: application/json
+Content-Length: 236
+Server: Werkzeug/0.8.3 Python/2.7.2
+Date: Tue, 15 Oct 2013 12:07:21 GMT
+
+{"meta": {"filters": {"last_name": "America"}}, "object_list":
+[{"first_name": "Joe", "last_name": "America", "id": 1,
+"ressource_uri": "/users/1/"}, {"first_name": "Bob", "last_name":
+"America", "id": 3, "ressource_uri": "/users/3/"}]
+
+
+Multiple filters are allowed:
+curl -i "http://localhost:5000/users/?last_name=America&first_name=Joe"
+HTTP/1.0 200 OK
+Content-Type: application/json
+Content-Length: 171
+Server: Werkzeug/0.8.3 Python/2.7.2
+Date: Tue, 15 Oct 2013 12:09:32 GMT
+
+{"meta": {"filters": {"first_name": "Joe", "last_name": "America"}},
+"object_list": [{"first_name": "Joe", "last_name": "America", "id": 1,
+"ressource_uri": "/users/1/"}]}
+
+
+
Error handling¶
Of course, If data is not formated as expected by the API, the base
@@ -224,9 +283,8 @@
Table Of Contents
Create a user
List and Get
Delete a user
-
-
-Update a User
+- Update a User
+- Filtering
- Error handling
- Missing data
- Invalid Data
diff --git a/docs/build/work_with_pagination.html b/docs/build/work_with_pagination.html
new file mode 100644
index 0000000..cb199ef
--- /dev/null
+++ b/docs/build/work_with_pagination.html
@@ -0,0 +1,299 @@
+
+
+
+
+
+
+
+ Working with Pagination — Python Rest Api Framework 0.1 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Working with Pagination¶
+
+Creating fixtures¶
+When your address book will be full of entry, you will need to add a
+pagination on your API. As it is a common need, REST API Framework
+implement a very easy way of doing so.
+Before you can play with the pagination process, you will need to
+create more data. You can create those records the way you want:
+
+- direct insert into the database
+
+sqlite3 adress_book.db
+INSERT INTO users VALUES ("Nick", "Furry", 6);
+
+
+
+- using the datastore directly
+
+store = SQLiteDataStore({"name": "adress_book.db", "table": "users"}, UserModel)
+store.create({"first_name": "Nick", "last_name": "Furry"})
+
+
+
+- using your API
+
+curl -i -H "Content-type: application/json" -X POST -d '{"first_name": "Nick", "last_name": "Furry"}' http://localhost:5000/users/
+
+
+each on of those methods have advantages and disavantages but they all
+make the work done. For this example, I propose to use the well know
+requests package with a script to create a bunch of random records:
+For this to work you need to install resquests : http://docs.python-requests.org/en/latest/user/install/#install
+import json
+import requests
+import random
+import string
+
+def get_random():
+ return ''.join(
+ random.choice(
+ string.ascii_letters) for x in range(
+ int(random.random() * 20)
+ )
+ )
+
+for i in range(200):
+ requests.post("http://localhost:5000/users/", data=json.dumps({"first_name": get_random(), "last_name": get_random()}))
+
+
+
+
+Pagination¶
+Now your datastore is filled with more than 200 records, it’s time to
+paginate. To do so import Pagination and change the controller part of
+your app.
+from rest_api_framework.pagination import Pagination
+
+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"],
+ "options": {"pagination": Pagination(20)}
+ }
+
+ view = {"response_class": JsonResponse,
+ "options": {"formaters": ["add_ressource_uri", remove_id]}}
+
+
+and try your new pagination:
+curl -i "http://localhost:5000/users/"
+HTTP/1.0 200 OK
+Content-Type: application/json
+Content-Length: 1811
+Server: Werkzeug/0.8.3 Python/2.7.2
+Date: Tue, 15 Oct 2013 11:32:55 GMT
+
+{"meta": {"count": 20, "total_count": 802, "next": "?offset=20",
+"filters": {}, "offset": 0, "previous": "null"}, "object_list":
+[{"first_name": "Captain", "last_name": "America",
+"ressource_uri": "/users/1/"}, {"first_name": "Captain",
+"last_name": "America", "ressource_uri": "/users/3/"},
+{"first_name": "John", "last_name": "Doe", "ressource_uri":
+"/users/4/"}, {"first_name": "arRFOSYZT", "last_name": "",
+"ressource_uri": "/users/5/"}, {"first_name": "iUJsYORMuYeMUDy",
+"last_name": "TqFpmcBQD", "ressource_uri": "/users/6/"},
+{"first_name": "EU", "last_name": "FMSAbcUJBSBDPaF",
+"ressource_uri": "/users/7/"}, {"first_name": "mWAwamrMQARXW",
+"last_name": "yMNpEnYOPzY", "ressource_uri": "/users/8/"},
+{"first_name": "y", "last_name": "yNiKP", "ressource_uri":
+"/users/9/"}, {"first_name": "s", "last_name": "TRT",
+"ressource_uri": "/users/10/"}, {"first_name": "", "last_name":
+"zFUaBd", "ressource_uri": "/users/11/"}, {"first_name": "WA",
+"last_name": "priJ", "ressource_uri": "/users/12/"},
+{"first_name": "XvpLttDqFmR", "last_name": "liU", "ressource_uri":
+"/users/13/"}, {"first_name": "ZhJqTgYoEUzmcN", "last_name":
+"KKDqHJwJMxPSaTX", "ressource_uri": "/users/14/"}, {"first_name":
+"qvUxiKIATdKdkC", "last_name": "wIVzfDlKCkjkHIaC",
+"ressource_uri": "/users/15/"}, {"first_name": "YSSMHxdDQQsW",
+"last_name": "UaKCKgKsgEe", "ressource_uri": "/users/16/"},
+{"first_name": "EKLFTPJLKDINZio", "last_name": "nuilPTzHqattX",
+"ressource_uri": "/users/17/"}, {"first_name": "SPcDBtmDIi",
+"last_name": "MrytYqElXiIxA", "ressource_uri": "/users/18/"},
+{"first_name": "OHxNppXiYp", "last_name": "AUvUXFRPICsJIB",
+"ressource_uri": "/users/19/"}, {"first_name": "WBFGxnoe",
+"last_name": "KG", "ressource_uri": "/users/20/"}, {"first_name":
+"i", "last_name": "ggLOcKPpMfgvVGtv", "ressource_uri":
+"/users/21/"}]}
+
+
+
+
+Browsering Through Paginated objects¶
+Of course you get 20 records but the most usefull part is the meta
+key:
+{"meta":
+ {"count": 20,
+ "total_count": 802,
+ "next": "?offset=20",
+ "filters": {},
+ "offset": 0,
+ "previous": "null"}
+}
+
+
+You can use the “next” key to retreive the 20 next rows:
+curl -i "http://localhost:5000/users/?offset=20"
+HTTP/1.0 200 OK
+Content-Type: application/json
+Content-Length: 1849
+Server: Werkzeug/0.8.3 Python/2.7.2
+Date: Tue, 15 Oct 2013 11:38:59 GMT
+
+
+{"meta": {"count": 20, "total_count": 802, "next": "?offset=40",
+"filters": {}, "offset": 20, "previous": "?offset=0"}, "object_list":
+[<snip for readability>]}
+
+
+Note
+The count and offset keywords can be easily changed to match your
+needs. pagination class may take an offset_key and count_key
+parameters. So if you prefer to use first_id and limit, you can
+change your Paginator class to do so:
+"options": {"pagination": Pagination(20,
+ offset_key="first_id",
+ count_key="limit")
+
+
+Wich will results in the following:
+curl -i "http://localhost:5000/users/"
+{"meta": {"first_id": 0, "total_count": 802, "next": "?first_id=20",
+"limit": 20, "filters": {}, "previous": "null"}, "object_list": [<snip
+for readability>]
+
+
+
+
+
+Pagination and Filters¶
+Pagination and filtering play nice together
+curl -i "http://localhost:5000/users/?last_name=America"
+HTTP/1.0 200 OK
+Content-Type: application/json
+Content-Length: 298
+Server: Werkzeug/0.8.3 Python/2.7.2
+Date: Tue, 15 Oct 2013 12:14:59 GMT
+
+{"meta": {"count": 20,
+ "total_count": 2,
+ "next": "null",
+ "filters": {"last_name": "America"},
+ "offset": 0,
+ "previous": "null"},
+ "object_list": [
+ {"first_name": "Joe",
+ "last_name": "America",
+ "ressource_uri": "/users/1/"},
+ {"first_name": "Bob",
+ "last_name": "America",
+ "ressource_uri": "/users/3/"}
+ ]
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/source/#related_ressources.rst# b/docs/source/#related_ressources.rst#
new file mode 100644
index 0000000..84fc2b9
--- /dev/null
+++ b/docs/source/#related_ressources.rst#
@@ -0,0 +1,7 @@
+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)
diff --git a/docs/source/.#related_ressources.rst b/docs/source/.#related_ressources.rst
new file mode 120000
index 0000000..67b9448
--- /dev/null
+++ b/docs/source/.#related_ressources.rst
@@ -0,0 +1 @@
+yohann@MacBook-Air-de-Yohann.local.36008
\ No newline at end of file
diff --git a/docs/source/index.rst b/docs/source/index.rst
index d4c2c38..d43f9fa 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -3,8 +3,15 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
-Welcome to Python Rest Api Framework's documentation!
-=====================================================
+Python Rest Api Framework's documentation
+=========================================
+
+Python REST API framework is a set of utilities based on werkzeug to
+easily build Restful API with a MVC pattern. Main features includes:
+Pagination, Authentication, Authorization, Filters, Partials Response,
+Error handling, data validators, data formaters...
+and more...
+
Contents:
@@ -13,12 +20,70 @@ Contents:
introduction
tutorial
- datastore
- controller
- pagination
- authentication
- multiple_endpoint
- references
+.. datastore
+.. controller
+.. pagination
+.. authentication
+.. multiple_endpoint
+.. references
+
+
+A Full working example
+----------------------
+
+.. 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
+ from rest_api_framework.datastore.validators import UniqueTogether
+ from rest_api_framework.pagination import Pagination
+
+
+ class UserModel(models.Model):
+ """
+ Define how to handle and validate your data.
+ """
+ fields = [models.StringField(name="first_name", required=True),
+ models.StringField(name="last_name", required=True),
+ models.PkField(name="id", required=True)
+ ]
+
+
+ def remove_id(response, obj):
+ """
+ Do not show the id in the response.
+ """
+ obj.pop(response.model.pk_field.name)
+ return obj
+
+
+ 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"],
+ "options": {"pagination": Pagination(20)}
+ }
+
+ view = {"response_class": JsonResponse,
+ "options": {"formaters": ["add_ressource_uri", remove_id]}}
+
+
+ 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)
Indices and tables
==================
@@ -26,4 +91,3 @@ Indices and tables
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
-
diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst
index c3f3823..90ce8cb 100644
--- a/docs/source/introduction.rst
+++ b/docs/source/introduction.rst
@@ -346,5 +346,5 @@ is the same as with the PythonListDataStore.
Where to go from here
---------------------
-* :doc:`Authentication and Authorization `
-* :doc:`multiple_endpoint`
+.. * :doc:`Authentication and Authorization `
+.. * :doc:`multiple_endpoint`
diff --git a/docs/source/representing_data.rst b/docs/source/representing_data.rst
index bb030d6..da59b3d 100644
--- a/docs/source/representing_data.rst
+++ b/docs/source/representing_data.rst
@@ -48,7 +48,7 @@ You can check that it work as expected:
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",
+ {"first_name": "Captain", "last_name": "America",
"ressource_uri": "/users/1/"}
Make things generics
@@ -77,6 +77,11 @@ Your code then become:
obj.pop(response.model.pk_field.name)
return obj
-And reuse this formatter as long as you need
+And reuse this formatter as long as you need.
-Next :doc:`related_ressources`
+Formaters are here to help you build clean and meaningful ressources
+representations. It should hide internal representation of your
+ressources and return all of the fields needed to manipulate and
+represent your data.
+
+Next :doc:`work_with_pagination`
diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst
index 6abcfa8..ccaefd5 100644
--- a/docs/source/tutorial.rst
+++ b/docs/source/tutorial.rst
@@ -8,4 +8,5 @@ Tutorial building an adressebook API
using_user_endpoint
adding_validator_datastore
representing_data
+ work_with_pagination
related_ressources
diff --git a/docs/source/using_user_endpoint.rst b/docs/source/using_user_endpoint.rst
index 28119fe..5994155 100644
--- a/docs/source/using_user_endpoint.rst
+++ b/docs/source/using_user_endpoint.rst
@@ -9,9 +9,11 @@ First you can check that your endpoint is up
HTTP/1.0 200 OK
Content-Type: application/json
- Content-Length: 2
+ Content-Length: 44
Server: Werkzeug/0.8.3 Python/2.7.2
- Date: Mon, 14 Oct 2013 12:52:22 GMT
+ Date: Tue, 15 Oct 2013 11:13:44 GMT
+
+ {"meta": {"filters": {}}, "object_list": []}
Your endpoint is responding but does not have any data. Let's add
some:
@@ -59,14 +61,14 @@ The list of users is also updated:
.. code-block:: bash
- curl -i "http://localhost:5000/users/1/"
+ curl -i "http://localhost:5000/users/"
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 83
Server: Werkzeug/0.8.3 Python/2.7.2
Date: Mon, 14 Oct 2013 17:03:00 GMT
- [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]
+ {"meta": {"filters": {}}, "object_list": [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]}
Delete a user
-------------
@@ -94,8 +96,35 @@ and now delete it:
Server: Werkzeug/0.8.3 Python/2.7.2
Date: Mon, 14 Oct 2013 20:41:46 GMT
+You can check that the user no longer exists:
+
+.. code-block:: bash
+
+ curl -i "http://localhost:5000/users/2/"
+ HTTP/1.0 404 NOT FOUND
+ Content-Type: application/json
+ Connection: close
+ Server: Werkzeug/0.8.3 Python/2.7.2
+ Date: Tue, 15 Oct 2013 11:16:33 GMT
+
+ {"error": "The requested URL was not found on the server.
If you entered the URL manually please check your spelling and try again.
"}
+
+And the list is also updated:
+
+.. code-block:: bash
+
+ curl -i "http://localhost:5000/users/"
+ HTTP/1.0 200 OK
+ Content-Type: application/json
+ Content-Length: 125
+ Server: Werkzeug/0.8.3 Python/2.7.2
+ Date: Tue, 15 Oct 2013 11:17:46 GMT
+
+ {"meta": {"filters": {}}, "object_list": [{"first_name": "John", "last_name": "Doe", "id": 1, "ressource_uri": "/users/1/"}]}
+
+
Update a User
-=============
+-------------
Let's go another time to the creation process:
@@ -114,24 +143,63 @@ America. Let's update this user:
.. code-block:: bash
- curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Captain", "last_name": "America"}' http://localhost:5000/users/3/
+ curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Capitain", "last_name": "America"}' http://localhost:5000/users/3/
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 58
Server: Werkzeug/0.8.3 Python/2.7.2
Date: Mon, 14 Oct 2013 20:57:47 GMT
-Partial update is also possible:
+ {"first_name": "Capitain", "last_name": "America", "id": 3, "ressource_uri": "/users/3/"}
+
+Argh! Thats a typo. the fist name is "Captain", not "Capitain". Let's
+correct this:
.. code-block:: bash
- curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Cap tain"}' http://localhost:5000/users/3/
+ curl -i -H "Content-type: application/json" -X PUT -d '{"first_name":"Captain"}' http://localhost:5000/users/3/
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 59
Server: Werkzeug/0.8.3 Python/2.7.2
Date: Mon, 14 Oct 2013 21:08:04 GMT
+ {"first_name": "Captain", "last_name": "America", "id": 3, "ressource_uri": "/users/3/"}
+
+
+Filtering
+---------
+
+Ressources can be filtered easily using parameters:
+
+.. code-block:: bash
+
+ curl -i "http://localhost:5000/users/?last_name=America"
+ HTTP/1.0 200 OK
+ Content-Type: application/json
+ Content-Length: 236
+ Server: Werkzeug/0.8.3 Python/2.7.2
+ Date: Tue, 15 Oct 2013 12:07:21 GMT
+
+ {"meta": {"filters": {"last_name": "America"}}, "object_list":
+ [{"first_name": "Joe", "last_name": "America", "id": 1,
+ "ressource_uri": "/users/1/"}, {"first_name": "Bob", "last_name":
+ "America", "id": 3, "ressource_uri": "/users/3/"}]
+
+Multiple filters are allowed:
+
+.. code-block:: bash
+
+ curl -i "http://localhost:5000/users/?last_name=America&first_name=Joe"
+ HTTP/1.0 200 OK
+ Content-Type: application/json
+ Content-Length: 171
+ Server: Werkzeug/0.8.3 Python/2.7.2
+ Date: Tue, 15 Oct 2013 12:09:32 GMT
+
+ {"meta": {"filters": {"first_name": "Joe", "last_name": "America"}},
+ "object_list": [{"first_name": "Joe", "last_name": "America", "id": 1,
+ "ressource_uri": "/users/1/"}]}
Error handling
--------------
diff --git a/docs/source/work_with_pagination.rst b/docs/source/work_with_pagination.rst
new file mode 100644
index 0000000..eebe89a
--- /dev/null
+++ b/docs/source/work_with_pagination.rst
@@ -0,0 +1,215 @@
+Working with Pagination
+=======================
+
+Creating fixtures
+-----------------
+
+When your address book will be full of entry, you will need to add a
+pagination on your API. As it is a common need, REST API Framework
+implement a very easy way of doing so.
+
+Before you can play with the pagination process, you will need to
+create more data. You can create those records the way you want:
+
+* direct insert into the database
+
+.. code-block:: bash
+
+ sqlite3 adress_book.db
+ INSERT INTO users VALUES ("Nick", "Furry", 6);
+
+* using the datastore directly
+
+.. code-block:: python
+
+ store = SQLiteDataStore({"name": "adress_book.db", "table": "users"}, UserModel)
+ store.create({"first_name": "Nick", "last_name": "Furry"})
+
+* using your API
+
+.. code-block:: python
+
+ curl -i -H "Content-type: application/json" -X POST -d '{"first_name": "Nick", "last_name": "Furry"}' http://localhost:5000/users/
+
+each on of those methods have advantages and disavantages but they all
+make the work done. For this example, I propose to use the well know
+requests package with a script to create a bunch of random records:
+
+For this to work you need to install resquests : http://docs.python-requests.org/en/latest/user/install/#install
+
+.. code-block:: python
+
+ import json
+ import requests
+ import random
+ import string
+
+ def get_random():
+ return ''.join(
+ random.choice(
+ string.ascii_letters) for x in range(
+ int(random.random() * 20)
+ )
+ )
+
+ for i in range(200):
+ requests.post("http://localhost:5000/users/", data=json.dumps({"first_name": get_random(), "last_name": get_random()}))
+
+Pagination
+----------
+
+Now your datastore is filled with more than 200 records, it's time to
+paginate. To do so import Pagination and change the controller part of
+your app.
+
+.. code-block:: python
+
+ from rest_api_framework.pagination import Pagination
+
+ 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"],
+ "options": {"pagination": Pagination(20)}
+ }
+
+ view = {"response_class": JsonResponse,
+ "options": {"formaters": ["add_ressource_uri", remove_id]}}
+
+and try your new pagination:
+
+.. code-block:: bash
+
+ curl -i "http://localhost:5000/users/"
+ HTTP/1.0 200 OK
+ Content-Type: application/json
+ Content-Length: 1811
+ Server: Werkzeug/0.8.3 Python/2.7.2
+ Date: Tue, 15 Oct 2013 11:32:55 GMT
+
+ {"meta": {"count": 20, "total_count": 802, "next": "?offset=20",
+ "filters": {}, "offset": 0, "previous": "null"}, "object_list":
+ [{"first_name": "Captain", "last_name": "America",
+ "ressource_uri": "/users/1/"}, {"first_name": "Captain",
+ "last_name": "America", "ressource_uri": "/users/3/"},
+ {"first_name": "John", "last_name": "Doe", "ressource_uri":
+ "/users/4/"}, {"first_name": "arRFOSYZT", "last_name": "",
+ "ressource_uri": "/users/5/"}, {"first_name": "iUJsYORMuYeMUDy",
+ "last_name": "TqFpmcBQD", "ressource_uri": "/users/6/"},
+ {"first_name": "EU", "last_name": "FMSAbcUJBSBDPaF",
+ "ressource_uri": "/users/7/"}, {"first_name": "mWAwamrMQARXW",
+ "last_name": "yMNpEnYOPzY", "ressource_uri": "/users/8/"},
+ {"first_name": "y", "last_name": "yNiKP", "ressource_uri":
+ "/users/9/"}, {"first_name": "s", "last_name": "TRT",
+ "ressource_uri": "/users/10/"}, {"first_name": "", "last_name":
+ "zFUaBd", "ressource_uri": "/users/11/"}, {"first_name": "WA",
+ "last_name": "priJ", "ressource_uri": "/users/12/"},
+ {"first_name": "XvpLttDqFmR", "last_name": "liU", "ressource_uri":
+ "/users/13/"}, {"first_name": "ZhJqTgYoEUzmcN", "last_name":
+ "KKDqHJwJMxPSaTX", "ressource_uri": "/users/14/"}, {"first_name":
+ "qvUxiKIATdKdkC", "last_name": "wIVzfDlKCkjkHIaC",
+ "ressource_uri": "/users/15/"}, {"first_name": "YSSMHxdDQQsW",
+ "last_name": "UaKCKgKsgEe", "ressource_uri": "/users/16/"},
+ {"first_name": "EKLFTPJLKDINZio", "last_name": "nuilPTzHqattX",
+ "ressource_uri": "/users/17/"}, {"first_name": "SPcDBtmDIi",
+ "last_name": "MrytYqElXiIxA", "ressource_uri": "/users/18/"},
+ {"first_name": "OHxNppXiYp", "last_name": "AUvUXFRPICsJIB",
+ "ressource_uri": "/users/19/"}, {"first_name": "WBFGxnoe",
+ "last_name": "KG", "ressource_uri": "/users/20/"}, {"first_name":
+ "i", "last_name": "ggLOcKPpMfgvVGtv", "ressource_uri":
+ "/users/21/"}]}
+
+Browsering Through Paginated objects
+------------------------------------
+
+Of course you get 20 records but the most usefull part is the meta
+key:
+
+.. code-block:: json
+
+ {"meta":
+ {"count": 20,
+ "total_count": 802,
+ "next": "?offset=20",
+ "filters": {},
+ "offset": 0,
+ "previous": "null"}
+ }
+
+You can use the "next" key to retreive the 20 next rows:
+
+.. code-block:: bash
+
+ curl -i "http://localhost:5000/users/?offset=20"
+ HTTP/1.0 200 OK
+ Content-Type: application/json
+ Content-Length: 1849
+ Server: Werkzeug/0.8.3 Python/2.7.2
+ Date: Tue, 15 Oct 2013 11:38:59 GMT
+
+.. code-block:: json
+
+ {"meta": {"count": 20, "total_count": 802, "next": "?offset=40",
+ "filters": {}, "offset": 20, "previous": "?offset=0"}, "object_list":
+ []}
+
+.. note::
+
+ The count and offset keywords can be easily changed to match your
+ needs. pagination class may take an offset_key and count_key
+ parameters. So if you prefer to use first_id and limit, you can
+ change your Paginator class to do so:
+
+ .. code-block:: python
+
+ "options": {"pagination": Pagination(20,
+ offset_key="first_id",
+ count_key="limit")
+
+ Wich will results in the following:
+
+ .. code-block:: bash
+
+ curl -i "http://localhost:5000/users/"
+ {"meta": {"first_id": 0, "total_count": 802, "next": "?first_id=20",
+ "limit": 20, "filters": {}, "previous": "null"}, "object_list": []
+
+
+Pagination and Filters
+----------------------
+
+Pagination and filtering play nice together
+
+.. code-block:: bash
+
+ curl -i "http://localhost:5000/users/?last_name=America"
+ HTTP/1.0 200 OK
+ Content-Type: application/json
+ Content-Length: 298
+ Server: Werkzeug/0.8.3 Python/2.7.2
+ Date: Tue, 15 Oct 2013 12:14:59 GMT
+
+ {"meta": {"count": 20,
+ "total_count": 2,
+ "next": "null",
+ "filters": {"last_name": "America"},
+ "offset": 0,
+ "previous": "null"},
+ "object_list": [
+ {"first_name": "Joe",
+ "last_name": "America",
+ "ressource_uri": "/users/1/"},
+ {"first_name": "Bob",
+ "last_name": "America",
+ "ressource_uri": "/users/3/"}
+ ]
+ }
diff --git a/rest_api_framework/controllers.py b/rest_api_framework/controllers.py
index 145d4e7..cd2c317 100644
--- a/rest_api_framework/controllers.py
+++ b/rest_api_framework/controllers.py
@@ -130,11 +130,22 @@ def paginate(self, request):
offset, count = None, None
request_kwargs = request.values.to_dict()
filters = request_kwargs
+
objs = self.datastore.get_list(offset=offset,
count=count,
**filters)
+ if self.pagination:
+ total = self.datastore.count(**filters)
+ meta = self.pagination.get_metadata(count=count,
+ offset=offset,
+ total=total,
+ **filters)
+ else:
+ meta = {"filters": {}}
+ for k, v in filters.iteritems():
+ meta["filters"][k] = v
- return self.view(objs=objs, status=200)
+ return self.view(objs=objs, meta=meta, status=200)
def get_list(self, request):
"""
@@ -198,7 +209,11 @@ def update(self, request, identifier):
"""
obj = self.datastore.get(identifier=identifier)
- obj = self.datastore.update(obj, json.loads(request.data))
+ try:
+ obj = self.datastore.update(obj, json.loads(request.data))
+ except ValueError:
+ raise BadRequest
+
return self.view(
objs=obj,
status=200)
diff --git a/rest_api_framework/datastore/simple.py b/rest_api_framework/datastore/simple.py
index 87fd66a..286a5d9 100644
--- a/rest_api_framework/datastore/simple.py
+++ b/rest_api_framework/datastore/simple.py
@@ -71,6 +71,9 @@ def create(self, data):
self.data.append(obj)
return obj[self.model.pk_field.name]
+ def count(self, **kwargs):
+ return len(self.filter(**kwargs))
+
def update(self, obj, data):
"""
Update a single object
diff --git a/rest_api_framework/datastore/sql.py b/rest_api_framework/datastore/sql.py
index 9689571..db33f94 100644
--- a/rest_api_framework/datastore/sql.py
+++ b/rest_api_framework/datastore/sql.py
@@ -69,6 +69,7 @@ def __init__(self, ressource_config, model, **options):
conn.close()
self.fields = self.model.get_fields()
+
def get_connector(self):
"""
return a sqlite3 connection to communicate with the table
@@ -86,6 +87,32 @@ def filter(self, **kwargs):
kwargs['query'] += ' FROM {0}'
return kwargs
+ def count(self, **data):
+ cdt = self.build_conditions(data)
+ if len(cdt) == 0:
+ query = "SELECT COUNT (*) FROM {0}".format(
+ self.ressource_config['table'])
+ else:
+ cdt = " AND ".join(cdt)
+ query = "SELECT COUNT (*) FROM {0} WHERE {1}".format(
+ self.ressource_config['table'],
+ cdt
+ )
+ cursor = self.get_connector().cursor()
+ cursor.execute(query)
+ return cursor.fetchone()[0]
+
+ def build_conditions(self, data):
+ return [
+ ["{0}='{1}'".format(
+ e[0], e[1]) for e in condition.iteritems()
+ ][0] for condition in self.get_conditions(data)]
+
+ def get_conditions(self, data):
+ return [
+ {k: v} for k, v in data.iteritems() if k not in ["query", "fields"]
+ ]
+
def paginate(self, data, **kwargs):
"""
paginate the result of filter using ids limits. Obviously, to
@@ -93,10 +120,9 @@ def paginate(self, data, **kwargs):
receive from the last call on this method. The max number of
row this method can give back depend on the paginate_by option.
"""
- print kwargs
+
+ where_query = self.build_conditions(data)
args = []
- where_query = [
- "{0}='{1}'".format(k, v) for k, v in data.iteritems() if k not in ["query", "fields"]]
limit = kwargs.pop("end", None)
if kwargs.get("start", None):
where_query.append(" id >=?")
@@ -105,14 +131,14 @@ def paginate(self, data, **kwargs):
if len(where_query) > 0:
data["query"] += " WHERE "
data["query"] += " AND ".join(where_query)
+ cursor = self.get_connector().cursor()
# a hook for ordering
data["query"] += " ORDER BY id ASC"
if limit:
data["query"] += " LIMIT {0}".format(limit)
- cursor = self.get_connector().cursor()
- print data["query"]
+
cursor.execute(data["query"].format(self.ressource_config['table']),
tuple(args)
)
@@ -210,11 +236,12 @@ def update(self, obj, data):
cursor = conn.cursor()
update = " ,".join(["{0}='{1}'".format(f, v) for f, v in zip(fields,
values)])
- query = "update {0} set {1}".format(
+ query = "update {0} set {1} WHERE {2}={3}".format(
self.ressource_config["table"],
- update
+ update,
+ self.model.pk_field.name,
+ obj[self.model.pk_field.name]
)
- print query
cursor.execute(query)
conn.commit()
conn.close()
diff --git a/rest_api_framework/pagination.py b/rest_api_framework/pagination.py
index f55cd14..4694979 100644
--- a/rest_api_framework/pagination.py
+++ b/rest_api_framework/pagination.py
@@ -27,3 +27,29 @@ def paginate(self, request):
if count > self.max:
count = self.max
return offset, count, request_kwargs
+
+ def get_metadata(self, total=0, offset=0, count=0, **filters):
+ meta = {self.offset_key: offset,
+ self.count_key: count,
+ "total_count": total,
+ "filters": {}
+ }
+ for k, v in filters.iteritems():
+ meta["filters"][k] = v
+ if offset == 0:
+ meta['previous'] = "null"
+ else:
+ meta["previous"] = offset - count
+ if meta["previous"] < 0:
+ meta["previous"] = 0
+ if meta['previous'] != "null":
+ meta["previous"] = "?{0}={1}".format(self.offset_key,
+ meta["previous"])
+
+ meta["next"] = offset + count
+ if meta["next"] > total:
+ meta["next"] = "null"
+ if meta['next'] != "null":
+ meta["next"] = "?{0}={1}".format(self.offset_key,
+ meta["next"])
+ return meta
diff --git a/rest_api_framework/views.py b/rest_api_framework/views.py
index 47c37a3..eed2efd 100644
--- a/rest_api_framework/views.py
+++ b/rest_api_framework/views.py
@@ -28,18 +28,31 @@ def __init__(self, model, ressource_name,
def __call__(self, *args, **kwargs):
+ meta = None
+
+ if "meta" in kwargs:
+ meta = kwargs.pop("meta")
+
if "objs" in kwargs:
objs = self.format(kwargs.pop('objs'))
- return Response(json.dumps(objs),
+ if meta:
+ response = {"meta": meta,
+ "object_list": objs}
+ else:
+ response = objs
+ return Response(json.dumps(response),
mimetype="application/json",
**kwargs)
else:
- return Response(*args,
- mimetype="application/json",
- **kwargs)
+ response = ""
+ if args:
+ response = json.dumps(*args)
+ return Response(response,
+ mimetype="application/json",
+ **kwargs)
def make_options(self, **options):
- print options
+ pass
def format(self, objs):
diff --git a/setup.cfg b/setup.cfg
index 7b58525..2268c04 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -7,4 +7,5 @@ with-coverage=1
cover-xml=1
cover-xml-file=report_coverage.xml
cover-package=rest_api_framework
-stop=1
\ No newline at end of file
+stop=1
+nocapture=1
\ No newline at end of file
diff --git a/tests/controllers_test.py b/tests/controllers_test.py
index 14da42c..65d95cb 100644
--- a/tests/controllers_test.py
+++ b/tests/controllers_test.py
@@ -65,8 +65,7 @@ def test_get_list(self):
response_wrapper=BaseResponse)
resp = client.get("/address/")
self.assertEqual(resp.status_code, 200)
- print resp.data
- self.assertIsInstance(json.loads(resp.data), list)
+ self.assertIsInstance(json.loads(resp.data)["object_list"], list)
def test_get(self):
client = Client(WSGIDispatcher([ApiApp]),
@@ -230,7 +229,8 @@ def test_base_pagination(self):
client = Client(WSGIDispatcher([ApiApp]),
response_wrapper=BaseResponse)
resp = client.get("/address/")
- self.assertEqual(len(json.loads(resp.data)), 20)
+
+ self.assertEqual(len(json.loads(resp.data)["object_list"]), 20)
def test_base_pagination_count(self):
client = Client(WSGIDispatcher([ApiApp]),
@@ -242,13 +242,13 @@ def test_base_pagination_count_overflow(self):
client = Client(WSGIDispatcher([ApiApp]),
response_wrapper=BaseResponse)
resp = client.get("/address/?count=200")
- self.assertEqual(len(json.loads(resp.data)), 20)
+ self.assertEqual(len(json.loads(resp.data)["object_list"]), 20)
def test_base_pagination_offset(self):
client = Client(WSGIDispatcher([ApiApp]),
response_wrapper=BaseResponse)
resp = client.get("/address/?offset=2")
- self.assertEqual(json.loads(resp.data)[0]['ressource_uri'],
+ self.assertEqual(json.loads(resp.data)["object_list"][0]['ressource_uri'],
"/address/2/")
@@ -261,7 +261,7 @@ def test_base_pagination(self):
client.post("/address/",
data=json.dumps({"name": "bob", "age": 34}))
resp = client.get("/address/")
- self.assertEqual(len(json.loads(resp.data)), 20)
+ self.assertEqual(len(json.loads(resp.data)["object_list"]), 20)
os.remove("test.db")
def test_base_pagination_offset(self):
@@ -271,7 +271,7 @@ def test_base_pagination_offset(self):
client.post("/address/",
data=json.dumps({"name": "bob", "age": 34}))
resp = client.get("/address/?offset=2")
- self.assertEqual(json.loads(resp.data)[0]['id'], 2)
+ self.assertEqual(json.loads(resp.data)["object_list"][0]['id'], 2)
os.remove("test.db")
def test_base_pagination_count(self):
@@ -292,7 +292,7 @@ def test_base_pagination_count_offset(self):
data=json.dumps({"name": "bob", "age": 34}))
resp = client.get("/address/?count=2&offset=4")
self.assertEqual(len(json.loads(resp.data)), 2)
- self.assertEqual(json.loads(resp.data)[0]['id'], 4)
+ self.assertEqual(json.loads(resp.data)["object_list"][0]['id'], 4)
os.remove("test.db")
@@ -303,7 +303,7 @@ def test_get_partial_list(self):
response_wrapper=BaseResponse)
resp = client.get("/address/?fields=age")
# we only want "age". get_list add id, JsonResponse add ressource_uri
- self.assertEqual(len(json.loads(resp.data)[0].keys()), 3)
+ self.assertEqual(len(json.loads(resp.data)["object_list"][0].keys()), 3)
def test_get_partial_raise(self):
client = Client(WSGIDispatcher([PartialApiApp]),
@@ -322,8 +322,7 @@ def test_get_partial_sql(self):
resp = client.get("/address/?fields=age")
# we only want "age". get_list add id, JsonResponse add ressource_uri
- print resp
- self.assertEqual(len(json.loads(resp.data)[0].keys()), 3)
+ self.assertEqual(len(json.loads(resp.data)["object_list"][0].keys()), 3)
os.remove("test.db")
def test_get_partial_sql_raise(self):
diff --git a/tests/datastore_test.py b/tests/datastore_test.py
index 51c55e3..dfd2ba3 100644
--- a/tests/datastore_test.py
+++ b/tests/datastore_test.py
@@ -244,7 +244,6 @@ def test_pagination(self):
ApiModel)
for i in range(100):
store.create({"name": "bob", "age": 34})
- print store.get_list(count=10)
self.assertEqual(len(store.get_list(count=10)), 10)
self.assertEqual(store.get_list(count=10)[-1]["id"], 10)
self.assertEqual(store.get_list(offset=15)[0]["id"], 15)
@@ -279,7 +278,6 @@ def test_create(self):
self.assertEqual(store.create({"name": "bob", "age": 34}), 1)
self.assertEqual(store.create({"name": "bob", "age": 35}), 2)
self.assertEqual(store.create({"name": "bob", "age": 34}), 3)
- print store.get(3)
self.assertEqual(store.get(3)["id"], 3)
os.remove("test.db")