Skip to content

Commit

Permalink
* Adding #192 box_dots treats all keys with periods in them as separa…
Browse files Browse the repository at this point in the history
…te keys (thanks to Rexbard)

* Adding #236 iPython detection to prevent adding attribute lookup words (thanks to Nishikant Parmar)
* Adding new DDBox class (Default Dots Box) that is a subclass of SBox
  • Loading branch information
cdgriffith committed Jan 28, 2023
1 parent 2c52667 commit 77e3587
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 16 deletions.
2 changes: 1 addition & 1 deletion AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ Code contributions:
- Ivan Pepelnjak (ipspace)
- Michał Górny (mgorny)
- Serge Lu (Serge45)
- Nishikant Parmar (nishikantparmariam)


Suggestions and bug reporting:
Expand Down Expand Up @@ -84,3 +83,4 @@ Suggestions and bug reporting:
- Rexbard
- Martin Schorfmann (schorfma)
- aviveh21
- Nishikant Parmar (nishikantparmariam)
6 changes: 4 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ Changelog
Version 7.0.0
-------------

* Adding #239 support for properties and setters in subclasses (thanks to Serge Lu)
* Adding #192 box_dots treats all keys with periods in them as separate keys (thanks to Rexbard)
* Adding #211 support for properties and setters in subclasses (thanks to Serge Lu and David Aronchick)
* Adding #226 namespace to track changes to the box (thanks to Jacob Hayes)
* Adding #236 ``getdoc`` method to prevent iPython from adding extra key (thanks to Nishikant Parmar)
* Adding #236 iPython detection to prevent adding attribute lookup words (thanks to Nishikant Parmar)
* Adding new DDBox class (Default Dots Box) that is a subclass of SBox
* Fixing #235 how ``|`` and ``+`` updates were performed for right operations (thanks to aviveh21)
* Fixing #234 typos (thanks to Martin Schorfmann)
* Fixing no implicit optionals with type hinting
Expand Down
3 changes: 2 additions & 1 deletion box/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from box.config_box import ConfigBox
from box.exceptions import BoxError, BoxKeyError
from box.from_file import box_from_file, box_from_string
from box.shorthand_box import SBox
from box.shorthand_box import SBox, DDBox
import box.converters

__all__ = [
Expand All @@ -20,4 +20,5 @@
"BoxKeyError",
"box_from_file",
"SBox",
"DDBox",
]
30 changes: 26 additions & 4 deletions box/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
except ImportError:
from collections.abc import Callable, Iterable, Mapping

try:
get_ipython()
except NameError:
ipython = False
else:
ipython = True

import box
from box.converters import (
BOX_PARAMETERS,
Expand Down Expand Up @@ -141,6 +148,7 @@ def _get_property_func(obj, key):
return attr.fget, attr.fset, attr.fdel



class Box(dict):
"""
Improved dictionary access through dot notation with additional tools.
Expand Down Expand Up @@ -174,7 +182,6 @@ class Box(dict):
"from_toml",
"to_toml",
"merge_update",
"getdoc",
] + [attr for attr in dir({}) if not attr.startswith("_")]

def __new__(
Expand Down Expand Up @@ -482,7 +489,19 @@ def __get_default(self, item, attr=False):
value = default_value
if self._box_config["default_box_create_on_get"]:
if not attr or not (item.startswith("_") and item.endswith("_")):
super().__setitem__(item, value)
if ipython and item in ("getdoc", "shape"):
return value
if self._box_config["box_dots"] and isinstance(item, str) and ("." in item or "[" in item):
first_item, children = _parse_box_dots(self, item, setting=True)
if first_item in self.keys():
if hasattr(self[first_item], "__setitem__"):
self[first_item].__setitem__(children, value)
else:
super().__setitem__(first_item, self._box_config["box_class"](
**self.__box_config(extra_namespace=first_item)))
self[first_item].__setitem__(children, value)
else:
super().__setitem__(item, value)
return value

def __box_config(self, extra_namespace: Any = NO_NAMESPACE) -> Dict:
Expand Down Expand Up @@ -596,6 +615,11 @@ def __setitem__(self, key, value):
if first_item in self.keys():
if hasattr(self[first_item], "__setitem__"):
return self[first_item].__setitem__(children, value)
elif self._box_config["default_box"]:
super().__setitem__(first_item, self._box_config["box_class"](**self.__box_config(extra_namespace=first_item)))
return self[first_item].__setitem__(children, value)
else:
raise BoxKeyError(f"'{self.__class__}' object has no attribute {first_item}")
value = self.__recast(key, value)
if key not in self.keys() and self._box_config["camel_killer_box"]:
if self._box_config["camel_killer_box"] and isinstance(key, str):
Expand Down Expand Up @@ -1123,5 +1147,3 @@ def from_msgpack(
) -> "Box":
raise BoxError('msgpack is unavailable on this system, please install the "msgpack" package')

def getdoc(self):
return Box.__doc__
21 changes: 20 additions & 1 deletion box/shorthand_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from box.box import Box

__all__ = ["SBox", "DDBox"]


class SBox(Box):
"""
Expand Down Expand Up @@ -41,10 +43,27 @@ def toml(self):
return self.to_toml()

def __repr__(self):
return "ShorthandBox({0})".format(str(self.to_dict()))
return f"SBox({self})"

def copy(self):
return SBox(super(SBox, self).copy())

def __copy__(self):
return SBox(super(SBox, self).copy())


class DDBox(SBox):

def __init__(self, *args, **kwargs):
kwargs["box_dots"] = True
kwargs["default_box"] = True
super().__init__(*args, **kwargs)

def __new__(cls, *args, **kwargs):
obj = super().__new__(cls, *args, **kwargs)
obj._box_config["box_dots"] = True
obj._box_config["default_box"] = True
return obj

def __repr__(self):
return f"DDBox({self})"
3 changes: 3 additions & 0 deletions box/shorthand_box.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ class SBox(Box):
def toml(self): ...
def copy(self): ...
def __copy__(self): ...

class DDBox(Box): ...

17 changes: 10 additions & 7 deletions test/test_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,7 @@ def test_box_dots(self):
b = Box(
{"my_key": {"does stuff": {"to get to": "where I want"}}, "key.with.list": [[[{"test": "value"}]]]},
box_dots=True,
default_box=True
)
for key in b.keys(dotted=True):
b[key]
Expand Down Expand Up @@ -1237,16 +1238,18 @@ def test_default_box_restricted_calls(self):
assert len(list(a.keys())) == 2

def test_default_dots(self):
bx1 = Box(default_box=True, box_dots=True)
bx1["a.a.a"]
assert bx1 == {"a": {"a":{"a":{}}}}

a = Box(default_box=True, box_dots=True)
a["a.a.a"]
assert a == {"a.a.a": {}}
a["a.a.a."]
a["a.a.a.."]
assert a == {"a.a.a": {"": {"": {}}}}
a["a."]
a["a.."]
assert a == {"a": {"": {"": {}}}}
a["b.b"] = 3
assert a == {"a.a.a": {"": {"": {}}}, "b.b": 3}
assert a == {"a": {"": {"": {}}}, "b": {"b": 3}}
a.b.b = 4
assert a == {"a.a.a": {"": {"": {}}}, "b.b": 3, "b": {"b": 4}}
assert a == {"a": {"": {"": {}}}, "b": {"b": 4}}
assert a["non.existent.key"] == {}

def test_merge_list_options(self):
Expand Down

0 comments on commit 77e3587

Please sign in to comment.