This repository has been archived by the owner on Oct 11, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
contacts.py
249 lines (209 loc) · 8.61 KB
/
contacts.py
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
"""
Riak contacts backend and collection.
"""
from twisted.internet.defer import inlineCallbacks, returnValue
from zope.interface import implementer
from vumi.persist.fields import ValidationError
from go.vumitools.contact import (
ContactStore, ContactNotFoundError, Contact)
from go.vumitools.contact.models import normalize_addr
from go_api.collections import ICollection
from go_api.collections.errors import (
CollectionObjectNotFound, CollectionUsageError)
from go_api.queue import PausingDeferredQueue
from utils import _get_page_of_keys, _fill_queue
def contact_to_dict(contact):
"""
Turn a contact into a dict we can return.
TODO: Move this into the contact model in vumi-go or something.
"""
contact_dict = {
u"extra": dict(contact.extra),
u"subscription": dict(contact.subscription),
}
for key, value in contact.get_data().iteritems():
if key.startswith(u"extras-") or key.startswith(u"subscription-"):
# We already have these.
continue
contact_dict[key] = value
return contact_dict
class RiakContactsBackend(object):
def __init__(self, riak_manager, max_contacts_per_page):
self.riak_manager = riak_manager
self.max_contacts_per_page = max_contacts_per_page
def get_contact_collection(self, owner_id):
contact_store = ContactStore(self.riak_manager, owner_id)
return RiakContactsCollection(
contact_store, self.max_contacts_per_page)
@implementer(ICollection)
class RiakContactsCollection(object):
def __init__(self, contact_store, max_contacts_per_page):
self.contact_store = contact_store
self.max_contacts_per_page = max_contacts_per_page
@staticmethod
def _pick_fields(data, keys):
"""
Return a sub-dictionary of all the items from ``data`` whose
keys are listed in ``keys``.
"""
return dict((k, data[k]) for k in keys if k in data)
@classmethod
def _pick_contact_fields(cls, data):
"""
Return a sub-dictionary of the items from ``data`` that are valid
contact fields.
"""
return cls._pick_fields(data, Contact.field_descriptors.keys())
@classmethod
def _check_contact_fields(cls, data):
"""
Return a sub-dictionary of the items from ``data`` that are valid
and raise a :class:`CollectionUsageError` if any fields are not.
"""
fields = cls._pick_contact_fields(data)
given_keys = set(data.keys())
valid_keys = set(fields.keys())
if given_keys != valid_keys:
raise CollectionUsageError(
"Invalid contact fields: %s" % ", ".join(
sorted(given_keys - valid_keys)))
return fields
def all_keys(self):
"""
Return an iterable over all keys in the collection. May return a
deferred instead of the iterable.
"""
raise NotImplementedError()
@inlineCallbacks
def _get_contacts_by_query(self, query):
try:
[field, value] = query.split('=')
except ValueError:
raise CollectionUsageError(
"Query must be of the form 'field=value'")
if field not in Contact.ADDRESS_FIELDS:
raise CollectionUsageError(
"Query field must be one of: %s" %
sorted(Contact.ADDRESS_FIELDS))
value = normalize_addr(field, value)
try:
contact = yield self.contact_store.contact_for_addr_field(
field, value, create=False)
except ContactNotFoundError:
raise CollectionObjectNotFound(
'Contact with %s %s' % (field, value))
returnValue([contact_to_dict(contact)])
def stream(self, query):
"""
Return a :class:`PausingDeferredQueue` of the objects in the
collection. May return a deferred instead of the
:class:`PausingDeferredQueue`. A queue item that is an instance of
:class:`PausingQueueCloseMarker` indicates the end of the queue.
:param unicode query:
Search term requested through the API. Defaults to ``None`` if no
search term was requested. Currently not implemented and will raise
a CollectionUsageError if not ``None``.
"""
if query is not None:
raise CollectionUsageError("query parameter not supported")
max_results = self.max_contacts_per_page
model_proxy = self.contact_store.contacts
user_account_key = self.contact_store.user_account_key
def get_page(cursor):
return _get_page_of_keys(
model_proxy, user_account_key, max_results, cursor)
def get_dict(key):
d = self.contact_store.get_contact_by_key(key)
d.addCallback(contact_to_dict)
return d
q = PausingDeferredQueue(backlog=1, size=max_results)
q.fill_d = _fill_queue(q, get_page, get_dict)
return q
@inlineCallbacks
def page(self, cursor, max_results, query):
"""
Generages a page which contains a subset of the objects in the
collection.
:param unicode cursor:
Used to determine the start point of the page. Defaults to ``None``
if no cursor was supplied.
:param int max_results:
Used to limit the number of results presented in a page. Defaults
to ``None`` if no limit was specified.
:param unicode query:
Search term requested through the API. Defaults to ``None`` if no
search term was requested. Query must be of the form `field=value`.
:return:
(cursor, data). ``cursor`` is an opaque string that refers to the
next page, and is ``None`` if this is the last page. ``data`` is a
list of all the objects within the page.
:rtype: tuple
"""
if query is not None:
contacts = yield self._get_contacts_by_query(query)
returnValue((None, contacts))
max_results = max_results or float('inf')
max_results = min(max_results, self.max_contacts_per_page)
model_proxy = self.contact_store.contacts
user_account_key = self.contact_store.user_account_key
cursor, contact_keys = yield _get_page_of_keys(
model_proxy, user_account_key, max_results, cursor)
contact_list = []
for key in contact_keys:
contact = yield self.contact_store.get_contact_by_key(key)
contact = contact_to_dict(contact)
contact_list.append(contact)
returnValue((cursor, contact_list))
@inlineCallbacks
def get(self, object_id):
"""
Return a single object from the collection. May return a deferred
instead of the object.
"""
try:
contact = yield self.contact_store.get_contact_by_key(object_id)
except ContactNotFoundError:
raise CollectionObjectNotFound(object_id, "Contact")
returnValue(contact_to_dict(contact))
@inlineCallbacks
def create(self, object_id, data):
"""
Create an object within the collection. May return a deferred.
If ``object_id`` is ``None``, an identifier will be generated.
"""
fields = self._check_contact_fields(data)
if object_id is not None:
raise CollectionUsageError(
"A contact key may not be specified in contact creation")
try:
contact = yield self.contact_store.new_contact(**fields)
except ValidationError, e:
raise CollectionUsageError(str(e))
returnValue((contact.key, contact_to_dict(contact)))
@inlineCallbacks
def update(self, object_id, data):
"""
Update an object. May return a deferred.
``object_id`` may not be ``None``.
"""
fields = self._check_contact_fields(data)
try:
contact = yield self.contact_store.update_contact(
object_id, **fields)
except ContactNotFoundError:
raise CollectionObjectNotFound(object_id, "Contact")
except ValidationError, e:
raise CollectionUsageError(str(e))
returnValue(contact_to_dict(contact))
@inlineCallbacks
def delete(self, object_id):
"""
Delete an object. May return a deferred.
"""
try:
contact = yield self.contact_store.get_contact_by_key(object_id)
except ContactNotFoundError:
raise CollectionObjectNotFound(object_id, "Contact")
contact_data = contact_to_dict(contact)
yield contact.delete()
returnValue(contact_data)