Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions addict/addict.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ def __setattr__(self, name, value):
raise AttributeError("'Dict' object attribute "
"'{0}' is read-only".format(name))
else:
self[name] = value
try:
self[name] = value
except KeyError:
self_type = type(self).__name__
raise AttributeError(
"'{}' object has no attribute '{}'".format(
self_type, name))

def __setitem__(self, name, value):
isFrozen = (hasattr(self, '__frozen') and
Expand Down Expand Up @@ -64,7 +70,12 @@ def _hook(cls, item):
return item

def __getattr__(self, item):
return self.__getitem__(item)
try:
return self.__getitem__(item)
except KeyError:
self_type = type(self).__name__
raise AttributeError("'{}' object has no attribute '{}'".format(
self_type, item))

def __missing__(self, name):
if object.__getattribute__(self, '__frozen'):
Expand Down
82 changes: 78 additions & 4 deletions test_addict.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,53 +524,127 @@ def test_top_freeze_against_top_key(self):
"Test that d.freeze() produces KeyError on d.missing."
d = self.dict_class()
self.assertEqual(d.missing, {})
self.assertEqual(d["missing"], {})
d.freeze()
with self.assertRaises(KeyError):
with self.assertRaises(AttributeError):
d.missing
with self.assertRaises(KeyError):
d["missing"]
d.unfreeze()
self.assertEqual(d.missing, {})
self.assertEqual(d["missing"], {})

def test_top_freeze_against_nested_key(self):
"Test that d.freeze() produces KeyError on d.inner.missing."
d = self.dict_class()
d.inner.present = TEST_VAL
self.assertIn("inner", d)
self.assertEqual(d.inner.missing, {})
self.assertEqual(d.inner["missing"], {})
d.freeze()
with self.assertRaises(KeyError):
with self.assertRaises(AttributeError):
d.inner.missing
with self.assertRaises(KeyError):
d.inner["missing"]
with self.assertRaises(AttributeError):
d.missing
with self.assertRaises(KeyError):
d["missing"]
d.unfreeze()
self.assertEqual(d.inner.missing, {})
self.assertEqual(d.inner["missing"], {})
self.assertEqual(d.missing, {})
self.assertEqual(d["missing"], {})

def test_nested_freeze_against_top_level(self):
"Test that d.inner.freeze() leaves top-level `d` unfrozen."
d = self.dict_class()
d.inner.present = TEST_VAL
self.assertEqual(d.inner.present, TEST_VAL)
self.assertEqual(d.inner["present"], TEST_VAL)
self.assertEqual(d.inner.missing, {})
self.assertEqual(d.inner["missing"], {})
self.assertEqual(d.missing, {})
self.assertEqual(d["missing"], {})
d.inner.freeze()
with self.assertRaises(KeyError):
with self.assertRaises(AttributeError):
d.inner.missing # d.inner is frozen
with self.assertRaises(KeyError):
d.inner["missing"]
self.assertEqual(d.missing, {}) # but not `d` itself
self.assertEqual(d["missing"], {})
d.inner.unfreeze()
self.assertEqual(d.inner.missing, {})
self.assertEqual(d.inner["missing"], {})

def test_top_freeze_disallows_new_key_addition(self):
"Test that d.freeze() disallows adding new keys in d."
d = self.dict_class({"oldKey": None})
d.freeze()
d.oldKey = TEST_VAL # Can set pre-existing key.
self.assertEqual(d.oldKey, TEST_VAL)
with self.assertRaises(KeyError):
with self.assertRaises(AttributeError):
d.newKey = TEST_VAL # But can't add a new key.
with self.assertRaises(KeyError):
d["newKey"] = TEST_VAL
self.assertNotIn("newKey", d)
d.unfreeze()
d.newKey = TEST_VAL
self.assertEqual(d.newKey, TEST_VAL)
self.assertEqual(d["newKey"], TEST_VAL)

def test_getattr_when_top_freeze_against_top_key(self):
"Test that d.freeze() is compatible with getattr on d.missing."
d = self.dict_class()
self.assertEqual(getattr(d, "missing"), {})
self.assertEqual(getattr(d, "missing", TEST_VAL), {})
d.freeze()
with self.assertRaises(AttributeError):
getattr(d, "missing")
self.assertEqual(getattr(d, "missing", TEST_VAL), TEST_VAL)
d.unfreeze()
self.assertEqual(getattr(d, "missing"), {})
self.assertEqual(getattr(d, "missing", TEST_VAL), {})

def test_getattr_when_top_freeze_against_nested_key(self):
"Test that d.freeze() is compatible when getattr on d.inner.missing."
d = self.dict_class()
d.inner.present = TEST_VAL
self.assertIn("inner", d)
self.assertEqual(getattr(d.inner, "missing"), {})
self.assertEqual(getattr(d.inner, "missing", TEST_VAL), {})
d.freeze()
with self.assertRaises(AttributeError):
getattr(d.inner, "missing")
self.assertEqual(getattr(d.inner, "missing", TEST_VAL), TEST_VAL)
with self.assertRaises(AttributeError):
getattr(d, "missing")
self.assertEqual(getattr(d, "missing", TEST_VAL), TEST_VAL)
d.unfreeze()
self.assertEqual(getattr(d.inner, "missing"), {})
self.assertEqual(getattr(d.inner, "missing", TEST_VAL), {})
self.assertEqual(getattr(d, "missing"), {})
self.assertEqual(getattr(d, "missing", TEST_VAL), {})

def test_getattr_when_nested_freeze_against_top_level(self):
"Test that d.inner.freeze() leaves top-level `d` unfrozen."
d = self.dict_class()
d.inner.present = TEST_VAL
self.assertEqual(getattr(d.inner, "present"), TEST_VAL)
self.assertEqual(getattr(d.inner, "present", None), TEST_VAL)
self.assertEqual(getattr(d.inner, "missing"), {})
self.assertEqual(getattr(d.inner, "missing", TEST_VAL), {})
self.assertEqual(getattr(d, "missing"), {})
self.assertEqual(getattr(d, "missing", TEST_VAL), {})
d.inner.freeze()
with self.assertRaises(AttributeError):
getattr(d.inner, "missing") # d.inner is frozen
self.assertEqual(getattr(d.inner, "missing", TEST_VAL), TEST_VAL)
self.assertEqual(getattr(d, "missing"), {}) # but not `d` itself
self.assertEqual(getattr(d, "missing", TEST_VAL), {})
d.inner.unfreeze()
self.assertEqual(getattr(d.inner, "missing"), {})
self.assertEqual(getattr(d.inner, "missing", TEST_VAL), {})

class DictTests(unittest.TestCase, AbstractTestsClass):
dict_class = Dict
Expand Down