diff --git a/restless/preparers.py b/restless/preparers.py index 5d9fed9..2f63bfe 100644 --- a/restless/preparers.py +++ b/restless/preparers.py @@ -2,8 +2,8 @@ class Preparer(object): """ A plain preparation object which just passes through data. - It also is relevant as the protocol subclasses should implement to work with - Restless. + It also is relevant as the protocol subclasses should implement to + work with Restless. """ def __init__(self): super(Preparer, self).__init__() @@ -41,8 +41,8 @@ def __init__(self, fields): def prepare(self, data): """ - Handles transforming the provided data into the fielded data that should - be exposed to the end user. + Handles transforming the provided data into the fielded data that + should be exposed to the end user. Uses the ``lookup_data`` method to traverse dotted paths. @@ -51,6 +51,7 @@ def prepare(self, data): result = {} if not self.fields: + # No fields specified. Serialize everything. return data @@ -61,15 +62,16 @@ def prepare(self, data): def lookup_data(self, lookup, data): """ - Given a lookup string, attempts to descend through nested data looking for - the value. + Given a lookup string, attempts to descend through nested data looking + for the value. - Can work with either dictionary-alikes or objects (or any combination of - those). + Can work with either dictionary-alikes or objects (or any combination + of those). Lookups should be a string. If it is a dotted path, it will be split on - ``.`` & it will traverse through to find the final value. If not, it will - simply attempt to find either a key or attribute of that name & return it. + ``.`` & it will traverse through to find the final value. If not, it + will simply attempt to find either a key or attribute of that name & + return it. Example:: @@ -90,7 +92,10 @@ def lookup_data(self, lookup, data): 'hello' >>> lookup_data('person.name', data) 'daniel' - + >>> lookup_data('greeting.se', data) + None + >>> lookup_data('idontexist', data) + None """ value = data parts = lookup.split('.') @@ -102,13 +107,15 @@ def lookup_data(self, lookup, data): remaining_lookup = '.'.join(parts[1:]) if hasattr(data, 'keys') and hasattr(data, '__getitem__'): + # Dictionary enough for us. - value = data[part] + value = data.get(part, None) else: + # Assume it's an object. - value = getattr(data, part) + value = getattr(data, part, None) - if not remaining_lookup: + if not remaining_lookup or not value: return value # There's more to lookup, so dive in recursively. diff --git a/tests/test_preparers.py b/tests/test_preparers.py index 560be24..e5e36fd 100644 --- a/tests/test_preparers.py +++ b/tests/test_preparers.py @@ -32,7 +32,7 @@ def setUp(self): awesome=True, depth=3 ), - }, + } } def test_dict_simple(self): @@ -51,18 +51,21 @@ def test_obj_nested(self): self.assertEqual(self.preparer.lookup_data('moof.buried.id', self.obj_data), 7) self.assertEqual(self.preparer.lookup_data('moof.buried.data.yes', self.obj_data), 'no') + def test_dict_nullable_fk(self): + self.assertEqual(self.preparer.lookup_data('more.this_does_not_exist', self.dict_data), None) + + def test_obj_nullable_fk(self): + self.assertEqual(self.preparer.lookup_data('moof.this_does_not_exist', self.obj_data), None) + def test_dict_miss(self): - with self.assertRaises(KeyError): - self.preparer.lookup_data('another', self.dict_data) + self.assertEqual(self.preparer.lookup_data('another', self.dict_data), None) def test_obj_miss(self): - with self.assertRaises(AttributeError): - self.preparer.lookup_data('whee', self.obj_data) + self.assertEqual(self.preparer.lookup_data('whee', self.obj_data), None) def test_empty_lookup(self): # We could possibly get here in the recursion. self.assertEqual(self.preparer.lookup_data('', 'Last value'), 'Last value') def test_complex_miss(self): - with self.assertRaises(AttributeError): - self.preparer.lookup_data('more.nested.nope', self.dict_data) + self.assertEquals(self.preparer.lookup_data('more.nested.nope', self.dict_data), None)