Skip to content

Commit

Permalink
Merge 31f07b8 into 75284f9
Browse files Browse the repository at this point in the history
  • Loading branch information
fmigneault committed May 1, 2021
2 parents 75284f9 + 31f07b8 commit 06e204f
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 6 deletions.
8 changes: 6 additions & 2 deletions .gitignore
Expand Up @@ -53,6 +53,10 @@ docs/_build/
# PyBuilder
target/


#OSX Files
# OSX Files
.DS_Store

# IDE caches
.idea/
.vscode/

34 changes: 34 additions & 0 deletions addict/addict.py
@@ -1,7 +1,9 @@
import copy
import json


class Dict(dict):
__json__ = False # override this for JSON representation

def __init__(__self, *args, **kwargs):
object.__setattr__(__self, '__parent', kwargs.pop('__parent', None))
Expand Down Expand Up @@ -157,3 +159,35 @@ def freeze(self, shouldFreeze=True):

def unfreeze(self):
self.freeze(False)

def json(self, force=True):
base = {}
for key, value in self.items():
if isinstance(value, (type(self), dict)):
base[key] = Dict(value).json()
elif isinstance(value, (list, tuple)):
base[key] = list(
(Dict(item).json() if callable(item.json) else Dict(item).json)
if isinstance(item, (type(self), dict)) or hasattr(item, "json")
else item
if isinstance(item, (int, float, bool, str, type(None)))
else str(item) if force else item
for item in value)
elif isinstance(value, (int, float, bool, str, type(None))):
base[key] = value
elif hasattr(value, "json"):
base[key] = value.json() if callable(value.json) else value.json
else:
base[key] = str(value) if force else value
return base

def __repr__(self):
if self.__json__:
cls = type(self)
try:
repr_ = json.dumps(self.json(force=False),
indent=2, ensure_ascii=False)
except Exception: # noqa
return dict.__repr__(self)
return "{0}.{1}\n{2}".format(cls.__module__, cls.__name__, repr_)
return dict.__repr__(self)
112 changes: 108 additions & 4 deletions test_addict.py
@@ -1,9 +1,20 @@
import json
import collections
import copy
import json
import unittest
import pickle
import sys
import uuid
from inspect import cleandoc
from addict import Dict

# patch auto ordered dict for Python < 3.7
# whenever tests need ordering compatibility
if sys.version_info < (3, 7):
_dict = collections.OrderedDict
else:
_dict = dict


# test whether unittests pass on child classes
class CHILD_CLASS(Dict):
Expand Down Expand Up @@ -267,7 +278,7 @@ def update():
org.update({'a': 2}, {'a': 1})
org = self.dict_class()
self.assertRaises(TypeError, update)

def test_ior_operator(self):
old = self.dict_class()
old.child.a = 'a'
Expand Down Expand Up @@ -337,7 +348,7 @@ def test_or_operator_with_lists(self):
org = org | someother
self.assertDictEqual(org, correct)
self.assertIsInstance(org.b[0], dict)

def test_ror_operator(self):
org = dict()
org['a'] = [1, 2, {'a': 'superman'}]
Expand All @@ -352,7 +363,7 @@ def test_ror_operator(self):
self.assertDictEqual(org, correct)
self.assertIsInstance(org, Dict)
self.assertIsInstance(org.b[0], dict)

def test_or_operator_type_error(self):
old = self.dict_class()
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -572,6 +583,99 @@ def test_top_freeze_disallows_new_key_addition(self):
d.newKey = TEST_VAL
self.assertEqual(d.newKey, TEST_VAL)

def test_json_conversion(self):
"Test that d.json() returns a JSON compatible nested dict."
class JsonClassMethod(object): # not JSON serializable
def json(self): # but implements json, so will use it to serialize
return {"json": "class"}

# same thing, but using property instead of method
class JsonClassProperty(object):
@property
def json(self):
return {"json": "attr"}

_uuid = uuid.uuid4() # not serializable directly, but string repr
d = self.dict_class({"dict": {"other": "value", "key": {"ok": "no"}},
"meth": {"cls": JsonClassMethod()},
"prop": JsonClassProperty(),
"sub": Dict({"ok": 1}), # nested
"uuid": _uuid,
"tuple": (1, 2, 3, False), # to JSON array
"list": [
"1", 2, 3.3, None, {"dict": {"ok": True}}
]})
expect = {
"dict": {"other": "value", "key": {"ok": "no"}},
"meth": {"cls": {"json": "class"}},
"prop": {"json": "attr"},
"sub": {"ok": 1},
"uuid": str(_uuid),
"tuple": [1, 2, 3, False],
"list": ["1", 2, 3.3, None, {"dict": {"ok": True}}]
}
self.assertEqual(d.json(), expect)

def test_json_repr(self):
"Test that JSON representation is used when requested with __json__"
class JsonRepr(self.dict_class):
# must enforce JSON repr method, so that existing repr don't break
# (backward compatibility)
__json__ = True

data = _dict([
("top", _dict([("x", 0), ("y", -1)])),
("dict", self.dict_class(_dict([("a", 1), ("b", None)]))),
("list", [Dict(_dict([("c", 3), ("d", "ok")])), 4.5])
])
d = JsonRepr(data)
_repr = cleandoc("""
test_addict.JsonRepr
{
"top": {
"x": 0,
"y": -1
},
"dict": {
"a": 1,
"b": null
},
"list": [
{
"c": 3,
"d": "ok"
},
4.5
]
}
""")
if sys.version_info < (3, ):
_repr = _repr.replace(",\n", ", \n")
self.assertEqual(repr(d), _repr)

# check that normal repr still works as usual (__json__ = False)
# don't need OrderedDict for Python 2.7 because handled the same way
data = {"dict": Dict({"a": 1, "b": None}),
"list": [Dict({"c": 3, "d": "ok"}), 4.5]}
d = self.dict_class(data)
self.assertEqual(repr(d), str(data))

def test_json_repr_not_serializable(self):
"Test that failing JSON representation falls back to the dict repr"
class JsonRepr(self.dict_class):
__json__ = True

class NotSerializable(object):
pass

data = _dict({"x": 1, "y": NotSerializable()})
json_d = JsonRepr(data)
dict_d = self.dict_class(data)
json_r = repr(json_d) # shouldn't raise
dict_r = repr(dict_d) # normal repr
self.assertEqual(json_r, dict_r)


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

Expand Down

0 comments on commit 06e204f

Please sign in to comment.