File: non_orm_data_sources.rst

package info (click to toggle)
django-tastypie 0.9.10-2
  • links: PTS
  • area: main
  • in suites: wheezy
  • size: 1,020 kB
  • sloc: python: 6,530; makefile: 78; sh: 45
file content (165 lines) | stat: -rw-r--r-- 6,198 bytes parent folder | download
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