Implement DictObject

This commit is contained in:
Lucidiot 2019-02-03 15:33:52 +01:00
parent b3bcce9203
commit c735cbbea6
No known key found for this signature in database
GPG Key ID: AE3F7205692FA205
2 changed files with 152 additions and 0 deletions

View File

@ -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)

70
urbantz/utils.py Normal file
View File

@ -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))