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 2022-06-18 22:06:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-python-box (Old)
and /work/SRC/openSUSE:Factory/.python-python-box.new.1548 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-python-box"
Sat Jun 18 22:06:24 2022 rev:7 rq:983572 version:6.0.2
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-python-box/python-python-box.changes
2021-12-09 19:46:21.449154866 +0100
+++
/work/SRC/openSUSE:Factory/.python-python-box.new.1548/python-python-box.changes
2022-06-18 22:06:28.271680092 +0200
@@ -1,0 +2,9 @@
+Fri Jun 17 12:20:56 UTC 2022 - Mark??ta Machov?? <[email protected]>
+
+- Update to 6.0.2
+ * Adding Cython support to greatly speed up normal Box operations on
supported systems
+ * Adding #196 support for sliceable boxes
+ * Changing #208 __repr__ to produce eval-able text
+ * Removing support for ruamel.yaml < 0.17
+
+-------------------------------------------------------------------
Old:
----
5.4.1.tar.gz
New:
----
6.0.2.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-python-box.spec ++++++
--- /var/tmp/diff_new_pack.EFaE5e/_old 2022-06-18 22:06:28.883680961 +0200
+++ /var/tmp/diff_new_pack.EFaE5e/_new 2022-06-18 22:06:28.887680966 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-python-box
#
-# Copyright (c) 2021 SUSE LLC
+# Copyright (c) 2022 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -20,7 +20,7 @@
# python_requires='>=3.6'
%define skip_python2 1
Name: python-python-box
-Version: 5.4.1
+Version: 6.0.2
Release: 0
Summary: Advanced Python dictionaries with dot notation access
License: MIT
@@ -34,12 +34,12 @@
# SECTION test requirements
BuildRequires: %{python_module msgpack >= 1.0.0}
BuildRequires: %{python_module pytest}
-BuildRequires: %{python_module ruamel.yaml >= 0.16.10}
-BuildRequires: %{python_module toml >= 0.10.1}
+BuildRequires: %{python_module ruamel.yaml >= 0.17}
+BuildRequires: %{python_module toml >= 0.10.2}
# /SECTION
Requires: python-msgpack >= 1.0.0
-Requires: python-ruamel.yaml >= 0.16.10
-Requires: python-toml >= 0.10.1
+Requires: python-ruamel.yaml >= 0.17
+Requires: python-toml >= 0.10.2
%python_subpackages
%description
++++++ 5.4.1.tar.gz -> 6.0.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/.github/workflows/pythonpublish.yml
new/Box-6.0.2/.github/workflows/pythonpublish.yml
--- old/Box-5.4.1/.github/workflows/pythonpublish.yml 2021-08-22
17:06:26.000000000 +0200
+++ new/Box-6.0.2/.github/workflows/pythonpublish.yml 2022-04-02
04:24:21.000000000 +0200
@@ -8,7 +8,7 @@
types: [created]
jobs:
- deploy:
+ deploy-generic:
runs-on: ubuntu-latest
@@ -29,3 +29,54 @@
run: |
python setup.py sdist bdist_wheel
twine upload dist/*
+
+ deploy-cython:
+ strategy:
+ matrix:
+ os: [macos-latest, windows-latest]
+ python-version: ["3.7", "3.8", "3.9", "3.10"]
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v1
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install setuptools wheel twine Cython --upgrade
+ - name: Build and publish
+ env:
+ TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+ TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+ run: |
+ python setup.py bdist_wheel
+ twine upload dist/*
+
+ deploy-cython-manylinux:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python 3.10
+ uses: actions/setup-python@v1
+ with:
+ python-version: "3.10"
+
+ - uses: RalfG/[email protected]_x86_64
+ with:
+ python-versions: 'cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310'
+ build-requirements: 'cython'
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install twine --upgrade
+
+ - name: Publish
+ env:
+ TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+ TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+ run: |
+ twine upload dist/*-manylinux*.whl
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/.github/workflows/tests.yml
new/Box-6.0.2/.github/workflows/tests.yml
--- old/Box-5.4.1/.github/workflows/tests.yml 2021-08-22 17:06:26.000000000
+0200
+++ new/Box-6.0.2/.github/workflows/tests.yml 2022-04-02 04:24:21.000000000
+0200
@@ -10,24 +10,28 @@
branches: [ master, development, develop, test, tests ]
jobs:
- package_checks:
- runs-on: ubuntu-latest
+ package-checks:
strategy:
matrix:
- python-version: [3.9]
-
+ python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "pypy-3.8"]
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
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 }}
+ - uses: actions/cache@v2
+ with:
+ path: ~/.cache/pip
+ key: package-check-${{ hashFiles('requirements.txt') }}-${{
hashFiles('requirements-test.txt') }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-test.txt
- pip install coveralls flake8 flake8-print mypy setuptools wheel twine
+ pip install coveralls flake8 flake8-print mypy setuptools wheel
twine Cython
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors, undefined names
or print statements
@@ -36,28 +40,89 @@
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
+ - name: Build Wheel and check distrubiton log description
run: |
python setup.py sdist bdist_wheel
twine check dist/*
+ - name: Test packaged wheel on *nix
+ if: matrix.os != 'windows-latest'
+ run: |
+ pip install dist/*.whl
+ rm -rf box
+ python -m pytest
+ - name: Test packaged wheel on Windows
+ if: matrix.os == 'windows-latest'
+ run: |
+ $wheel = (Get-ChildItem dist\*.whl | Sort lastWriteTime |
Select-Object -last 1).Name
+ pip install dist\${wheel}
+ Remove-item box -recurse -force
+ python -m pytest
+ - name: Upload wheel artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: python_box
+ path: dist/*.whl
- test:
+ package-manylinux-checks:
runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python 3.10
+ uses: actions/setup-python@v1
+ with:
+ python-version: "3.10"
+
+ - uses: actions/cache@v2
+ with:
+ path: ~/.cache/pip
+ key: package-manylinux-check-${{ hashFiles('requirements.txt')
}}-${{ hashFiles('requirements-test.txt') }}
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+ pip install -r requirements-test.txt
+ pip install coveralls flake8 flake8-print mypy setuptools wheel
twine Cython
+
+ - uses: RalfG/[email protected]_x86_64
+ with:
+ python-versions: 'cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310'
+ build-requirements: 'cython'
+
+ - name: Test packaged wheel on linux
+ run: |
+ pip install dist/*cp310-manylinux*.whl
+ rm -rf box
+ python -m pytest
+
+ - name: Upload wheel artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: python_box
+ path: dist/*-manylinux*.whl
+
+ test:
strategy:
matrix:
- python-version: [3.6, 3.7, 3.8, 3.9, 3.10-dev, pypy3]
-
+ python-version: ["3.7", "3.8", "3.9", "3.10"]
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
+ - uses: actions/cache@v2
+ with:
+ path: ~/.cache/pip
+ key: test-${{ hashFiles('requirements.txt') }}-${{
hashFiles('requirements-test.txt') }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-test.txt
+ pip install setuptools wheel Cython
+ python setup.py build_ext --inplace
- name: Test with pytest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/.gitignore new/Box-6.0.2/.gitignore
--- old/Box-5.4.1/.gitignore 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/.gitignore 2022-04-02 04:24:21.000000000 +0200
@@ -94,3 +94,6 @@
.pypirc
release.bat
coverage/
+# don't upload cython files
+box/*.c
+.pytest_cache/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/.pre-commit-config.yaml
new/Box-6.0.2/.pre-commit-config.yaml
--- old/Box-5.4.1/.pre-commit-config.yaml 2021-08-22 17:06:26.000000000
+0200
+++ new/Box-6.0.2/.pre-commit-config.yaml 2022-04-02 04:24:21.000000000
+0200
@@ -1,7 +1,7 @@
repos:
-- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.0.1
- hooks:
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.1.0
+ hooks:
# Identify invalid files
- id: check-ast
- id: check-yaml
@@ -27,13 +27,32 @@
- id: check-executables-have-shebangs
- id: end-of-file-fixer
exclude: ^test/data/.+
-- repo: https://github.com/ambv/black
- rev: 21.7b0
- hooks:
+
+- repo: https://github.com/ambv/black
+ rev: 22.1.0
+ hooks:
- id: black
args: [--config=.black.toml]
-- repo: https://github.com/pre-commit/mirrors-mypy
- rev: 'v0.910'
- hooks:
+
+- repo: local
+ hooks:
+ - id: cythonize-check
+ name: Cythonize
+ entry: python setup.py build_ext --inplace
+ language: system
+ types: [python]
+ pass_filenames: false
+ # cythonize must come before the pytest to make sure we update all c code
+ - id: pytest-check
+ name: Check pytest
+ entry: pytest
+ language: system
+ pass_filenames: false
+ always_run: true
+
+- repo: https://github.com/pre-commit/mirrors-mypy
+ rev: 'v0.931'
+ hooks:
- id: mypy
- additional_dependencies: [ruamel.yaml,toml,msgpack]
+ types: [python]
+ additional_dependencies:
[ruamel.yaml,toml,msgpack,types-PyYAML,types-toml]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/CHANGES.rst new/Box-6.0.2/CHANGES.rst
--- old/Box-5.4.1/CHANGES.rst 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/CHANGES.rst 2022-04-02 04:24:21.000000000 +0200
@@ -1,6 +1,31 @@
Changelog
=========
+Version 6.0.2
+-------------
+
+* Fixing that the typing `pyi` files were not included in the manifest (thanks
to Julian Torres)
+
+Version 6.0.1
+-------------
+
+* Fixing #218 Box dots would not raise KeyError on bad key (thanks to Cliff
Wells)
+* Fixing #217 wording in readme overview needed updated (thanks to Julie Jones)
+
+Version 6.0.0
+-------------
+
+* Adding Cython support to greatly speed up normal Box operations on supported
systems
+* Adding #161 support for access box dots with `get` and checking with `in`
(thanks to scott-createplay)
+* Adding #183 support for all allowed character sets (thanks to Giulio
Malventi)
+* Adding #196 support for sliceable boxes (thanks to Dias)
+* Adding #164 default_box_create_on_get toggle to disable setting box variable
on get request (thanks to ipcoder)
+* Changing #208 __repr__ to produce `eval`-able text (thanks to Jeff Robbins)
+* Changing #215 support ruamel.yaml new syntax (thanks to Ivan Pepelnjak)
+* Changing `update` and `merge_update` to not use a keyword that could cause
issues in rare circumstances
+* Changing internal `_safe_key` logic to be twice as fast
+* Removing support for ruamel.yaml < 0.17
+
Version 5.4.1
-------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/LICENSE new/Box-6.0.2/LICENSE
--- old/Box-5.4.1/LICENSE 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/LICENSE 2022-04-02 04:24:21.000000000 +0200
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2017-2020 Chris Griffith
+Copyright (c) 2017-2022 Chris Griffith
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/MANIFEST.in new/Box-6.0.2/MANIFEST.in
--- old/Box-5.4.1/MANIFEST.in 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/MANIFEST.in 2022-04-02 04:24:21.000000000 +0200
@@ -2,3 +2,7 @@
include AUTHORS.rst
include CHANGES.rst
include box/py.typed
+include box/*.c
+include box/*.so
+include box/*.pyd
+include box/*.pyi
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/README.rst new/Box-6.0.2/README.rst
--- old/Box-5.4.1/README.rst 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/README.rst 2022-04-02 04:24:21.000000000 +0200
@@ -23,36 +23,78 @@
Install
=======
+**Version Pin Your Box!**
+
+If you aren't in the habit of version pinning your libraries, it will
eventually bite you.
+Box has a `list of breaking change
<https://github.com/cdgriffith/Box/wiki/Major-Version-Breaking-Changes>`_
between major versions you should always check out before updating.
+
+requirements.txt
+----------------
+
+.. code:: text
+
+ python-box[all]~=6.0
+
+As Box adheres to semantic versioning (aka API changes will only occur on
between major version),
+it is best to use `Compatible release
<https://www.python.org/dev/peps/pep-0440/#compatible-release>`_ matching using
the `~=` clause.
+
+Install from command line
+-------------------------
+
.. code:: bash
- pip install --upgrade python-box[all]
+ pip install python-box[all]~=6.0 --upgrade
-Box 5 is no longer forcing install of external dependencies such as yaml and
toml. Instead you can specify which you want,
-for example, `all` is shorthand for:
+Install with selected dependencies
+----------------------------------
+
+Box is no longer forcing install of external dependencies such as yaml and
toml. Instead you can specify which you want,
+for example, `[all]` is shorthand for:
.. code:: bash
- pip install --upgrade python-box[ruamel.yaml,toml,msgpack]
+ pip install python-box[ruamel.yaml,toml,msgpack]~=6.0 --upgrade
-But you can also sub out "ruamel.yaml" for "PyYAML".
+But you can also sub out `ruamel.yaml` for `PyYAML`.
Check out `more details
<https://github.com/cdgriffith/Box/wiki/Installation>`_ on installation details.
-Box 5 is tested on python 3.6+ and pypy3, if you are upgrading from previous
versions, please look through
-`any breaking changes and new features
<https://github.com/cdgriffith/Box/wiki/Major-Version-Breaking-Changes-and-New-Features>`_.
+Box 6 is tested on python 3.6+, if you are upgrading from previous versions,
please look through
+`any breaking changes and new features
<https://github.com/cdgriffith/Box/wiki/Major-Version-Breaking-Changes>`_.
+
+Optimized Version
+-----------------
+Box 6 is introducing Cython optimizations for major platforms by default.
+Loading large data sets can be up to 10x faster!
+
+If you are **not** on a x86_64 supported system you will need to do some extra
work to install the optimized version.
+There will be an warning of "WARNING: Cython not installed, could not optimize
box" during install.
+You will need python development files, system compiler, and the python
packages `Cython` and `wheel`.
+
+**Linux Example:**
+
+First make sure you have python development files installed (`python3-dev` or
`python3-devel` in most repos).
+You will then need `Cython` and `wheel` installed and then install (or
re-install with `--force`) `python-box`.
+
+.. code:: bash
+
+ pip install Cython wheel
+ pip install python-box[all]~=6.0 --upgrade --force
If you have any issues please open a github issue with the error you are
experiencing!
Overview
========
-`Box` is designed to be an easy drop in transparently replacements for
-dictionaries, thanks to Python's
-duck typing capabilities, which adds dot notation access. Any sub
-dictionaries or ones set after initiation will be automatically converted to
-a `Box` object. You can always run `.to_dict()` on it to return the object
-and all sub objects back into a regular dictionary.
+`Box` is designed to be a near transparent drop in replacements for
+dictionaries that add dot notation access and other powerful feature.
+
+There are a lot of `types of boxes
<https://github.com/cdgriffith/Box/wiki/Types-of-Boxes>`_
+to customize it for your needs, as well as handy `converters
<https://github.com/cdgriffith/Box/wiki/Converters>`_!
+
+Keep in mind any sub dictionaries or ones set after initiation will be
automatically converted to
+a `Box` object, and lists will be converted to `BoxList`, all other objects
stay intact.
Check out the `Quick Start
<https://github.com/cdgriffith/Box/wiki/Quick-Start>`_ for more in depth
details.
@@ -76,7 +118,7 @@
small_box = Box({'data': 2, 'count': 5})
small_box.data == small_box['data'] == getattr(small_box, 'data')
-All dicts (and lists) added to a `Box` will be converted on lookup to a `Box`
(or `BoxList`),
+All dicts (and lists) added to a `Box` will be converted on insertion to a
`Box` (or `BoxList`),
allowing for recursive dot notation access.
`Box` also includes helper functions to transform it back into a `dict`,
@@ -96,7 +138,7 @@
License
=======
-MIT License, Copyright (c) 2017-2020 Chris Griffith. See LICENSE_ file.
+MIT License, Copyright (c) 2017-2022 Chris Griffith. See LICENSE_ file.
.. |BoxImage| image::
https://raw.githubusercontent.com/cdgriffith/Box/master/box_logo.png
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/box/__init__.py
new/Box-6.0.2/box/__init__.py
--- old/Box-5.4.1/box/__init__.py 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/box/__init__.py 2022-04-02 04:24:21.000000000 +0200
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
__author__ = "Chris Griffith"
-__version__ = "5.4.1"
+__version__ = "6.0.2"
from box.box import Box
from box.box_list import BoxList
@@ -10,6 +10,7 @@
from box.exceptions import BoxError, BoxKeyError
from box.from_file import box_from_file
from box.shorthand_box import SBox
+import box.converters
__all__ = [
"Box",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/box/box.py new/Box-6.0.2/box/box.py
--- old/Box-5.4.1/box/box.py 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/box/box.py 2022-04-02 04:24:21.000000000 +0200
@@ -1,17 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
-# Copyright (c) 2017-2020 - Chris Griffith - MIT License
+# Copyright (c) 2017-2022 - Chris Griffith - MIT License
"""
Improved dictionary access through dot notation with additional tools.
"""
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
+from typing import Any, Dict, Generator, List, Tuple, Union, Type
try:
from typing import Callable, Iterable, Mapping
@@ -131,6 +130,7 @@
:param default_box_attr: Specify the default replacement.
WARNING: If this is not the default 'Box', it will not be recursive
:param default_box_none_transform: When using default_box, treat keys with
none values as absent. True by default
+ :param default_box_create_on_get: On lookup of a key that doesn't exist,
create it if missing
:param frozen_box: After creation, the box cannot be modified
:param camel_killer_box: Convert CamelCase to snake_case
:param conversion_box: Check for near matching keys as attributes
@@ -162,6 +162,7 @@
default_box: bool = False,
default_box_attr: Any = NO_DEFAULT,
default_box_none_transform: bool = True,
+ default_box_create_on_get: bool = True,
frozen_box: bool = False,
camel_killer_box: bool = False,
conversion_box: bool = True,
@@ -171,7 +172,7 @@
box_intact_types: Union[Tuple, List] = (),
box_recast: Dict = None,
box_dots: bool = False,
- box_class: Union[Dict, "Box"] = None,
+ box_class: Union[Dict, Type["Box"]] = None,
**kwargs: Any,
):
"""
@@ -185,6 +186,7 @@
"default_box": default_box,
"default_box_attr": cls.__class__ if default_box_attr is
NO_DEFAULT else default_box_attr,
"default_box_none_transform": default_box_none_transform,
+ "default_box_create_on_get": default_box_create_on_get,
"conversion_box": conversion_box,
"box_safe_prefix": box_safe_prefix,
"frozen_box": frozen_box,
@@ -205,6 +207,7 @@
default_box: bool = False,
default_box_attr: Any = NO_DEFAULT,
default_box_none_transform: bool = True,
+ default_box_create_on_get: bool = True,
frozen_box: bool = False,
camel_killer_box: bool = False,
conversion_box: bool = True,
@@ -214,7 +217,7 @@
box_intact_types: Union[Tuple, List] = (),
box_recast: Dict = None,
box_dots: bool = False,
- box_class: Union[Dict, "Box"] = None,
+ box_class: Union[Dict, Type["Box"]] = None,
**kwargs: Any,
):
super().__init__()
@@ -224,6 +227,7 @@
"default_box": default_box,
"default_box_attr": self.__class__ if default_box_attr is
NO_DEFAULT else default_box_attr,
"default_box_none_transform": default_box_none_transform,
+ "default_box_create_on_get": default_box_create_on_get,
"conversion_box": conversion_box,
"box_safe_prefix": box_safe_prefix,
"frozen_box": frozen_box,
@@ -331,17 +335,12 @@
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:
@@ -352,6 +351,21 @@
return list(items)
+ def __contains__(self, item):
+ in_me = super().__contains__(item)
+ if not self._box_config["box_dots"] or not isinstance(item, str):
+ return in_me
+ if in_me:
+ return True
+ if "." not in item:
+ return False
+ try:
+ first_item, children = _parse_box_dots(self, item)
+ except BoxError:
+ return False
+ else:
+ return children in self[first_item]
+
def keys(self, dotted: Union[bool] = False):
if not dotted:
return super().keys()
@@ -434,8 +448,9 @@
value = default_value.copy()
else:
value = default_value
- if not attr or not (item.startswith("_") and item.endswith("_")):
- super().__setitem__(item, value)
+ if self._box_config["default_box_create_on_get"]:
+ if not attr or not (item.startswith("_") and item.endswith("_")):
+ super().__setitem__(item, value)
return value
def __box_config(self) -> Dict:
@@ -496,7 +511,7 @@
except BoxError:
if self._box_config["default_box"] and not _ignore_default:
return self.__get_default(item)
- raise
+ raise BoxKeyError(str(item)) from _exception_cause(err)
if first_item in self.keys():
if hasattr(self[first_item], "__getitem__"):
return self[first_item][children]
@@ -507,6 +522,13 @@
if self._box_config["default_box"] and not _ignore_default:
return self.__get_default(item)
raise BoxKeyError(str(err)) from _exception_cause(err)
+ except TypeError as err:
+ if isinstance(item, slice):
+ new_box = self._box_config["box_class"](**self.__box_config())
+ for x in list(super().keys())[item.start : item.stop :
item.step]:
+ new_box[x] = self[x]
+ return new_box
+ raise BoxTypeError(str(err)) from _exception_cause(err)
def __getattr__(self, item):
try:
@@ -547,12 +569,13 @@
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]
@@ -567,7 +590,10 @@
and isinstance(key, str)
and ("." in key or "[" in key)
):
- first_item, children = _parse_box_dots(self, key)
+ try:
+ first_item, children = _parse_box_dots(self, key)
+ except BoxError:
+ raise BoxKeyError(str(key)) from None
if hasattr(self[first_item], "__delitem__"):
return self[first_item].__delitem__(children)
if key not in self.keys() and self._box_config["camel_killer_box"]:
@@ -637,7 +663,7 @@
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())
@@ -666,21 +692,27 @@
out_dict[k] = v.to_list()
return out_dict
- def update(self, __m=None, **kwargs):
+ def update(self, *args, **kwargs):
if self._box_config["frozen_box"]:
raise BoxError("Box is frozen")
-
- if __m:
- if hasattr(__m, "keys"):
- for k in __m:
- self.__convert_and_store(k, __m[k])
+ if (len(args) + int(bool(kwargs))) > 1:
+ raise BoxTypeError(f"update expected at most 1 argument, got
{len(args) + int(bool(kwargs))}")
+ single_arg = next(iter(args), None)
+ if single_arg:
+ if hasattr(single_arg, "keys"):
+ for k in single_arg:
+ self.__convert_and_store(k, single_arg[k])
else:
- for k, v in __m:
+ for k, v in single_arg:
self.__convert_and_store(k, v)
for k in kwargs:
self.__convert_and_store(k, kwargs[k])
- def merge_update(self, __m=None, **kwargs):
+ def merge_update(self, *args, **kwargs):
+ merge_type = None
+ if "box_merge_lists" in kwargs:
+ merge_type = kwargs.pop("box_merge_lists")
+
def convert_and_set(k, v):
intact_type = self._box_config["box_intact_types"] and
isinstance(v, self._box_config["box_intact_types"])
if isinstance(v, dict) and not intact_type:
@@ -692,7 +724,6 @@
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
@@ -703,13 +734,17 @@
return
self.__setitem__(k, v)
- if __m:
- if hasattr(__m, "keys"):
- for key in __m:
- convert_and_set(key, __m[key])
+ if (len(args) + int(bool(kwargs))) > 1:
+ raise BoxTypeError(f"merge_update expected at most 1 argument, got
{len(args) + int(bool(kwargs))}")
+ single_arg = next(iter(args), None)
+ if single_arg:
+ if hasattr(single_arg, "keys"):
+ for k in single_arg:
+ convert_and_set(k, single_arg[k])
else:
- for key, value in __m:
- convert_and_set(key, value)
+ for k, v in single_arg:
+ convert_and_set(k, v)
+
for key in kwargs:
convert_and_set(key, kwargs[key])
@@ -730,7 +765,10 @@
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])
@@ -739,10 +777,18 @@
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:
@@ -760,7 +806,7 @@
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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/box/box.pyi new/Box-6.0.2/box/box.pyi
--- old/Box-5.4.1/box/box.pyi 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/box/box.pyi 2022-04-02 04:24:21.000000000 +0200
@@ -9,6 +9,7 @@
default_box: bool = ...,
default_box_attr: Any = ...,
default_box_none_transform: bool = ...,
+ default_box_create_on_get: bool = ...,
frozen_box: bool = ...,
camel_killer_box: bool = ...,
conversion_box: bool = ...,
@@ -27,6 +28,7 @@
default_box: bool = ...,
default_box_attr: Any = ...,
default_box_none_transform: bool = ...,
+ default_box_create_on_get: bool = ...,
frozen_box: bool = ...,
camel_killer_box: bool = ...,
conversion_box: bool = ...,
@@ -54,8 +56,8 @@
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 __getitem__(self, item: Any, _ignore_default: bool = ...) -> Any: ...
+ def __getattr__(self, item: Any) -> Any: ...
def __setitem__(self, key: Any, value: Any): ...
def __setattr__(self, key: Any, value: Any): ...
def __delitem__(self, key: Any): ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/box/box_list.py
new/Box-6.0.2/box/box_list.py
--- old/Box-5.4.1/box/box_list.py 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/box/box_list.py 2022-04-02 04:24:21.000000000 +0200
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
-# Copyright (c) 2017-2020 - Chris Griffith - MIT License
+# Copyright (c) 2017-2022 - Chris Griffith - MIT License
import copy
import re
from os import PathLike
@@ -133,7 +133,7 @@
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())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/box/config_box.py
new/Box-6.0.2/box/config_box.py
--- old/Box-5.4.1/box/config_box.py 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/box/config_box.py 2022-04-02 04:24:21.000000000 +0200
@@ -123,7 +123,7 @@
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())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/box/converters.py
new/Box-6.0.2/box/converters.py
--- old/Box-5.4.1/box/converters.py 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/box/converters.py 2022-04-02 04:24:21.000000000 +0200
@@ -8,22 +8,28 @@
from io import StringIO
from os import PathLike
from pathlib import Path
-from typing import Union
+from typing import Union, Optional, Dict
from box.exceptions import BoxError
-yaml_available = True
+pyyaml_available = True
+ruamel_available = True
toml_available = True
msgpack_available = True
try:
- import ruamel.yaml as yaml
+ from ruamel.yaml import version_info, YAML
except ImportError:
- try:
- import yaml # type: ignore
- except ImportError:
- yaml = None # type: ignore
- yaml_available = False
+ ruamel_available = False
+else:
+ if version_info[1] < 17:
+ ruamel_available = False
+
+try:
+ import yaml
+except ImportError:
+ pyyaml_available = False
+
try:
import toml
except ImportError:
@@ -36,6 +42,8 @@
msgpack = None # type: ignore
msgpack_available = False
+yaml_available = pyyaml_available or ruamel_available
+
BOX_PARAMETERS = (
"default_box",
"default_box_attr",
@@ -44,7 +52,6 @@
"camel_killer_box",
"box_safe_prefix",
"box_duplicates",
- "ordered_box",
"default_box_none_transform",
"box_dots",
"modify_tuples_box",
@@ -111,14 +118,39 @@
default_flow_style: bool = False,
encoding: str = "utf-8",
errors: str = "strict",
+ ruamel_typ: str = "rt",
+ ruamel_attrs: Optional[Dict] = None,
**yaml_kwargs,
):
+ if not ruamel_attrs:
+ ruamel_attrs = {}
if filename:
_exists(filename, create=True)
with open(filename, "w", encoding=encoding, errors=errors) as f:
- yaml.dump(obj, stream=f, default_flow_style=default_flow_style,
**yaml_kwargs)
+ if ruamel_available:
+ yaml_dumper = YAML(typ=ruamel_typ)
+ yaml_dumper.default_flow_style = default_flow_style
+ for attr, value in ruamel_attrs.items():
+ setattr(yaml_dumper, attr, value)
+ return yaml_dumper.dump(obj, stream=f, **yaml_kwargs)
+ elif pyyaml_available:
+ return yaml.dump(obj, stream=f,
default_flow_style=default_flow_style, **yaml_kwargs)
+ else:
+ raise BoxError("No YAML Parser available, please install
ruamel.yaml>0.17 or PyYAML")
+
else:
- return yaml.dump(obj, default_flow_style=default_flow_style,
**yaml_kwargs)
+ if ruamel_available:
+ yaml_dumper = YAML(typ=ruamel_typ)
+ yaml_dumper.default_flow_style = default_flow_style
+ for attr, value in ruamel_attrs.items():
+ setattr(yaml_dumper, attr, value)
+ with StringIO() as string_stream:
+ yaml_dumper.dump(obj, stream=string_stream, **yaml_kwargs)
+ return string_stream.getvalue()
+ elif pyyaml_available:
+ return yaml.dump(obj, default_flow_style=default_flow_style,
**yaml_kwargs)
+ else:
+ raise BoxError("No YAML Parser available, please install
ruamel.yaml>0.17 or PyYAML")
def _from_yaml(
@@ -126,16 +158,38 @@
filename: Union[str, PathLike] = None,
encoding: str = "utf-8",
errors: str = "strict",
+ ruamel_typ: str = "rt",
+ ruamel_attrs: Optional[Dict] = None,
**kwargs,
):
- if "Loader" not in kwargs:
- kwargs["Loader"] = yaml.SafeLoader
+ if not ruamel_attrs:
+ ruamel_attrs = {}
if filename:
_exists(filename)
with open(filename, "r", encoding=encoding, errors=errors) as f:
- data = yaml.load(f, **kwargs)
+ if ruamel_available:
+ yaml_loader = YAML(typ=ruamel_typ)
+ for attr, value in ruamel_attrs.items():
+ setattr(yaml_loader, attr, value)
+ data = yaml_loader.load(stream=f)
+ elif pyyaml_available:
+ if "Loader" not in kwargs:
+ kwargs["Loader"] = yaml.SafeLoader
+ data = yaml.load(f, **kwargs)
+ else:
+ raise BoxError("No YAML Parser available, please install
ruamel.yaml>0.15 or PyYAML")
elif yaml_string:
- data = yaml.load(yaml_string, **kwargs)
+ if ruamel_available:
+ yaml_loader = YAML(typ=ruamel_typ)
+ for attr, value in ruamel_attrs.items():
+ setattr(yaml_loader, attr, value)
+ data = yaml_loader.load(stream=yaml_string)
+ elif pyyaml_available:
+ if "Loader" not in kwargs:
+ kwargs["Loader"] = yaml.SafeLoader
+ data = yaml.load(yaml_string, **kwargs)
+ else:
+ raise BoxError("No YAML Parser available, please install
ruamel.yaml>0.17 or PyYAML")
else:
raise BoxError("from_yaml requires a string or filename")
return data
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/box/converters.pyi
new/Box-6.0.2/box/converters.pyi
--- old/Box-5.4.1/box/converters.pyi 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/box/converters.pyi 2022-04-02 04:24:21.000000000 +0200
@@ -1,6 +1,6 @@
from box.exceptions import BoxError as BoxError
from os import PathLike as PathLike
-from typing import Any, Union
+from typing import Any, Union, Optional, Dict
yaml_available: bool
toml_available: bool
@@ -25,6 +25,8 @@
default_flow_style: bool = False,
encoding: str = "utf-8",
errors: str = "strict",
+ ruamel_typ: str = "rt",
+ ruamel_attrs: Optional[Dict] = None,
**yaml_kwargs,
) -> Any: ...
def _from_yaml(
@@ -32,6 +34,8 @@
filename: Union[str, PathLike] = None,
encoding: str = "utf-8",
errors: str = "strict",
+ ruamel_typ: str = "rt",
+ ruamel_attrs: Optional[Dict] = None,
**kwargs,
) -> Any: ...
def _to_toml(obj, filename: Union[str, PathLike] = None, encoding: str =
"utf-8", errors: str = "strict") -> Any: ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/box/shorthand_box.py
new/Box-6.0.2/box/shorthand_box.py
--- old/Box-5.4.1/box/shorthand_box.py 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/box/shorthand_box.py 2022-04-02 04:24:21.000000000 +0200
@@ -41,7 +41,7 @@
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())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/requirements-dev.txt
new/Box-6.0.2/requirements-dev.txt
--- old/Box-5.4.1/requirements-dev.txt 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/requirements-dev.txt 2022-04-02 04:24:21.000000000 +0200
@@ -1,4 +1,5 @@
# Files needed for pre-commit hooks
black>=21.7b0
+Cython>=0.29
mypy>=0.910
-pre-commit>=4.0.1
+pre-commit>=2.15
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/requirements-test.txt
new/Box-6.0.2/requirements-test.txt
--- old/Box-5.4.1/requirements-test.txt 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/requirements-test.txt 2022-04-02 04:24:21.000000000 +0200
@@ -2,7 +2,8 @@
msgpack>=1.0
pytest>=5.4.1
pytest-cov>=2.8.1
-ruamel.yaml>=0.16,<0.17
+ruamel.yaml>=0.17
toml>=0.10.2
+types-PyYAML>=6.0.3
types-toml>=0.1.3
wheel>=0.34.2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/requirements.txt
new/Box-6.0.2/requirements.txt
--- old/Box-5.4.1/requirements.txt 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/requirements.txt 2022-04-02 04:24:21.000000000 +0200
@@ -1,3 +1,3 @@
msgpack>=1.0.0
-ruamel.yaml>=0.16.10,<0.17
+ruamel.yaml>=0.17
toml>=0.10.2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/setup.py new/Box-6.0.2/setup.py
--- old/Box-5.4.1/setup.py 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/setup.py 2022-04-02 04:24:21.000000000 +0200
@@ -5,11 +5,23 @@
import multiprocessing # noqa: F401
import os
import re
+from pathlib import Path
+import sys
from setuptools import setup
root = os.path.abspath(os.path.dirname(__file__))
+try:
+ from Cython.Build import cythonize
+except ImportError:
+ extra = None
+else:
+ extra = cythonize(
+ [str(file.relative_to(root)) for file in Path(root,
"box").glob("*.py") if file.name != "__init__.py"],
+ compiler_directives={"language_level": 3},
+ )
+
with open(os.path.join(root, "box", "__init__.py"), "r") as init_file:
init_content = init_file.read()
@@ -31,6 +43,7 @@
long_description_content_type="text/x-rst",
py_modules=["box"],
packages=["box"],
+ ext_modules=extra,
python_requires=">=3.6",
include_package_data=True,
platforms="any",
@@ -41,6 +54,7 @@
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: Implementation :: CPython",
"Development Status :: 5 - Production/Stable",
"Natural Language :: English",
@@ -52,11 +66,14 @@
"Topic :: Software Development :: Libraries :: Python Modules",
],
extras_require={
- "all": ["ruamel.yaml", "toml", "msgpack"],
- "yaml": ["ruamel.yaml"],
- "ruamel.yaml": ["ruamel.yaml"],
+ "all": ["ruamel.yaml>=0.17", "toml", "msgpack"],
+ "yaml": ["ruamel.yaml>=0.17"],
+ "ruamel.yaml": ["ruamel.yaml>=0.17"],
"PyYAML": ["PyYAML"],
"toml": ["toml"],
"msgpack": ["msgpack"],
},
)
+
+if not extra:
+ print("WARNING: Cython not installed, could not optimize box.",
file=sys.stderr)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/test/common.py new/Box-6.0.2/test/common.py
--- old/Box-5.4.1/test/common.py 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/test/common.py 2022-04-02 04:24:21.000000000 +0200
@@ -83,7 +83,7 @@
[1, 2, 3],
{},
([], {}),
- lambda x: x ** 2,
+ lambda x: x**2,
function_example,
ClassExample(),
) # type: ignore
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/test/test_box.py
new/Box-6.0.2/test/test_box.py
--- old/Box-5.4.1/test/test_box.py 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/test/test_box.py 2022-04-02 04:24:21.000000000 +0200
@@ -9,6 +9,7 @@
import shutil
from multiprocessing import Queue
from pathlib import Path
+from io import StringIO
from test.common import (
data_json_file,
data_yaml_file,
@@ -23,10 +24,11 @@
)
import pytest
-import ruamel.yaml as yaml
+from ruamel.yaml import YAML
-from box import Box, BoxError, BoxKeyError, BoxList, ConfigBox, SBox, box
-from box.box import _get_dot_paths # type: ignore
+from box import Box, BoxError, BoxKeyError, BoxList, ConfigBox, SBox
+from box.box import _get_dot_paths, _camel_killer, _recursive_tuples # type:
ignore
+from box.converters import BOX_PARAMETERS
def mp_queue_test(q):
@@ -58,8 +60,8 @@
assert Box()._safe_attr(356) == "x356"
def test_camel_killer(self):
- assert box._camel_killer("CamelCase") == "camel_case"
- assert box._camel_killer("Terrible321KeyA") == "terrible321_key_a"
+ assert _camel_killer("CamelCase") == "camel_case"
+ assert _camel_killer("Terrible321KeyA") == "terrible321_key_a"
bx = Box(camel_killer_box=True, conversion_box=False)
bx.DeadCamel = 3
@@ -88,7 +90,7 @@
assert len(bx1.keys()) == 0
def test_recursive_tuples(self):
- out = box._recursive_tuples(
+ out = _recursive_tuples(
({"test": "a"}, ({"second": "b"}, {"third": "c"}, ("fourth",))),
dict, recreate_tuples=True
)
assert isinstance(out, tuple)
@@ -108,7 +110,7 @@
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"
@@ -208,13 +210,15 @@
def test_to_yaml_basic(self):
a = Box(test_dict)
- assert yaml.load(a.to_yaml(), Loader=yaml.SafeLoader) == test_dict
+ yaml = YAML(typ="safe")
+ assert yaml.load(a.to_yaml()) == test_dict
def test_to_yaml_file(self):
a = Box(test_dict)
a.to_yaml(tmp_yaml_file)
with open(tmp_yaml_file) as f:
- data = yaml.load(f, Loader=yaml.SafeLoader)
+ yaml = YAML(typ="safe")
+ data = yaml.load(f)
assert data == test_dict
def test_dir(self):
@@ -347,7 +351,11 @@
assert bx.key1 == "value1"
def test_from_yaml(self):
- bx = Box.from_yaml(yaml.dump(test_dict), conversion_box=False,
default_box=True)
+ yaml = YAML(typ="safe")
+ with StringIO() as sio:
+ yaml.dump(test_dict, sio)
+ data = sio.getvalue()
+ bx = Box.from_yaml(data, conversion_box=False, default_box=True)
assert isinstance(bx, Box)
assert bx.key1 == "value1"
assert bx.Key_2 == Box()
@@ -752,6 +760,13 @@
assert isinstance(bx.get("b", {}), Box)
assert "a" in bx.get("a", Box(a=1, conversion_box=False))
assert isinstance(bx.get("a", [1, 2]), BoxList)
+ bx_dot = Box(a=Box(b=Box(c="me!")), box_dots=True)
+ assert bx_dot.get("a.b.c") == "me!"
+
+ def test_contains(self):
+ bx_dot = Box(a=Box(b=Box(c=Box())), box_dots=True)
+ assert "a.b.c" in bx_dot
+ assert "a.b.c.d" not in bx_dot
def test_get_default_box(self):
bx = Box(default_box=True)
@@ -917,6 +932,16 @@
_parse_box_dots({}, "-")
+ with pytest.raises(KeyError):
+ b["a.b"]
+ with pytest.raises(BoxKeyError):
+ b["a.b"]
+
+ with pytest.raises(KeyError):
+ del b["a.b"]
+ with pytest.raises(BoxKeyError):
+ del b["a.b"]
+
def test_unicode(self):
bx = Box()
bx["\U0001f631"] = 4
@@ -1278,6 +1303,55 @@
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 box.d.e["f.g"] is True
assert isinstance(box["e.f"], BoxList)
assert box.e.f[1] == 2
+
+ def test_box_slice(self):
+ data = Box(qwe=123, asd=234, q=1)
+ assert data[:-1] == Box(qwe=123, asd=234)
+
+ def test_box_kwargs_should_not_be_included(self):
+ params = {
+ "default_box": True,
+ "default_box_attr": True,
+ "conversion_box": True,
+ "frozen_box": True,
+ "camel_killer_box": True,
+ "box_safe_prefix": "x",
+ "box_duplicates": "error",
+ "default_box_none_transform": True,
+ "box_dots": True,
+ "modify_tuples_box": True,
+ "box_intact_types": (),
+ "box_recast": True,
+ }
+
+ bx = Box(**params)
+ assert bx == Box()
+
+ for param in BOX_PARAMETERS:
+ assert param in params
+
+ def test_box_greek(self):
+ # WARNING ?? is ord 956 whereas ?? is ord 181 and will not work due to
python NFKC normalization
+ a = Box()
+ a.??eq = 1
+ a.??eq = 2
+ assert a == Box({"??eq": 1, "??eq": 2})
+
+ def test_box_default_not_create_on_get(self):
+ box = Box(default_box=True)
+
+ assert box.a.b.c == Box()
+
+ assert box == Box(a=Box(b=Box(c=Box())))
+ assert "c" in box.a.b
+
+ box2 = Box(default_box=True, default_box_create_on_get=False)
+
+ assert box2.a.b.c == Box()
+
+ assert "c" not in box2.a.b
+
+ assert box2 == Box()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/test/test_box_list.py
new/Box-6.0.2/test/test_box_list.py
--- old/Box-5.4.1/test/test_box_list.py 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/test/test_box_list.py 2022-04-02 04:24:21.000000000 +0200
@@ -6,10 +6,11 @@
import os
import shutil
from pathlib import Path
+from io import StringIO
from test.common import test_root, tmp_dir
import pytest
-import ruamel.yaml as yaml
+from ruamel.yaml import YAML
import toml
from box import Box, BoxError, BoxList
@@ -32,7 +33,7 @@
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})
@@ -83,17 +84,20 @@
def test_box_list_to_yaml(self):
bl = BoxList([{"item": 1, "CamelBad": 2}])
- assert yaml.load(bl.to_yaml(), Loader=yaml.SafeLoader)[0]["item"] == 1
+ yaml = YAML()
+ assert yaml.load(bl.to_yaml())[0]["item"] == 1
def test_box_list_from_yaml(self):
alist = [{"item": 1}, {"CamelBad": 2}]
- yaml_list = yaml.dump(alist)
- bl = BoxList.from_yaml(yaml_list, camel_killer_box=True)
+ yaml = YAML()
+ with StringIO() as sio:
+ yaml.dump(alist, stream=sio)
+ bl = BoxList.from_yaml(sio.getvalue(), camel_killer_box=True)
assert bl[0].item == 1
assert bl[1].camel_bad == 2
with pytest.raises(BoxError):
- BoxList.from_yaml(yaml.dump({"a": 2}))
+ BoxList.from_yaml("a: 2")
def test_box_list_to_toml(self):
bl = BoxList([{"item": 1, "CamelBad": 2}])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/test/test_config_box.py
new/Box-6.0.2/test/test_config_box.py
--- old/Box-5.4.1/test/test_config_box.py 2021-08-22 17:06:26.000000000
+0200
+++ new/Box-6.0.2/test/test_config_box.py 2022-04-02 04:24:21.000000000
+0200
@@ -38,7 +38,7 @@
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)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/test/test_converters.py
new/Box-6.0.2/test/test_converters.py
--- old/Box-5.4.1/test/test_converters.py 2021-08-22 17:06:26.000000000
+0200
+++ new/Box-6.0.2/test/test_converters.py 2022-04-02 04:24:21.000000000
+0200
@@ -8,7 +8,7 @@
import msgpack
import pytest
-import ruamel.yaml as yaml
+from ruamel.yaml import YAML
from box import BoxError
from box.converters import _from_toml, _to_json, _to_msgpack, _to_toml,
_to_yaml
@@ -81,7 +81,8 @@
assert "Rick Moranis" in movie_string
_to_yaml(movie_data, filename=m_file)
assert "Rick Moranis" in open(m_file).read()
- assert yaml.load(open(m_file), Loader=yaml.SafeLoader) ==
yaml.load(movie_string, Loader=yaml.SafeLoader)
+ yaml = YAML()
+ assert yaml.load(open(m_file)) == yaml.load(movie_string)
def test_to_msgpack(self):
m_file = os.path.join(tmp_dir, "movie_data")
@@ -90,3 +91,14 @@
_to_msgpack(movie_data, filename=m_file)
assert b"Rick Moranis" in open(m_file, "rb").read()
assert msgpack.unpack(open(m_file, "rb")) == msgpack.unpackb(msg_data)
+
+ def test_to_yaml_ruamel(self):
+ movie_string = _to_yaml(movie_data, ruamel_attrs={"width": 12})
+ multiline_except = """ - name: Roger
+ Rees
+ imdb: nm0715953
+ role: Sheriff
+ of Rottingham
+ - name: Amy
+ Yasbeck"""
+ assert multiline_except in movie_string
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Box-5.4.1/test/test_sbox.py
new/Box-6.0.2/test/test_sbox.py
--- old/Box-5.4.1/test/test_sbox.py 2021-08-22 17:06:26.000000000 +0200
+++ new/Box-6.0.2/test/test_sbox.py 2022-04-02 04:24:21.000000000 +0200
@@ -3,7 +3,7 @@
import json
from test.common import test_dict
-import ruamel.yaml as yaml
+from ruamel.yaml import YAML
from box import Box, SBox
@@ -17,9 +17,10 @@
assert isinstance(pbox.inner, SBox)
assert pbox.inner.camel_case == "Item"
assert json.loads(pbox.json)["inner"]["camel_case"] == "Item"
- test_item = yaml.load(pbox.yaml, Loader=yaml.SafeLoader)
+ yaml = YAML()
+ test_item = yaml.load(pbox.yaml)
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"')