From 4b66113d193690191d488e45818f80a1f7f85285 Mon Sep 17 00:00:00 2001 From: Chris Griffith Date: Thu, 20 Aug 2020 13:54:19 -0500 Subject: [PATCH] Version 5.1.1 Bugfix dots default (#166) --- .github/workflows/tests.yml | 8 ++++---- .pre-commit-config.yaml | 2 +- CHANGES.rst | 23 +++++++++++++--------- box/__init__.py | 4 ++-- box/box.py | 28 +++++++++++++++------------ box/box_list.py | 20 +++++++++---------- box/converters.py | 2 +- box/from_file.py | 13 +++++++------ setup.py | 7 ++++--- test/test_box.py | 38 ++++++++++++++++++++++++------------- test/test_box_list.py | 5 ++--- test/test_config_box.py | 3 ++- test/test_converters.py | 9 ++++----- test/test_from_file.py | 4 ++-- test/test_sbox.py | 4 ++-- 15 files changed, 96 insertions(+), 74 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 68bcb2a..364b7e0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,9 +5,9 @@ name: Tests on: push: - branches: [ master, development ] + branches: [ master, development, develop, test, tests ] pull_request: - branches: [ master, development ] + branches: [ master, development, develop, test, tests ] jobs: package_checks: @@ -45,12 +45,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, pypy3] + python-version: [3.6, 3.7, 3.8, 3.9.0-rc.1, pypy3] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0b5c57d..9b33903 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: debug-statements - id: check-yaml - repo: https://github.com/ambv/black - rev: 19.10b0 + rev: stable hooks: - id: black args: [--config=.black.toml] diff --git a/CHANGES.rst b/CHANGES.rst index 72fc58c..4be6f0b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,35 +1,40 @@ Changelog ========= +Version 5.1.1 +------------- + +* Adding testing for Python 3.9 +* Fixing #165 `box_dots` to work with `default_box` Version 5.1.0 ------------- -* Adding `dotted` option for `items` function (thanks to ipcoder) -* Fixing bug in box.set_default where value is dictionary, return the internal value and not detached temporary (thanks to Noam Graetz) +* Adding #152 `dotted` option for `items` function (thanks to ipcoder) +* Fixing #157 bug in box.set_default where value is dictionary, return the internal value and not detached temporary (thanks to Noam Graetz) * Removing warnings on import if optional libraries are missing Version 5.0.1 ------------- -* Fixing default box saving internal method calls and restricted options (thanks to Marcelo Huerta) +* Fixing #155 default box saving internal method calls and restricted options (thanks to Marcelo Huerta) Version 5.0.0 ------------- * Adding support for msgpack converters `to_msgpack` and `from_msgpack` -* Adding support for comparision of `Box` to other boxes or dicts via the `-` sub operator #144 (thanks to Hitz) +* Adding #144 support for comparision of `Box` to other boxes or dicts via the `-` sub operator (thanks to Hitz) * Adding support to `|` union boxes like will come default in Python 3.9 from PEP 0584 * Adding `mypy` type checking, `black` formatting and other checks on commit -* Adding new parameter `box_class` for cleaner inheritance #148 (thanks to David Aronchick) -* Adding `dotted` option for `keys` method to return box_dots style keys (thanks to ipcoder) +* Adding #148 new parameter `box_class` for cleaner inheritance (thanks to David Aronchick) +* Adding #152 `dotted` option for `keys` method to return box_dots style keys (thanks to ipcoder) * Fixing box_dots to properly delete items from lists * Fixing box_dots to properly find items with dots in their key * Fixing that recast of subclassses of `Box` or `BoxList` were not fed box properties (thanks to Alexander Kapustin) -* Changing that sub boxes are always created to properly propagate settings and copy objects #150 (thanks to ipcoder) -* Changing that default_box will not raise key errors on `pop` #67 (thanks to Patrock) +* Changing #150 that sub boxes are always created to properly propagate settings and copy objects (thanks to ipcoder) +* Changing #67 that default_box will not raise key errors on `pop` (thanks to Patrock) * Changing `to_csv` and `from_csv` to have same string and filename options as all other transforms -* Changing back to no required external imports, instead have extra requires like [all] (thanks to wim glenn) +* Changing #127 back to no required external imports, instead have extra requires like [all] (thanks to wim glenn) * Changing from putting all details in README.rst to a github wiki at https://github.com/cdgriffith/Box/wiki * Changing `BoxList.box_class` to be stored in `BoxList.box_options` dict as `box_class` * Changing `del` will raise `BoxKeyError`, subclass of both `KeyError` and `BoxError` diff --git a/box/__init__.py b/box/__init__.py index f128dfc..191029e 100644 --- a/box/__init__.py +++ b/box/__init__.py @@ -2,11 +2,11 @@ # -*- coding: utf-8 -*- __author__ = "Chris Griffith" -__version__ = "5.1.0" +__version__ = "5.1.1" from box.box import Box from box.box_list import BoxList from box.config_box import ConfigBox -from box.shorthand_box import SBox from box.exceptions import BoxError, BoxKeyError from box.from_file import box_from_file +from box.shorthand_box import SBox diff --git a/box/box.py b/box/box.py index 17cc034..42aaaf9 100644 --- a/box/box.py +++ b/box/box.py @@ -9,26 +9,25 @@ import re import string import warnings -from os import PathLike -from collections.abc import Iterable, Mapping, Callable +from collections.abc import Callable, Iterable, Mapping from keyword import kwlist -from typing import Any, Union, Tuple, List, Dict, Generator - +from os import PathLike +from typing import Any, Dict, Generator, List, Tuple, Union import box from box.converters import ( - _to_json, + BOX_PARAMETERS, _from_json, + _from_msgpack, _from_toml, - _to_toml, _from_yaml, - _to_yaml, + _to_json, _to_msgpack, - _from_msgpack, - BOX_PARAMETERS, - yaml_available, - toml_available, + _to_toml, + _to_yaml, msgpack_available, + toml_available, + yaml_available, ) from box.exceptions import BoxError, BoxKeyError, BoxTypeError, BoxValueError, BoxWarning @@ -454,7 +453,12 @@ def __getitem__(self, item, _ignore_default=False): if item == "_box_config": raise BoxKeyError("_box_config should only exist as an attribute and is never defaulted") from None if self._box_config["box_dots"] and isinstance(item, str) and ("." in item or "[" in item): - first_item, children = _parse_box_dots(self, item) + try: + first_item, children = _parse_box_dots(self, item) + except BoxError: + if self._box_config["default_box"] and not _ignore_default: + return self.__get_default(item) + raise if first_item in self.keys(): if hasattr(self[first_item], "__getitem__"): return self[first_item][children] diff --git a/box/box_list.py b/box/box_list.py index d3608c6..f19c79d 100644 --- a/box/box_list.py +++ b/box/box_list.py @@ -5,25 +5,25 @@ import copy import re from os import PathLike -from typing import Iterable, Type, Union from pathlib import Path +from typing import Iterable, Type, Union import box from box.converters import ( - _to_yaml, - _from_yaml, - _to_json, + BOX_PARAMETERS, + _from_csv, _from_json, - _to_toml, + _from_msgpack, _from_toml, + _from_yaml, _to_csv, - _from_csv, + _to_json, _to_msgpack, - _from_msgpack, - BOX_PARAMETERS, - yaml_available, - toml_available, + _to_toml, + _to_yaml, msgpack_available, + toml_available, + yaml_available, ) from box.exceptions import BoxError, BoxTypeError diff --git a/box/converters.py b/box/converters.py index 8acf139..95e8550 100644 --- a/box/converters.py +++ b/box/converters.py @@ -5,9 +5,9 @@ import csv import json -from pathlib import Path from io import StringIO from os import PathLike +from pathlib import Path from typing import Union from box.exceptions import BoxError diff --git a/box/from_file.py b/box/from_file.py index 02b3403..2cf60e1 100644 --- a/box/from_file.py +++ b/box/from_file.py @@ -1,9 +1,14 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- from json import JSONDecodeError -from pathlib import Path from os import PathLike -from typing import Union, Callable, Dict +from pathlib import Path +from typing import Callable, Dict, Union + +from box.box import Box +from box.box_list import BoxList +from box.converters import msgpack_available, toml_available, yaml_available +from box.exceptions import BoxError try: from ruamel.yaml import YAMLError @@ -23,10 +28,6 @@ except ImportError: UnpackException = False # type: ignore -from box.exceptions import BoxError -from box.box import Box -from box.box_list import BoxList -from box.converters import yaml_available, toml_available, msgpack_available __all__ = ["box_from_file"] diff --git a/setup.py b/setup.py index 32b0b38..6247423 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,12 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from setuptools import setup +# Must import multiprocessing as a fix for issues with testing, experienced on win10 +import multiprocessing import os import re -# Fix for issues with testing, experienced on win10 -import multiprocessing +from setuptools import setup root = os.path.abspath(os.path.dirname(__file__)) @@ -40,6 +40,7 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Development Status :: 5 - Production/Stable", "Natural Language :: English", diff --git a/test/test_box.py b/test/test_box.py index 07affeb..80b2831 100644 --- a/test/test_box.py +++ b/test/test_box.py @@ -5,29 +5,28 @@ import json import os import pickle +import platform import shutil from multiprocessing import Queue from pathlib import Path -import platform - -import pytest -import ruamel.yaml as yaml - -from box import box -from box import Box, BoxError, BoxKeyError, BoxList, SBox, ConfigBox from test.common import ( - test_dict, - extended_test_dict, - tmp_dir, - tmp_json_file, - tmp_yaml_file, - movie_data, data_json_file, data_yaml_file, + extended_test_dict, + movie_data, + test_dict, test_root, + tmp_dir, + tmp_json_file, tmp_msgpack_file, + tmp_yaml_file, ) +import pytest +import ruamel.yaml as yaml + +from box import Box, BoxError, BoxKeyError, BoxList, ConfigBox, SBox, box + def mp_queue_test(q): bx = q.get() @@ -1139,3 +1138,16 @@ def test_default_box_restricted_calls(self): a = Box(default_box=True) a._test_thing_ assert len(list(a.keys())) == 0 + + def test_default_dots(self): + 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["b.b"] = 3 + assert a == {"a.a.a": {"": {"": {}}}, "b.b": 3} + a.b.b = 4 + assert a == {"a.a.a": {"": {"": {}}}, "b.b": 3, "b": {"b": 4}} + assert a["non.existent.key"] == {} diff --git a/test/test_box_list.py b/test/test_box_list.py index 9c1e6cf..5e975db 100644 --- a/test/test_box_list.py +++ b/test/test_box_list.py @@ -6,14 +6,13 @@ import os import shutil from pathlib import Path +from test.common import test_root, tmp_dir import pytest import ruamel.yaml as yaml import toml -from box import BoxList, Box, BoxError - -from test.common import tmp_dir, test_root +from box import Box, BoxError, BoxList class TestBoxList: diff --git a/test/test_config_box.py b/test/test_config_box.py index 61fa62a..1cb41e2 100644 --- a/test/test_config_box.py +++ b/test/test_config_box.py @@ -1,9 +1,10 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from box import Box, ConfigBox from test.common import test_dict +from box import Box, ConfigBox + class TestConfigBox: def test_config_box(self): diff --git a/test/test_converters.py b/test/test_converters.py index ce442ca..a7e9947 100644 --- a/test/test_converters.py +++ b/test/test_converters.py @@ -1,18 +1,17 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import json import os import shutil from pathlib import Path -import json +from test.common import movie_data, tmp_dir +import msgpack import pytest import ruamel.yaml as yaml -import msgpack from box import BoxError -from box.converters import _to_toml, _from_toml, _to_json, _to_yaml, _to_msgpack -from test.common import tmp_dir, movie_data - +from box.converters import _from_toml, _to_json, _to_msgpack, _to_toml, _to_yaml toml_string = """[movies.Spaceballs] imdb_stars = 7.1 diff --git a/test/test_from_file.py b/test/test_from_file.py index ed82fd6..90b8076 100644 --- a/test/test_from_file.py +++ b/test/test_from_file.py @@ -1,11 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- from pathlib import Path +from test.common import test_root import pytest -from box import box_from_file, Box, BoxList, BoxError -from test.common import test_root +from box import Box, BoxError, BoxList, box_from_file class TestFromFile: diff --git a/test/test_sbox.py b/test/test_sbox.py index 5143756..dd28465 100644 --- a/test/test_sbox.py +++ b/test/test_sbox.py @@ -1,11 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- import json +from test.common import test_dict import ruamel.yaml as yaml -from box import SBox, Box -from test.common import test_dict +from box import Box, SBox class TestSBox: