Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-python-box for 
openSUSE:Factory checked in at 2021-12-09 19:45:42
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-python-box (Old)
 and      /work/SRC/openSUSE:Factory/.python-python-box.new.2520 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-python-box"

Thu Dec  9 19:45:42 2021 rev:6 rq:937740 version:5.4.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-python-box/python-python-box.changes      
2020-08-06 10:42:38.138135529 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-python-box.new.2520/python-python-box.changes
    2021-12-09 19:46:21.449154866 +0100
@@ -1,0 +2,35 @@
+Wed Dec  8 09:55:04 UTC 2021 - [email protected]
+
+- version update to 5.4.1
+  Version 5.4.1
+  -------------
+  * Fixing #205 setdefault behavior with box_dots (thanks to  Ivan Pepelnjak)
+  Version 5.4.0
+  -------------
+  * Adding py.typed for mypy support (thanks to Dominic)
+  * Adding testing for Python 3.10-dev
+  * Fixing #189 by adding mappings for mypy
+  * Fixing setdefault behavior with box_dots (thanks to ipcoder)
+  * Changing #193 how magic methods are handled with default_box (thanks to 
Rexbard)
+  Version 5.3.0
+  -------------
+  * Adding support for functions to box_recast (thanks to Jacob Hayes)
+  * Adding #181 support for extending or adding new items to list during 
`merge_update`  (thanks to Marcos Dione)
+  * Fixing maintain stacktrace cause for BoxKeyError and BoxValueError (thanks 
to Jacob Hayes)
+  * Fixing #177 that emtpy yaml files raised errors instead of returning empty 
objects (thanks to Tim Schwenke)
+  * Fixing #171 that `popitems` wasn't first checking if box was frozen 
(thanks to Varun Madiath)
+  * Changing all files to LF line endings
+  * Removing duplicate `box_recast` calls (thanks to Jacob Hayes)
+  * Removing coveralls code coverage, due to repeated issues with service
+  Version 5.2.0
+  -------------
+  * Adding checks for frozen boxes to `pop`, `popitem` and `clear` (thanks to 
Varun Madiath)
+  * Fixing requirements-test.txt (thanks to Fabian Affolter)
+  * Fixing Flake8 conflicts with black (thanks to Varun Madiath)
+  * Fixing coveralls update (thanks to Varun Madiath)
+  Version 5.1.1
+  -------------
+  * Adding testing for Python 3.9
+  * Fixing #165 `box_dots` to work with `default_box`
+  
+-------------------------------------------------------------------

Old:
----
  5.1.0.tar.gz

New:
----
  5.4.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-python-box.spec ++++++
--- /var/tmp/diff_new_pack.OVRo8N/_old  2021-12-09 19:46:22.121155189 +0100
+++ /var/tmp/diff_new_pack.OVRo8N/_new  2021-12-09 19:46:22.125155191 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-python-box
 #
-# Copyright (c) 2020 SUSE LLC
+# Copyright (c) 2021 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -20,14 +20,13 @@
 # python_requires='>=3.6'
 %define skip_python2 1
 Name:           python-python-box
-Version:        5.1.0
+Version:        5.4.1
 Release:        0
 Summary:        Advanced Python dictionaries with dot notation access
 License:        MIT
 Group:          Development/Languages/Python
 URL:            https://github.com/cdgriffith/Box
 Source:         https://github.com/cdgriffith/Box/archive/%{version}.tar.gz
-BuildRequires:  %{python_module pytest-runner}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros

++++++ 5.1.0.tar.gz -> 5.4.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/.github/workflows/pythonpublish.yml 
new/Box-5.4.1/.github/workflows/pythonpublish.yml
--- old/Box-5.1.0/.github/workflows/pythonpublish.yml   2020-07-23 
23:07:29.000000000 +0200
+++ new/Box-5.4.1/.github/workflows/pythonpublish.yml   2021-08-22 
17:06:26.000000000 +0200
@@ -1,4 +1,4 @@
-# This workflows will upload a Python Package using Twine when a release is 
created
+# This workflow will upload a Python Package using Twine when a release is 
created
 # For more information see: 
https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
 
 name: Upload Python Package
@@ -17,11 +17,11 @@
     - name: Set up Python
       uses: actions/setup-python@v1
       with:
-        python-version: '3.8'
+        python-version: '3.9'
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip
-        pip install setuptools wheel twine
+        pip install setuptools wheel twine --upgrade
     - name: Build and publish
       env:
         TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/.github/workflows/tests.yml 
new/Box-5.4.1/.github/workflows/tests.yml
--- old/Box-5.1.0/.github/workflows/tests.yml   2020-07-23 23:07:29.000000000 
+0200
+++ new/Box-5.4.1/.github/workflows/tests.yml   2021-08-22 17:06:26.000000000 
+0200
@@ -5,16 +5,16 @@
 
 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:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [3.8]
+        python-version: [3.9]
 
     steps:
       - uses: actions/checkout@v2
@@ -33,7 +33,7 @@
           # stop the build if there are Python syntax errors, undefined names 
or print statements
           flake8 box --count --select=E9,F63,F7,F82,T001,T002,T003,T004 
--show-source --statistics
           # exit-zero treats all errors as warnings.
-          flake8 . --count --exit-zero --max-complexity=20 
--max-line-length=120 --statistics
+          flake8 . --count --exit-zero --max-complexity=20 
--max-line-length=120 --statistics --extend-ignore E203
       - name: Run mypy
         run: mypy box
       - name: Check distrubiton log description
@@ -45,12 +45,12 @@
     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, 3.10-dev, 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
@@ -58,11 +58,8 @@
         python -m pip install --upgrade pip
         pip install -r requirements.txt
         pip install -r requirements-test.txt
-        pip install coveralls
     - name: Test with pytest
       env:
-        COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
       run: |
         pytest --cov=box test/
-        coveralls
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/.pre-commit-config.yaml 
new/Box-5.4.1/.pre-commit-config.yaml
--- old/Box-5.1.0/.pre-commit-config.yaml       2020-07-23 23:07:29.000000000 
+0200
+++ new/Box-5.4.1/.pre-commit-config.yaml       2021-08-22 17:06:26.000000000 
+0200
@@ -1,21 +1,39 @@
 repos:
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v2.5.0
+    rev: v4.0.1
     hooks:
-    - id: mixed-line-ending
-    - id: trailing-whitespace
+    # Identify invalid files
+    - id: check-ast
+    - id: check-yaml
+    - id: check-json
+    - id: check-toml
+    # git checks
+    - id: check-merge-conflict
+    - id: check-added-large-files
+      exclude: ^test/data/.+
+    - id: detect-private-key
+    - id: check-case-conflict
+    # Python checks
+    - id: check-docstring-first
+    - id: debug-statements
     - id: requirements-txt-fixer
     - id: fix-encoding-pragma
-    - id: check-byte-order-marker
-    - id: debug-statements
-    - id: check-yaml
+    - id: fix-byte-order-marker
+    # General quality checks
+    - id: mixed-line-ending
+      args: [--fix=lf]
+    - id: trailing-whitespace
+      args: [--markdown-linebreak-ext=md]
+    - id: check-executables-have-shebangs
+    - id: end-of-file-fixer
+      exclude: ^test/data/.+
 -   repo: https://github.com/ambv/black
-    rev: 19.10b0
+    rev: 21.7b0
     hooks:
     - id: black
       args: [--config=.black.toml]
 -   repo: https://github.com/pre-commit/mirrors-mypy
-    rev: 'v0.770'
+    rev: 'v0.910'
     hooks:
     - id: mypy
       additional_dependencies: [ruamel.yaml,toml,msgpack]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/AUTHORS.rst new/Box-5.4.1/AUTHORS.rst
--- old/Box-5.1.0/AUTHORS.rst   2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/AUTHORS.rst   2021-08-22 17:06:26.000000000 +0200
@@ -22,6 +22,11 @@
 - Jonas Irgens Kylling (jkylling)
 - Bruno Rocha (rochacbruno)
 - Noam Graetz (NoamGraetz2)
+- Fabian Affolter (fabaff)
+- Varun Madiath (vamega)
+- Jacob Hayes (JacobHayes)
+- Dominic (Yobmod)
+- Ivan Pepelnjak (ipspace)
 
 Suggestions and bug reporting:
 
@@ -69,3 +74,7 @@
 - David Aronchick (aronchick)
 - Alexander Kapustin (dyens)
 - Marcelo Huerta (richieadler)
+- Tim Schwenke (trallnag)
+- Marcos Dione (mdione-cloudian)
+- Varun Madiath (vamega)
+- Rexbard
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/CHANGES.rst new/Box-5.4.1/CHANGES.rst
--- old/Box-5.1.0/CHANGES.rst   2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/CHANGES.rst   2021-08-22 17:06:26.000000000 +0200
@@ -1,35 +1,75 @@
 Changelog
 =========
 
+Version 5.4.1
+-------------
+
+* Fixing #205 setdefault behavior with box_dots (thanks to  Ivan Pepelnjak)
+
+Version 5.4.0
+-------------
+
+* Adding py.typed for mypy support (thanks to Dominic)
+* Adding testing for Python 3.10-dev
+* Fixing #189 by adding mappings for mypy
+* Fixing setdefault behavior with box_dots (thanks to ipcoder)
+* Changing #193 how magic methods are handled with default_box (thanks to 
Rexbard)
+
+
+Version 5.3.0
+-------------
+
+* Adding support for functions to box_recast (thanks to Jacob Hayes)
+* Adding #181 support for extending or adding new items to list during 
`merge_update`  (thanks to Marcos Dione)
+* Fixing maintain stacktrace cause for BoxKeyError and BoxValueError (thanks 
to Jacob Hayes)
+* Fixing #177 that emtpy yaml files raised errors instead of returning empty 
objects (thanks to Tim Schwenke)
+* Fixing #171 that `popitems` wasn't first checking if box was frozen (thanks 
to Varun Madiath)
+* Changing all files to LF line endings
+* Removing duplicate `box_recast` calls (thanks to Jacob Hayes)
+* Removing coveralls code coverage, due to repeated issues with service
+
+Version 5.2.0
+-------------
+
+* Adding checks for frozen boxes to `pop`, `popitem` and `clear` (thanks to 
Varun Madiath)
+* Fixing requirements-test.txt (thanks to Fabian Affolter)
+* Fixing Flake8 conflicts with black (thanks to Varun Madiath)
+* Fixing coveralls update (thanks to Varun Madiath)
+
+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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/MANIFEST.in new/Box-5.4.1/MANIFEST.in
--- old/Box-5.1.0/MANIFEST.in   2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/MANIFEST.in   2021-08-22 17:06:26.000000000 +0200
@@ -1,3 +1,4 @@
 include LICENSE
 include AUTHORS.rst
 include CHANGES.rst
+include box/py.typed
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/README.rst new/Box-5.4.1/README.rst
--- old/Box-5.1.0/README.rst    2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/README.rst    2021-08-22 17:06:26.000000000 +0200
@@ -1,4 +1,4 @@
-|BuildStatus| |CoverageStatus| |License|
+|BuildStatus| |License|
 
 |BoxImage|
 
@@ -103,8 +103,6 @@
    :target: https://github.com/cdgriffith/Box
 .. |BuildStatus| image:: 
https://github.com/cdgriffith/Box/workflows/Tests/badge.svg?branch=master
    :target: https://github.com/cdgriffith/Box/actions?query=workflow%3ATests
-.. |CoverageStatus| image:: 
https://img.shields.io/coveralls/cdgriffith/Box/master.svg?maxAge=2592000
-   :target: https://coveralls.io/r/cdgriffith/Box?branch=master
 .. |License| image:: https://img.shields.io/pypi/l/python-box.svg
    :target: https://pypi.python.org/pypi/python-box/
 
@@ -113,4 +111,4 @@
 .. _`Wrapt Documentation`: https://wrapt.readthedocs.io/en/latest
 .. _reusables: https://github.com/cdgriffith/reusables#reusables
 .. _created: 
https://github.com/cdgriffith/Reusables/commit/df20de4db74371c2fedf1578096f3e29c93ccdf3#diff-e9a0f470ef3e8afb4384dc2824943048R51
-.. _LICENSE: https://github.com/cdgriffith/Box/blob/master/LICENSE
\ No newline at end of file
+.. _LICENSE: https://github.com/cdgriffith/Box/blob/master/LICENSE
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/box/__init__.py 
new/Box-5.4.1/box/__init__.py
--- old/Box-5.1.0/box/__init__.py       2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/box/__init__.py       2021-08-22 17:06:26.000000000 +0200
@@ -2,11 +2,21 @@
 # -*- coding: utf-8 -*-
 
 __author__ = "Chris Griffith"
-__version__ = "5.1.0"
+__version__ = "5.4.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
+
+__all__ = [
+    "Box",
+    "BoxList",
+    "ConfigBox",
+    "BoxError",
+    "BoxKeyError",
+    "box_from_file",
+    "SBox",
+]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/box/box.py new/Box-5.4.1/box/box.py
--- old/Box-5.1.0/box/box.py    2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/box/box.py    2021-08-22 17:06:26.000000000 +0200
@@ -9,26 +9,29 @@
 import re
 import string
 import warnings
-from os import PathLike
-from collections.abc import Iterable, Mapping, Callable
 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
 
+try:
+    from typing import Callable, Iterable, Mapping
+except ImportError:
+    from collections.abc import Callable, Iterable, Mapping
 
 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
 
@@ -43,6 +46,16 @@
 NO_DEFAULT = object()
 
 
+def _exception_cause(e):
+    """
+    Unwrap BoxKeyError and BoxValueError errors to their cause.
+
+    Use with `raise ... from _exception_cause(err)` to avoid deeply nested 
stacktraces, but keep the
+    context.
+    """
+    return e.__cause__ if isinstance(e, (BoxKeyError, BoxValueError)) else e
+
+
 def _camel_killer(attr):
     """
     CamelKiller, qu'est-ce que c'est?
@@ -80,6 +93,28 @@
     raise BoxError("Could not split box dots properly")
 
 
+def _get_dot_paths(bx, current=""):
+    """A generator of all the end node keys in a box in box_dots format"""
+
+    def handle_dicts(sub_bx, paths=""):
+        for key, value in sub_bx.items():
+            yield f"{paths}.{key}" if paths else key
+            if isinstance(value, dict):
+                yield from handle_dicts(value, f"{paths}.{key}" if paths else 
key)
+            elif isinstance(value, list):
+                yield from handle_lists(value, f"{paths}.{key}" if paths else 
key)
+
+    def handle_lists(bx_list, paths=""):
+        for i, value in enumerate(bx_list):
+            yield f"{paths}[{i}]"
+            if isinstance(value, list):
+                yield from handle_lists(value, f"{paths}[{i}]")
+            if isinstance(value, dict):
+                yield from handle_dicts(value, f"{paths}[{i}]")
+
+    yield from handle_dicts(bx, current)
+
+
 def _get_box_config():
     return {
         # Internal use only
@@ -228,47 +263,47 @@
 
         self._box_config["__created"] = True
 
-    def __add__(self, other: dict):
+    def __add__(self, other: Mapping[Any, Any]):
         if not isinstance(other, dict):
-            raise BoxTypeError(f"Box can only merge two boxes or a box and a 
dictionary.")
+            raise BoxTypeError("Box can only merge two boxes or a box and a 
dictionary.")
         new_box = self.copy()
         new_box.merge_update(other)
         return new_box
 
-    def __radd__(self, other: dict):
+    def __radd__(self, other: Mapping[Any, Any]):
         if not isinstance(other, dict):
-            raise BoxTypeError(f"Box can only merge two boxes or a box and a 
dictionary.")
+            raise BoxTypeError("Box can only merge two boxes or a box and a 
dictionary.")
         new_box = self.copy()
         new_box.merge_update(other)
         return new_box
 
-    def __iadd__(self, other: dict):
+    def __iadd__(self, other: Mapping[Any, Any]):
         if not isinstance(other, dict):
-            raise BoxTypeError(f"Box can only merge two boxes or a box and a 
dictionary.")
+            raise BoxTypeError("Box can only merge two boxes or a box and a 
dictionary.")
         self.merge_update(other)
         return self
 
-    def __or__(self, other: dict):
+    def __or__(self, other: Mapping[Any, Any]):
         if not isinstance(other, dict):
-            raise BoxTypeError(f"Box can only merge two boxes or a box and a 
dictionary.")
+            raise BoxTypeError("Box can only merge two boxes or a box and a 
dictionary.")
         new_box = self.copy()
         new_box.update(other)
         return new_box
 
-    def __ror__(self, other: dict):
+    def __ror__(self, other: Mapping[Any, Any]):
         if not isinstance(other, dict):
-            raise BoxTypeError(f"Box can only merge two boxes or a box and a 
dictionary.")
+            raise BoxTypeError("Box can only merge two boxes or a box and a 
dictionary.")
         new_box = self.copy()
         new_box.update(other)
         return new_box
 
-    def __ior__(self, other: dict):
+    def __ior__(self, other: Mapping[Any, Any]):
         if not isinstance(other, dict):
-            raise BoxTypeError(f"Box can only merge two boxes or a box and a 
dictionary.")
+            raise BoxTypeError("Box can only merge two boxes or a box and a 
dictionary.")
         self.update(other)
         return self
 
-    def __sub__(self, other: dict):
+    def __sub__(self, other: Mapping[Any, Any]):
         frozen = self._box_config["frozen_box"]
         config = self.__box_config()
         config["frozen_box"] = False
@@ -399,7 +434,7 @@
             value = default_value.copy()
         else:
             value = default_value
-        if not attr or (not item.startswith("_") and not item.endswith("_")):
+        if not attr or not (item.startswith("_") and item.endswith("_")):
             super().__setitem__(item, value)
         return value
 
@@ -412,13 +447,14 @@
 
     def __recast(self, item, value):
         if self._box_config["box_recast"] and item in 
self._box_config["box_recast"]:
+            recast = self._box_config["box_recast"][item]
             try:
-                if issubclass(self._box_config["box_recast"][item], (Box, 
box.BoxList)):
-                    return self._box_config["box_recast"][item](value, 
**self.__box_config())
+                if isinstance(recast, type) and issubclass(recast, (Box, 
box.BoxList)):
+                    return recast(value, **self.__box_config())
                 else:
-                    return self._box_config["box_recast"][item](value)
-            except ValueError:
-                raise BoxValueError(f'Cannot convert {value} to 
{self._box_config["box_recast"][item]}') from None
+                    return recast(value)
+            except ValueError as err:
+                raise BoxValueError(f"Cannot convert {value} to {recast}") 
from _exception_cause(err)
         return value
 
     def __convert_and_store(self, item, value):
@@ -452,9 +488,15 @@
             return super().__getitem__(item)
         except KeyError as err:
             if item == "_box_config":
-                raise BoxKeyError("_box_config should only exist as an 
attribute and is never defaulted") from None
+                cause = _exception_cause(err)
+                raise BoxKeyError("_box_config should only exist as an 
attribute and is never defaulted") from cause
             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]
@@ -464,7 +506,7 @@
                     return super().__getitem__(converted)
             if self._box_config["default_box"] and not _ignore_default:
                 return self.__get_default(item)
-            raise BoxKeyError(str(err)) from None
+            raise BoxKeyError(str(err)) from _exception_cause(err)
 
     def __getattr__(self, item):
         try:
@@ -474,20 +516,22 @@
                 value = object.__getattribute__(self, item)
         except AttributeError as err:
             if item == "__getstate__":
-                raise BoxKeyError(item) from None
+                raise BoxKeyError(item) from _exception_cause(err)
             if item == "_box_config":
-                raise BoxError("_box_config key must exist") from None
+                raise BoxError("_box_config key must exist") from 
_exception_cause(err)
             if self._box_config["conversion_box"]:
                 safe_key = self._safe_attr(item)
                 if safe_key in self._box_config["__safe_keys"]:
                     return 
self.__getitem__(self._box_config["__safe_keys"][safe_key])
             if self._box_config["default_box"]:
+                if item.startswith("_") and item.endswith("_"):
+                    raise BoxKeyError(f"{item}: Does not exist and internal 
methods are never defaulted")
                 return self.__get_default(item, attr=True)
-            raise BoxKeyError(str(err)) from None
+            raise BoxKeyError(str(err)) from _exception_cause(err)
         return value
 
     def __setitem__(self, key, value):
-        if key != "_box_config" and self._box_config["__created"] and 
self._box_config["frozen_box"]:
+        if key != "_box_config" and self._box_config["frozen_box"] and 
self._box_config["__created"]:
             raise BoxError("Box is frozen")
         if self._box_config["box_dots"] and isinstance(key, str) and ("." in 
key or "[" in key):
             first_item, children = _parse_box_dots(self, key, setting=True)
@@ -509,7 +553,6 @@
             raise BoxKeyError(f'Key name "{key}" is protected')
         if key == "_box_config":
             return object.__setattr__(self, key, value)
-        value = self.__recast(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]
@@ -536,7 +579,7 @@
         try:
             super().__delitem__(key)
         except KeyError as err:
-            raise BoxKeyError(str(err)) from None
+            raise BoxKeyError(str(err)) from _exception_cause(err)
 
     def __delattr__(self, item):
         if self._box_config["frozen_box"]:
@@ -554,9 +597,12 @@
                     self.__delitem__(self._box_config["__safe_keys"][safe_key])
                     del self._box_config["__safe_keys"][safe_key]
                     return
-            raise BoxKeyError(str(err)) from None
+            raise BoxKeyError(str(err)) from _exception_cause(err)
 
     def pop(self, key, *args):
+        if self._box_config["frozen_box"]:
+            raise BoxError("Box is frozen")
+
         if args:
             if len(args) != 1:
                 raise BoxError('pop() takes only one optional argument 
"default"')
@@ -576,10 +622,14 @@
             return item
 
     def clear(self):
+        if self._box_config["frozen_box"]:
+            raise BoxError("Box is frozen")
         super().clear()
         self._box_config["__safe_keys"].clear()
 
     def popitem(self):
+        if self._box_config["frozen_box"]:
+            raise BoxError("Box is frozen")
         try:
             key = next(self.__iter__())
         except StopIteration:
@@ -617,6 +667,9 @@
         return out_dict
 
     def update(self, __m=None, **kwargs):
+        if self._box_config["frozen_box"]:
+            raise BoxError("Box is frozen")
+
         if __m:
             if hasattr(__m, "keys"):
                 for k in __m:
@@ -639,6 +692,15 @@
                     return
             if isinstance(v, list) and not intact_type:
                 v = box.BoxList(v, **self.__box_config())
+                merge_type = kwargs.get("box_merge_lists")
+                if merge_type == "extend" and k in self and 
isinstance(self[k], list):
+                    self[k].extend(v)
+                    return
+                if merge_type == "unique" and k in self and 
isinstance(self[k], list):
+                    for item in v:
+                        if item not in self[k]:
+                            self[k].append(item)
+                    return
             self.__setitem__(k, v)
 
         if __m:
@@ -655,6 +717,10 @@
         if item in self:
             return self[item]
 
+        if self._box_config["box_dots"]:
+            if item in _get_dot_paths(self):
+                return self[item]
+
         if isinstance(default, dict):
             default = self._box_config["box_class"](default, 
**self.__box_config())
         if isinstance(default, list):
@@ -813,6 +879,8 @@
                     box_args[arg] = kwargs.pop(arg)
 
             data = _from_yaml(yaml_string=yaml_string, filename=filename, 
encoding=encoding, errors=errors, **kwargs)
+            if not data:
+                return cls(**box_args)
             if not isinstance(data, dict):
                 raise BoxError(f"yaml data not returned as a dictionary but 
rather a {type(data).__name__}")
             return cls(data, **box_args)
@@ -909,7 +977,12 @@
             return _to_msgpack(self.to_dict(), filename=filename, **kwargs)
 
         @classmethod
-        def from_msgpack(cls, msgpack_bytes: bytes = None, filename: 
Union[str, PathLike] = None, **kwargs,) -> "Box":
+        def from_msgpack(
+            cls,
+            msgpack_bytes: bytes = None,
+            filename: Union[str, PathLike] = None,
+            **kwargs,
+        ) -> "Box":
             """
             Transforms msgpack bytes or file into a Box object
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/box/box.pyi new/Box-5.4.1/box/box.pyi
--- old/Box-5.1.0/box/box.pyi   1970-01-01 01:00:00.000000000 +0100
+++ new/Box-5.4.1/box/box.pyi   2021-08-22 17:06:26.000000000 +0200
@@ -0,0 +1,115 @@
+from collections.abc import Mapping
+from os import PathLike
+from typing import Any, Dict, Generator, List, Optional, Tuple, Union
+
+class Box(dict):
+    def __new__(
+        cls: Any,
+        *args: Any,
+        default_box: bool = ...,
+        default_box_attr: Any = ...,
+        default_box_none_transform: bool = ...,
+        frozen_box: bool = ...,
+        camel_killer_box: bool = ...,
+        conversion_box: bool = ...,
+        modify_tuples_box: bool = ...,
+        box_safe_prefix: str = ...,
+        box_duplicates: str = ...,
+        box_intact_types: Union[Tuple, List] = ...,
+        box_recast: Dict = ...,
+        box_dots: bool = ...,
+        box_class: Union[Dict, Box] = ...,
+        **kwargs: Any,
+    ) -> Any: ...
+    def __init__(
+        self,
+        *args: Any,
+        default_box: bool = ...,
+        default_box_attr: Any = ...,
+        default_box_none_transform: bool = ...,
+        frozen_box: bool = ...,
+        camel_killer_box: bool = ...,
+        conversion_box: bool = ...,
+        modify_tuples_box: bool = ...,
+        box_safe_prefix: str = ...,
+        box_duplicates: str = ...,
+        box_intact_types: Union[Tuple, List] = ...,
+        box_recast: Dict = ...,
+        box_dots: bool = ...,
+        box_class: Union[Dict, Box] = ...,
+        **kwargs: Any,
+    ) -> None: ...
+    def __add__(self, other: Mapping[Any, Any]) -> Any: ...
+    def __radd__(self, other: Mapping[Any, Any]) -> Any: ...
+    def __iadd__(self, other: Mapping[Any, Any]) -> Any: ...
+    def __or__(self, other: Mapping[Any, Any]) -> Any: ...
+    def __ror__(self, other: Mapping[Any, Any]) -> Any: ...
+    def __ior__(self, other: Mapping[Any, Any]) -> Any: ...
+    def __sub__(self, other: Mapping[Any, Any]) -> Any: ...
+    def __hash__(self) -> Any: ...  # type: ignore[override]
+    def __dir__(self): ...
+    def keys(self, dotted: Union[bool] = ...) -> Any: ...
+    def items(self, dotted: Union[bool] = ...) -> Any: ...
+    def get(self, key: Any, default: Any = ...): ...
+    def copy(self) -> Box: ...
+    def __copy__(self) -> Box: ...
+    def __deepcopy__(self, memodict: Any = ...) -> Box: ...
+    def __getitem__(self, item: Any, _ignore_default: bool = ...): ...
+    def __getattr__(self, item: Any): ...
+    def __setitem__(self, key: Any, value: Any): ...
+    def __setattr__(self, key: Any, value: Any): ...
+    def __delitem__(self, key: Any): ...
+    def __delattr__(self, item: Any) -> None: ...
+    def pop(self, key: Any, *args: Any): ...
+    def clear(self) -> None: ...
+    def popitem(self): ...
+    def __iter__(self) -> Generator: ...
+    def __reversed__(self) -> Generator: ...
+    def to_dict(self) -> Dict: ...
+    def update(self, __m: Optional[Any] = ..., **kwargs: Any) -> None: ...
+    def merge_update(self, __m: Optional[Any] = ..., **kwargs: Any) -> None: 
...
+    def setdefault(self, item: Any, default: Optional[Any] = ...): ...
+    def to_json(
+        self, filename: Union[str, PathLike] = ..., encoding: str = ..., 
errors: str = ..., **json_kwargs: Any
+    ) -> Any: ...
+    @classmethod
+    def from_json(
+        cls: Any,
+        json_string: str = ...,
+        filename: Union[str, PathLike] = ...,
+        encoding: str = ...,
+        errors: str = ...,
+        **kwargs: Any,
+    ) -> Box: ...
+    def to_yaml(
+        self,
+        filename: Union[str, PathLike] = ...,
+        default_flow_style: bool = ...,
+        encoding: str = ...,
+        errors: str = ...,
+        **yaml_kwargs: Any,
+    ) -> Any: ...
+    @classmethod
+    def from_yaml(
+        cls: Any,
+        yaml_string: str = ...,
+        filename: Union[str, PathLike] = ...,
+        encoding: str = ...,
+        errors: str = ...,
+        **kwargs: Any,
+    ) -> Box: ...
+    def to_toml(self, filename: Union[str, PathLike] = ..., encoding: str = 
..., errors: str = ...) -> Any: ...
+    @classmethod
+    def from_toml(
+        cls: Any,
+        toml_string: str = ...,
+        filename: Union[str, PathLike] = ...,
+        encoding: str = ...,
+        errors: str = ...,
+        **kwargs: Any,
+    ) -> Box: ...
+    def to_msgpack(self, filename: Union[str, PathLike] = ..., **kwargs: Any) 
-> Any: ...
+    @classmethod
+    def from_msgpack(
+        cls: Any, msgpack_bytes: bytes = ..., filename: Union[str, PathLike] = 
..., **kwargs: Any
+    ) -> Box: ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/box/box_list.py 
new/Box-5.4.1/box/box_list.py
--- old/Box-5.1.0/box/box_list.py       2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/box/box_list.py       2021-08-22 17:06:26.000000000 +0200
@@ -6,24 +6,23 @@
 import re
 from os import PathLike
 from typing import Iterable, Type, Union
-from pathlib import Path
 
 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
 
@@ -217,10 +216,10 @@
         :param kwargs: parameters to pass to `Box()` or `json.loads`
         :return: BoxList object from json data
         """
-        bx_args = {}
+        box_args = {}
         for arg in list(kwargs.keys()):
             if arg in BOX_PARAMETERS:
-                bx_args[arg] = kwargs.pop(arg)
+                box_args[arg] = kwargs.pop(arg)
 
         data = _from_json(
             json_string, filename=filename, encoding=encoding, errors=errors, 
multiline=multiline, **kwargs
@@ -228,7 +227,7 @@
 
         if not isinstance(data, list):
             raise BoxError(f"json data not returned as a list, but rather a 
{type(data).__name__}")
-        return cls(data, **bx_args)
+        return cls(data, **box_args)
 
     if yaml_available:
 
@@ -278,15 +277,17 @@
             :param kwargs: parameters to pass to `BoxList()` or `yaml.load`
             :return: BoxList object from yaml data
             """
-            bx_args = {}
+            box_args = {}
             for arg in list(kwargs.keys()):
                 if arg in BOX_PARAMETERS:
-                    bx_args[arg] = kwargs.pop(arg)
+                    box_args[arg] = kwargs.pop(arg)
 
             data = _from_yaml(yaml_string=yaml_string, filename=filename, 
encoding=encoding, errors=errors, **kwargs)
+            if not data:
+                return cls(**box_args)
             if not isinstance(data, list):
                 raise BoxError(f"yaml data not returned as a list but rather a 
{type(data).__name__}")
-            return cls(data, **bx_args)
+            return cls(data, **box_args)
 
     else:
 
@@ -354,15 +355,15 @@
             :param kwargs: parameters to pass to `Box()`
             :return:
             """
-            bx_args = {}
+            box_args = {}
             for arg in list(kwargs.keys()):
                 if arg in BOX_PARAMETERS:
-                    bx_args[arg] = kwargs.pop(arg)
+                    box_args[arg] = kwargs.pop(arg)
 
             data = _from_toml(toml_string=toml_string, filename=filename, 
encoding=encoding, errors=errors)
             if key_name not in data:
                 raise BoxError(f"{key_name} was not found.")
-            return cls(data[key_name], **bx_args)
+            return cls(data[key_name], **box_args)
 
     else:
 
@@ -408,15 +409,15 @@
             :param kwargs: parameters to pass to `Box()`
             :return:
             """
-            bx_args = {}
+            box_args = {}
             for arg in list(kwargs.keys()):
                 if arg in BOX_PARAMETERS:
-                    bx_args[arg] = kwargs.pop(arg)
+                    box_args[arg] = kwargs.pop(arg)
 
             data = _from_msgpack(msgpack_bytes=msgpack_bytes, 
filename=filename, **kwargs)
             if not isinstance(data, list):
                 raise BoxError(f"msgpack data not returned as a list but 
rather a {type(data).__name__}")
-            return cls(data, **bx_args)
+            return cls(data, **box_args)
 
     else:
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/box/box_list.pyi 
new/Box-5.4.1/box/box_list.pyi
--- old/Box-5.1.0/box/box_list.pyi      1970-01-01 01:00:00.000000000 +0100
+++ new/Box-5.4.1/box/box_list.pyi      2021-08-22 17:06:26.000000000 +0200
@@ -0,0 +1,85 @@
+import box
+from box.converters import (
+    BOX_PARAMETERS as BOX_PARAMETERS,
+    msgpack_available as msgpack_available,
+    toml_available as toml_available,
+    yaml_available as yaml_available,
+)
+from box.exceptions import BoxError as BoxError, BoxTypeError as BoxTypeError
+from os import PathLike as PathLike
+from typing import Any, Iterable, Optional, Type, Union
+
+class BoxList(list):
+    def __new__(cls, *args: Any, **kwargs: Any): ...
+    box_options: Any = ...
+    box_org_ref: Any = ...
+    def __init__(self, iterable: Iterable = ..., box_class: Type[box.Box] = 
..., **box_options: Any) -> None: ...
+    def __getitem__(self, item: Any): ...
+    def __delitem__(self, key: Any): ...
+    def __setitem__(self, key: Any, value: Any): ...
+    def append(self, p_object: Any) -> None: ...
+    def extend(self, iterable: Any) -> None: ...
+    def insert(self, index: Any, p_object: Any) -> None: ...
+    def __copy__(self): ...
+    def __deepcopy__(self, memo: Optional[Any] = ...): ...
+    def __hash__(self) -> Any: ...  # type: ignore[override]
+    def to_list(self): ...
+    def to_json(
+        self,
+        filename: Union[str, PathLike] = ...,
+        encoding: str = ...,
+        errors: str = ...,
+        multiline: bool = ...,
+        **json_kwargs: Any,
+    ) -> Any: ...
+    @classmethod
+    def from_json(
+        cls: Any,
+        json_string: str = ...,
+        filename: Union[str, PathLike] = ...,
+        encoding: str = ...,
+        errors: str = ...,
+        multiline: bool = ...,
+        **kwargs: Any,
+    ) -> Any: ...
+    def to_yaml(
+        self,
+        filename: Union[str, PathLike] = ...,
+        default_flow_style: bool = ...,
+        encoding: str = ...,
+        errors: str = ...,
+        **yaml_kwargs: Any,
+    ) -> Any: ...
+    @classmethod
+    def from_yaml(
+        cls: Any,
+        yaml_string: str = ...,
+        filename: Union[str, PathLike] = ...,
+        encoding: str = ...,
+        errors: str = ...,
+        **kwargs: Any,
+    ) -> Any: ...
+    def to_toml(
+        self, filename: Union[str, PathLike] = ..., key_name: str = ..., 
encoding: str = ..., errors: str = ...
+    ) -> Any: ...
+    @classmethod
+    def from_toml(
+        cls: Any,
+        toml_string: str = ...,
+        filename: Union[str, PathLike] = ...,
+        key_name: str = ...,
+        encoding: str = ...,
+        errors: str = ...,
+        **kwargs: Any,
+    ) -> Any: ...
+    def to_msgpack(self, filename: Union[str, PathLike] = ..., **kwargs: Any) 
-> Any: ...
+    @classmethod
+    def from_msgpack(
+        cls: Any, msgpack_bytes: bytes = ..., filename: Union[str, PathLike] = 
..., **kwargs: Any
+    ) -> Any: ...
+    def to_csv(self, filename: Union[str, PathLike] = ..., encoding: str = 
..., errors: str = ...) -> Any: ...
+    @classmethod
+    def from_csv(
+        cls: Any, csv_string: str = ..., filename: Union[str, PathLike] = ..., 
encoding: str = ..., errors: str = ...
+    ) -> Any: ...
+    def _dotted_helper(self) -> Any: ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/box/config_box.py 
new/Box-5.4.1/box/config_box.py
--- old/Box-5.1.0/box/config_box.py     2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/box/config_box.py     2021-08-22 17:06:26.000000000 +0200
@@ -87,7 +87,7 @@
             raise err
         return float(item)
 
-    def list(self, item, default=None, spliter=",", strip=True, mod=None):
+    def list(self, item, default=None, spliter: str = ",", strip=True, 
mod=None):
         """
         Return value of key as a list
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/box/config_box.pyi 
new/Box-5.4.1/box/config_box.pyi
--- old/Box-5.1.0/box/config_box.pyi    1970-01-01 01:00:00.000000000 +0100
+++ new/Box-5.4.1/box/config_box.pyi    2021-08-22 17:06:26.000000000 +0200
@@ -0,0 +1,15 @@
+from box.box import Box as Box
+from typing import Any, Optional
+
+class ConfigBox(Box):
+    def __getattr__(self, item: Any): ...
+    def __dir__(self): ...
+    def bool(self, item: Any, default: Optional[Any] = ...): ...
+    def int(self, item: Any, default: Optional[Any] = ...): ...
+    def float(self, item: Any, default: Optional[Any] = ...): ...
+    def list(self, item: Any, default: Optional[Any] = ..., spliter: str = 
..., strip: bool = ..., mod: Optional[Any] = ...): ...  # type: ignore
+    def getboolean(self, item: Any, default: Optional[Any] = ...): ...
+    def getint(self, item: Any, default: Optional[Any] = ...): ...
+    def getfloat(self, item: Any, default: Optional[Any] = ...): ...
+    def copy(self): ...
+    def __copy__(self): ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/box/converters.py 
new/Box-5.4.1/box/converters.py
--- old/Box-5.1.0/box/converters.py     2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/box/converters.py     2021-08-22 17:06:26.000000000 +0200
@@ -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
@@ -24,12 +24,12 @@
     except ImportError:
         yaml = None  # type: ignore
         yaml_available = False
-
 try:
     import toml
 except ImportError:
     toml = None  # type: ignore
     toml_available = False
+
 try:
     import msgpack  # type: ignore
 except ImportError:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/box/converters.pyi 
new/Box-5.4.1/box/converters.pyi
--- old/Box-5.1.0/box/converters.pyi    1970-01-01 01:00:00.000000000 +0100
+++ new/Box-5.4.1/box/converters.pyi    2021-08-22 17:06:26.000000000 +0200
@@ -0,0 +1,52 @@
+from box.exceptions import BoxError as BoxError
+from os import PathLike as PathLike
+from typing import Any, Union
+
+yaml_available: bool
+toml_available: bool
+msgpack_available: bool
+BOX_PARAMETERS: Any
+
+def _exists(filename: Union[str, PathLike], create: bool = False) -> Any: ...
+def _to_json(
+    obj, filename: Union[str, PathLike] = None, encoding: str = "utf-8", 
errors: str = "strict", **json_kwargs
+) -> Any: ...
+def _from_json(
+    json_string: str = None,
+    filename: Union[str, PathLike] = None,
+    encoding: str = "utf-8",
+    errors: str = "strict",
+    multiline: bool = False,
+    **kwargs,
+) -> Any: ...
+def _to_yaml(
+    obj,
+    filename: Union[str, PathLike] = None,
+    default_flow_style: bool = False,
+    encoding: str = "utf-8",
+    errors: str = "strict",
+    **yaml_kwargs,
+) -> Any: ...
+def _from_yaml(
+    yaml_string: str = None,
+    filename: Union[str, PathLike] = None,
+    encoding: str = "utf-8",
+    errors: str = "strict",
+    **kwargs,
+) -> Any: ...
+def _to_toml(obj, filename: Union[str, PathLike] = None, encoding: str = 
"utf-8", errors: str = "strict") -> Any: ...
+def _from_toml(
+    toml_string: str = None, filename: Union[str, PathLike] = None, encoding: 
str = "utf-8", errors: str = "strict"
+) -> Any: ...
+def _to_msgpack(obj, filename: Union[str, PathLike] = None, **kwargs) -> Any: 
...
+def _from_msgpack(msgpack_bytes: bytes = None, filename: Union[str, PathLike] 
= None, **kwargs) -> Any: ...
+def _to_csv(
+    box_list, filename: Union[str, PathLike] = None, encoding: str = "utf-8", 
errors: str = "strict", **kwargs
+) -> Any: ...
+def _from_csv(
+    csv_string: str = None,
+    filename: Union[str, PathLike] = None,
+    encoding: str = "utf-8",
+    errors: str = "strict",
+    **kwargs,
+) -> Any: ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/box/exceptions.pyi 
new/Box-5.4.1/box/exceptions.pyi
--- old/Box-5.1.0/box/exceptions.pyi    1970-01-01 01:00:00.000000000 +0100
+++ new/Box-5.4.1/box/exceptions.pyi    2021-08-22 17:06:26.000000000 +0200
@@ -0,0 +1,5 @@
+class BoxError(Exception): ...
+class BoxKeyError(BoxError, KeyError, AttributeError): ...
+class BoxTypeError(BoxError, TypeError): ...
+class BoxValueError(BoxError, ValueError): ...
+class BoxWarning(UserWarning): ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/box/from_file.py 
new/Box-5.4.1/box/from_file.py
--- old/Box-5.1.0/box/from_file.py      2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/box/from_file.py      2021-08-22 17:06:26.000000000 +0200
@@ -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
@@ -14,7 +19,7 @@
         YAMLError = False  # type: ignore
 
 try:
-    from toml import TomlDecodeError
+    from toml import TomlDecodeError  # type: ignore
 except ImportError:
     TomlDecodeError = False  # type: ignore
 
@@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/box/from_file.pyi 
new/Box-5.4.1/box/from_file.pyi
--- old/Box-5.1.0/box/from_file.pyi     1970-01-01 01:00:00.000000000 +0100
+++ new/Box-5.4.1/box/from_file.pyi     2021-08-22 17:06:26.000000000 +0200
@@ -0,0 +1,8 @@
+from box.box import Box
+from box.box_list import BoxList
+from os import PathLike
+from typing import Any, Union
+
+def box_from_file(
+    file: Union[str, PathLike], file_type: str = ..., encoding: str = ..., 
errors: str = ..., **kwargs: Any
+) -> Union[Box, BoxList]: ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/box/shorthand_box.pyi 
new/Box-5.4.1/box/shorthand_box.pyi
--- old/Box-5.1.0/box/shorthand_box.pyi 1970-01-01 01:00:00.000000000 +0100
+++ new/Box-5.4.1/box/shorthand_box.pyi 2021-08-22 17:06:26.000000000 +0200
@@ -0,0 +1,13 @@
+from box.box import Box as Box
+
+class SBox(Box):
+    @property
+    def dict(self): ...
+    @property
+    def json(self): ...
+    @property
+    def yaml(self): ...
+    @property
+    def toml(self): ...
+    def copy(self): ...
+    def __copy__(self): ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/requirements-dev.txt 
new/Box-5.4.1/requirements-dev.txt
--- old/Box-5.1.0/requirements-dev.txt  2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/requirements-dev.txt  2021-08-22 17:06:26.000000000 +0200
@@ -1,4 +1,4 @@
 # Files needed for pre-commit hooks
-black>=19.10b0
-mypy>=0.770
-pre-commit>=2.2.0
+black>=21.7b0
+mypy>=0.910
+pre-commit>=4.0.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/requirements-test.txt 
new/Box-5.4.1/requirements-test.txt
--- old/Box-5.1.0/requirements-test.txt 2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/requirements-test.txt 2021-08-22 17:06:26.000000000 +0200
@@ -1,5 +1,8 @@
 coverage>=5.0.4
-pytest-cov>=2.8.1
+msgpack>=1.0
 pytest>=5.4.1
-reusables>=0.9.5
+pytest-cov>=2.8.1
+ruamel.yaml>=0.16,<0.17
+toml>=0.10.2
+types-toml>=0.1.3
 wheel>=0.34.2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/requirements.txt 
new/Box-5.4.1/requirements.txt
--- old/Box-5.1.0/requirements.txt      2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/requirements.txt      2021-08-22 17:06:26.000000000 +0200
@@ -1,3 +1,3 @@
 msgpack>=1.0.0
-ruamel.yaml>=0.16.10
-toml>=0.10.1
+ruamel.yaml>=0.16.10,<0.17
+toml>=0.10.2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/setup.py new/Box-5.4.1/setup.py
--- old/Box-5.1.0/setup.py      2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/setup.py      2021-08-22 17:06:26.000000000 +0200
@@ -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  # noqa: F401
 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",
@@ -52,6 +53,7 @@
     ],
     extras_require={
         "all": ["ruamel.yaml", "toml", "msgpack"],
+        "yaml": ["ruamel.yaml"],
         "ruamel.yaml": ["ruamel.yaml"],
         "PyYAML": ["PyYAML"],
         "toml": ["toml"],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/test/test_box.py 
new/Box-5.4.1/test/test_box.py
--- old/Box-5.1.0/test/test_box.py      2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/test/test_box.py      2021-08-22 17:06:26.000000000 +0200
@@ -5,29 +5,29 @@
 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
+from box.box import _get_dot_paths  # type: ignore
+
 
 def mp_queue_test(q):
     bx = q.get()
@@ -307,6 +307,30 @@
         assert a.key3.item == 2
         assert a.lister[0].gah == 7
 
+    def test_set_default_box_dots(self):
+        a = Box(box_dots=True)
+        a["x"] = {"y": 10}
+        a.setdefault("x.y", 20)
+        assert a["x.y"] == 10
+
+        a["lists"] = [[[{"test": "here"}], {1, 2}], (4, 5)]
+        assert list(_get_dot_paths(a)) == [
+            "x",
+            "x.y",
+            "lists",
+            "lists[0]",
+            "lists[0][0]",
+            "lists[0][0][0]",
+            "lists[0][0][0].test",
+            "lists[0][1]",
+            "lists[1]",
+        ]
+
+        t = Box({"a": 1}, default_box=True, box_dots=True, 
default_box_none_transform=False)
+        assert t.setdefault("b", [1, 2]) == [1, 2]
+        assert t == Box(a=1, b=[1, 2])
+        assert t.setdefault("c", [{"d": 2}]) == BoxList([{"d": 2}])
+
     def test_from_json_file(self):
         bx = Box.from_json(filename=data_json_file)
         assert isinstance(bx, Box)
@@ -329,17 +353,17 @@
         assert bx.Key_2 == Box()
 
     def test_bad_from_json(self):
-        with pytest.raises(BoxError) as err:
+        with pytest.raises(BoxError):
             Box.from_json()
 
-        with pytest.raises(BoxError) as err2:
+        with pytest.raises(BoxError):
             Box.from_json(json_string="[1]")
 
     def test_bad_from_yaml(self):
-        with pytest.raises(BoxError) as err:
+        with pytest.raises(BoxError):
             Box.from_yaml()
 
-        with pytest.raises(BoxError) as err2:
+        with pytest.raises(BoxError):
             Box.from_yaml("lol")
 
     def test_conversion_box(self):
@@ -371,6 +395,21 @@
         with pytest.raises(TypeError):
             hash(bx)
 
+        with pytest.raises(BoxError):
+            bx.clear()
+
+        with pytest.raises(BoxError):
+            bx.pop("alist")
+
+        with pytest.raises(BoxError):
+            bx.popitem()
+
+        with pytest.raises(BoxError):
+            bx.popitem()
+
+        with pytest.raises(BoxError):
+            bx.update({"another_list": []})
+
         bx2 = Box(test_dict)
         with pytest.raises(TypeError):
             hash(bx2)
@@ -568,7 +607,7 @@
         assert a[1].b == 3
 
     def test_duplicate_errors(self):
-        with pytest.raises(BoxError) as err:
+        with pytest.raises(BoxError):
             Box({"?a": 1, "!a": 3}, box_duplicates="error")
 
         Box({"?a": 1, "!a": 3}, box_duplicates="ignore")
@@ -756,7 +795,9 @@
 
     def test_inheritance(self):
         data = {
-            "users": [{"users": [{"name": "B"}]},],
+            "users": [
+                {"users": [{"name": "B"}]},
+            ],
         }
 
         class UsersBoxList(BoxList):
@@ -974,6 +1015,21 @@
         with pytest.raises(ValueError):
             b["sub_box"] = {"id": "bad_id"}
 
+    def test_nontype_recast(self):
+        class CustomError(ValueError):
+            pass
+
+        def cast_id(val) -> int:
+            if val == "bad_id":
+                raise CustomError()
+            return int(val)
+
+        b = Box(id="6", box_recast={"id": cast_id})
+        assert isinstance(b.id, int)
+        with pytest.raises(ValueError) as exc_info:
+            b["sub_box"] = {"id": "bad_id"}
+        assert isinstance(exc_info.value.__cause__, CustomError)
+
     def test_box_dots(self):
         b = Box(
             {"my_key": {"does stuff": {"to get to": "where I want"}}, 
"key.with.list": [[[{"test": "value"}]]]},
@@ -1137,5 +1193,91 @@
 
     def test_default_box_restricted_calls(self):
         a = Box(default_box=True)
-        a._test_thing_
+        with pytest.raises(BoxKeyError):
+            a._test_thing_
         assert len(list(a.keys())) == 0
+
+        # Based on argparse.parse_args internal behavior, the following
+        # creates the attribute in hasattr due to default_box=True, then
+        # deletes it in delattr.
+        if hasattr(a, "_unrecognized_args"):
+            delattr(a, "_unrecognized_args")
+
+        a._allowed_prefix
+        a.allowed_postfix_
+        assert len(list(a.keys())) == 2
+
+    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"] == {}
+
+    def test_merge_list_options(self):
+        a = Box()
+        a.merge_update({"lister": ["a"]})
+        a.merge_update({"lister": ["a", "b", "c"]}, box_merge_lists="extend")
+        assert a.lister == ["a", "a", "b", "c"]
+        a.merge_update({"lister": ["a", "b", "c"]}, box_merge_lists="unique")
+        assert a.lister == ["a", "a", "b", "c"]
+        a.merge_update({"lister": ["a", "d", "b", "c"]}, 
box_merge_lists="unique")
+        assert a.lister == ["a", "a", "b", "c", "d"]
+        a.merge_update({"key1": {"new": 5}, "Key 2": {"add_key": 6}, "lister": 
["a"]})
+        assert a.lister == ["a"]
+
+    def test_box_from_empty_yaml(self):
+        out = Box.from_yaml("---")
+        assert out == Box()
+
+        out2 = BoxList.from_yaml("---")
+        assert out2 == BoxList()
+
+    def test_setdefault_simple(self):
+        box = Box({"a": 1})
+        box.setdefault("b", 2)
+        box.setdefault("c", "test")
+        box.setdefault("d", {"e": True})
+        box.setdefault("f", [1, 2])
+
+        assert box["b"] == 2
+        assert box["c"] == "test"
+        assert isinstance(box["d"], Box)
+        assert box["d"]["e"] == True
+        assert isinstance(box["f"], BoxList)
+        assert box["f"][1] == 2
+
+    def test_setdefault_dots(self):
+        box = Box({"a": 1}, box_dots=True)
+        box.setdefault("b", 2)
+        box.c = {"d": 3}
+        box.setdefault("c.e", "test")
+        box.setdefault("d", {"e": True})
+        box.setdefault("f", [1, 2])
+
+        assert box.b == 2
+        assert box.c.e == "test"
+        assert isinstance(box["d"], Box)
+        assert box.d.e == True
+        assert isinstance(box["f"], BoxList)
+        assert box.f[1] == 2
+
+    def test_setdefault_dots_default(self):
+        box = Box({"a": 1}, box_dots=True, default_box=True)
+        box.b.c.d.setdefault("e", 2)
+        box.c.setdefault("e", "test")
+        box.d.e.setdefault("f", {"g": True})
+        box.e.setdefault("f", [1, 2])
+
+        assert box["b.c.d"].e == 2
+        assert box.c.e == "test"
+        assert isinstance(box["d.e.f"], Box)
+        assert box.d.e["f.g"] == True
+        assert isinstance(box["e.f"], BoxList)
+        assert box.e.f[1] == 2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/test/test_box_list.py 
new/Box-5.4.1/test/test_box_list.py
--- old/Box-5.1.0/test/test_box_list.py 2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/test/test_box_list.py 2021-08-22 17:06:26.000000000 +0200
@@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/test/test_config_box.py 
new/Box-5.4.1/test/test_config_box.py
--- old/Box-5.1.0/test/test_config_box.py       2020-07-23 23:07:29.000000000 
+0200
+++ new/Box-5.4.1/test/test_config_box.py       2021-08-22 17:06:26.000000000 
+0200
@@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/test/test_converters.py 
new/Box-5.4.1/test/test_converters.py
--- old/Box-5.1.0/test/test_converters.py       2020-07-23 23:07:29.000000000 
+0200
+++ new/Box-5.4.1/test/test_converters.py       2021-08-22 17:06:26.000000000 
+0200
@@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/test/test_from_file.py 
new/Box-5.4.1/test/test_from_file.py
--- old/Box-5.1.0/test/test_from_file.py        2020-07-23 23:07:29.000000000 
+0200
+++ new/Box-5.4.1/test/test_from_file.py        2021-08-22 17:06:26.000000000 
+0200
@@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Box-5.1.0/test/test_sbox.py 
new/Box-5.4.1/test/test_sbox.py
--- old/Box-5.1.0/test/test_sbox.py     2020-07-23 23:07:29.000000000 +0200
+++ new/Box-5.4.1/test/test_sbox.py     2021-08-22 17:06:26.000000000 +0200
@@ -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:

Reply via email to