Skip to content

Commit

Permalink
Version 3.2.2 (#63)
Browse files Browse the repository at this point in the history
* Adding hash abilities to new frozen BoxList
* Fixing hashing returned unpredictable values (thanks to cebaa)
* Fixing update to not handle protected words correctly (thanks to deluxghost)
* Removing non-collection support for mapping and callable identification
  • Loading branch information
cdgriffith authored Oct 31, 2018
1 parent ae49ac2 commit f3ec16e
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 30 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ python:
- "3.6"
matrix:
include:
- python: 3.7
- python: "3.7"
dist: xenial
sudo: true
install:
Expand Down
2 changes: 2 additions & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ Suggestions and bug reporting:
- (richieadler)
- V.Anh Tran (tranvietanh1991)
- (ipcoder)
- (cebaa)
- (deluxghost)
8 changes: 8 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
---------

Version 3.2.2
~~~~~~~~~~~~~

* Adding hash abilities to new frozen BoxList
* Fixing hashing returned unpredictable values (thanks to cebaa)
* Fixing update to not handle protected words correctly (thanks to deluxghost)
* Removing non-collection support for mapping and callable identification

Version 3.2.1
~~~~~~~~~~~~~

Expand Down
41 changes: 17 additions & 24 deletions box.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,22 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
# Copyright (c) 2017 - Chris Griffith - MIT License
# Copyright (c) 2017-2018 - Chris Griffith - MIT License
"""
Improved dictionary access through dot notation with additional tools.
"""
import string
import sys
import json
from uuid import uuid4
import re
import collections
import copy
from keyword import kwlist
import warnings

try:
from collections.abc import Iterable, Mapping
from collections.abc import Iterable, Mapping, Callable
except ImportError:
try:
from collections import Iterable, Mapping
except ImportError:
Mapping = dict
Iterable = (tuple, list)
from collections import Iterable, Mapping, Callable

yaml_support = True

Expand Down Expand Up @@ -238,7 +232,6 @@ def _get_box_config(cls, kwargs):
# Internal use only
'__converted': set(),
'__box_heritage': kwargs.pop('__box_heritage', None),
'__hash': None,
'__created': False,
# Can be changed by user after box creation
'default_box': kwargs.pop('default_box', False),
Expand All @@ -255,10 +248,7 @@ def _get_box_config(cls, kwargs):

class Box(dict):
"""
The lists are turned into BoxLists
so that they can also intercept incoming items and turn
them into Boxes.
Improved dictionary access through dot notation with additional tools.
:param default_box: Similar to defaultdict, return a default value
:param default_box_attr: Specify the default replacement.
Expand Down Expand Up @@ -345,12 +335,10 @@ def box_it_up(self):

def __hash__(self):
if self._box_config['frozen_box']:
if not self._box_config['__hash']:
hashing = hash(uuid4().hex)
for item in self.items():
hashing ^= hash(item)
self._box_config['__hash'] = hashing
return self._box_config['__hash']
hashing = 54321
for item in self.items():
hashing ^= hash(item)
return hashing
raise TypeError("unhashable type: 'Box'")

def __dir__(self):
Expand Down Expand Up @@ -447,7 +435,7 @@ def __get_default(self, item):
if default_value is self.__class__:
return self.__class__(__box_heritage=(self, item),
**self.__box_config())
elif isinstance(default_value, collections.Callable):
elif isinstance(default_value, Callable):
return default_value()
elif hasattr(default_value, 'copy'):
return default_value.copy()
Expand Down Expand Up @@ -557,8 +545,6 @@ def __setattr__(self, key, value):
if key == _camel_killer(each_key):
self[each_key] = value
break
else:
self[key] = value
else:
self[key] = value
else:
Expand Down Expand Up @@ -669,7 +655,7 @@ def update(self, item=None, **kwargs):
v = BoxList(v)
try:
self.__setattr__(k, v)
except TypeError:
except (AttributeError, TypeError):
self.__setitem__(k, v)

def setdefault(self, item, default=None):
Expand Down Expand Up @@ -849,6 +835,13 @@ def __deepcopy__(self, memodict=None):
out.append(copy.deepcopy(k))
return out

def __hash__(self):
if self.box_options.get('frozen_box'):
hashing = 98765
hashing ^= hash(tuple(self))
return hashing
raise TypeError("unhashable type: 'BoxList'")

def to_list(self):
new_list = []
for x in self:
Expand Down
25 changes: 21 additions & 4 deletions test/test_functional_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,13 @@ def test_to_json(self):

def test_to_yaml(self):
a = Box(test_dict)
assert yaml.load(a.to_yaml()) == test_dict
assert yaml.load(a.to_yaml(), Loader=yaml.SafeLoader) == test_dict

def test_to_yaml_file(self):
a = Box(test_dict)
a.to_yaml(tmp_yaml_file)
with open(tmp_yaml_file) as f:
data = yaml.load(f)
data = yaml.load(f, Loader=yaml.SafeLoader)
assert data == test_dict

def test_boxlist(self):
Expand Down Expand Up @@ -213,7 +213,9 @@ def test_update(self):
'lister': ['a']})
a.update([('asdf', 'fdsa')])
a.update(testkey=66)
a.update({'items': 'test'})

assert a['items'] == 'test'
assert a.key1.new == 5
assert a['Key 2'].add_key == 6
assert "Key5" in a['Key 2'].Key4
Expand Down Expand Up @@ -321,6 +323,20 @@ def test_frozen(self):

assert hash(bx3)

def test_hashing(self):
bx1 = Box(t=3, g=4, frozen_box=True)
bx2 = Box(g=4, t=3, frozen_box=True)
assert hash(bx1) == hash(bx2)

bl1 = BoxList([1, 2, 3, 4], frozen_box=True)
bl2 = BoxList([1, 2, 3, 4], frozen_box=True)
bl3 = BoxList([2, 1, 3, 4], frozen_box=True)
assert hash(bl2) == hash(bl1)
assert hash(bl3) != hash(bl2)

with pytest.raises(TypeError):
hash(BoxList([1, 2, 3]))

def test_frozen_list(self):
bl = BoxList([5, 4, 3], frozen_box=True)
with pytest.raises(BoxError):
Expand Down Expand Up @@ -396,7 +412,8 @@ def test_property_box(self):
assert isinstance(pbox.inner, SBox)
assert pbox.inner.camel_case == 'Item'
assert json.loads(pbox.json)['inner']['CamelCase'] == 'Item'
assert yaml.load(pbox.yaml)['inner']['CamelCase'] == 'Item'
test_item = yaml.load(pbox.yaml, Loader=yaml.SafeLoader)
assert test_item['inner']['CamelCase'] == 'Item'
assert repr(pbox['inner']).startswith('<ShorthandBox')
assert not isinstance(pbox.dict, Box)
assert pbox.dict['inner']['CamelCase'] == 'Item'
Expand All @@ -417,7 +434,7 @@ def test_box_list_from_json(self):

def test_box_list_to_yaml(self):
bl = BoxList([{'item': 1, 'CamelBad': 2}])
assert yaml.load(bl.to_yaml())[0]['item'] == 1
assert yaml.load(bl.to_yaml(), Loader=yaml.SafeLoader)[0]['item'] == 1

def test_box_list_from_yaml(self):
alist = [{'item': 1}, {'CamelBad': 2}]
Expand Down
3 changes: 2 additions & 1 deletion test/test_unittests_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,5 @@ def test_to_yaml(self):
assert "Rick Moranis" in movie_string
box._to_yaml(movie_data, filename=m_file)
assert "Rick Moranis" in open(m_file).read()
assert yaml.load(open(m_file)) == yaml.load(movie_string)
assert yaml.load(open(m_file), Loader=yaml.SafeLoader) == yaml.load(
movie_string, Loader=yaml.SafeLoader)

0 comments on commit f3ec16e

Please sign in to comment.