Merge lp:~thisfred/desktopcouch/a-b-c-1-2-3 into lp:desktopcouch
- a-b-c-1-2-3
- Merge into trunk
Proposed by
Eric Casteleijn
Status: | Merged |
---|---|
Approved by: | Vincenzo Di Somma |
Approved revision: | 215 |
Merged at revision: | 215 |
Proposed branch: | lp:~thisfred/desktopcouch/a-b-c-1-2-3 |
Merge into: | lp:desktopcouch |
Diff against target: |
874 lines (+272/-298) 6 files modified
desktopcouch/records/doc/field_registry.txt (+2/-2) desktopcouch/records/field_registry.py (+61/-18) desktopcouch/records/record.py (+66/-142) desktopcouch/records/server_base.py (+3/-2) desktopcouch/records/tests/test_field_registry.py (+16/-13) desktopcouch/records/tests/test_record.py (+124/-121) |
To merge this branch: | bzr merge lp:~thisfred/desktopcouch/a-b-c-1-2-3 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Vincenzo Di Somma (community) | Approve | ||
Manuel de la Peña (community) | Approve | ||
Review via email: mp+41421@code.launchpad.net |
Commit message
Makes RecordDict and MergeableList use Abstract Base Classes, so the code is smaller, and the interfaces are more guaranteed.
Description of the change
Makes RecordDict and MergeableList use Abstract Base Classes, so the code is smaller, and the interfaces are more guaranteed.
Note this has some small and rather trivial lint issues, which I have already fixed on other branches, and I don't want to generate more conflicts than I have to.
To post a comment you must log in.
Revision history for this message
Nicola Larosa (teknico) wrote : | # |
You might want to add a comment to record.py, right before importing MutableSequence and MutableMapping, to the effect that it requires at least Python 2.6 .
This will make this and subsequent versions of desktopcouch not usable by ubuntuone-servers, until all server machines are upgraded to Lucid (10.04).
Revision history for this message
Vincenzo Di Somma (vds) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'desktopcouch/records/doc/field_registry.txt' |
2 | --- desktopcouch/records/doc/field_registry.txt 2010-11-10 19:33:59 +0000 |
3 | +++ desktopcouch/records/doc/field_registry.txt 2010-11-22 03:51:10 +0000 |
4 | @@ -172,7 +172,7 @@ |
5 | ... losslessly as possible. |
6 | ... """ |
7 | ... |
8 | -... def getValue(self, record): |
9 | +... def get_value(self, record): |
10 | ... """Get the value for the registered field.""" |
11 | ... score = record.get(self._fieldname) |
12 | ... stars = score / 20 |
13 | @@ -181,7 +181,7 @@ |
14 | ... stars += 1 |
15 | ... return "*" * stars |
16 | ... |
17 | -... def setValue(self, record, value): |
18 | +... def set_value(self, record, value): |
19 | ... """Set the value for the registered field.""" |
20 | ... if value is None: |
21 | ... self.deleteValue(record) |
22 | |
23 | === modified file 'desktopcouch/records/field_registry.py' |
24 | --- desktopcouch/records/field_registry.py 2010-11-10 19:33:59 +0000 |
25 | +++ desktopcouch/records/field_registry.py 2010-11-22 03:51:10 +0000 |
26 | @@ -20,6 +20,7 @@ |
27 | """Registry utility classes""" |
28 | |
29 | import copy |
30 | +import warnings |
31 | |
32 | from desktopcouch.records.record import MergeableList |
33 | ANNOTATION_NAMESPACE = 'application_annotations' |
34 | @@ -41,7 +42,7 @@ |
35 | record = self.record_class() |
36 | for key, val in data.items(): |
37 | if key in self.field_registry: |
38 | - self.field_registry[key].setValue(record, val) |
39 | + self.field_registry[key].set_value(record, val) |
40 | else: |
41 | record.application_annotations.setdefault( |
42 | self.app_name, {}).setdefault( |
43 | @@ -57,7 +58,7 @@ |
44 | for key, value in annotations.items(): |
45 | data[key] = value |
46 | for key in self.field_registry: |
47 | - data[key] = self.field_registry[key].getValue(record) |
48 | + data[key] = self.field_registry[key].get_value(record) |
49 | return data |
50 | |
51 | |
52 | @@ -68,22 +69,43 @@ |
53 | """initialize the fieldname""" |
54 | self._fieldname = fieldname |
55 | |
56 | - def getValue(self, record): |
57 | + def get_value(self, record): |
58 | """get the value for the registered field""" |
59 | return record.get(self._fieldname) |
60 | |
61 | - def deleteValue(self, record): |
62 | + def getValue(self, record): # pylint: disable=C0103 |
63 | + """Deprecated""" |
64 | + warnings.warn( |
65 | + ".getValue is deprecated, use .get_value instead", |
66 | + DeprecationWarning, stacklevel=2) |
67 | + return self.get_value(record) |
68 | + |
69 | + def delete_value(self, record): |
70 | """Delete a value""" |
71 | if self._fieldname in record: |
72 | del record[self._fieldname] |
73 | |
74 | - def setValue(self, record, value): |
75 | + def deleteValue(self, record): # pylint: disable=C0103 |
76 | + """Deprecated.""" |
77 | + warnings.warn( |
78 | + ".deleteValue is deprecated, use .delete_value instead", |
79 | + DeprecationWarning, stacklevel=2) |
80 | + return self.delete_value(record) |
81 | + |
82 | + def set_value(self, record, value): |
83 | """set the value for the registered field""" |
84 | if value is None: |
85 | - self.deleteValue(record) |
86 | + self.delete_value(record) |
87 | return |
88 | record[self._fieldname] = value |
89 | |
90 | + def setValue(self, record, value): # pylint: disable=C0103 |
91 | + """Deprecated.""" |
92 | + warnings.warn( |
93 | + ".setValue is deprecated, use .set_value instead", |
94 | + DeprecationWarning, stacklevel=2) |
95 | + return self.set_value(record, value) |
96 | + |
97 | |
98 | class MergeableListFieldMapping(object): |
99 | """Mapping between MergeableLists and application fields.""" |
100 | @@ -104,13 +126,13 @@ |
101 | private = annotation.setdefault('private_application_annotations', {}) |
102 | return private |
103 | |
104 | - def _uuidLookup(self, record): |
105 | - """return the uuid key to lookup the registered field""" |
106 | + def _uuid_lookup(self, record): |
107 | + """Return the uuid key to lookup the registered field.""" |
108 | return self.get_application_annotations(record).get(self._uuid_field) |
109 | |
110 | - def getValue(self, record): |
111 | - """get the value for the registered field""" |
112 | - uuid_key = self._uuidLookup(record) |
113 | + def get_value(self, record): |
114 | + """Get the value for the registered field.""" |
115 | + uuid_key = self._uuid_lookup(record) |
116 | try: |
117 | return record[ |
118 | self._root_list].get_value_for_uuid(uuid_key).get( |
119 | @@ -118,12 +140,19 @@ |
120 | except KeyError: |
121 | return None |
122 | |
123 | - def deleteValue(self, record): |
124 | + def getValue(self, record): # pylint: disable=C0103 |
125 | + """Deprecated""" |
126 | + warnings.warn( |
127 | + ".getValue is deprecated, use .get_value instead", |
128 | + DeprecationWarning, stacklevel=2) |
129 | + return self.get_value(record) |
130 | + |
131 | + def delete_value(self, record): |
132 | """Delete a value""" |
133 | root_list = record.get(self._root_list, None) |
134 | if not root_list: |
135 | return |
136 | - uuid_key = self._uuidLookup(record) |
137 | + uuid_key = self._uuid_lookup(record) |
138 | if not uuid_key: |
139 | return |
140 | # pylint: disable=W0212 |
141 | @@ -131,10 +160,17 @@ |
142 | del root_list._data[uuid_key][self._field_name] |
143 | # pylint: enable=W0212 |
144 | |
145 | - def setValue(self, record, value): |
146 | - """set the value for the registered field""" |
147 | + def deleteValue(self, record): # pylint: disable=C0103 |
148 | + """Deprecated.""" |
149 | + warnings.warn( |
150 | + ".deleteValue is deprecated, use .delete_value instead", |
151 | + DeprecationWarning, stacklevel=2) |
152 | + return self.delete_value(record) |
153 | + |
154 | + def set_value(self, record, value): |
155 | + """Set the value for the registered field.""" |
156 | if not value: |
157 | - self.deleteValue(record) |
158 | + self.delete_value(record) |
159 | return |
160 | root_list = record.get(self._root_list, None) |
161 | application_annotations = self.get_application_annotations(record) |
162 | @@ -142,12 +178,12 @@ |
163 | values.update({self._field_name: value}) |
164 | if not root_list: |
165 | # This is the first value we add to the list |
166 | - ml = MergeableList.from_list([values]) |
167 | + ml = MergeableList([values]) |
168 | record[self._root_list] = ml |
169 | uuid_key = record[self._root_list].get_uuid_for_index(-1) |
170 | application_annotations[self._uuid_field] = uuid_key |
171 | return |
172 | - uuid_key = self._uuidLookup(record) |
173 | + uuid_key = self._uuid_lookup(record) |
174 | if not uuid_key: |
175 | # We add a new value to an existing list |
176 | root_list.append(values) |
177 | @@ -164,3 +200,10 @@ |
178 | application_annotations[self._uuid_field] = uuid_key |
179 | return |
180 | record_dict[self._field_name] = value |
181 | + |
182 | + def setValue(self, record, value): # pylint: disable=C0103 |
183 | + """Deprecated.""" |
184 | + warnings.warn( |
185 | + ".setValue is deprecated, use .set_value instead", |
186 | + DeprecationWarning, stacklevel=2) |
187 | + return self.set_value(record, value) |
188 | |
189 | === modified file 'desktopcouch/records/record.py' |
190 | --- desktopcouch/records/record.py 2010-11-15 23:19:39 +0000 |
191 | +++ desktopcouch/records/record.py 2010-11-22 03:51:10 +0000 |
192 | @@ -23,7 +23,11 @@ |
193 | |
194 | import re |
195 | import uuid |
196 | +import warnings |
197 | + |
198 | from time import strptime |
199 | +from collections import MutableSequence, MutableMapping |
200 | + |
201 | |
202 | RX = re.compile('[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') |
203 | |
204 | @@ -35,17 +39,34 @@ |
205 | key.startswith(('_', 'application_annotations'))) |
206 | |
207 | |
208 | +def is_mergeable_list(dictionary): |
209 | + """Tests wether the dictionary represents a mergeable list.""" |
210 | + if not dictionary: |
211 | + return False |
212 | + if not isinstance(dictionary, MutableMapping): |
213 | + return False |
214 | + if not '_order' in dictionary: |
215 | + for key in dictionary: |
216 | + if not RX.match(key): |
217 | + return False |
218 | + for key in dictionary: |
219 | + if not key == '_order' and not RX.match(key): |
220 | + return False |
221 | + return True |
222 | + |
223 | + |
224 | def is_uuid_dict(dictionary): |
225 | - """Test whether the passed object is a dictionary like object with |
226 | - uuids for keys. |
227 | - """ |
228 | + """Deprecated.""" |
229 | + warnings.warn( |
230 | + ".is_uuid_dict is deprecated, use .is_mergeable_list instead", |
231 | + DeprecationWarning, stacklevel=2) |
232 | return dictionary and not [ |
233 | key for key in dictionary if (not RX.match(key) and key != '_order')] |
234 | |
235 | |
236 | def record_factory(data): |
237 | """Create a RecordDict or RecordList from passed data.""" |
238 | - if isinstance(data, dict): |
239 | + if isinstance(data, MutableMapping): |
240 | return _build_from_dict(data) |
241 | return data |
242 | |
243 | @@ -63,8 +84,8 @@ |
244 | |
245 | def validate(data): |
246 | """Validate underlying data for Record objects.""" |
247 | - if isinstance(data, dict): |
248 | - if is_uuid_dict(data): |
249 | + if isinstance(data, MutableMapping): |
250 | + if is_mergeable_list(data): |
251 | return |
252 | if any(RX.match(key) for key in data): |
253 | raise IllegalKeyException( |
254 | @@ -163,8 +184,8 @@ |
255 | |
256 | def __getitem__(self, key): |
257 | value = self._data[key] |
258 | - if isinstance(value, dict): |
259 | - if is_uuid_dict(value): |
260 | + if isinstance(value, MutableMapping): |
261 | + if is_mergeable_list(value): |
262 | result = MergeableList(value) |
263 | else: |
264 | result = RecordDict(value) |
265 | @@ -172,7 +193,7 @@ |
266 | return value |
267 | |
268 | def __setitem__(self, key, item): |
269 | - if isinstance(item, dict): |
270 | + if isinstance(item, MutableMapping): |
271 | item = record_factory(item) |
272 | if hasattr(item, '_data'): |
273 | item = item._data # pylint: disable=W0212 |
274 | @@ -181,11 +202,8 @@ |
275 | def __delitem__(self, key): |
276 | del self._data[key] |
277 | |
278 | - def __len__(self): |
279 | - return len(self._data) |
280 | - |
281 | - |
282 | -class RecordDict(RecordData): |
283 | + |
284 | +class RecordDict(RecordData, MutableMapping): |
285 | """An object that represents an desktop CouchDB record fragment, |
286 | but behaves like a dictionary. |
287 | |
288 | @@ -201,62 +219,33 @@ |
289 | for key in self._data: |
290 | yield key |
291 | |
292 | - def __contains__(self, key): |
293 | - return key in self._data |
294 | - |
295 | - def __cmp__(self, value): |
296 | - if isinstance(value, RecordDict): |
297 | - return cmp(self._data, value._data) # pylint: disable=W0212 |
298 | - return -1 |
299 | - |
300 | - def get(self, key, default=None): |
301 | - """D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.""" |
302 | - if not key in self._data: |
303 | - return default |
304 | - return self[key] |
305 | - |
306 | - def update(self, data): |
307 | - """D.update(E, **F) -> None. Update D from dict/iterable E and F. |
308 | - |
309 | - If E has a .keys() method, does: for k in E: D[k] = E[k] |
310 | - If E lacks .keys() method, does: for (k, v) in E: D[k] = v |
311 | - In either case, this is followed by: for k in F: D[k] = F[k] |
312 | - |
313 | - """ |
314 | - for key, val in data.items(): |
315 | - self[key] = val |
316 | - |
317 | - def items(self): |
318 | - """D.items() -> list of D's (key, value) pairs, as 2-tuples.""" |
319 | - return [(key, self[key]) for key in self] |
320 | - |
321 | - def keys(self): |
322 | - """D.keys() -> list of D's keys.""" |
323 | - return self._data.keys() |
324 | - |
325 | - def setdefault(self, key, default): |
326 | + def __len__(self): |
327 | + return len(self._data) |
328 | + |
329 | + def setdefault(self, key, default=None): |
330 | """D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D.""" |
331 | - if not key in self: |
332 | + try: |
333 | + return self[key] |
334 | + except KeyError: |
335 | self[key] = default |
336 | + # NOTE: since we (possibly) mangle the default value in |
337 | + # __setitem__, we can't just 'return default' here, as |
338 | + # MutableMapping does. |
339 | return self[key] |
340 | |
341 | - def has_key(self, key): |
342 | - """D.has_key(k) -> True if D has a key k, else False.""" |
343 | - return key in self |
344 | - |
345 | - |
346 | -class MergeableList(RecordData): |
347 | + |
348 | +class MergeableList(RecordData, MutableSequence): |
349 | """An object that represents a list of complex values.""" |
350 | |
351 | - @classmethod |
352 | - def from_list(cls, data): |
353 | - """Create a RecordList from passed data.""" |
354 | - if not data: |
355 | - raise ValueError("Can't set empty list values.'") |
356 | - result = cls({}) |
357 | - for value in data: |
358 | - result.append(record_factory(value)) |
359 | - return result |
360 | + def __init__(self, data): |
361 | + if isinstance(data, MutableMapping): |
362 | + super(MergeableList, self).__init__(data) |
363 | + else: |
364 | + self._data = {} |
365 | + for value in data: |
366 | + self.append(record_factory(value)) |
367 | + self._attachments = dict() |
368 | + self._detached = set() |
369 | |
370 | def __getitem__(self, index): |
371 | if not isinstance(index, int): |
372 | @@ -265,12 +254,12 @@ |
373 | key = self.get_uuid_for_index(index) |
374 | return super(MergeableList, self).__getitem__(key) |
375 | |
376 | - def __setitem__(self, index, item): |
377 | + def __setitem__(self, index, value): |
378 | if not isinstance(index, int): |
379 | raise TypeError( |
380 | "list indices must be integers, not %s" % type(index)) |
381 | key = self.get_uuid_for_index(index) |
382 | - super(MergeableList, self).__setitem__(key, item) |
383 | + super(MergeableList, self).__setitem__(key, value) |
384 | |
385 | def __delitem__(self, index): |
386 | if not isinstance(index, int): |
387 | @@ -281,37 +270,19 @@ |
388 | self._data['_order'].remove(key) |
389 | super(MergeableList, self).__delitem__(key) |
390 | |
391 | - def __iter__(self): |
392 | - return (self[index] for index in range(len(self))) |
393 | - |
394 | def __len__(self): |
395 | - length = len(self._data) |
396 | if '_order' in self._data: |
397 | - return length - 1 |
398 | - return length |
399 | - |
400 | - def __contains__(self, value): |
401 | - if hasattr(value, '_data'): |
402 | - value = value._data # pylint: disable=W0212 |
403 | - return value in self._data.values() |
404 | - |
405 | - def __cmp__(self, value): |
406 | - """ |
407 | - Implement the compare to be able to compare with other mergeable |
408 | - lists, tuples and lists. |
409 | - """ |
410 | - if (hasattr(value, "__iter__") |
411 | - and hasattr(value, "__getitem__") |
412 | - and hasattr(value, "__len__")): |
413 | - # we should have the same value in the same order |
414 | - length = len(value) |
415 | - if len(self) == length: |
416 | - for index in range(len(self)): |
417 | - cmp_value = cmp(self[index], value[index]) |
418 | - if cmp_value != 0: |
419 | - return cmp_value |
420 | - return 0 |
421 | - return -1 |
422 | + return len(self._data) - 1 |
423 | + return len(self._data) |
424 | + |
425 | + def insert(self, index, value): |
426 | + """Insert value at index.""" |
427 | + new_uuid = str(uuid.uuid4()) |
428 | + if not '_order' in self._data: |
429 | + self._data['_order'] = sorted( |
430 | + [key for key in self._data.keys() if RX.match(key)]) |
431 | + self._data['_order'].insert(index, new_uuid) |
432 | + super(MergeableList, self).__setitem__(new_uuid, value) |
433 | |
434 | def _get_ordered_keys(self): |
435 | """Get list of uuid keys ordered by 'order' property or uuid key.""" |
436 | @@ -334,53 +305,6 @@ |
437 | """ |
438 | return super(MergeableList, self).__getitem__(lookup_uuid) |
439 | |
440 | - def append(self, value): |
441 | - """L.append(object) -- append object to end.""" |
442 | - new_uuid = str(uuid.uuid4()) |
443 | - self._data.setdefault('_order', sorted(self._data.keys())).append( |
444 | - new_uuid) |
445 | - super(MergeableList, self).__setitem__(new_uuid, value) |
446 | - |
447 | - def remove(self, value): |
448 | - """L.remove(value) -- remove first occurrence of value. |
449 | - |
450 | - Raises ValueError if the value is not present. |
451 | - |
452 | - """ |
453 | - if len(self) == 1: |
454 | - raise ValueError("MergeableList cannot be empty.") |
455 | - index = 0 |
456 | - for current_value in self: |
457 | - # important! use the data in self first 'cause mergeable lists |
458 | - # can be compared with list and tuples but no the other way around |
459 | - if cmp(current_value, value) == 0: |
460 | - del self[index] |
461 | - return |
462 | - index += 1 |
463 | - raise ValueError("list.remove(x): x not in list") |
464 | - |
465 | - def pop(self, index=None): |
466 | - """L.pop([index]) -> item -- remove and return item at |
467 | - index. (default last.) |
468 | - |
469 | - Raises IndexError if list is empty or index is out of range. |
470 | - """ |
471 | - if len(self) == 1: |
472 | - raise ValueError("MergeableList cannot be empty.") |
473 | - if index is None: |
474 | - index = -1 |
475 | - value = self[index] |
476 | - del self[index] |
477 | - return value |
478 | - |
479 | - def index(self, key): |
480 | - """L.index(value, [start, [stop]]) -> integer -- return first |
481 | - index of value. |
482 | - |
483 | - Raises ValueError if the value is not present. |
484 | - """ |
485 | - return self.__getitem__(key) |
486 | - |
487 | |
488 | class Record(RecordDict): |
489 | """An object that represents a Desktop CouchDB record.""" |
490 | |
491 | === modified file 'desktopcouch/records/server_base.py' |
492 | --- desktopcouch/records/server_base.py 2010-11-18 18:32:29 +0000 |
493 | +++ desktopcouch/records/server_base.py 2010-11-22 03:51:10 +0000 |
494 | @@ -215,7 +215,8 @@ |
495 | wrapper=None, **options): |
496 | """Pass-through to CouchDB library. Deprecated.""" |
497 | warnings.warn( |
498 | - "._temporary_query is deprecated.", DeprecationWarning) |
499 | + "._temporary_query is deprecated.", DeprecationWarning, |
500 | + stacklevel=2) |
501 | return self.db.query(map_fun, reduce_fun, language, |
502 | wrapper, **options) |
503 | |
504 | @@ -520,7 +521,7 @@ |
505 | """ |
506 | warnings.warn( |
507 | ".get_records is deprecated, use .get_all_records instead", |
508 | - DeprecationWarning) |
509 | + DeprecationWarning, stacklevel=2) |
510 | view_name = "get_records_and_type" |
511 | view_map_js = """ |
512 | function(doc) { |
513 | |
514 | === modified file 'desktopcouch/records/tests/test_field_registry.py' |
515 | --- desktopcouch/records/tests/test_field_registry.py 2010-11-12 14:36:14 +0000 |
516 | +++ desktopcouch/records/tests/test_field_registry.py 2010-11-22 03:51:10 +0000 |
517 | @@ -32,6 +32,9 @@ |
518 | 'record_type': 'http://example.com/test', |
519 | 'simple_field': 23, |
520 | 'test_fields': { |
521 | + '_order': [ |
522 | + 'd6d2c23b-279c-45c8-afb2-ec84ee7c81c3', |
523 | + 'e47455fb-da05-481e-a2c7-88f14d5cc163'], |
524 | 'e47455fb-da05-481e-a2c7-88f14d5cc163': |
525 | {'the_field': 'the value', |
526 | 'not_the_field': 'different value'}, |
527 | @@ -79,20 +82,20 @@ |
528 | """Test the simple field mapping object.""" |
529 | r = Record(self.test_record) |
530 | mapping = SimpleFieldMapping('simple_field') |
531 | - self.assertEqual(23, mapping.getValue(r)) |
532 | - mapping.setValue(r, 'Fnord') |
533 | - self.assertEqual('Fnord', mapping.getValue(r)) |
534 | + self.assertEqual(23, mapping.get_value(r)) |
535 | + mapping.set_value(r, 'Fnord') |
536 | + self.assertEqual('Fnord', mapping.get_value(r)) |
537 | |
538 | def test_mergeable_list_field_mapping0(self): |
539 | """Test the MergeableListFieldMapping object.""" |
540 | record = Record(self.test_record) |
541 | mapping = MergeableListFieldMapping( |
542 | 'Test App', 'test_field', 'test_fields', 'the_field') |
543 | - self.assertEqual('the value', mapping.getValue(record)) |
544 | - mapping.setValue(record, 'a new value') |
545 | - self.assertEqual('a new value', mapping.getValue(record)) |
546 | - mapping.deleteValue(record) |
547 | - self.assertEqual(None, mapping.getValue(record)) |
548 | + self.assertEqual('the value', mapping.get_value(record)) |
549 | + mapping.set_value(record, 'a new value') |
550 | + self.assertEqual('a new value', mapping.get_value(record)) |
551 | + mapping.delete_value(record) |
552 | + self.assertEqual(None, mapping.get_value(record)) |
553 | |
554 | def test_mergeable_list_field_mapping1(self): |
555 | """Test the MergeableListFieldMapping object.""" |
556 | @@ -100,11 +103,11 @@ |
557 | record = Record(self.test_record) |
558 | mapping = MergeableListFieldMapping( |
559 | 'Test App', 'test_field', 'test_fields', 'the_field') |
560 | - self.assertEqual('the value', mapping.getValue(record)) |
561 | + self.assertEqual('the value', mapping.get_value(record)) |
562 | del record._data['test_fields'][ # pylint: disable=W0212 |
563 | 'e47455fb-da05-481e-a2c7-88f14d5cc163'] |
564 | - mapping.deleteValue(record) |
565 | - self.assertEqual(None, mapping.getValue(record)) |
566 | + mapping.delete_value(record) |
567 | + self.assertEqual(None, mapping.get_value(record)) |
568 | # pylint: enable=W0212 |
569 | |
570 | def test_mergeable_list_field_mapping_empty_field(self): |
571 | @@ -112,8 +115,8 @@ |
572 | record = Record(self.test_record) |
573 | mapping = MergeableListFieldMapping( |
574 | 'Test App', 'test_field', 'test_fields', 'the_field') |
575 | - mapping.setValue(record, '') |
576 | - self.assertEqual(None, mapping.getValue(record)) |
577 | + mapping.set_value(record, '') |
578 | + self.assertEqual(None, mapping.get_value(record)) |
579 | |
580 | |
581 | class TestTransformer(TestCase): |
582 | |
583 | === modified file 'desktopcouch/records/tests/test_record.py' |
584 | --- desktopcouch/records/tests/test_record.py 2010-11-15 22:39:27 +0000 |
585 | +++ desktopcouch/records/tests/test_record.py 2010-11-22 03:51:10 +0000 |
586 | @@ -22,16 +22,125 @@ |
587 | import doctest |
588 | import os |
589 | from testtools import TestCase |
590 | +from collections import MutableSequence, MutableMapping |
591 | |
592 | -# pylint does not like relative imports from containing packages |
593 | -# pylint: disable=F0401 |
594 | import desktopcouch |
595 | from desktopcouch.records.server import CouchDatabase |
596 | from desktopcouch.records.record import (Record, RecordDict, MergeableList, |
597 | - record_factory, IllegalKeyException, validate, NoRecordTypeSpecified) |
598 | + record_factory, IllegalKeyException, validate, NoRecordTypeSpecified, |
599 | + is_mergeable_list) |
600 | import desktopcouch.tests as test_environment |
601 | |
602 | |
603 | +class TestRecordDict(TestCase): |
604 | + """Test the record dictionary functionality.""" |
605 | + |
606 | + def test_abc(self): |
607 | + """Test RecordDict is a mutable mapping.""" |
608 | + record_dict = RecordDict({}) |
609 | + self.assertTrue(isinstance(record_dict, MutableMapping)) |
610 | + |
611 | + |
612 | +class TestMergeableList(TestCase): |
613 | + """Test the mergeable list functionality.""" |
614 | + |
615 | + def test_is_mergeable_list(self): |
616 | + """Test the is_mergeable_list checker.""" |
617 | + self.assertTrue(is_mergeable_list({'_order': []})) |
618 | + self.assertTrue(is_mergeable_list({ |
619 | + '_order': [ |
620 | + 'd6d2c23b-279c-45c8-afb2-ec84ee7c81c3', |
621 | + 'e47455fb-da05-481e-a2c7-88f14d5cc163'], |
622 | + 'd6d2c23b-279c-45c8-afb2-ec84ee7c81c3': 'foo', |
623 | + 'e47455fb-da05-481e-a2c7-88f14d5cc163': 'baz'})) |
624 | + self.assertFalse(is_mergeable_list(None)) |
625 | + self.assertFalse(is_mergeable_list({})) |
626 | + self.assertFalse(is_mergeable_list({ |
627 | + 'foo': 'bar', |
628 | + 'baz': 'qux'})) |
629 | + |
630 | + def test_abc(self): |
631 | + """Test MergeableList is a mutable sequence.""" |
632 | + mergeable_list = MergeableList(['foo', 'bar']) |
633 | + self.assertTrue(isinstance(mergeable_list, MutableSequence)) |
634 | + |
635 | + def test_insert(self): |
636 | + """Test the insert method.""" |
637 | + mergeable_list = MergeableList(['foo', 'bar']) |
638 | + mergeable_list.insert(0, 'baz') |
639 | + self.assertEquals( |
640 | + ['baz', 'foo', 'bar'], [item for item in mergeable_list]) |
641 | + |
642 | + mergeable_list.insert(-1, 'qux') |
643 | + self.assertEquals( |
644 | + ['baz', 'foo', 'qux', 'bar'], |
645 | + [item for item in mergeable_list]) |
646 | + mergeable_list.insert(2, 'fnord') |
647 | + self.assertEquals( |
648 | + ['baz', 'foo', 'fnord', 'qux', 'bar'], |
649 | + [item for item in mergeable_list]) |
650 | + |
651 | + def test_index(self): |
652 | + """Test the subset of list behavior we'll be supporting""" |
653 | + mergeable_list = MergeableList(['foo', 'bar']) |
654 | + self.assertEqual("foo", mergeable_list[0]) |
655 | + self.assertEqual("bar", mergeable_list[1]) |
656 | + self.assertEqual("bar", mergeable_list[-1]) |
657 | + |
658 | + def test_del(self): |
659 | + """Test deletion of uuid keys.""" |
660 | + mergeable_list = MergeableList(['foo', 'bar', 'baz']) |
661 | + del mergeable_list[1] |
662 | + self.assertEqual(2, len(mergeable_list)) |
663 | + self.assertEqual("foo", mergeable_list[0]) |
664 | + self.assertEqual("baz", mergeable_list[1]) |
665 | + |
666 | + def test_append(self): |
667 | + """Test append.""" |
668 | + mergeable_list = MergeableList(['foo', 'bar']) |
669 | + mergeable_list.append('baz') |
670 | + self.assertEqual(3, len(mergeable_list)) |
671 | + self.assertEqual('baz', mergeable_list[-1]) |
672 | + |
673 | + def test_remove(self): |
674 | + """Test remove a normal value""" |
675 | + mergeable_list = MergeableList(['foo', 'bar']) |
676 | + value = 'baz' |
677 | + mergeable_list.append(value) |
678 | + mergeable_list.remove(value) |
679 | + self.assertFalse(value in mergeable_list) |
680 | + |
681 | + def test_remove_missing(self): |
682 | + """Test missing data rmeoval""" |
683 | + mergeable_list = MergeableList(['foo', 'bar']) |
684 | + self.assertRaises(ValueError, mergeable_list.remove, 'qux') |
685 | + |
686 | + def test_pop(self): |
687 | + """Test the pop method when working with a correct index.""" |
688 | + value = [1, 2, 3, 4, 5] |
689 | + mergeable_list = MergeableList(value) |
690 | + # test with negative index |
691 | + popped_value = mergeable_list.pop(-2) |
692 | + self.assertEqual(value[-2], popped_value) |
693 | + # test with positive index |
694 | + popped_value = mergeable_list.pop(1) |
695 | + self.assertEqual(value[1], popped_value) |
696 | + |
697 | + def test_pop_wrong_index(self): |
698 | + """Test pop when index is out or range.""" |
699 | + value = [1, 2, 3, 4, 5] |
700 | + mergeable_list = MergeableList(value) |
701 | + self.assertRaises(IndexError, mergeable_list.pop, |
702 | + len(value) * 2) |
703 | + |
704 | + def test_pop_no_index(self): |
705 | + """Test pop without an index argument.""" |
706 | + value = [1, 2, 3, 4, 5] |
707 | + mergeable_list = MergeableList(value) |
708 | + popped_value = mergeable_list.pop() |
709 | + self.assertEqual(value[-1], popped_value) |
710 | + |
711 | + |
712 | class TestRecords(TestCase): |
713 | """Test the record functionality""" |
714 | |
715 | @@ -46,12 +155,15 @@ |
716 | "field11s": "value11s", |
717 | "field12s": "value12s"}, |
718 | "subfield_uuid": { |
719 | - "e47455fb-da05-481e-a2c7-88f14d5cc163": { |
720 | - "field11": "value11", |
721 | - "field12": "value12"}, |
722 | - "d6d2c23b-279c-45c8-afb2-ec84ee7c81c3": { |
723 | - "field21": "value21", |
724 | - "field22": "value22"}}, |
725 | + '_order': [ |
726 | + 'd6d2c23b-279c-45c8-afb2-ec84ee7c81c3', |
727 | + 'e47455fb-da05-481e-a2c7-88f14d5cc163'], |
728 | + "e47455fb-da05-481e-a2c7-88f14d5cc163": { |
729 | + "field11": "value11", |
730 | + "field12": "value12"}, |
731 | + "d6d2c23b-279c-45c8-afb2-ec84ee7c81c3": { |
732 | + "field21": "value21", |
733 | + "field22": "value22"}}, |
734 | "application_annotations": { |
735 | "my_awesome_app": {"foo": "bar", "some_setting": "set_to"}}, |
736 | "record_type": "http://fnord.org/smorgasbord", |
737 | @@ -158,113 +270,6 @@ |
738 | """Are subdicts with multiple entries supported?""" |
739 | self.assertEqual(2, len(self.record["subfield_uuid"])) |
740 | |
741 | - def test_mergeable_list_index(self): |
742 | - """Test the subset of list behavior we'll be supporting""" |
743 | - self.assertEqual("value21", self.record["subfield_uuid"][0]["field21"]) |
744 | - self.assertEqual("value22", self.record["subfield_uuid"][0]["field22"]) |
745 | - self.assertEqual("value11", self.record["subfield_uuid"][-1]["field11"]) |
746 | - self.assertEqual("value12", self.record["subfield_uuid"][-1]["field12"]) |
747 | - |
748 | - def test_mergeable_list_del(self): |
749 | - """Test deletion of uuid keys.""" |
750 | - del self.record["subfield_uuid"][0] |
751 | - self.assertEqual(1, len(self.record["subfield_uuid"])) |
752 | - self.assertEqual("value11", self.record["subfield_uuid"][0]["field11"]) |
753 | - self.assertEqual("value12", self.record["subfield_uuid"][0]["field12"]) |
754 | - |
755 | - def test_mergeable_list_set_value_in_list_item(self): |
756 | - """Test assignment by index.""" |
757 | - self.record["subfield_uuid"][0]["field12"] = "new exciting value" |
758 | - self.assertEqual( |
759 | - "new exciting value", self.record["subfield_uuid"][0]["field12"]) |
760 | - |
761 | - def test_mergeable_list_append(self): |
762 | - """Test append.""" |
763 | - self.record["subfield_uuid"].append( |
764 | - {"field31": "value31", "field32": "value32"}) |
765 | - self.assertEqual(3, len(self.record["subfield_uuid"])) |
766 | - self.assertEqual("value32", self.record["subfield_uuid"][-1]["field32"]) |
767 | - |
768 | - def test_mergeable_list_append_record_dict(self): |
769 | - """Test appending a RecordDict value.""" |
770 | - value = RecordDict({ |
771 | - "field31": "value31", |
772 | - "field32": "value32"}) |
773 | - self.record["subfield_uuid"].append(value) |
774 | - self.assertEqual(3, len(self.record["subfield_uuid"])) |
775 | - self.assertEqual("value32", self.record["subfield_uuid"][-1]["field32"]) |
776 | - |
777 | - def test_mergeable_list_remove(self): |
778 | - """Test remove a normal value""" |
779 | - value = "string" |
780 | - self.record["subfield_uuid"].append(value) |
781 | - self.record["subfield_uuid"].remove(value) |
782 | - self.assertFalse(value in self.record["subfield_uuid"]) |
783 | - |
784 | - def test_mergeable_list_remove_record_dict(self): |
785 | - """Test remove a RecordDict value""" |
786 | - value = RecordDict({ |
787 | - "field31": "value31", |
788 | - "field32": "value32"}) |
789 | - self.record["subfield_uuid"].append(value) |
790 | - self.record["subfield_uuid"].remove(value) |
791 | - self.assertFalse(value in self.record["subfield_uuid"]) |
792 | - |
793 | - def test_mergeable_list_remove_list(self): |
794 | - """Test list removal""" |
795 | - value = [1, 2, 3, 4, 5] |
796 | - self.record["subfield_uuid"].append(value) |
797 | - self.record["subfield_uuid"].remove(value) |
798 | - self.assertFalse(value in self.record["subfield_uuid"]) |
799 | - |
800 | - def test_mergeable_list_remove_tuple(self): |
801 | - """Test tuple removal""" |
802 | - value = (1, 2, 3, 4, 5) |
803 | - self.record["subfield_uuid"].append(value) |
804 | - self.record["subfield_uuid"].remove(value) |
805 | - self.assertFalse(value in self.record["subfield_uuid"]) |
806 | - |
807 | - def test_mergeable_list_remove_missing(self): |
808 | - """Test missing data rmeoval""" |
809 | - self.assertRaises(ValueError, self.record["subfield_uuid"].remove, |
810 | - "missing_data") |
811 | - |
812 | - def test_mergeable_list_remove_last(self): |
813 | - """Test that exception is raised when removing last item.""" |
814 | - self.record["subfield_uuid"] = MergeableList.from_list([1]) |
815 | - self.assertRaises(ValueError, self.record["subfield_uuid"].remove, 1) |
816 | - |
817 | - def test_mergeable_list_pop_correct_index(self): |
818 | - """Test the pop method when working with a correct index.""" |
819 | - value = [1, 2, 3, 4, 5] |
820 | - self.record["subfield_uuid"] = MergeableList.from_list(value) |
821 | - # test with negative index |
822 | - popped_value = self.record["subfield_uuid"].pop(-2) |
823 | - self.assertEqual(value[-2], popped_value) |
824 | - # test with positive index |
825 | - popped_value = self.record["subfield_uuid"].pop(1) |
826 | - self.assertEqual(value[1], popped_value) |
827 | - |
828 | - def test_mergeable_list_pop_wrong_index(self): |
829 | - """Test pop when index is out or range.""" |
830 | - value = [1, 2, 3, 4, 5] |
831 | - self.record["subfield_uuid"] = MergeableList.from_list(value) |
832 | - self.assertRaises(IndexError, self.record["subfield_uuid"].pop, |
833 | - len(value) * 2) |
834 | - |
835 | - def test_mergeable_list_pop_no_index(self): |
836 | - """Test pop without an index argument.""" |
837 | - value = [1, 2, 3, 4, 5] |
838 | - self.record["subfield_uuid"] = MergeableList.from_list(value) |
839 | - popped_value = self.record["subfield_uuid"].pop() |
840 | - self.assertEqual(value[-1], popped_value) |
841 | - |
842 | - |
843 | - def test_mergeable_list_pop_last(self): |
844 | - """Test that exception is raised when poping last item""" |
845 | - self.record["subfield_uuid"] = MergeableList.from_list([1]) |
846 | - self.assertRaises(ValueError, self.record["subfield_uuid"].pop, 0) |
847 | - |
848 | def test_list(self): |
849 | """Test assigning lists to a key results in mergeable lists.""" |
850 | rec = Record({'record_type': 'http://fnord.org/smorgasbord'}) |
851 | @@ -275,7 +280,7 @@ |
852 | def test_mergeable_list(self): |
853 | """Test assigning lists to a key results in mergeable lists.""" |
854 | rec = Record({'record_type': 'http://fnord.org/smorgasbord'}) |
855 | - rec['key'] = MergeableList.from_list([1, 2, 3, 4]) |
856 | + rec['key'] = MergeableList([1, 2, 3, 4]) |
857 | self.assert_(isinstance(rec['key'], MergeableList)) |
858 | self.assertEqual([1, 2, 3, 4], [value for value in rec['key']]) |
859 | |
860 | @@ -307,12 +312,10 @@ |
861 | |
862 | def test_uuid_like_keys(self): |
863 | """Test that appropriate errors are raised.""" |
864 | - # pylint: disable=W0212 |
865 | - keys = self.record["subfield_uuid"]._data.keys() |
866 | - # pylint: enable=W0212 |
867 | self.assertRaises( |
868 | IllegalKeyException, |
869 | - self.record["subfield"].__setitem__, keys[0], 'stuff') |
870 | + self.record["subfield"].__setitem__, |
871 | + 'd6d2c23b-279c-45c8-afb2-ec84ee7c81c3', 'stuff') |
872 | |
873 | def test_record_type(self): |
874 | """Test the record_type property.""" |
+1, good job you workaholic!