Implement DictObject
This commit is contained in:
parent
b3bcce9203
commit
c735cbbea6
|
@ -0,0 +1,82 @@
|
|||
from unittest import TestCase
|
||||
from urbantz.utils import to_camel_case, DictObject
|
||||
|
||||
|
||||
class TestUtils(TestCase):
|
||||
|
||||
test_data = {
|
||||
'first_name': 'Chuck',
|
||||
'lastName': 'Norris',
|
||||
'nested_dict': {
|
||||
'key': 'value',
|
||||
},
|
||||
'nested_list': ['a', 'b', 'c'],
|
||||
}
|
||||
|
||||
def test_to_camel_case(self):
|
||||
self.assertEqual(to_camel_case('abcdef'), 'abcdef')
|
||||
self.assertEqual(to_camel_case('some_thing'), 'someThing')
|
||||
self.assertEqual(to_camel_case('a_b_c_d_e_f'), 'aBCDEF')
|
||||
self.assertEqual(to_camel_case('hEllO_wOrLd'), 'hEllOWOrLd')
|
||||
|
||||
def test_dictobject_is_dict(self):
|
||||
"""
|
||||
Test the DictObject at least acts like a dict
|
||||
"""
|
||||
d = DictObject(self.test_data)
|
||||
self.assertIsInstance(d, dict)
|
||||
self.assertTrue(d)
|
||||
|
||||
# Dict comparison
|
||||
self.assertEqual(d, self.test_data)
|
||||
self.assertEqual(dict(d), d)
|
||||
|
||||
# Get, set, delete items
|
||||
self.assertEqual(d['first_name'], 'Chuck')
|
||||
self.assertIn('first_name', d)
|
||||
del d['first_name']
|
||||
self.assertNotIn('first_name', d)
|
||||
d['first_name'] = 'Clutch'
|
||||
self.assertEqual(d['first_name'], 'Clutch')
|
||||
self.assertListEqual(
|
||||
list(d.keys()),
|
||||
['lastName', 'nested_dict', 'nested_list', 'first_name'],
|
||||
)
|
||||
self.assertListEqual(
|
||||
list(d.values()),
|
||||
['Norris', {'key': 'value'}, ['a', 'b', 'c'], 'Clutch'],
|
||||
)
|
||||
self.assertListEqual(list(d.items()), [
|
||||
('lastName', 'Norris'),
|
||||
('nested_dict', {'key': 'value'}),
|
||||
('nested_list', ['a', 'b', 'c']),
|
||||
('first_name', 'Clutch'),
|
||||
])
|
||||
|
||||
def test_dictobject_attributes(self):
|
||||
"""
|
||||
Test DictObject translates attributes to items
|
||||
"""
|
||||
|
||||
d = DictObject(self.test_data)
|
||||
self.assertEqual(d.first_name, 'Chuck')
|
||||
self.assertEqual(d.lastName, 'Norris')
|
||||
del d.first_name
|
||||
self.assertNotIn('first_name', d)
|
||||
with self.assertRaises(AttributeError):
|
||||
d.nope
|
||||
|
||||
def test_dictobject_camel_case(self):
|
||||
"""
|
||||
Test DictObject turns snake_cased attribute or item names into
|
||||
camelCased names and tries again when not found
|
||||
"""
|
||||
d = DictObject(self.test_data)
|
||||
self.assertEqual(d['lastName'], 'Norris')
|
||||
self.assertEqual(d['last_name'], 'Norris')
|
||||
self.assertEqual(d.lastName, 'Norris')
|
||||
self.assertEqual(d.last_name, 'Norris')
|
||||
self.assertIn('lastName', d)
|
||||
self.assertIn('last_name', d)
|
||||
del d.last_name
|
||||
self.assertNotIn('lastName', d)
|
|
@ -0,0 +1,70 @@
|
|||
from urbantz.base import JSONSerializable
|
||||
import re
|
||||
|
||||
|
||||
def to_camel_case(value):
|
||||
return re.sub(r'_(.)', lambda m: m.group(1).upper(), value)
|
||||
|
||||
|
||||
class DictObject(JSONSerializable, dict):
|
||||
"""
|
||||
A utility class that turns a dict's items into object attributes.
|
||||
This also performs snake to camel case conversion:
|
||||
if a key is missing in the original dict, the key gets converted
|
||||
to camel case.
|
||||
|
||||
>>> d = DictObject(first_name='Chuck', lastName='Norris')
|
||||
>>> d.first_name
|
||||
'Chuck'
|
||||
>>> d.last_name
|
||||
'Norris'
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Create a new DictObject.
|
||||
All arguments and keyword arguments are like ``dict``.
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.__dict__.update(self)
|
||||
|
||||
def toJSON(self):
|
||||
return dict(self)
|
||||
|
||||
@classmethod
|
||||
def fromJSON(cls, payload):
|
||||
return cls(payload)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self.__getitem__(name)
|
||||
except KeyError:
|
||||
return super().__getattr__(name)
|
||||
|
||||
def __missing__(self, name):
|
||||
camel = to_camel_case(name)
|
||||
if name == camel: # Prevent recursion
|
||||
raise KeyError(name)
|
||||
return self.__getitem__(camel)
|
||||
|
||||
def __delattr__(self, name):
|
||||
if name in self:
|
||||
try:
|
||||
self.__delitem__(name)
|
||||
return
|
||||
except KeyError:
|
||||
pass
|
||||
super().__delattr__(self, name)
|
||||
|
||||
def __delitem__(self, name):
|
||||
try:
|
||||
super().__delitem__(name)
|
||||
except KeyError:
|
||||
camel = to_camel_case(name)
|
||||
if name == camel:
|
||||
raise
|
||||
super().__delitem__(camel)
|
||||
|
||||
def __contains__(self, name):
|
||||
return super().__contains__(name) \
|
||||
or super().__contains__(to_camel_case(name))
|
Reference in New Issue