Skip to content

Commit

Permalink
* Changing #208 __repr__ to produce eval-able text (thanks to Jeff …
Browse files Browse the repository at this point in the history
…Robbins)

* Fixing internal `_safe_key` logic to be twice as fast
  • Loading branch information
cdgriffith committed Nov 11, 2021
1 parent 3551f75 commit b3ba1fb
Show file tree
Hide file tree
Showing 12 changed files with 42 additions and 34 deletions.
12 changes: 6 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ repos:
- id: end-of-file-fixer
exclude: ^test/data/.+

- repo: https://github.com/ambv/black
rev: 21.7b0
hooks:
- id: black
args: [--config=.black.toml]

- repo: local
hooks:
- id: cythonize-check
Expand All @@ -44,12 +50,6 @@ repos:
pass_filenames: false
always_run: true

- repo: https://github.com/ambv/black
rev: 21.7b0
hooks:
- id: black
args: [--config=.black.toml]

- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v0.910'
hooks:
Expand Down
6 changes: 4 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
Changelog
=========

Version 5.5.0
Version 6.0.0
-------------

* Adding Cython support to greatly speed up normal Box operations on supported systems
* Fixing `update` and `merge_update` used a keyword that could cause issues in rare circumstances
* Changing #208 __repr__ to produce `eval`-able text (thanks to Jeff Robbins)
* Changing `update` and `merge_update` to not use a keyword that could cause issues in rare circumstances
* Fixing internal `_safe_key` logic to be twice as fast

Version 5.4.1
-------------
Expand Down
2 changes: 1 addition & 1 deletion box/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-

__author__ = "Chris Griffith"
__version__ = "5.5.0"
__version__ = "6.0.0"

from box.box import Box
from box.box_list import BoxList
Expand Down
38 changes: 22 additions & 16 deletions box/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
"""
import copy
import re
import string
import warnings
from keyword import kwlist
from keyword import iskeyword
from os import PathLike
from typing import Any, Dict, Generator, List, Tuple, Union

Expand Down Expand Up @@ -331,17 +330,12 @@ def __hash__(self):
raise BoxTypeError('unhashable type: "Box"')

def __dir__(self):
allowed = string.ascii_letters + string.digits + "_"
items = set(super().__dir__())
# Only show items accessible by dot notation
for key in self.keys():
key = str(key)
if " " not in key and key[0] not in string.digits and key not in kwlist:
for letter in key:
if letter not in allowed:
break
else:
items.add(key)
if key.isidentifier() and not iskeyword(key):
items.add(key)

for key in self.keys():
if key not in items:
Expand Down Expand Up @@ -547,12 +541,13 @@ def __setitem__(self, key, value):
self.__convert_and_store(key, value)

def __setattr__(self, key, value):
if key != "_box_config" and self._box_config["frozen_box"] and self._box_config["__created"]:
if key == "_box_config":
return object.__setattr__(self, key, value)
if self._box_config["frozen_box"] and self._box_config["__created"]:
raise BoxError("Box is frozen")
if key in self._protected_keys:
raise BoxKeyError(f'Key name "{key}" is protected')
if key == "_box_config":
return object.__setattr__(self, key, value)

safe_key = self._safe_attr(key)
if safe_key in self._box_config["__safe_keys"]:
key = self._box_config["__safe_keys"][safe_key]
Expand Down Expand Up @@ -637,7 +632,7 @@ def popitem(self):
return key, self.pop(key)

def __repr__(self) -> str:
return f"<Box: {self.to_dict()}>"
return f"Box({self})"

def __str__(self) -> str:
return str(self.to_dict())
Expand Down Expand Up @@ -739,7 +734,10 @@ def setdefault(self, item, default=None):

def _safe_attr(self, attr):
"""Convert a key into something that is accessible as an attribute"""
allowed = string.ascii_letters + string.digits + "_"
if isinstance(attr, str):
# By assuming most people are using string first we get substantial speed ups
if attr.isidentifier() and not iskeyword(attr):
return attr

if isinstance(attr, tuple):
attr = "_".join([str(x) for x in attr])
Expand All @@ -748,10 +746,18 @@ def _safe_attr(self, attr):
if self.__box_config()["camel_killer_box"]:
attr = _camel_killer(attr)

if attr.isidentifier() and not iskeyword(attr):
return attr

if sum(1 for character in attr if character.isidentifier() and not iskeyword(character)) == 0:
attr = f'{self.__box_config()["box_safe_prefix"]}{attr}'
if attr.isidentifier() and not iskeyword(attr):
return attr

out = []
last_safe = 0
for i, character in enumerate(attr):
if character in allowed:
if f"x{character}".isidentifier():
last_safe = i
out.append(character)
elif not out:
Expand All @@ -769,7 +775,7 @@ def _safe_attr(self, attr):
else:
out = f'{self.__box_config()["box_safe_prefix"]}{out}'

if out in kwlist:
if iskeyword(out):
out = f'{self.__box_config()["box_safe_prefix"]}{out}'

return out
Expand Down
2 changes: 1 addition & 1 deletion box/box_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def _dotted_helper(self):
return keys

def __repr__(self):
return f"<BoxList: {self.to_list()}>"
return f"BoxList({self.to_list()})"

def __str__(self):
return str(self.to_list())
Expand Down
2 changes: 1 addition & 1 deletion box/config_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def getfloat(self, item, default=None):
return self.float(item, default)

def __repr__(self):
return "<ConfigBox: {0}>".format(str(self.to_dict()))
return "ConfigBox({0})".format(str(self.to_dict()))

def copy(self):
return ConfigBox(super().copy())
Expand Down
2 changes: 1 addition & 1 deletion box/shorthand_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def toml(self):
return self.to_toml()

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

def copy(self):
return SBox(super(SBox, self).copy())
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import os
import re
from pathlib import Path
import warnings
import sys

from setuptools import setup

Expand Down Expand Up @@ -76,4 +76,4 @@
)

if not extra:
warnings.warn("WARNING: Cython not installed, could not optimize box.")
print("WARNING: Cython not installed, could not optimize box.", file=sys.stderr)
2 changes: 1 addition & 1 deletion test/test_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def test_box(self):
assert "TEST_KEY" not in bx.to_dict(), bx.to_dict()
assert isinstance(bx["Key 2"].Key4, Box)
assert "'key1': 'value1'" in str(bx)
assert repr(bx).startswith("<Box:")
assert repr(bx).startswith("Box(")
bx2 = Box([((3, 4), "A"), ("_box_config", "test")])
assert bx2[(3, 4)] == "A"
assert bx2["_box_config"] == "test"
Expand Down
2 changes: 1 addition & 1 deletion test/test_box_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def test_box_list(self):
assert new_list[-1].item == 22
new_list.append([{"bad_item": 33}])
assert new_list[-1][0].bad_item == 33
assert repr(new_list).startswith("<BoxList:")
assert repr(new_list).startswith("BoxList(")
for x in new_list.to_list():
assert not isinstance(x, (BoxList, Box))
new_list.insert(0, {"test": 5})
Expand Down
2 changes: 1 addition & 1 deletion test/test_config_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def test_config_box(self):
assert cns.bb.getfloat("Wooo", 4.4) == 4.4
assert cns.bb.getboolean("huh", True) is True
assert cns.bb.list("Waaaa", [1]) == [1]
assert repr(cns).startswith("<ConfigBox")
assert repr(cns).startswith("ConfigBox(")

def test_dir(self):
b = ConfigBox(test_dict)
Expand Down
2 changes: 1 addition & 1 deletion test/test_sbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def test_property_box(self):
assert json.loads(pbox.json)["inner"]["camel_case"] == "Item"
test_item = yaml.load(pbox.yaml, Loader=yaml.SafeLoader)
assert test_item["inner"]["camel_case"] == "Item"
assert repr(pbox["inner"]).startswith("<ShorthandBox")
assert repr(pbox["inner"]).startswith("ShorthandBox(")
assert not isinstance(pbox.dict, Box)
assert pbox.dict["inner"]["camel_case"] == "Item"
assert pbox.toml.startswith('key1 = "value1"')

0 comments on commit b3ba1fb

Please sign in to comment.