Repository navigation
Subclassing Box
Box is built to be subclassed. Nested dictionaries become instances of your class, and so do
copies, pickles and anything loaded with from_json, from_yaml and friends.
New in Box 8.
Set _box_defaults to the options your class should use when they are not passed:
from box import Box
class Config(Box):
_box_defaults = {"default_box": True, "box_dots": True}
config = Config()
config["server.host"] = "localhost"
print(config)
# {'server': {'host': 'localhost'}}
print(type(config.server))
# <class '__main__.Config'>An option passed explicitly still wins, Config(default_box=False) turns it off again.
Subclasses of Config inherit its defaults and only need to list what they change:
class FrozenConfig(Config):
_box_defaults = {"frozen_box": True}A misspelled option name raises BoxError when the class is created.
Box creates new instances of your class internally (for nested dictionaries, copies and
pickling) and passes its options to them as keyword arguments. If you override __init__,
accept *args and **kwargs and pass them on:
class Settings(Box):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# your setup hereAn __init__ that only accepts its own arguments fails with
TypeError: __init__() got an unexpected keyword argument 'default_box'. If all you need is
different option defaults, use _box_defaults instead of an __init__.
Methods and properties on your class take priority over keys with the same name for attribute access, the key is still there with item access:
class Printer(Box):
def print(self):
return "method"
p = Printer(print="key")
p.print()
# 'method'
p["print"]
# 'key'To make Box reject keys that clash with your methods, add the names to _protected_keys:
class Printer(Box):
_protected_keys = Box._protected_keys + ["print"]
def print(self):
return "method"
Printer().print = "key"
# box.exceptions.BoxKeyError: 'Key name "print" is protected'New in Box 8.
Attributes listed in _box_internal_attrs are stored on the instance, not in the data, so they
never show up in to_dict(), iteration, comparisons or to_json():
class Document(Box):
_box_internal_attrs = ("source_path",)
doc = Document(title="Notes")
doc.source_path = "/tmp/notes.json"
print(doc.to_dict())
# {'title': 'Notes'}
print(doc.source_path)
# /tmp/notes.jsoncopy() and deepcopy() carry them over. Nested boxes start without them, and they can be
set even on a frozen box.
Changed in Box 8: copy(), copy.copy() and copy.deepcopy() return an instance of your
class. Before Box 8 they returned a plain Box.
These documents are for Box 5.0+