1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
|
.. _ref-non_orm_data_sources:
========================================
Using Tastypie With Non-ORM Data Sources
========================================
Much of this documentation demonstrates the use of Tastypie with Django's ORM.
You might think that Tastypie depended on the ORM, when in fact, it was
purpose-built to handle non-ORM data. This documentation should help you get
started providing APIs using other data sources.
Virtually all of the code that makes Tastypie actually process requests &
return data is within the ``Resource`` class. ``ModelResource`` is actually a
light wrapper around ``Resource`` that provides ORM-specific access. The
methods that ``ModelResource`` overrides are the same ones you'll need to
override when hooking up your data source.
Approach
========
When working with ``Resource``, many things are handled for you. All the
authentication/authorization/caching/serialization/throttling bits should work
as normal and Tastypie can support all the REST-style methods. Schemas &
discovery views all work the same as well.
What you don't get out of the box are the fields you're choosing to expose &
the lowest level data access methods. If you want a full read-write API, there
are nine methods you need to implement. They are:
* ``get_resource_uri``
* ``get_object_list``
* ``obj_get_list``
* ``obj_get``
* ``obj_create``
* ``obj_update``
* ``obj_delete_list``
* ``obj_delete``
* ``rollback``
If read-only is all you're exposing, you can cut that down to four methods to
override.
Using Riak for MessageResource
==============================
As an example, we'll take integrating with Riak_ (a Dynamo-like NoSQL store)
since it has both a simple API and demonstrate what hooking up to a
non-relational datastore looks like::
# We need a generic object to shove data in/get data from.
# Riak generally just tosses around dictionaries, so we'll lightly
# wrap that.
class RiakObject(object):
def __init__(self, initial=None):
self.__dict__['_data'] = {}
if hasattr(initial, 'items'):
self.__dict__['_data'] = initial
def __getattr__(self, name):
return self._data.get(name, None)
def __setattr__(self, name, value):
self.__dict__['_data'][name] = value
def to_dict(self):
return self._data
class MessageResource(Resource):
# Just like a Django ``Form`` or ``Model``, we're defining all the
# fields we're going to handle with the API here.
uuid = fields.CharField(attribute='uuid')
user_uuid = fields.CharField(attribute='user_uuid')
message = fields.CharField(attribute='message')
created = fields.IntegerField(attribute='created')
class Meta:
resource_name = 'riak'
object_class = RiakObject
authorization = Authorization()
# Specific to this resource, just to get the needed Riak bits.
def _client(self):
return riak.RiakClient()
def _bucket(self):
client = self._client()
# Note that we're hard-coding the bucket to use. Fine for
# example purposes, but you'll want to abstract this.
return client.bucket('messages')
# The following methods will need overriding regardless of your
# data source.
def get_resource_uri(self, bundle_or_obj):
kwargs = {
'resource_name': self._meta.resource_name,
}
if isinstance(bundle_or_obj, Bundle):
kwargs['pk'] = bundle_or_obj.obj.uuid
else:
kwargs['pk'] = bundle_or_obj.uuid
if self._meta.api_name is not None:
kwargs['api_name'] = self._meta.api_name
return self._build_reverse_url("api_dispatch_detail", kwargs=kwargs)
def get_object_list(self, request):
query = self._client().add('messages')
query.map("function(v) { var data = JSON.parse(v.values[0].data); return [[v.key, data]]; }")
results = []
for result in query.run():
new_obj = RiakObject(initial=result[1])
new_obj.uuid = result[0]
results.append(new_obj)
return results
def obj_get_list(self, request=None, **kwargs):
# Filtering disabled for brevity...
return self.get_object_list(request)
def obj_get(self, request=None, **kwargs):
bucket = self._bucket()
message = bucket.get(kwargs['pk'])
return RiakObject(initial=message.get_data())
def obj_create(self, bundle, request=None, **kwargs):
bundle.obj = RiakObject(initial=kwargs)
bundle = self.full_hydrate(bundle)
bucket = self._bucket()
new_message = bucket.new(bundle.obj.uuid, data=bundle.obj.to_dict())
new_message.store()
return bundle
def obj_update(self, bundle, request=None, **kwargs):
return self.obj_create(bundle, request, **kwargs)
def obj_delete_list(self, request=None, **kwargs):
bucket = self._bucket()
for key in bucket.get_keys():
obj = bucket.get(key)
obj.delete()
def obj_delete(self, request=None, **kwargs):
bucket = self._bucket()
obj = bucket.get(kwargs['pk'])
obj.delete()
def rollback(self, bundles):
pass
This represents a full, working, Riak-powered API endpoint. All REST-style
actions (GET/POST/PUT/DELETE) all work correctly. The only shortcut taken in
this example was skipping filter-abilty, as adding in the MapReduce bits would
have decreased readability.
All said and done, just nine methods needed overriding, eight of which were
highly specific to how data access is done.
.. _Riak: http://www.basho.com/products_riak_overview.php
|