Merge lp:~thisfred/desktopcouch/a-b-c-1-2-3 into lp:desktopcouch

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
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
Manuel de la Peña (mandel) wrote :

+1, good job you workaholic!

review: Approve
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."""

Subscribers

People subscribed via source and target branches