Skip to content

Commit

Permalink
Version 2.1.0 (#3)
Browse files Browse the repository at this point in the history
* Adding update, set_default and dir support

* Adding tests, fixing up readme

* Python2.x compatibility
  • Loading branch information
cdgriffith authored Mar 22, 2017
1 parent 10d55a1 commit a948c69
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 10 deletions.
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Version 2.1.0
=============

* Adding `.update` and `.set_default` functionality
* Adding `dir` support

Version 2.0.0
=============

Expand Down
8 changes: 2 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -294,21 +294,17 @@ config values into python types. It supports `list`, `bool`, `int` and `float`.
Similar Libraries
-----------------
**Bunch**
* Does not work recursively.
**EasyDict**
* EasyDict not have a way to make sub items recursively back into a regular dictionary.
* Adding new dicts to lists in the dictionary does not make them into EasyDicts.
* Both EasyDicts `str` and `repr` print a dictionary look alike, `Box` makes it clear in repr that it is a Box object.
* Both EasyDicts `str` and `repr` print a dictionary look alike, `Box` makes it clear in `repr` that it is a Box object.
**addict**
* Adding new dicts or lists does not make them into `addict.Dict` objects.
* Is a default dictionary, as in it will never fail on lookup.
* Both EasyDicts `str` and `repr` print a dictionary look alike, `Box` makes it clear in repr that it is a Box object.
* Both `addict.Dict`'s `str` and `repr` print a dictionary look alike, `Box` makes it clear in `repr` that it is a Box object.
License
Expand Down
81 changes: 78 additions & 3 deletions box.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
Improved dictionary management. Inspired by
javascript style referencing, as it's one of the few things they got right.
"""
import string
import sys
import json

Expand All @@ -28,7 +29,7 @@

__all__ = ['Box', 'ConfigBox', 'LightBox', 'BoxList']
__author__ = "Chris Griffith"
__version__ = "2.0.0"
__version__ = "2.1.0"


class LightBox(dict):
Expand Down Expand Up @@ -98,8 +99,52 @@ def __str__(self):
return str(self.to_dict())

def __call__(self, *args, **kwargs):
""" Return keys as a tuple"""
return tuple(sorted(self.keys()))

def __dir__(self):
builtins = ("True", "False", "None", "if", "elif", "else", "for",
"in", "not", "is", "def", "class", "return", "yield",
"except", "while", "raise")
allowed = string.ascii_letters + string.digits + "_"

out = dir(dict) + ['to_dict', 'to_json']
# Only show items accessible by dot notation
for key in self.keys():
if (" " not in key and
key[0] not in string.digits and
key not in builtins):
for letter in key:
if letter not in allowed:
break
else:
out.append(key)

if yaml_support:
out.append('to_yaml')
return out

def update(self, item=None, **kwargs):
if not item:
item = kwargs
iter_over = item.items() if hasattr(item, 'items') else item
for k, v in iter_over:
if isinstance(v, dict):
v = Box(v)
if k in self and isinstance(self[k], dict):
self[k].update(v)
continue
self.__setattr__(k, v)

def setdefault(self, item, default=None):
if item in self:
return self[item]

if isinstance(default, dict):
default = Box(default)
self[item] = default
return default

def to_dict(self, in_dict=None):
"""
Turn the Box and sub Boxes back into a native
Expand Down Expand Up @@ -233,6 +278,31 @@ def to_dict(self, in_dict=None):
out_dict[k] = v
return out_dict

def update(self, item=None, **kwargs):
if not item:
item = kwargs
iter_over = item.items() if hasattr(item, 'items') else item
for k, v in iter_over:
if isinstance(v, dict):
v = Box(v)
if k in self and isinstance(self[k], dict):
self[k].update(v)
continue
elif isinstance(v, list):
v = BoxList(v)
self.__setattr__(k, v)

def setdefault(self, item, default=None):
if item in self:
return self[item]

if isinstance(default, dict):
default = Box(default)
elif isinstance(default, list):
default = BoxList(default)
self[item] = default
return default


class BoxList(list):
"""
Expand Down Expand Up @@ -296,8 +366,8 @@ class ConfigBox(LightBox):
"""

_protected_keys = dir({}) + ['to_dict', 'tree_view',
'bool', 'int', 'float', 'list', 'getboolean',
_protected_keys = dir({}) + ['to_dict', 'bool', 'int', 'float',
'list', 'getboolean', 'to_json', 'to_yaml',
'getfloat', 'getint']

def __getattr__(self, item):
Expand All @@ -308,6 +378,11 @@ def __getattr__(self, item):
except AttributeError:
return super(ConfigBox, self).__getattr__(item.lower())

def __dir__(self):
return super(ConfigBox, self).__dir__() + ['bool', 'int', 'float',
'list', 'getboolean',
'getfloat', 'getint']

def bool(self, item, default=None):
""" Return value of key as a boolean
Expand Down
2 changes: 1 addition & 1 deletion requirements-test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ pytest
coverage >= 3.6
tox
pytest-cov
pyyaml
PyYAML
74 changes: 74 additions & 0 deletions test/test_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,77 @@ def test_boxlist(self):
assert isinstance(str(new_list), str)
assert isinstance(new_list[1], BoxList)
assert not isinstance(new_list.to_list(), BoxList)

def test_dir(self):
test_dict = {'key1': 'value1',
'not$allowed': 'fine_value',
"Key 2": {"Key 3": "Value 3",
"Key4": {"Key5": "Value5"}}}
a = Box(test_dict)
assert 'key1' in dir(a)
assert 'not$allowed' not in dir(a)
assert 'Key4' in a['Key 2']
for item in ('to_yaml', 'to_dict', 'to_json'):
assert item in dir(a)

b = ConfigBox(test_dict)

for item in ('to_yaml', 'to_dict', 'to_json', 'int', 'list', 'float'):
assert item in dir(b)

def test_update(self):
test_dict = {'key1': 'value1',
"Key 2": {"Key 3": "Value 3",
"Key4": {"Key5": "Value5"}}}
a = Box(test_dict)
a.update({'key1': {'new': 5}, 'Key 2': {"add_key": 6},
'lister': ['a']})
a.update([('asdf', 'fdsa')])
a.update(testkey=66)

assert a.key1.new == 5
assert a['Key 2'].add_key == 6
assert "Key5" in a['Key 2'].Key4
assert isinstance(a.key1, Box)
assert isinstance(a.lister, BoxList)
assert a.asdf == 'fdsa'
assert a.testkey == 66

b = LightBox(test_dict)
b.update([('asdf', 'fdsa')])
b.update(testkey=66)
b.update({'key1': {'new': 5}, 'Key 2': {"add_key": 6}})

assert b.key1.new == 5
assert b['Key 2'].add_key == 6
assert "Key5" in b['Key 2'].Key4
assert isinstance(b.key1, LightBox)
assert b.asdf == 'fdsa'
assert b.testkey == 66

def test_set_default(self):
test_dict = {'key1': 'value1',
"Key 2": {"Key 3": "Value 3",
"Key4": {"Key5": "Value5"}}}
a = Box(test_dict)

new = a.setdefault("key3", {'item': 2})
new_list = a.setdefault("lister", [{'gah': 7}])
assert a.setdefault("key1", False) == 'value1'

assert new == Box(item=2)
assert new_list == BoxList([{'gah': 7}])
assert a.key3.item == 2
assert a.lister[0].gah == 7

b = LightBox(test_dict)

new = b.setdefault("key3", {'item': 2})
new_list = b.setdefault("lister", [{'gah': 7}])

assert b.setdefault("key1", False) == 'value1'
assert new == Box(item=2)
assert new_list == [{'gah': 7}]
assert b.key3.item == 2
assert b.lister[0]["gah"] == 7
assert not isinstance(b.lister, BoxList)

0 comments on commit a948c69

Please sign in to comment.