Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix the error type for missing attributes and getattr compatibility issues #146

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
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