From a6dd19634bbecb328c15662996e1cad5812dfbba Mon Sep 17 00:00:00 2001 From: Zhang Yanpo Date: Wed, 7 Jan 2026 15:21:42 +0800 Subject: [PATCH] feat: migrate to pyproject.toml and modern tooling Migrate from setup.py to PEP 621 pyproject.toml, switch to MkDocs for documentation and ruff for linting. Changes: - Replace setup.py with pyproject.toml - Switch from Sphinx to MkDocs + mkdocstrings - Replace flake8 with ruff for linting - Update GitHub Actions to v5 - Fix type comparison to use `is not` instead of `!=` --- .github/workflows/python-package.yml | 137 +- .github/workflows/python-publish.yml | 44 +- .gitignore | 3 + .readthedocs.yaml | 16 + README.md | 1 - __init__.py | 6 +- _building/.gitignore | 129 -- _building/Makefile | 3 - _building/README.md | 35 +- _building/README.md.j2 | 38 - _building/__init__.py | 129 -- _building/build_readme.py | 85 - _building/build_setup.py | 190 --- _building/building-requirements.txt | 7 - _building/common.mk | 23 +- _building/install.sh | 12 - _building/populate.py | 23 +- _building/requirements.txt | 1 - dictutil.py | 70 +- docs/Makefile | 20 - docs/index.md | 49 + docs/make.bat | 35 - docs/source/conf.py | 28 - docs/source/index.rst | 63 - fixed_keys_dict.py | 7 +- mkdocs.yml | 30 + pyproject.toml | 57 + requirements.txt | 4 - setup.py | 23 - synopsis.py | 52 +- test/test_k3dict.py | 2236 ++++++++++++++------------ 31 files changed, 1489 insertions(+), 2067 deletions(-) create mode 100644 .readthedocs.yaml delete mode 100644 _building/.gitignore delete mode 100644 _building/Makefile delete mode 100644 _building/README.md.j2 delete mode 100644 _building/__init__.py delete mode 100644 _building/build_readme.py delete mode 100644 _building/build_setup.py delete mode 100644 _building/building-requirements.txt delete mode 100755 _building/install.sh delete mode 100644 _building/requirements.txt delete mode 100644 docs/Makefile create mode 100644 docs/index.md delete mode 100644 docs/make.bat delete mode 100644 docs/source/conf.py delete mode 100644 docs/source/index.rst create mode 100644 mkdocs.yml create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 setup.py diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 57ad3e7..832a966 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -1,6 +1,3 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - name: Unit test on: @@ -9,116 +6,50 @@ on: jobs: ut: - runs-on: ${{ matrix.os }} strategy: matrix: - # Github action runner update ubuntu to 22.04, which does not have - # python-3.6 in it: https://github.com/actions/setup-python/issues/544#issuecomment-1320295576 - # To fix it: use os: [ubuntu-20.04] instead os: [ubuntu-latest] python-version: [3.9, "3.10", 3.11, 3.12] steps: - - uses: actions/checkout@v5 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi - - name: Install npm dependencies - run: | - if [ -f package.json ]; then npm install; fi - - # manually add module binary path to github ci - echo "add node module path: $GITHUB_WORKSPACE/node_modules/.bin/" - echo "$GITHUB_WORKSPACE/node_modules/.bin/" >> $GITHUB_PATH - - - name: Install apt dependencies - run: | - if [ -f packages.txt ]; then cat packages.txt | xargs sudo apt-get install; fi - - name: Test with pytest - env: - # interactive command such as k3handy.cmdtty to run git, git complains - # if no TERM set: - # out: - (press RETURN) - # err: WARNING: terminal is not fully functional - # And waiting for a RETURN to press for ever - TERM: xterm - run: | - cp setup.py .. - cd .. - python setup.py install - cd - - - if [ -f sudo_test ]; then - sudo env "PATH=$PATH" pytest -v - else - pytest -v - fi - - - uses: actions/upload-artifact@v4 - if: failure() - with: - path: test/ + - uses: actions/checkout@v5 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest k3ut + pip install -e . + - name: Test with pytest + run: pytest -v build_doc: - - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - python-version: [3.9, "3.10", 3.11, 3.12] - + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi - - - name: Test building doc - run: | - pip install -r _building/building-requirements.txt - make -C docs html + - uses: actions/checkout@v5 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Build docs + run: | + pip install -e . + pip install mkdocs mkdocs-material "mkdocstrings[python]" + mkdocs build lint: - - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - python-version: [3.9, "3.10", 3.11, 3.12] - + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi - - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - uses: actions/checkout@v5 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Lint with ruff + run: | + pip install ruff + ruff check . + ruff format --check . diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 3fd68f1..af931b2 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,6 +1,3 @@ -# This workflows 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 on: @@ -10,27 +7,26 @@ on: jobs: deploy: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - cp setup.py .. - cd .. - python setup.py sdist bdist_wheel - pip install dist/*.tar.gz - python -c 'import '${GITHUB_REPOSITORY#*/} - twine upload dist/* + - uses: actions/checkout@v5 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build twine + + - name: Build package + run: python -m build + + - name: Publish to PyPI + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: twine upload dist/* diff --git a/.gitignore b/.gitignore index 7b9468e..94d8aab 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,6 @@ dmypy.json # Pyre type checker .pyre/ + +.claude/ +site/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..3e517df --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,16 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +mkdocs: + configuration: mkdocs.yml + +python: + install: + - method: pip + path: . + extra_requirements: + - docs diff --git a/README.md b/README.md index ca250fe..2c832fa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # k3dict [![Action-CI](https://github.com/pykit3/k3dict/actions/workflows/python-package.yml/badge.svg)](https://github.com/pykit3/k3dict/actions/workflows/python-package.yml) -[![Build Status](https://travis-ci.com/pykit3/k3dict.svg?branch=master)](https://travis-ci.com/pykit3/k3dict) [![Documentation Status](https://readthedocs.org/projects/k3dict/badge/?version=stable)](https://k3dict.readthedocs.io/en/stable/?badge=stable) [![Package](https://img.shields.io/pypi/pyversions/k3dict)](https://pypi.org/project/k3dict) diff --git a/__init__.py b/__init__.py index b8777c0..7b8aca5 100644 --- a/__init__.py +++ b/__init__.py @@ -12,8 +12,9 @@ # from .proc import CalledProcessError # from .proc import ProcError -__version__ = "0.1.2" -__name__ = "k3dict" +from importlib.metadata import version + +__version__ = version("k3dict") from .dictutil import ( add, @@ -54,7 +55,6 @@ "make_getter_str", "make_setter", "subdict", - "AttrDict", "AttrDictCopy", "NoSuchKey", diff --git a/_building/.gitignore b/_building/.gitignore deleted file mode 100644 index b6e4761..0000000 --- a/_building/.gitignore +++ /dev/null @@ -1,129 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ diff --git a/_building/Makefile b/_building/Makefile deleted file mode 100644 index e5b5931..0000000 --- a/_building/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -# make readme name -readme: - cd .. && python _building/build_readme.py diff --git a/_building/README.md b/_building/README.md index 50205e1..0ee458e 100644 --- a/_building/README.md +++ b/_building/README.md @@ -1,23 +1,24 @@ -# building -building toolkit for pykit3 repos +# _building -This repo should be included in a package, e.g.: +Shared build configuration for pykit3 packages. -``` -vcs/pykit3/k3handy/ -▸ .git/ -▸ .github/ -▸ __pycache__/ -▾ _building/ <-- this repo - ... -``` +## Commands -# Publish python package: +All commands use the `pk3` package: -- `make build_setup_py` does the following steps: - - Builds the `setup.py` and commit it. - - Add a git tag with the name of `"v" + __init__.__ver__`. +```bash +make test # Run tests with pytest +make lint # Format and lint with ruff +make cov # Generate coverage report +make doc # Build documentation with mkdocs +make readme # Generate README.md from docstrings +make release # Create git tag from version in pyproject.toml +make publish # Build and upload to PyPI +``` -- Then `git push` the tag, github Action in the `.github/workflows/python-pubish.yml` will publish a package to `pypi`. +## Release Process - The action spec is copied from template repo: `github.com/pykit3/tmpl`. +1. Update version in `pyproject.toml` +2. Run `make release` to create git tag +3. Run `git push --tags` to trigger GitHub Actions +4. GitHub Actions automatically publishes to PyPI diff --git a/_building/README.md.j2 b/_building/README.md.j2 deleted file mode 100644 index d9baf2c..0000000 --- a/_building/README.md.j2 +++ /dev/null @@ -1,38 +0,0 @@ -# {{ name }} - -[![Action-CI](https://github.com/pykit3/{{ name }}/actions/workflows/python-package.yml/badge.svg)](https://github.com/pykit3/{{ name }}/actions/workflows/python-package.yml) -[![Build Status](https://travis-ci.com/pykit3/{{ name }}.svg?branch=master)](https://travis-ci.com/pykit3/{{ name }}) -[![Documentation Status](https://readthedocs.org/projects/{{ name }}/badge/?version=stable)](https://{{ name }}.readthedocs.io/en/stable/?badge=stable) -[![Package](https://img.shields.io/pypi/pyversions/{{ name }})](https://pypi.org/project/{{ name }}) - -{{ description }} - -{{ name }} is a component of [pykit3] project: a python3 toolkit set. - -{{ package_doc }} - - -# Install - -``` -pip install {{ name }} -``` - -# Synopsis - -```python -{{ synopsis }} -``` - -# Author - -Zhang Yanpo (张炎泼) - -# Copyright and License - -The MIT License (MIT) - -Copyright (c) 2015 Zhang Yanpo (张炎泼) - - -[pykit3]: https://github.com/pykit3 diff --git a/_building/__init__.py b/_building/__init__.py deleted file mode 100644 index 1055c67..0000000 --- a/_building/__init__.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -import importlib.util -import sys - -# sys.path.insert(0, os.path.abspath('..')) -# sys.path.insert(0, os.path.abspath('../..')) -# sys.path.insert(0, os.path.abspath('../../..')) - -# __title__ = 'requests' -# __description__ = 'Python HTTP for Humans.' -# __url__ = 'https://requests.readthedocs.io' -# __version__ = '2.23.0' - -__author__ = "Zhang Yanpo" -__author_email__ = "drdr.xp@gmail.com" -__license__ = "MIT" -__copyright__ = "Copyright 2020 Zhang Yanpo" - - -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.napoleon", - # "sphinx.ext.intersphinx", - # "sphinx.ext.todo", - # "sphinx.ext.viewcode", -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - - -master_doc = "index" - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "alabaster" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] -html_static_path = [] - - -def load_parent_package(): - """ - Load the parent directory as a package module. - - Returns: - tuple: (package_name, package_module) - """ - import os - - parent_dir = os.path.dirname(os.path.dirname(__file__)) - if parent_dir not in sys.path: - sys.path.insert(0, parent_dir) - - # Read the __init__.py file to get the package name - init_file = os.path.join(parent_dir, "__init__.py") - package_name = None - - with open(init_file, "r") as f: - for line in f: - if line.strip().startswith("__name__"): - # Extract package name from __name__ = "package_name" - package_name = line.split("=")[1].strip().strip("\"'") - break - - if not package_name: - # Fallback: use directory name - package_name = os.path.basename(parent_dir) - - # Load the module with proper package context using importlib - spec = importlib.util.spec_from_file_location(package_name, init_file) - pkg = importlib.util.module_from_spec(spec) - sys.modules[package_name] = pkg # Add to sys.modules so relative imports work - spec.loader.exec_module(pkg) - - return package_name, pkg - - -def sphinx_confs(): - """ - Load repo dir as a package - - `readthedocs` use branch name as dir! - Thus the following does not work:: - - import pk3proc - """ - - print("sys.path:", sys.path) - - package_name, pkg = load_parent_package() - - return ( - pkg.__name__, - pkg, - pkg.__version__, - __author__, - __copyright__, - extensions, - templates_path, - exclude_patterns, - master_doc, - html_theme, - html_static_path, - ) diff --git a/_building/build_readme.py b/_building/build_readme.py deleted file mode 100644 index d211e29..0000000 --- a/_building/build_readme.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -import doctest -import os -import sys - -import jinja2 -import yaml - -from __init__ import load_parent_package - -# xxx/_building/build_readme.py -this_base = os.path.dirname(__file__) - -j2vars = {} - -# let it be able to find indirectly dependent package locally -# e.g.: `k3fs` depends on `k3confloader` -sys.path.insert(0, os.path.abspath("..")) - -# load package name from __init__.py -package_name, pkg = load_parent_package() -j2vars["name"] = package_name - - -def get_gh_config(): - with open(".github/settings.yml", "r") as f: - cont = f.read() - - cfg = yaml.safe_load(cont) - tags = cfg["repository"]["topics"].split(",") - tags = [x.strip() for x in tags] - cfg["repository"]["topics"] = tags - return cfg - - -cfg = get_gh_config() -j2vars["description"] = cfg["repository"]["description"] - - -def get_examples(pkg): - doc = pkg.__doc__ - parser = doctest.DocTestParser() - es = parser.get_examples(doc) - rst = [] - for e in es: - rst.append(">>> " + e.source.strip()) - rst.append(e.want.strip()) - - rst = "\n".join(rst) - - for fn in ( - "synopsis.txt", - "synopsis.py", - ): - try: - with open(fn, "r") as f: - rst += "\n" + f.read() - - except FileNotFoundError: - pass - - return rst - - -j2vars["synopsis"] = get_examples(pkg) -j2vars["package_doc"] = pkg.__doc__ - - -def render_j2(tmpl_path, tmpl_vars, output_path): - template_loader = jinja2.FileSystemLoader(searchpath="./") - template_env = jinja2.Environment( - loader=template_loader, undefined=jinja2.StrictUndefined - ) - template = template_env.get_template(tmpl_path) - - txt = template.render(tmpl_vars) - - with open(output_path, "w") as f: - f.write(txt) - - -if __name__ == "__main__": - render_j2("_building/README.md.j2", j2vars, "README.md") diff --git a/_building/build_setup.py b/_building/build_setup.py deleted file mode 100644 index ca2ec6c..0000000 --- a/_building/build_setup.py +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -""" -build steup.py for this package. -""" - -import ast -import subprocess -import sys -from string import Template - -import requirements -import yaml - -if hasattr(sys, "getfilesystemencoding"): - defenc = sys.getfilesystemencoding() -if defenc is None: - defenc = sys.getdefaultencoding() - - -def parse_assignment(filename, var_name): - """Parse a Python file and extract the value of a variable assignment using AST.""" - with open(filename, "r") as f: - content = f.read() - - tree = ast.parse(content) - - for node in ast.walk(tree): - if isinstance(node, ast.Assign): - # Check if this assignment is to our target variable - for target in node.targets: - if isinstance(target, ast.Name) and target.id == var_name: - # Extract the literal value - if isinstance(node.value, ast.Constant): # Python 3.8+ - return node.value.value - elif isinstance(node.value, ast.Str): # Python < 3.8 - return node.value.s - elif isinstance(node.value, ast.Num): # Python < 3.8 - return node.value.n - - return None - - -def get_name(): - name = parse_assignment("__init__.py", "__name__") - return name if name is not None else "k3git" # fallback - - -name = get_name() - - -def get_ver(): - version = parse_assignment("__init__.py", "__version__") - if version is None: - raise ValueError("Could not find __version__ in __init__.py") - return version - - -def get_gh_config(): - with open(".github/settings.yml", "r") as f: - cont = f.read() - - cfg = yaml.safe_load(cont) - tags = cfg["repository"]["topics"].split(",") - tags = [x.strip() for x in tags] - cfg["repository"]["topics"] = tags - return cfg - - -def get_travis(): - try: - with open(".travis.yml", "r") as f: - cont = f.read() - except OSError: - return None - - cfg = yaml.safe_load(cont) - return cfg - - -def get_compatible(): - # https://pypi.org/classifiers/ - - rst = [] - t = get_travis() - if t is None: - return ["Programming Language :: Python :: 3"] - - for v in t["python"]: - if v.startswith("pypy"): - v = "Implementation :: PyPy" - rst.append("Programming Language :: Python :: {}".format(v)) - - return rst - - -def get_req(): - try: - with open("requirements.txt", "r") as f: - req = list(requirements.parse(f)) - except OSError: - req = [] - - # req.name, req.specs, req.extras - # Django [('>=', '1.11'), ('<', '1.12')] - # six [('==', '1.10.0')] - req = [x.name + ",".join([a + b for a, b in x.specs]) for x in req] - - return req - - -cfg = get_gh_config() - -ver = get_ver() -description = cfg["repository"]["description"] -long_description = open("README.md").read() -req = get_req() -prog = get_compatible() - - -tmpl = """# DO NOT EDIT!!! built with `python _building/build_setup.py` -import setuptools -setuptools.setup( - name="${name}", - packages=["${name}"], - version="$ver", - license='MIT', - description=$description, - long_description=$long_description, - long_description_content_type="text/markdown", - author='Zhang Yanpo', - author_email='drdr.xp@gmail.com', - url='https://github.com/pykit3/$name', - keywords=$topics, - python_requires='>=3.0', - - install_requires=$req, - classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries', - ] + $prog, -) -""" - -s = Template(tmpl) -rst = s.substitute( - name=name, - ver=ver, - description=repr(description), - long_description=repr(long_description), - topics=repr(cfg["repository"]["topics"]), - req=repr(req), - prog=repr(prog), -) -with open("setup.py", "w") as f: - f.write(rst) - - -sb = subprocess.Popen( - ["git", "add", "setup.py"], - encoding=defenc, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, -) -out, err = sb.communicate() -if sb.returncode != 0: - raise Exception("failure to add: ", out, err) - -sb = subprocess.Popen( - ["git", "commit", "setup.py", "-m", "release: v" + ver], - encoding=defenc, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, -) -out, err = sb.communicate() -if sb.returncode != 0: - raise Exception("failure to commit new release: " + ver, out, err) - - -sb = subprocess.Popen( - ["git", "tag", "v" + ver], - encoding=defenc, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, -) -out, err = sb.communicate() -if sb.returncode != 0: - raise Exception("failure to add tag: " + ver, out, err) diff --git a/_building/building-requirements.txt b/_building/building-requirements.txt deleted file mode 100644 index bd303df..0000000 --- a/_building/building-requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -# requirements for building doc, setup.py etc - -semantic_version>=2.9,<3.0 -jinja2>=3.0,<4.0 -PyYAML>=6.0,<7.0 - -sphinx>=4.3,<6.0 diff --git a/_building/common.mk b/_building/common.mk index dbac102..571715a 100644 --- a/_building/common.mk +++ b/_building/common.mk @@ -1,14 +1,19 @@ all: test lint readme doc -.PHONY: test lint +.PHONY: test lint cov sudo_test: - sudo env "PATH=$$PATH" UT_DEBUG=0 PYTHONPATH="$$(cd ..; pwd)" python -m unittest discover -c --failfast -s . + sudo env "PATH=$$PATH" UT_DEBUG=0 pytest -v test: - env "PATH=$$PATH" UT_DEBUG=0 PYTHONPATH="$$(cd ..; pwd)" python -m unittest discover -c --failfast -s . + env UT_DEBUG=0 pytest -v + +cov: + coverage run --source=. -m pytest + coverage html + open htmlcov/index.html doc: - make -C docs html + mkdocs build lint: # ruff format: fast Python code formatter (Black-compatible) @@ -21,13 +26,13 @@ static_check: uvx mypy . --ignore-missing-imports readme: - python _building/build_readme.py + pk3 readme -build_setup_py: - PYTHONPATH="$$(cd ..; pwd)" python _building/build_setup.py +release: + pk3 tag publish: - ./_building/publish.sh + pk3 publish install: - ./_building/install.sh + pip install -e . diff --git a/_building/install.sh b/_building/install.sh deleted file mode 100755 index e82727c..0000000 --- a/_building/install.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - - -pwd="$(pwd)" -name="${pwd##*/}" -pip uninstall -y $name - -cp setup.py .. -( -cd .. -python setup.py install -) diff --git a/_building/populate.py b/_building/populate.py index 77deb12..d0a8731 100755 --- a/_building/populate.py +++ b/_building/populate.py @@ -15,22 +15,22 @@ def pjoin(*args): def cp(fn): - - cur = os.path.abspath('.') + cur = os.path.abspath(".") name = os.path.split(cur)[1] - t = '_building/tmpl/' - relfn = fn[len(t):] + t = "_building/tmpl/" + relfn = fn[len(t) :] base = os.path.split(fn)[0] if not os.path.exists(base): os.makedirs(base) src = fn - dst = re.sub(r'xxnamexx', name, relfn) + dst = re.sub(r"xxnamexx", name, relfn) - vs = {'name': name, - 'nameBig': name[0].upper() + name[1:], - } + vs = { + "name": name, + "nameBig": name[0].upper() + name[1:], + } print("populate ", src, " to ", dst) render_j2(src, vs, dst) @@ -39,14 +39,13 @@ def cp(fn): def render_j2(tmpl_path, tmpl_vars, output_path): - template_loader = jinja2.FileSystemLoader(searchpath='./') - template_env = jinja2.Environment(loader=template_loader, - undefined=jinja2.StrictUndefined) + template_loader = jinja2.FileSystemLoader(searchpath="./") + template_env = jinja2.Environment(loader=template_loader, undefined=jinja2.StrictUndefined) template = template_env.get_template(tmpl_path) txt = template.render(tmpl_vars) - with open(output_path, 'w') as f: + with open(output_path, "w") as f: f.write(txt) diff --git a/_building/requirements.txt b/_building/requirements.txt deleted file mode 100644 index 49fe098..0000000 --- a/_building/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -setuptools diff --git a/dictutil.py b/dictutil.py index 6734d6e..048b2ae 100644 --- a/dictutil.py +++ b/dictutil.py @@ -5,9 +5,7 @@ import operator -def depth_iter(mydict, ks=None, maxdepth=10240, - intermediate=False, empty_leaf=False, - is_allowed=None): +def depth_iter(mydict, ks=None, maxdepth=10240, intermediate=False, empty_leaf=False, is_allowed=None): """ mydict: the dict that you want to iterate on. @@ -41,7 +39,6 @@ def depth_iter(mydict, ks=None, maxdepth=10240, dickeys = sorted(mydict.keys()) for k in dickeys: - v = mydict[k] ks.append(k) @@ -51,7 +48,6 @@ def depth_iter(mydict, ks=None, maxdepth=10240, yield ks, v else: if isinstance(v, dict): - if is_allowed is not None: if is_allowed(ks, v): yield ks, v @@ -59,12 +55,14 @@ def depth_iter(mydict, ks=None, maxdepth=10240, if intermediate or (empty_leaf and len(v) == 0): yield ks, v - for _ks, v in depth_iter(v, ks, - maxdepth=maxdepth, - intermediate=intermediate, - empty_leaf=empty_leaf, - is_allowed=is_allowed, - ): + for _ks, v in depth_iter( + v, + ks, + maxdepth=maxdepth, + intermediate=intermediate, + empty_leaf=empty_leaf, + is_allowed=is_allowed, + ): yield _ks, v else: if is_allowed is None or is_allowed(ks, v): @@ -112,16 +110,15 @@ def get(dic, key_path, vars=None, default=0, ignore_vars_key_error=None): if ignore_vars_key_error is None: ignore_vars_key_error = True - _default = vars.get('_default', default) + _default = vars.get("_default", default) node = dic _keys = key_path if isinstance(key_path, str): - _keys = key_path.split('.') + _keys = key_path.split(".") for k in _keys: - try: key = _translate_var(k, vars) except KeyError: @@ -139,36 +136,33 @@ def get(dic, key_path, vars=None, default=0, ignore_vars_key_error=None): def make_getter_str(key_path, default=0): - s = 'lambda dic, vars={}: dic' + s = "lambda dic, vars={}: dic" _keys = key_path if isinstance(key_path, str): - _keys = key_path.split('.') + _keys = key_path.split(".") for k in _keys: - k_str = _translate_var_str(k) - s += '.get(%s, {})' % (k_str, ) + s += ".get(%s, {})" % (k_str,) - s = s[:-3] + 'vars.get("_default", ' + repr(default) + '))' + s = s[:-3] + 'vars.get("_default", ' + repr(default) + "))" return s def _translate_var(k, vars): if isinstance(k, str): - if k.startswith('$'): + if k.startswith("$"): k = k[1:] if k in vars: return str(vars[k]) else: - raise KeyError('{k} does not exist in vars: {vars}'.format( - k=k, vars=vars)) + raise KeyError("{k} does not exist in vars: {vars}".format(k=k, vars=vars)) else: return k elif isinstance(k, tuple): - return tuple(_translate_var(kk, vars) for kk in k) else: @@ -177,7 +171,7 @@ def _translate_var(k, vars): def _translate_var_str(k): if isinstance(k, str): - if k.startswith('$'): + if k.startswith("$"): return 'str(vars.get("%s", "_"))' % (k[1:],) else: return '"' + k + '"' @@ -185,7 +179,7 @@ def _translate_var_str(k): elif isinstance(k, tuple): s = "(" for kk in k: - s += _translate_var_str(kk) + ',' + s += _translate_var_str(kk) + "," return s + ")" else: return repr(k) @@ -219,17 +213,15 @@ def make_setter(key_path, value=None, incr=False): """ _keys = key_path if isinstance(key_path, str): - _keys = key_path.split('.') + _keys = key_path.split(".") def_val = value def _set_dict(dic, value=None, vars={}): - - k = 'self' - _node = {'self': dic} + k = "self" + _node = {"self": dic} for _k in _keys: - if k not in _node: _node[k] = {} _node = _node[k] @@ -266,8 +258,7 @@ class NoSuchKey(object): def _contains(a, b, has_compared): - - if type(a) != type(b): + if type(a) is not type(b): return False if not isinstance(a, _iter_types): @@ -304,7 +295,6 @@ def contains(a, b): class AttrDict(dict): - """ a = AttrDict({1:2}) # {1:2} a = AttrDict(x=3) # {"x":3} @@ -341,25 +331,22 @@ def __init__(self, *args, **kwargs): class AttrDictCopy(dict): - # Allow to set attribute or key. # But when get attribute or key, the value is copied before returning. # To prevent changing original data. def __getattr__(self, k): - if k not in self: - raise AttributeError(repr(k) + ' not found') + raise AttributeError(repr(k) + " not found") return self[k] def __setattr__(self, k, v): - raise AttributeError('AttrDictCopy does not allow to set attribute') + raise AttributeError("AttrDictCopy does not allow to set attribute") def __getitem__(self, k): - if k not in self: - raise KeyError(repr(k) + ' not found') + raise KeyError(repr(k) + " not found") v = super(AttrDictCopy, self).__getitem__(k) if isinstance(v, AttrDictCopy): @@ -371,7 +358,7 @@ def __getitem__(self, k): return copy.deepcopy(v) def __setitem__(self, k, v): - raise KeyError('AttrDictCopy does not allow to set key') + raise KeyError("AttrDictCopy does not allow to set key") def as_dict(self): d = {} @@ -407,7 +394,6 @@ def attrdict_copy(*args, **kwargs): def _attrdict(attrdict_clz, d, ref): - if not isinstance(d, dict): return d @@ -509,7 +495,6 @@ def subdict(source, flds, use_default=False, default=None, deepcopy=False, deepc result = {} for k in flds: - if k in source: val = source[k] result[k] = _copy_value(val, deepcopy) @@ -528,7 +513,6 @@ def subdict(source, flds, use_default=False, default=None, deepcopy=False, deepc def _copy_value(val, deepcopy=False): - if deepcopy: return copy.deepcopy(val) diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index a4de0bf..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -W -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..be7851b --- /dev/null +++ b/docs/index.md @@ -0,0 +1,49 @@ +# k3dict + +[![Action-CI](https://github.com/pykit3/k3dict/actions/workflows/python-package.yml/badge.svg)](https://github.com/pykit3/k3dict/actions/workflows/python-package.yml) +[![Documentation Status](https://readthedocs.org/projects/k3dict/badge/?version=stable)](https://k3dict.readthedocs.io/en/stable/?badge=stable) +[![Package](https://img.shields.io/pypi/pyversions/k3dict)](https://pypi.org/project/k3dict) + +Dict operation utilities including iteration, getter/setter makers, and attribute access. + +k3dict is a component of [pykit3](https://github.com/pykit3) project: a python3 toolkit set. + +## Installation + +```bash +pip install k3dict +``` + +## Quick Start + +```python +import k3dict + +mydict = {'a': + {'a.a': 'v-a.a', + 'a.b': {'a.b.a': 'v-a.b.a'}, + 'a.c': {'a.c.a': {'a.c.a.a': 'v-a.c.a.a'}} + } + } + +# depth-first iterate the dict +for rst in k3dict.depth_iter(mydict): + print(rst) + +# output: +# (['a', 'a.c', 'a.c.a', 'a.c.a.a'], 'v-a.c.a.a') +# (['a', 'a.b', 'a.b.a'], 'v-a.b.a') +# (['a', 'a.a'], 'v-a.a') + +# breadth-first iterate the dict +for rst in k3dict.breadth_iter(mydict): + print(rst) +``` + +## API Reference + +::: k3dict + +## License + +The MIT License (MIT) - Copyright (c) 2015 Zhang Yanpo (张炎泼) diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 6247f7e..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index e436b3e..0000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,28 +0,0 @@ -import os -import sys - -sys.path.insert(0, os.path.abspath("../..")) - -# In order to find indirect dependency -sys.path.insert(0, os.path.abspath("../../..")) - -# use a try to force not to reorder sys.path and import. -try: - import _building -except Exception as e: - raise e - - -( - project, - pkg, - release, - author, - copyright, - extensions, - templates_path, - exclude_patterns, - master_doc, - html_theme, - html_static_path, -) = _building.sphinx_confs() diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index b05c63b..0000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,63 +0,0 @@ -.. k3dict documentation master file, created by - sphinx-quickstart on Thu May 14 16:58:55 2020. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -k3dict -============ - -.. automodule:: k3dict - -Documentation for the Code -************************** - -Exceptions ----------- - -.. autoexception:: NoSuchKey - - -Classes ----------- - -.. autoclass:: AttrDict - -.. autoclass:: AttrDictCopy - - -Functions ---------- - -.. autofunction:: add - -.. autofunction:: attrdict - -.. autofunction:: addto - -.. autofunction:: attrdict_copy - -.. autofunction:: breadth_iter - -.. autofunction:: combine - -.. autofunction:: combineto - -.. autofunction:: contains - -.. autofunction:: depth_iter - -.. autofunction:: get - -.. autofunction:: make_getter - -.. autofunction:: make_getter_str - -.. autofunction:: subdict - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` \ No newline at end of file diff --git a/fixed_keys_dict.py b/fixed_keys_dict.py index a74af8b..9dfd9ee 100644 --- a/fixed_keys_dict.py +++ b/fixed_keys_dict.py @@ -10,6 +10,7 @@ class FixedKeysDict(dict): :param argkv: as builtin **dict**. :return: An instance of dictutil.FixedKeysDict. """ + # {'key', value_constructor} keys_default = {} @@ -29,14 +30,12 @@ def __init__(self, *args, **argkv): self[k] = self.keys_default[k]() def __setitem__(self, key, value): - try: value = self.keys_default[key](value) except KeyError: - raise KeyError('key: {key} is invalid'.format(key=key)) + raise KeyError("key: {key} is invalid".format(key=key)) except (ValueError, TypeError) as e: - raise ValueError('value: {value} is invalid. {e}'.format( - value=repr(value), e=repr(e))) + raise ValueError("value: {value} is invalid. {e}".format(value=repr(value), e=repr(e))) super(FixedKeysDict, self).__setitem__(key, value) diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..2861f1b --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,30 @@ +site_name: k3dict +site_description: Dict operation utilities including iteration, getter/setter makers, and attribute access +site_url: https://k3dict.readthedocs.io +repo_url: https://github.com/pykit3/k3dict +repo_name: pykit3/k3dict + +theme: + name: material + palette: + primary: blue + accent: blue + +plugins: + - search + - mkdocstrings: + handlers: + python: + paths: [.] + options: + show_source: true + show_root_heading: true + heading_level: 2 + +nav: + - Home: index.md + +markdown_extensions: + - admonition + - pymdownx.highlight + - pymdownx.superfences diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..094224f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,57 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "k3dict" +version = "0.1.9" +description = "Dict operation utilities including iteration, getter/setter makers, and attribute access" +readme = "README.md" +license = {text = "MIT"} +requires-python = ">=3.9" +authors = [ + { name = "Zhang Yanpo", email = "drdr.xp@gmail.com" } +] +keywords = ["dict", "dictionary", "iteration", "attrdict", "getter", "setter"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +dependencies = [] + +[project.urls] +Homepage = "https://github.com/pykit3/k3dict" +Documentation = "https://k3dict.readthedocs.io" + +[project.optional-dependencies] +dev = [ + "pytest>=7.0", + "ruff", + "coverage", +] +publish = [ + "build", + "twine", + "pk3", +] +docs = [ + "mkdocs>=1.5", + "mkdocs-material>=9.0", + "mkdocstrings[python]>=0.24", +] + +[tool.setuptools] +packages = ["k3dict"] + +[tool.setuptools.package-dir] +k3dict = "." + +[tool.ruff] +line-length = 120 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 7517f06..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ --r _building/requirements.txt - - -k3ut>=0.1.15,<0.2 \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 93667e0..0000000 --- a/setup.py +++ /dev/null @@ -1,23 +0,0 @@ -# DO NOT EDIT!!! built with `python _building/build_setup.py` -import setuptools -setuptools.setup( - name="k3dict", - packages=["k3dict"], - version="0.1.2", - license='MIT', - description='It provides with several dict operation functions.', - long_description='# k3dict\n\n[![Action-CI](https://github.com/pykit3/k3dict/actions/workflows/python-package.yml/badge.svg)](https://github.com/pykit3/k3dict/actions/workflows/python-package.yml)\n[![Build Status](https://travis-ci.com/pykit3/k3dict.svg?branch=master)](https://travis-ci.com/pykit3/k3dict)\n[![Documentation Status](https://readthedocs.org/projects/k3dict/badge/?version=stable)](https://k3dict.readthedocs.io/en/stable/?badge=stable)\n[![Package](https://img.shields.io/pypi/pyversions/k3dict)](https://pypi.org/project/k3dict)\n\nIt provides with several dict operation functions.\n\nk3dict is a component of [pykit3] project: a python3 toolkit set.\n\n\nk3dict\n\nIt provides with several dict operation functions.\n\n# Status\n\nThis library is considered production ready.\n\n\n\n\n# Install\n\n```\npip install k3dict\n```\n\n# Synopsis\n\n```python\n\nimport k3dict\n\nmydict = {\'a\':\n {\'a.a\': \'v-a.a\',\n \'a.b\': {\'a.b.a\': \'v-a.b.a\'},\n \'a.c\': {\'a.c.a\': {\'a.c.a.a\': \'v-a.c.a.a\'}}\n }\n }\n\n# depth-first iterative the dict\nfor rst in k3dict.depth_iter(mydict):\n print(rst)\n\n# output:\n# ([\'a\', \'a.c\', \'a.c.a\', \'a.c.a.a\'], \'v-a.c.a.a\')\n# ([\'a\', \'a.b\', \'a.b.a\'], \'v-a.b.a\')\n# ([\'a\', \'a.a\'], \'v-a.a\')\n\nfor rst in k3dict.breadth_iter(mydict):\n print(rst)\n\n# output:\n# ([\'a\'], {\'a.c\': {\'a.c.a\': {\'a.c.a.a\': \'v-a.c.a.a\'}}, \'a.b\': {\'a.b.a\': \'v-a.b.a\'}\n# , \'a.a\': \'v-a.a\'})\n# ([\'a\', \'a.a\'], \'v-a.a\')\n# ([\'a\', \'a.b\'], {\'a.b.a\': \'v-a.b.a\'})\n# ([\'a\', \'a.b\', \'a.b.a\'], \'v-a.b.a\')\n# ([\'a\', \'a.c\'], {\'a.c.a\': {\'a.c.a.a\': \'v-a.c.a.a\'}})\n# ([\'a\', \'a.c\', \'a.c.a\'], {\'a.c.a.a\': \'v-a.c.a.a\'})\n# ([\'a\', \'a.c\', \'a.c.a\', \'a.c.a.a\'], \'v-a.c.a.a\')\n#\n\nrecords = [\n {"event": \'log in\',\n "time": {"hour": 10, "minute": 30, }, },\n\n {"event": \'post a blog\',\n "time": {"hour": 10, "minute": 40, }, },\n\n {"time": {"hour": 11, "minute": 20, }, },\n\n {"event": \'log out\',\n "time": {"hour": 11, "minute": 20, }, },\n]\n\nget_event = k3dict.make_getter(\'event\', default="NOTHING DONE")\nget_time = k3dict.make_getter(\'time.$field\')\n\nfor record in records:\n\n ev = get_event(record)\n\n tm = "%d:%d" % (get_time(record, {"field": "hour"}),\n get_time(record, {"field": "minute"}))\n\n print("{ev:<12} at {tm}".format(ev=ev, tm=tm))\n\n# output:\n# log in at 10:30\n# post a blog at 10:40\n# NOTHING DONE at 11:20\n# log out at 11:20\n\n```\n\n# Author\n\nZhang Yanpo (张炎泼) \n\n# Copyright and License\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 Zhang Yanpo (张炎泼) \n\n\n[pykit3]: https://github.com/pykit3', - long_description_content_type="text/markdown", - author='Zhang Yanpo', - author_email='drdr.xp@gmail.com', - url='https://github.com/pykit3/k3dict', - keywords=['python', 'dictionary', 'util'], - python_requires='>=3.0', - - install_requires=['k3ut>=0.1.15,<0.2'], - classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries', - ] + ['Programming Language :: Python :: 3'], -) diff --git a/synopsis.py b/synopsis.py index f4c79e0..64eb949 100644 --- a/synopsis.py +++ b/synopsis.py @@ -1,11 +1,6 @@ import k3dict -mydict = {'a': - {'a.a': 'v-a.a', - 'a.b': {'a.b.a': 'v-a.b.a'}, - 'a.c': {'a.c.a': {'a.c.a.a': 'v-a.c.a.a'}} - } - } +mydict = {"a": {"a.a": "v-a.a", "a.b": {"a.b.a": "v-a.b.a"}, "a.c": {"a.c.a": {"a.c.a.a": "v-a.c.a.a"}}}} # depth-first iterative the dict for rst in k3dict.depth_iter(mydict): @@ -31,27 +26,42 @@ # records = [ - {"event": 'log in', - "time": {"hour": 10, "minute": 30, }, }, - - {"event": 'post a blog', - "time": {"hour": 10, "minute": 40, }, }, - - {"time": {"hour": 11, "minute": 20, }, }, - - {"event": 'log out', - "time": {"hour": 11, "minute": 20, }, }, + { + "event": "log in", + "time": { + "hour": 10, + "minute": 30, + }, + }, + { + "event": "post a blog", + "time": { + "hour": 10, + "minute": 40, + }, + }, + { + "time": { + "hour": 11, + "minute": 20, + }, + }, + { + "event": "log out", + "time": { + "hour": 11, + "minute": 20, + }, + }, ] -get_event = k3dict.make_getter('event', default="NOTHING DONE") -get_time = k3dict.make_getter('time.$field') +get_event = k3dict.make_getter("event", default="NOTHING DONE") +get_time = k3dict.make_getter("time.$field") for record in records: - ev = get_event(record) - tm = "%d:%d" % (get_time(record, {"field": "hour"}), - get_time(record, {"field": "minute"})) + tm = "%d:%d" % (get_time(record, {"field": "hour"}), get_time(record, {"field": "minute"})) print("{ev:<12} at {tm}".format(ev=ev, tm=tm)) diff --git a/test/test_k3dict.py b/test/test_k3dict.py index 3c678e9..4cc6ef6 100644 --- a/test/test_k3dict.py +++ b/test/test_k3dict.py @@ -12,154 +12,137 @@ class TestDictDeepIter(unittest.TestCase): - def test_depth_iter_default(self): - cases = ( ({}, []), - ({'k1': 'v1'}, - [(['k1'], 'v1')] - ), - ({'k1': 'v1', 'k2': 'v2'}, - [ - (['k1'], 'v1'), - (['k2'], 'v2'), - ]), - ({'k1': {'k11': 'v11'}, - 'k2': 'v2', - 'empty': {}, - }, - ([ - (['k1', 'k11'], 'v11'), - (['k2'], 'v2'), - ]), - ) + ({"k1": "v1"}, [(["k1"], "v1")]), + ( + {"k1": "v1", "k2": "v2"}, + [ + (["k1"], "v1"), + (["k2"], "v2"), + ], + ), + ( + { + "k1": {"k11": "v11"}, + "k2": "v2", + "empty": {}, + }, + ( + [ + (["k1", "k11"], "v11"), + (["k2"], "v2"), + ] + ), + ), ) for _in, _out in cases: - idx = 0 for rst in k3dict.depth_iter(_in): self.assertEqual( _out[idx], rst, - ('input: {_in}, output: {rst}, expected: {_out}').format( - _in=repr(_in), - _out=repr(rst), - rst=repr(_out[idx]) - ) + ("input: {_in}, output: {rst}, expected: {_out}").format( + _in=repr(_in), _out=repr(rst), rst=repr(_out[idx]) + ), ) idx = idx + 1 def test_depth_iter_ks(self): - idx = 0 - ks = ['mykey'] + ks = ["mykey"] - _in = {'k1': {'k11': 'v11'}, 'k2': 'v2'} + _in = {"k1": {"k11": "v11"}, "k2": "v2"} _out = [ - (['mykey', 'k1', 'k11'], 'v11'), - (['mykey', 'k2'], 'v2'), + (["mykey", "k1", "k11"], "v11"), + (["mykey", "k2"], "v2"), ] - _mes = 'test argument ks in k3dict.dict_depth_iter()' + _mes = "test argument ks in k3dict.dict_depth_iter()" for rst in k3dict.depth_iter(_in, ks=ks): self.assertEqual( _out[idx], rst, - ('input: {_in}, output: {rst}, expected: {_out},' - 'message: {_mes}').format( - _in=repr(_in), - _out=repr(rst), - rst=repr(_out[idx]), - _mes=_mes - ) + ("input: {_in}, output: {rst}, expected: {_out},message: {_mes}").format( + _in=repr(_in), _out=repr(rst), rst=repr(_out[idx]), _mes=_mes + ), ) idx = idx + 1 def test_depth_iter_maxdepth(self): - idx = 0 _in = { - 'k1': { - 'k11': { - 'k111': { - 'k1111': 'v1111' - }, - 'empty': {}, + "k1": { + "k11": { + "k111": {"k1111": "v1111"}, + "empty": {}, }, }, } _out = [ - (['k1'], {'k11': {'k111': {'k1111': 'v1111'}, 'empty': {}}}), - (['k1', 'k11'], {'k111': {'k1111': 'v1111'}, 'empty': {}}), - (['k1', 'k11', 'empty'], {}), - (['k1', 'k11', 'k111'], {'k1111': 'v1111'}), - (['k1', 'k11', 'k111', 'k1111'], 'v1111') + (["k1"], {"k11": {"k111": {"k1111": "v1111"}, "empty": {}}}), + (["k1", "k11"], {"k111": {"k1111": "v1111"}, "empty": {}}), + (["k1", "k11", "empty"], {}), + (["k1", "k11", "k111"], {"k1111": "v1111"}), + (["k1", "k11", "k111", "k1111"], "v1111"), ] for depth in range(1, 5): for rst in k3dict.depth_iter(_in, maxdepth=depth): - dd('depth:', depth) - dd('_in:', _in) - dd('_out[idx]:', _out[idx]) - dd('rst:', rst) + dd("depth:", depth) + dd("_in:", _in) + dd("_out[idx]:", _out[idx]) + dd("rst:", rst) self.assertEqual(_out[idx], rst) idx = idx + 1 def test_depth_iter_intermediate(self): - _in = { - 'k1': { - 'k11': { - 'k111': { - 'k1111': 'v1111' - }, - 'empty': {}, + "k1": { + "k11": { + "k111": {"k1111": "v1111"}, + "empty": {}, }, }, } _out = [ - (['k1'], {'k11': {'k111': {'k1111': 'v1111'}, 'empty': {}}}), - (['k1', 'k11'], {'k111': {'k1111': 'v1111'}, 'empty': {}}), - (['k1', 'k11', 'empty'], {}), - (['k1', 'k11', 'k111'], {'k1111': 'v1111'}), - (['k1', 'k11', 'k111', 'k1111'], 'v1111') + (["k1"], {"k11": {"k111": {"k1111": "v1111"}, "empty": {}}}), + (["k1", "k11"], {"k111": {"k1111": "v1111"}, "empty": {}}), + (["k1", "k11", "empty"], {}), + (["k1", "k11", "k111"], {"k1111": "v1111"}), + (["k1", "k11", "k111", "k1111"], "v1111"), ] idx = 0 for rst in k3dict.depth_iter(_in, intermediate=True): - self.assertEqual( - _out[idx], - rst) + self.assertEqual(_out[idx], rst) idx = idx + 1 self.assertEqual(len(_out), idx) def test_depth_iter_empty_as_leaf(self): - _in = { - 'k1': { - 'k11': { - 'k111': { - 'k1111': 'v1111' - }, - 'empty': {}, + "k1": { + "k11": { + "k111": {"k1111": "v1111"}, + "empty": {}, }, }, } _out = [ - (['k1', 'k11', 'empty'], {}), - (['k1', 'k11', 'k111', 'k1111'], 'v1111'), + (["k1", "k11", "empty"], {}), + (["k1", "k11", "k111", "k1111"], "v1111"), ] idx = 0 @@ -170,24 +153,21 @@ def test_depth_iter_empty_as_leaf(self): self.assertEqual(len(_out), idx) def test_depth_iter_empty_as_leaf_and_intermediate(self): - _in = { - 'k1': { - 'k11': { - 'k111': { - 'k1111': 'v1111' - }, - 'empty': {}, + "k1": { + "k11": { + "k111": {"k1111": "v1111"}, + "empty": {}, }, }, } _out = [ - (['k1'], {'k11': {'k111': {'k1111': 'v1111'}, 'empty': {}}}), - (['k1', 'k11'], {'k111': {'k1111': 'v1111'}, 'empty': {}}), - (['k1', 'k11', 'empty'], {}), - (['k1', 'k11', 'k111'], {'k1111': 'v1111'}), - (['k1', 'k11', 'k111', 'k1111'], 'v1111') + (["k1"], {"k11": {"k111": {"k1111": "v1111"}, "empty": {}}}), + (["k1", "k11"], {"k111": {"k1111": "v1111"}, "empty": {}}), + (["k1", "k11", "empty"], {}), + (["k1", "k11", "k111"], {"k1111": "v1111"}), + (["k1", "k11", "k111", "k1111"], "v1111"), ] idx = 0 @@ -198,52 +178,56 @@ def test_depth_iter_empty_as_leaf_and_intermediate(self): self.assertEqual(len(_out), idx) def test_depth_iter_is_allowed(self): - _in = { - 'k1': { - 'k11': { - 'k111': { - 'k1111': 'v1111' - }, - 'empty': {}, + "k1": { + "k11": { + "k111": {"k1111": "v1111"}, + "empty": {}, }, }, } cases = ( - ({'is_allowed': lambda ks, v: isinstance(v, dict) and len(v) == 0, - }, - ['k1', 'k11', 'empty'], {}, - 'choose only empty dict value', - ), - - ({'is_allowed': lambda ks, v: isinstance(v, str), - }, - ['k1', 'k11', 'k111', 'k1111'], 'v1111', - 'choose only string value', - ), - - ({'is_allowed': lambda ks, v: isinstance(v, str), - 'intermediate': True, - }, - ['k1', 'k11', 'k111', 'k1111'], 'v1111', - 'is_allowed should override intermediate', - ), - - ({'is_allowed': lambda ks, v: isinstance(v, str), - 'empty_leaf': True, - }, - ['k1', 'k11', 'k111', 'k1111'], 'v1111', - 'is_allowed should override empty_leaf', - ), - + ( + { + "is_allowed": lambda ks, v: isinstance(v, dict) and len(v) == 0, + }, + ["k1", "k11", "empty"], + {}, + "choose only empty dict value", + ), + ( + { + "is_allowed": lambda ks, v: isinstance(v, str), + }, + ["k1", "k11", "k111", "k1111"], + "v1111", + "choose only string value", + ), + ( + { + "is_allowed": lambda ks, v: isinstance(v, str), + "intermediate": True, + }, + ["k1", "k11", "k111", "k1111"], + "v1111", + "is_allowed should override intermediate", + ), + ( + { + "is_allowed": lambda ks, v: isinstance(v, str), + "empty_leaf": True, + }, + ["k1", "k11", "k111", "k1111"], + "v1111", + "is_allowed should override empty_leaf", + ), ) for kwargs, expected_ks, expected_v, msg in cases: - dd(msg) dd(kwargs) - dd('expected:', expected_ks, expected_v) + dd("expected:", expected_ks, expected_v) for ks, v in k3dict.depth_iter(_in, **kwargs): self.assertEqual(expected_ks, ks) @@ -251,143 +235,145 @@ def test_depth_iter_is_allowed(self): class TestDictBreadthIter(unittest.TestCase): - def test_breadth_iter_default(self): - cases = ( ({}, []), - ({'k1': 'v1'}, [(['k1'], 'v1')]), - ({'k1': 'v1', 'k2': 'v2'}, [(['k1'], 'v1'), (['k2'], 'v2')]), - ({'k1': {'k11': 'v11'}, 'k2': 'v2'}, - ([(['k1'], {'k11': 'v11'}), - (['k2'], 'v2'), - (['k1', 'k11'], 'v11') - ]) - ) + ({"k1": "v1"}, [(["k1"], "v1")]), + ({"k1": "v1", "k2": "v2"}, [(["k1"], "v1"), (["k2"], "v2")]), + ({"k1": {"k11": "v11"}, "k2": "v2"}, ([(["k1"], {"k11": "v11"}), (["k2"], "v2"), (["k1", "k11"], "v11")])), ) for _in, _out in cases: - idx = 0 for rst in k3dict.breadth_iter(_in): self.assertEqual( _out[idx], rst, - ('input: {_in}, output: {rst}, expected: {_out}').format( + ("input: {_in}, output: {rst}, expected: {_out}").format( _in=repr(_in), _out=repr(rst), rst=repr(_out[idx]), - ) + ), ) idx = idx + 1 class TestGetter(unittest.TestCase): - def test_getter_str(self): - cases = ( - ('', - [''], - ('',), - 'lambda dic, vars={}: dic.get("", vars.get("_default", 0))' - ), - - ('.', - ['', ''], - ('', '',), - 'lambda dic, vars={}: dic.get("", {}).get("", vars.get("_default", 0))' - ), - - ('...', - ['', '', '', ''], - ('', '', '', ''), - 'lambda dic, vars={}: dic.get("", {}).get("", {}).get("", {}).get("", vars.get("_default", 0))' - ), - - ('.x', - ['', 'x'], - ('', 'x'), - 'lambda dic, vars={}: dic.get("", {}).get("x", vars.get("_default", 0))' - ), - - ('x..', - ['x', '', ''], - ('x', '', ''), - 'lambda dic, vars={}: dic.get("x", {}).get("", {}).get("", vars.get("_default", 0))' - ), - - ('x', - ['x'], - ('x',), - 'lambda dic, vars={}: dic.get("x", vars.get("_default", 0))' - ), - - ('x.y', - ['x', 'y'], - ('x', 'y',), - 'lambda dic, vars={}: dic.get("x", {}).get("y", vars.get("_default", 0))' - ), - - ('x.y.zz', - ['x', 'y', 'zz'], - ('x', 'y', 'zz',), - 'lambda dic, vars={}: dic.get("x", {}).get("y", {}).get("zz", vars.get("_default", 0))' - ), - + ("", [""], ("",), 'lambda dic, vars={}: dic.get("", vars.get("_default", 0))'), + ( + ".", + ["", ""], + ( + "", + "", + ), + 'lambda dic, vars={}: dic.get("", {}).get("", vars.get("_default", 0))', + ), + ( + "...", + ["", "", "", ""], + ("", "", "", ""), + 'lambda dic, vars={}: dic.get("", {}).get("", {}).get("", {}).get("", vars.get("_default", 0))', + ), + (".x", ["", "x"], ("", "x"), 'lambda dic, vars={}: dic.get("", {}).get("x", vars.get("_default", 0))'), + ( + "x..", + ["x", "", ""], + ("x", "", ""), + 'lambda dic, vars={}: dic.get("x", {}).get("", {}).get("", vars.get("_default", 0))', + ), + ("x", ["x"], ("x",), 'lambda dic, vars={}: dic.get("x", vars.get("_default", 0))'), + ( + "x.y", + ["x", "y"], + ( + "x", + "y", + ), + 'lambda dic, vars={}: dic.get("x", {}).get("y", vars.get("_default", 0))', + ), + ( + "x.y.zz", + ["x", "y", "zz"], + ( + "x", + "y", + "zz", + ), + 'lambda dic, vars={}: dic.get("x", {}).get("y", {}).get("zz", vars.get("_default", 0))', + ), # dynamic - ('$', - ['$'], - ('$',), - 'lambda dic, vars={}: dic.get(str(vars.get("", "_")), vars.get("_default", 0))' - ), - - ('$xx', - ['$xx'], - ('$xx',), - 'lambda dic, vars={}: dic.get(str(vars.get("xx", "_")), vars.get("_default", 0))' - ), - - ('$xx.$yy', - ['$xx', '$yy'], - ('$xx', '$yy',), - 'lambda dic, vars={}: dic.get(str(vars.get("xx", "_")), {}).get(str(vars.get("yy", "_")), vars.get("_default", 0))' - ), - - (None, - [1, '1', 1.0, ()], - (1, '1', 1.0, ()), - 'lambda dic, vars={}: dic.get(1, {}).get("1", {}).get(1.0, {}).get((), vars.get("_default", 0))', - ), - - (None, - [('x',), ('y',)], - (('x',), ('y',)), - 'lambda dic, vars={}: dic.get(("x",), {}).get(("y",), vars.get("_default", 0))', - ), - - (None, - [(1,), ('1', (1.0,),)], - ((1,), ('1', (1.0,),)), - 'lambda dic, vars={}: dic.get((1,), {}).get(("1",(1.0,),), vars.get("_default", 0))', - ), - - (None, - [(1,), ('$xx', (1.0,), '$yy'), ], - ((1,), ('$xx', (1.0,), '$yy'), ), - 'lambda dic, vars={}: dic.get((1,), {}).get((str(vars.get("xx", "_")),(1.0,),str(vars.get("yy", "_")),), vars.get("_default", 0))', - ), - - (None, - [{1: 1}, [1, '1']], - ({1: 1}, [1, '1']), - 'lambda dic, vars={}: dic.get({1: 1}, {}).get([1, \'1\'], vars.get("_default", 0))') + ("$", ["$"], ("$",), 'lambda dic, vars={}: dic.get(str(vars.get("", "_")), vars.get("_default", 0))'), + ( + "$xx", + ["$xx"], + ("$xx",), + 'lambda dic, vars={}: dic.get(str(vars.get("xx", "_")), vars.get("_default", 0))', + ), + ( + "$xx.$yy", + ["$xx", "$yy"], + ( + "$xx", + "$yy", + ), + 'lambda dic, vars={}: dic.get(str(vars.get("xx", "_")), {}).get(str(vars.get("yy", "_")), vars.get("_default", 0))', + ), + ( + None, + [1, "1", 1.0, ()], + (1, "1", 1.0, ()), + 'lambda dic, vars={}: dic.get(1, {}).get("1", {}).get(1.0, {}).get((), vars.get("_default", 0))', + ), + ( + None, + [("x",), ("y",)], + (("x",), ("y",)), + 'lambda dic, vars={}: dic.get(("x",), {}).get(("y",), vars.get("_default", 0))', + ), + ( + None, + [ + (1,), + ( + "1", + (1.0,), + ), + ], + ( + (1,), + ( + "1", + (1.0,), + ), + ), + 'lambda dic, vars={}: dic.get((1,), {}).get(("1",(1.0,),), vars.get("_default", 0))', + ), + ( + None, + [ + (1,), + ("$xx", (1.0,), "$yy"), + ], + ( + (1,), + ("$xx", (1.0,), "$yy"), + ), + 'lambda dic, vars={}: dic.get((1,), {}).get((str(vars.get("xx", "_")),(1.0,),str(vars.get("yy", "_")),), vars.get("_default", 0))', + ), + ( + None, + [{1: 1}, [1, "1"]], + ({1: 1}, [1, "1"]), + "lambda dic, vars={}: dic.get({1: 1}, {}).get([1, '1'], vars.get(\"_default\", 0))", + ), ) for _in_str, _in_list, _in_tuple, _out in cases: - self.assert_make_getter_str(_in_str, _out) self.assert_make_getter_str(_in_list, _out) @@ -398,38 +384,25 @@ def assert_make_getter_str(self, _in, _out, default=0): if _in is None: return rst = k3dict.make_getter_str(_in, default=default) - self.assertEqual(_out, rst, - 'input: {_in}, expected: {_out}, actual: {rst}'.format( - _in=repr(_in), - _out=repr(_out), - rst=repr(rst), - ) - ) + self.assertEqual( + _out, + rst, + "input: {_in}, expected: {_out}, actual: {rst}".format( + _in=repr(_in), + _out=repr(_out), + rst=repr(rst), + ), + ) def test_getter_str_default(self): - cases = ( - - ('x', None, - 'lambda dic, vars={}: dic.get("x", vars.get("_default", None))' - ), - - ('x', 1, - 'lambda dic, vars={}: dic.get("x", vars.get("_default", 1))' - ), - - ('x', True, - 'lambda dic, vars={}: dic.get("x", vars.get("_default", True))' - ), - - ('x', "abc", - 'lambda dic, vars={}: dic.get("x", vars.get("_default", \'abc\'))' - ), - + ("x", None, 'lambda dic, vars={}: dic.get("x", vars.get("_default", None))'), + ("x", 1, 'lambda dic, vars={}: dic.get("x", vars.get("_default", 1))'), + ("x", True, 'lambda dic, vars={}: dic.get("x", vars.get("_default", True))'), + ("x", "abc", 'lambda dic, vars={}: dic.get("x", vars.get("_default", \'abc\'))'), ) for _in_str, _default, _out in cases: - _in_list = [_in_str] _in_tuple = (_in_str,) @@ -440,107 +413,144 @@ def test_getter_str_default(self): self.assert_make_getter_str(_in_tuple, _out, default=_default) def test_getter_vars(self): - cases = ( - - ('', [''], ('',), - 0, - {"x": 1, "": 1}, {}, - 1 - ), - - ('.', ['', ''], ('', '',), - 0, - {'': {'': 1}}, {}, - 1 - ), - - ('...', ['', '', '', ''], ('', '', '', ''), - 0, - {'': {'': {'': {'': 1}}}}, {}, - 1 - ), - - ('.x', ['', 'x'], ('', 'x',), - 0, - {'': {'x': 1}}, {}, - 1 - ), - - ('x..', ['x', '', ''], ('x', '', '',), - 0, - {'x': {'': {'': 1}}}, {}, - 1 - ), - - ('x', ['x'], ('x',), 0, - {}, {}, - 0 - ), - - ('x', ['x'], ('x',), 0, - {'x': {'y': 3}}, {}, - {'y': 3} - ), - - ('x', ['x'], ('x',), 55, - {}, {}, - 55 - ), - - ('x', ['x'], ('x',), 55, - {}, {'_default': 66}, - 66 - ), - - ('x', ['x'], ('x',), 55, - {"x": 1}, {}, - 1 - ), - - ('x.y', ['x', 'y'], ('x', 'y',), 55, - {"x": {"y": 3}}, {}, - 3 - ), - - ('x.z', ['x', 'z'], ('x', 'z',), 55, - {"x": {"y": 3}}, {}, - 55 - ), - - ('x.$v', ['x', '$v'], ('x', '$v',), 55, - {"x": {"y": 3}}, {}, - 55 - ), - - ('x.$var_name', ['x', '$var_name'], ('x', '$var_name',), 55, - {"x": {"y": 3}}, {"var_name": "y"}, - 3 - ), - - ('x.$var_name.z', ['x', '$var_name', 'z'], ('x', '$var_name', 'z',), 55, - {"x": {"y": {}}}, {"var_name": "y"}, - 55 - ), - - ('x.$var_name.z', ['x', '$var_name', 'z'], ('x', '$var_name', 'z',), 55, - {"x": {"y": {"z": 4}}}, {"var_name": "y"}, - 4 - ), - - (None, [1, '1', 1.0, ()], (1, '1', 1.0, ()), 55, - {1: {'1': {1.0: {(): 3}}}}, {}, - 3 - ), - - (None, [(1,), ('$xx', (1.0,), '$yy'), ], ((1,), ('$xx', (1.0,), '$yy'),), 55, - {(1,): {('xx', (1.0,), 'yy'): 3}}, {"xx": "xx", "yy": "yy"}, - 3 - ), + ("", [""], ("",), 0, {"x": 1, "": 1}, {}, 1), + ( + ".", + ["", ""], + ( + "", + "", + ), + 0, + {"": {"": 1}}, + {}, + 1, + ), + ("...", ["", "", "", ""], ("", "", "", ""), 0, {"": {"": {"": {"": 1}}}}, {}, 1), + ( + ".x", + ["", "x"], + ( + "", + "x", + ), + 0, + {"": {"x": 1}}, + {}, + 1, + ), + ( + "x..", + ["x", "", ""], + ( + "x", + "", + "", + ), + 0, + {"x": {"": {"": 1}}}, + {}, + 1, + ), + ("x", ["x"], ("x",), 0, {}, {}, 0), + ("x", ["x"], ("x",), 0, {"x": {"y": 3}}, {}, {"y": 3}), + ("x", ["x"], ("x",), 55, {}, {}, 55), + ("x", ["x"], ("x",), 55, {}, {"_default": 66}, 66), + ("x", ["x"], ("x",), 55, {"x": 1}, {}, 1), + ( + "x.y", + ["x", "y"], + ( + "x", + "y", + ), + 55, + {"x": {"y": 3}}, + {}, + 3, + ), + ( + "x.z", + ["x", "z"], + ( + "x", + "z", + ), + 55, + {"x": {"y": 3}}, + {}, + 55, + ), + ( + "x.$v", + ["x", "$v"], + ( + "x", + "$v", + ), + 55, + {"x": {"y": 3}}, + {}, + 55, + ), + ( + "x.$var_name", + ["x", "$var_name"], + ( + "x", + "$var_name", + ), + 55, + {"x": {"y": 3}}, + {"var_name": "y"}, + 3, + ), + ( + "x.$var_name.z", + ["x", "$var_name", "z"], + ( + "x", + "$var_name", + "z", + ), + 55, + {"x": {"y": {}}}, + {"var_name": "y"}, + 55, + ), + ( + "x.$var_name.z", + ["x", "$var_name", "z"], + ( + "x", + "$var_name", + "z", + ), + 55, + {"x": {"y": {"z": 4}}}, + {"var_name": "y"}, + 4, + ), + (None, [1, "1", 1.0, ()], (1, "1", 1.0, ()), 55, {1: {"1": {1.0: {(): 3}}}}, {}, 3), + ( + None, + [ + (1,), + ("$xx", (1.0,), "$yy"), + ], + ( + (1,), + ("$xx", (1.0,), "$yy"), + ), + 55, + {(1,): {("xx", (1.0,), "yy"): 3}}, + {"xx": "xx", "yy": "yy"}, + 3, + ), ) for _in_str, _in_str_list, _in_str_tuple, _default, _dic, _vars, _out in cases: - self.assert_getter_and_maker(_in_str, _dic, _out, _vars, _default) self.assert_getter_and_maker(_in_str_list, _dic, _out, _vars, _default) @@ -549,10 +559,11 @@ def test_getter_vars(self): def test_getter_type_error(self): cases = ( - ([{1: 1}, [1, '1']], - {},), - (({1: 1}, [1, '1']), - {}), + ( + [{1: 1}, [1, "1"]], + {}, + ), + (({1: 1}, [1, "1"]), {}), ) for _in, _dic in cases: @@ -563,220 +574,325 @@ def test_getter_type_error(self): k3dict.get(_dic, _in) def assert_getter_and_maker(self, _in, _dic, _out, _vars, _default): - if _in is None: return acc = k3dict.make_getter(_in, default=_default) rst = acc(_dic, vars=_vars) - self.assertEqual(_out, rst, - 'input: {_in}, {_default}, {_dic}, {_vars} expected: {_out}, actual: {rst}'.format( - _in=repr(_in), - _default=repr(_default), - _dic=repr(_dic), - _vars=repr(_vars), - _out=repr(_out), - rst=repr(rst), - ) - ) + self.assertEqual( + _out, + rst, + "input: {_in}, {_default}, {_dic}, {_vars} expected: {_out}, actual: {rst}".format( + _in=repr(_in), + _default=repr(_default), + _dic=repr(_dic), + _vars=repr(_vars), + _out=repr(_out), + rst=repr(rst), + ), + ) # test k3dict.get() with the same set of cases rst = k3dict.get(_dic, _in, vars=_vars, default=_default) - dd('k3dict.get({dic}, {key_path} vars={vars}, default={default})'.format( - dic=_dic, - key_path=_in, - vars=_vars, - default=_default, - )) + dd( + "k3dict.get({dic}, {key_path} vars={vars}, default={default})".format( + dic=_dic, + key_path=_in, + vars=_vars, + default=_default, + ) + ) dd(rst) - self.assertEqual(_out, rst, - 'input: {_in}, {_default}, {_dic}, {_vars} expected: {_out}, actual: {rst}'.format( - _in=repr(_in), - _default=repr(_default), - _dic=repr(_dic), - _vars=repr(_vars), - _out=repr(_out), - rst=repr(rst), - ) - ) + self.assertEqual( + _out, + rst, + "input: {_in}, {_default}, {_dic}, {_vars} expected: {_out}, actual: {rst}".format( + _in=repr(_in), + _default=repr(_default), + _dic=repr(_dic), + _vars=repr(_vars), + _out=repr(_out), + rst=repr(rst), + ), + ) def test_get_ignore_vars_key_error(self): - cases = ( - ({}, '$a', {"a": "x"}, None, 0), - ({}, '$a', {"a": "x"}, True, 0), - ({}, ['$a', ('$a',), ], {"a": "x"}, True, 0), + ({}, "$a", {"a": "x"}, None, 0), + ({}, "$a", {"a": "x"}, True, 0), + ( + {}, + [ + "$a", + ("$a",), + ], + {"a": "x"}, + True, + 0, + ), ) for case in cases: - - dd('case: ', case) + dd("case: ", case) dic, key_path, vars, ign, expected = case rst = k3dict.get(dic, key_path, vars=vars, ignore_vars_key_error=ign) - dd('rst: ', rst) + dd("rst: ", rst) self.assertEqual(expected, rst) with self.assertRaises(KeyError): - k3dict.get({}, '$a', {}, ignore_vars_key_error=False) + k3dict.get({}, "$a", {}, ignore_vars_key_error=False) with self.assertRaises(KeyError): - k3dict.get({}, (('1', '$a',),), {}, ignore_vars_key_error=False) + k3dict.get( + {}, + ( + ( + "1", + "$a", + ), + ), + {}, + ignore_vars_key_error=False, + ) with self.assertRaises(KeyError): - k3dict.get({}, [('1', '$a',)], {}, ignore_vars_key_error=False) + k3dict.get( + {}, + [ + ( + "1", + "$a", + ) + ], + {}, + ignore_vars_key_error=False, + ) class TestSetter(unittest.TestCase): - def test_setter(self): - cases = ( - ('', {}, - 10, - {'': 10} - ), - - ('.', {}, - 10, - {'': {'': 10}}), - - ('...', {}, - 10, - {'': {'': {'': {'': 10}}}} - ), - - ('.x', {}, - 10, - {'': {'x': 10}}, - ), - - ('x..', {}, - 10, - {'x': {'': {'': 10}}}, - ), - - ('a', {}, - 3, - {'a': 3}, - ), - - ('a', {'exist': 44}, - 3, - {'a': 3, 'exist': 44}, - ), - - ('a.b.c', {'exist': 44}, - 3, - {'a': {'b': {'c': 3}}, 'exist': 44}, - ), - - ('a.b.c', {'a': {'exist': 44}}, - 3, - {'a': {'b': {'c': 3}, 'exist': 44}}, - ), - - (['a', 'b', 'c'], {'a': {'exist': 44}}, - 3, - {'a': {'b': {'c': 3}, 'exist': 44}}, - ), - - (('a', 'b', 'c'), {'a': {'exist': 44}}, - 3, - {'a': {'b': {'c': 3}, 'exist': 44}}, - ), - - ([1, '1', 1.0, ()], {'a': {'exist': 44}}, - 3, - {'a': {'exist': 44}, 1: {'1': {1.0: {(): 3}}}}, - ), - - ((1, '1', 1.0, ()), {'a': {'exist': 44}}, - 3, - {'a': {'exist': 44}, 1: {'1': {1.0: {(): 3}}}}, - ), - - ([(1,), ('1', (1.0,),)], {'a': {'exist': 44}}, - 3, - {'a': {'exist': 44}, (1,): {('1', (1.0,),): 3}}, - ), - - (((1,), ('1', (1.0,),)), {'a': {'exist': 44}}, - 3, - {'a': {'exist': 44}, (1,): {('1', (1.0,),): 3}}, - ), - + ("", {}, 10, {"": 10}), + (".", {}, 10, {"": {"": 10}}), + ("...", {}, 10, {"": {"": {"": {"": 10}}}}), + ( + ".x", + {}, + 10, + {"": {"x": 10}}, + ), + ( + "x..", + {}, + 10, + {"x": {"": {"": 10}}}, + ), + ( + "a", + {}, + 3, + {"a": 3}, + ), + ( + "a", + {"exist": 44}, + 3, + {"a": 3, "exist": 44}, + ), + ( + "a.b.c", + {"exist": 44}, + 3, + {"a": {"b": {"c": 3}}, "exist": 44}, + ), + ( + "a.b.c", + {"a": {"exist": 44}}, + 3, + {"a": {"b": {"c": 3}, "exist": 44}}, + ), + ( + ["a", "b", "c"], + {"a": {"exist": 44}}, + 3, + {"a": {"b": {"c": 3}, "exist": 44}}, + ), + ( + ("a", "b", "c"), + {"a": {"exist": 44}}, + 3, + {"a": {"b": {"c": 3}, "exist": 44}}, + ), + ( + [1, "1", 1.0, ()], + {"a": {"exist": 44}}, + 3, + {"a": {"exist": 44}, 1: {"1": {1.0: {(): 3}}}}, + ), + ( + (1, "1", 1.0, ()), + {"a": {"exist": 44}}, + 3, + {"a": {"exist": 44}, 1: {"1": {1.0: {(): 3}}}}, + ), + ( + [ + (1,), + ( + "1", + (1.0,), + ), + ], + {"a": {"exist": 44}}, + 3, + { + "a": {"exist": 44}, + (1,): { + ( + "1", + (1.0,), + ): 3 + }, + }, + ), + ( + ( + (1,), + ( + "1", + (1.0,), + ), + ), + {"a": {"exist": 44}}, + 3, + { + "a": {"exist": 44}, + (1,): { + ( + "1", + (1.0,), + ): 3 + }, + }, + ), ) for _key_path, _dic, _value, _expect in cases: - _set = k3dict.make_setter(_key_path) rst = _set(_dic, _value) - self.assertEqual(_value, rst, - 'input: {_key_path}, {_dic}; expected return value: {_value}, actual: {rst}'.format( - _key_path=repr(_key_path), - _dic=repr(_dic), - _value=repr(_value), - rst=repr(rst), - ) - ) - - self.assertEqual(_expect, _dic, - 'input: {_key_path}, {_dic}; expected dict: {_expect}, actual: {rst}'.format( - _key_path=repr(_key_path), - _dic=repr(_dic), - _expect=repr(_expect), - rst=repr(rst), - ) - ) + self.assertEqual( + _value, + rst, + "input: {_key_path}, {_dic}; expected return value: {_value}, actual: {rst}".format( + _key_path=repr(_key_path), + _dic=repr(_dic), + _value=repr(_value), + rst=repr(rst), + ), + ) - def test_setter_default(self): + self.assertEqual( + _expect, + _dic, + "input: {_key_path}, {_dic}; expected dict: {_expect}, actual: {rst}".format( + _key_path=repr(_key_path), + _dic=repr(_dic), + _expect=repr(_expect), + rst=repr(rst), + ), + ) + def test_setter_default(self): cases = ( - - ('a', 'value', {}, - {'a': 'value'}, - ), - - ('exist', 'value', {'exist': 44}, - {'exist': 'value'}, - ), - - ('a.exist', 'value', {'a': {'exist': 44}}, - {'a': {'exist': 'value'}}, - ), - - ('a.exist', lambda *x: '_def', {'a': {'exist': 44}}, - {'a': {'exist': '_def'}}, - ), - - ('a.exist', lambda vars: vars.get('foo'), {'a': {'exist': 44}}, - {'a': {'exist': 'foo'}}, - ), - - ([1, '1', 1.0, ()], lambda vars: vars.get('foo'), {'a': {'exist': 44}}, - {'a': {'exist': 44}, 1: {'1': {1.0: {(): 'foo'}}}}, - ), - - ((1, '1', 1.0, ()), lambda vars: vars.get('foo'), {'a': {'exist': 44}}, - {'a': {'exist': 44}, 1: {'1': {1.0: {(): 'foo'}}}}, - ), - - ([(1,), ('1', (1.0,),)], lambda vars: vars.get('foo'), {'a': {'exist': 44}}, - {'a': {'exist': 44}, (1,): {('1', (1.0,),): 'foo'}}, - ), - - (((1,), ('1', (1.0,),)), lambda vars: vars.get('foo'), {'a': {'exist': 44}}, - {'a': {'exist': 44}, (1,): {('1', (1.0,),): 'foo'}}, - ), - + ( + "a", + "value", + {}, + {"a": "value"}, + ), + ( + "exist", + "value", + {"exist": 44}, + {"exist": "value"}, + ), + ( + "a.exist", + "value", + {"a": {"exist": 44}}, + {"a": {"exist": "value"}}, + ), + ( + "a.exist", + lambda *x: "_def", + {"a": {"exist": 44}}, + {"a": {"exist": "_def"}}, + ), + ( + "a.exist", + lambda vars: vars.get("foo"), + {"a": {"exist": 44}}, + {"a": {"exist": "foo"}}, + ), + ( + [1, "1", 1.0, ()], + lambda vars: vars.get("foo"), + {"a": {"exist": 44}}, + {"a": {"exist": 44}, 1: {"1": {1.0: {(): "foo"}}}}, + ), + ( + (1, "1", 1.0, ()), + lambda vars: vars.get("foo"), + {"a": {"exist": 44}}, + {"a": {"exist": 44}, 1: {"1": {1.0: {(): "foo"}}}}, + ), + ( + [ + (1,), + ( + "1", + (1.0,), + ), + ], + lambda vars: vars.get("foo"), + {"a": {"exist": 44}}, + { + "a": {"exist": 44}, + (1,): { + ( + "1", + (1.0,), + ): "foo" + }, + }, + ), + ( + ( + (1,), + ( + "1", + (1.0,), + ), + ), + lambda vars: vars.get("foo"), + {"a": {"exist": 44}}, + { + "a": {"exist": 44}, + (1,): { + ( + "1", + (1.0,), + ): "foo" + }, + }, + ), ) - _vars = {'foo': 'foo'} + _vars = {"foo": "foo"} for _key_path, _default, _dic, _expect in cases: - _set = k3dict.make_setter(_key_path, value=_default) rst = _set(_dic, vars=_vars) @@ -785,72 +901,79 @@ def test_setter_default(self): else: _def = _default - self.assertEqual(_def, rst, - 'input: {_key_path}, {_dic}; expected return value: {_def}, actual: {rst}'.format( - _key_path=repr(_key_path), - _dic=repr(_dic), - _def=repr(_def), - rst=repr(rst), - ) - ) - - self.assertEqual(_expect, _dic, - 'input: {_key_path}, {_dic}; expected dict: {_expect}, actual: {rst}'.format( - _key_path=repr(_key_path), - _dic=repr(_dic), - _expect=repr(_expect), - rst=repr(rst), - ) - ) - - rst = _set(_dic, 'specified', vars=_vars) - self.assertEqual('specified', rst) + self.assertEqual( + _def, + rst, + "input: {_key_path}, {_dic}; expected return value: {_def}, actual: {rst}".format( + _key_path=repr(_key_path), + _dic=repr(_dic), + _def=repr(_def), + rst=repr(rst), + ), + ) + + self.assertEqual( + _expect, + _dic, + "input: {_key_path}, {_dic}; expected dict: {_expect}, actual: {rst}".format( + _key_path=repr(_key_path), + _dic=repr(_dic), + _expect=repr(_expect), + rst=repr(rst), + ), + ) + + rst = _set(_dic, "specified", vars=_vars) + self.assertEqual("specified", rst) def test_setter_vars_inexistent(self): - - _set = k3dict.make_setter('$a') + _set = k3dict.make_setter("$a") with self.assertRaises(KeyError): _set({}, vars={}) - _set = k3dict.make_setter(['$a']) + _set = k3dict.make_setter(["$a"]) with self.assertRaises(KeyError): _set({}, vars={}) - _set = k3dict.make_setter(('$a',)) + _set = k3dict.make_setter(("$a",)) with self.assertRaises(KeyError): _set({}, vars={}) def test_setter_vars(self): - cases = ( - - ('$a', 1, {}, - {'aa': 1}, - ), - - ('$a.$foo', 1, {}, - {'aa': {'bar': 1}}, - ), - - ([(1,), ('$a', (1.0,), '$foo')], 1, {}, - {(1,): {('aa', (1.0,), 'bar'): 1}} - ), - - (((1,), ('$a', (1.0,), '$foo'),), 1, {}, - {(1,): {('aa', (1.0,), 'bar'): 1}} - ), - + ( + "$a", + 1, + {}, + {"aa": 1}, + ), + ( + "$a.$foo", + 1, + {}, + {"aa": {"bar": 1}}, + ), + ([(1,), ("$a", (1.0,), "$foo")], 1, {}, {(1,): {("aa", (1.0,), "bar"): 1}}), + ( + ( + (1,), + ("$a", (1.0,), "$foo"), + ), + 1, + {}, + {(1,): {("aa", (1.0,), "bar"): 1}}, + ), ) - _vars = {'foo': 'bar', - 'a': 'aa', - } + _vars = { + "foo": "bar", + "a": "aa", + } for _key_path, _default, _dic, _expect in cases: - _set = k3dict.make_setter(_key_path, value=_default) rst = _set(_dic, vars=_vars) @@ -859,150 +982,210 @@ def test_setter_vars(self): else: _def = _default - self.assertEqual(_def, rst, - 'input: {_key_path}, {_dic}; expected return value: {_def}, actual: {rst}'.format( - _key_path=repr(_key_path), - _dic=repr(_dic), - _def=repr(_def), - rst=repr(rst), - ) - ) - - self.assertEqual(_expect, _dic, - 'input: {_key_path}, {_dic}; expected dict: {_expect}, actual: {rst}'.format( - _key_path=repr(_key_path), - _dic=repr(_dic), - _expect=repr(_expect), - rst=repr(rst), - ) - ) - - rst = _set(_dic, 'specified', vars=_vars) - self.assertEqual('specified', rst) - - def test_setter_incr(self): + self.assertEqual( + _def, + rst, + "input: {_key_path}, {_dic}; expected return value: {_def}, actual: {rst}".format( + _key_path=repr(_key_path), + _dic=repr(_dic), + _def=repr(_def), + rst=repr(rst), + ), + ) - cases = ( + self.assertEqual( + _expect, + _dic, + "input: {_key_path}, {_dic}; expected dict: {_expect}, actual: {rst}".format( + _key_path=repr(_key_path), + _dic=repr(_dic), + _expect=repr(_expect), + rst=repr(rst), + ), + ) - ('a', - ['a'], - ('a',), - 1, {}, - {'a': 1}, - ), - - ('a', - ['a'], - ('a',), - 1, {'a': 1}, - {'a': 2}, - ), - - ('a', - ['a'], - ('a',), - 'foo', {}, - {'a': 'foo'}, - ), - - ('a', - ['a'], - ('a',), - 'foo', {'a': 'bar'}, - {'a': 'barfoo'}, - ), - - ('a', - ['a'], - ('a',), - (1, 2), {}, - {'a': (1, 2)}, - ), - - ('a', - ['a'], - ('a',), - (1, 2), {'a': (0, 1)}, - {'a': (0, 1, 1, 2)}, - ), - - ('a', - ['a'], - ('a',), - [1, 2], {}, - {'a': [1, 2]}, - ), - - ('a', - ['a'], - ('a',), - [1, 2], {'a': [0, 1]}, - {'a': [0, 1, 1, 2]}, - ), - - ('$a.$foo', - ['$a', '$foo'], - ('$a', '$foo',), - 1, {}, - {'aa': {'bar': 1}}, - ), - - ('$a.$foo', - ['$a', '$foo'], - ('$a', '$foo',), - 1.1, {}, - {'aa': {'bar': 1.1}}, - ), - - ('$a.$foo', - ['$a', '$foo'], - ('$a', '$foo',), - 'suffix', {'aa': {'bar': 'prefix-'}}, - {'aa': {'bar': 'prefix-suffix'}}, - ), - - ('$a.$foo', - ['$a', '$foo'], - ('$a', '$foo',), - ('b',), {'aa': {}}, - {'aa': {'bar': ('b',)}}, - ), - - ('$a.$foo', - ['$a', '$foo'], - ('$a', '$foo',), - ('b',), {'aa': {'bar': ('a',)}}, - {'aa': {'bar': ('a', 'b',)}}, - ), - - ('$a.$foo', - ['$a', '$foo'], - ('$a', '$foo',), - ['b', ], {'aa': {'bar': ['a', ]}}, - {'aa': {'bar': ['a', 'b', ]}}, - ), - - (None, - [1, '1', 1.0, ()], - (1, '1', 1.0, ()), - 11, - {1: {'1': {1.0: {(): 100}}}}, - {1: {'1': {1.0: {(): 111}}}}, - ), - - (None, - [(1,), ('$foo', (1.0,), '$a')], - ((1,), ('$foo', (1.0,), '$a'),), - 11, - {(1,): {('bar', (1.0,), 'aa'): 100}}, - {(1,): {('bar', (1.0,), 'aa'): 111}}, - ), + rst = _set(_dic, "specified", vars=_vars) + self.assertEqual("specified", rst) + def test_setter_incr(self): + cases = ( + ( + "a", + ["a"], + ("a",), + 1, + {}, + {"a": 1}, + ), + ( + "a", + ["a"], + ("a",), + 1, + {"a": 1}, + {"a": 2}, + ), + ( + "a", + ["a"], + ("a",), + "foo", + {}, + {"a": "foo"}, + ), + ( + "a", + ["a"], + ("a",), + "foo", + {"a": "bar"}, + {"a": "barfoo"}, + ), + ( + "a", + ["a"], + ("a",), + (1, 2), + {}, + {"a": (1, 2)}, + ), + ( + "a", + ["a"], + ("a",), + (1, 2), + {"a": (0, 1)}, + {"a": (0, 1, 1, 2)}, + ), + ( + "a", + ["a"], + ("a",), + [1, 2], + {}, + {"a": [1, 2]}, + ), + ( + "a", + ["a"], + ("a",), + [1, 2], + {"a": [0, 1]}, + {"a": [0, 1, 1, 2]}, + ), + ( + "$a.$foo", + ["$a", "$foo"], + ( + "$a", + "$foo", + ), + 1, + {}, + {"aa": {"bar": 1}}, + ), + ( + "$a.$foo", + ["$a", "$foo"], + ( + "$a", + "$foo", + ), + 1.1, + {}, + {"aa": {"bar": 1.1}}, + ), + ( + "$a.$foo", + ["$a", "$foo"], + ( + "$a", + "$foo", + ), + "suffix", + {"aa": {"bar": "prefix-"}}, + {"aa": {"bar": "prefix-suffix"}}, + ), + ( + "$a.$foo", + ["$a", "$foo"], + ( + "$a", + "$foo", + ), + ("b",), + {"aa": {}}, + {"aa": {"bar": ("b",)}}, + ), + ( + "$a.$foo", + ["$a", "$foo"], + ( + "$a", + "$foo", + ), + ("b",), + {"aa": {"bar": ("a",)}}, + { + "aa": { + "bar": ( + "a", + "b", + ) + } + }, + ), + ( + "$a.$foo", + ["$a", "$foo"], + ( + "$a", + "$foo", + ), + [ + "b", + ], + { + "aa": { + "bar": [ + "a", + ] + } + }, + { + "aa": { + "bar": [ + "a", + "b", + ] + } + }, + ), + ( + None, + [1, "1", 1.0, ()], + (1, "1", 1.0, ()), + 11, + {1: {"1": {1.0: {(): 100}}}}, + {1: {"1": {1.0: {(): 111}}}}, + ), + ( + None, + [(1,), ("$foo", (1.0,), "$a")], + ( + (1,), + ("$foo", (1.0,), "$a"), + ), + 11, + {(1,): {("bar", (1.0,), "aa"): 100}}, + {(1,): {("bar", (1.0,), "aa"): 111}}, + ), ) - _vars = {'foo': 'bar', - 'a': 'aa', - } + _vars = { + "foo": "bar", + "a": "aa", + } for _in_str, _in_list, _in_tuple, _default, _dic, _expect in cases: self.assert_make_setter(_default, copy.deepcopy(_dic), _expect, _in_str, _vars) @@ -1010,109 +1193,96 @@ def test_setter_incr(self): self.assert_make_setter(_default, copy.deepcopy(_dic), _expect, _in_tuple, _vars) def assert_make_setter(self, _default, _dic, _expect, _in, _vars): - if _in is None: return dd(_in, _default, _dic, _expect) _set = k3dict.make_setter(_in, value=_default, incr=True) rst = _set(_dic, vars=_vars) - dd('rst:', rst) - self.assertEqual(_expect, _dic, - 'input: {_key_path}, {_dic}; expected dict: {_expect}, actual: {rst}'.format( - _key_path=repr(_in), - _dic=repr(_dic), - _expect=repr(_expect), - rst=repr(rst), - ) - ) + dd("rst:", rst) + self.assertEqual( + _expect, + _dic, + "input: {_key_path}, {_dic}; expected dict: {_expect}, actual: {rst}".format( + _key_path=repr(_in), + _dic=repr(_dic), + _expect=repr(_expect), + rst=repr(rst), + ), + ) class TestAttrDict(unittest.TestCase): - def test_attrdict(self): - cases = ( - ([], - {}, - {} - ), - - ([], - {'a': 2}, - {'a': 2} - ), - - ([{'x': 2}], - {'a': 4}, - {'x': 2, 'a': 4}, - ), + ([], {}, {}), + ([], {"a": 2}, {"a": 2}), + ( + [{"x": 2}], + {"a": 4}, + {"x": 2, "a": 4}, + ), ) for args, kwargs, expected in cases: rst = k3dict.attrdict(*args, **kwargs) - self.assertEqual(expected, rst, - 'input: {a} {kw} {e}, rst: {rst}'.format( - a=args, kw=kwargs, e=expected, rst=rst)) + self.assertEqual( + expected, rst, "input: {a} {kw} {e}, rst: {rst}".format(a=args, kw=kwargs, e=expected, rst=rst) + ) for k in rst: self.assertEqual(rst[k], getattr(rst, k)) def test_dict_method(self): - ad = k3dict.attrdict(a=1, b=2) - self.assertEqual(['a', 'b'], list(ad.keys())) + self.assertEqual(["a", "b"], list(ad.keys())) self.assertEqual([1, 2], list(ad.values())) - self.assertEqual([('a', 1), ('b', 2)], list(ad.items())) + self.assertEqual([("a", 1), ("b", 2)], list(ad.items())) def test_recursive(self): - - ad = k3dict.attrdict( - x=1, y={'a': 3, 'b': dict(c=4), 'd': k3dict.attrdict(z=5)}) + ad = k3dict.attrdict(x=1, y={"a": 3, "b": dict(c=4), "d": k3dict.attrdict(z=5)}) self.assertEqual(1, ad.x) - self.assertEqual({'a': 3, 'b': {'c': 4}, 'd': {'z': 5}}, ad.y) + self.assertEqual({"a": 3, "b": {"c": 4}, "d": {"z": 5}}, ad.y) self.assertEqual(3, ad.y.a) self.assertEqual(4, ad.y.b.c) self.assertEqual(5, ad.y.d.z) def test_writable(self): - ad = k3dict.attrdict(a={}, b={1: 2}) - ad['x'] = 4 + ad["x"] = 4 self.assertEqual(4, ad.x) - self.assertEqual(4, ad['x']) + self.assertEqual(4, ad["x"]) ad.y = 5 self.assertEqual(5, ad.y) - self.assertEqual(5, ad['y']) + self.assertEqual(5, ad["y"]) def test_attrdict_copy(self): - # reference type value are always copied when accessing - ad = k3dict.attrdict_copy(a={}, b={'x': {'foo': 'bar'}}) + ad = k3dict.attrdict_copy(a={}, b={"x": {"foo": "bar"}}) self.assertIsNot(ad.a, ad.a) self.assertIsNot(ad.b.x, ad.b.x) - self.assertIsNot(ad['a'], ad['a']) - self.assertIsNot(ad['b']['x'], ad['b']['x']) + self.assertIsNot(ad["a"], ad["a"]) + self.assertIsNot(ad["b"]["x"], ad["b"]["x"]) # value got is still a AttrDictCopy instance b = ad.b self.assertIsNot(b.x, b.x) - self.assertIsNot(b['x'], b['x']) + self.assertIsNot(b["x"], b["x"]) # it does not change the original value with self.assertRaises(KeyError): - ad['b'] = 10 + ad["b"] = 10 with self.assertRaises(KeyError): - ad.b['x'] = 10 + ad.b["x"] = 10 with self.assertRaises(KeyError): - b['x'] = 10 + b["x"] = 10 with self.assertRaises(AttributeError): ad.b = 10 @@ -1124,68 +1294,58 @@ def test_attrdict_copy(self): ad.y = 2 def test_attrdict_copy_as_dict(self): - - ad = k3dict.attrdict_copy(a={}, b={'x': {}}) + ad = k3dict.attrdict_copy(a={}, b={"x": {}}) d = ad.as_dict() - b2 = d['b'] - d['b']['x'] = 100 - self.assertEqual(100, b2['x']) + b2 = d["b"] + d["b"]["x"] = 100 + self.assertEqual(100, b2["x"]) def test_attr_overriding(self): - ad = k3dict.attrdict(items=1) with self.assertRaises(TypeError): list(ad.items()) def test_ref_to_same_item(self): - - x = {'a': 1} + x = {"a": 1} ad = k3dict.attrdict(u=x, v=x) self.assertIs(ad.u, ad.v) - x['x'] = x + x["x"] = x ad = k3dict.attrdict(x) self.assertTrue(isinstance(ad, k3dict.AttrDict)) self.assertTrue(isinstance(ad.x, k3dict.AttrDict)) - self.assertTrue(ad.x is not ad, 'attrdict does create a new dict') - self.assertTrue(ad.x.x is ad.x, 'circular references work for all dict items.') - self.assertTrue(ad.x.x.x is ad.x.x, 'circular references work for all dict items.(2)') + self.assertTrue(ad.x is not ad, "attrdict does create a new dict") + self.assertTrue(ad.x.x is ad.x, "circular references work for all dict items.") + self.assertTrue(ad.x.x.x is ad.x.x, "circular references work for all dict items.(2)") class TestIsSubDict(unittest.TestCase): - def test_dict(self): - for a, b, expected in [ (1, 1, True), (1, 2, False), ("x", "x", True), ("x", "b", False), (None, None, True), - # value is a `type` ({"a": list}, {"a": 1}, False), ({"a": list}, {}, True), ({}, {"a": list}, False), - ({"a": 1}, {"a": 1}, True), ({}, {"a": None}, False), ({"a": 1}, {}, True), ({"a": 1}, None, False), (None, {"a": 1}, False), - ({"a": (1, 2)}, {"a": (1, 2)}, True), ({"a": (1, 2, 3)}, {"a": (1, 2)}, True), - ({"a": 1}, [], False), ({"a": [1, (2, 3)]}, {"a": [1, (2,)]}, True), ({"a": [1, (2, 3)]}, {"a": [1, (2, 4)]}, False), ({"a": [1, (2, 3)]}, {"a": [1, (2, 3, 4)]}, False), - ({"a": 1, "b": 2}, {"a": 1}, True), ({"a": 1}, {"a": 1, "b": 2}, False), ({"a": 1, "b": {"c": 3, "d": 4}}, {"a": 1, "b": {"d": 4}}, True), @@ -1199,13 +1359,11 @@ def test_dict(self): self.assertEqual(expected, rst) def test_intern_string(self): - - a = {'key': '123'} - b = {'key': ''.join(['1', '2', '3'])} + a = {"key": "123"} + b = {"key": "".join(["1", "2", "3"])} self.assertTrue(k3dict.contains(a, b)) def test_recursive_dict(self): - # a -> {} # ^ | # `---' @@ -1255,179 +1413,146 @@ def test_recursive_dict(self): self.assertEqual(k3dict.contains(a, b), False) def test_recursive_dict_with_list(self): - a = {'k': [0, 2]} - a['k'][0] = {'k': [0, 2]} - a['k'][0]['k'][0] = a + a = {"k": [0, 2]} + a["k"][0] = {"k": [0, 2]} + a["k"][0]["k"][0] = a - b = {'k': [0, 2]} - b['k'][0] = b + b = {"k": [0, 2]} + b["k"][0] = b self.assertEqual(k3dict.contains(a, b), True) class TestAdd(unittest.TestCase): - def test_add(self): cases = ( - ({}, None, {}, {}), - ({}, 'foo', {}, {}), - ({}, 123, {}, {}), - ({}, {}, {}, {}), - ({'a': 1}, {}, {}, {'a': 1}), - ({}, {'a': 2}, {}, {'a': 2}), - ({'a': 1}, {'a': 2}, {}, {'a': 3}), - ({'a': '1'}, {'a': '2'}, {}, {'a': '12'}), - ({'a': '1'}, {'b': '2'}, {}, {'a': '1', 'b': '2'}), - ({'a': '1'}, {'b': {}}, {}, {'a': '1', 'b': {}}), - ({'a': {}}, {'b': {}}, {}, {'a': {}, 'b': {}}), - ({'a': {}}, {'a': {}}, {}, {'a': {}}), - - ({'a': {'k1': 1}}, - {'a': {}}, - {}, - {'a': {'k1': 1}}), - - ({'a': {'k1': 1}}, - {'a': {'k2': 1}}, - {}, - {'a': {'k1': 1, 'k2': 1}}), - - ({'a': {'k1': 1}}, - {'a': {'k1': 1}}, - {}, - {'a': {'k1': 2}}), - - ({'a': {'k1': 1}}, - {'a': {'k1': 1}}, - {'exclude': {'a': True}, }, - {'a': {'k1': 1}}), - - ({'a': {'k1': 1}}, - {'a': {'k1': 1}}, - {'exclude': {'a': {}}, }, - {'a': {'k1': 2}}), - - ({'a': {}}, - {'a': {'k1': 1}}, - {'exclude': {'a': {}}, }, - {'a': {'k1': 1}}), - - ({'a': {}}, - {'a': {'k1': 1}}, - {'exclude': {'a': True}, }, - {'a': {}}), - - ({'a': {'k1': 1}}, - {'a': {'k1': 1}}, - {'exclude': {'a': {}}, }, - {'a': {'k1': 2}}), - - ({'a': {'k1': 1}}, - {'a': {'k1': 1}}, - {'exclude': {'b': True}, }, - {'a': {'k1': 2}}), - - ({'a': {'k1': 1}}, - {'a': {'k1': 1}}, - {'exclude': {'b': True}, }, - {'a': {'k1': 2}}), - - ({'a': {'k1': 1}}, - {'a': {'k1': 1}}, - {'exclude': {'a': {'k1': True}}, 'recursive': True}, - {'a': {'k1': 1}}), - - ({'a': {'k1': 1}}, - {'a': {'k1': 1}}, - {'exclude': {'a': {'k2': True}}, 'recursive': True}, - {'a': {'k1': 2}}), - - ({'a': {'k1': 1}, 'b': 1}, - {'a': {'k1': 1, 'k2': 1}, 'b': 1}, - {'recursive': False}, - {'a': {'k1': 1}, 'b': 2}), + ({}, None, {}, {}), + ({}, "foo", {}, {}), + ({}, 123, {}, {}), + ({}, {}, {}, {}), + ({"a": 1}, {}, {}, {"a": 1}), + ({}, {"a": 2}, {}, {"a": 2}), + ({"a": 1}, {"a": 2}, {}, {"a": 3}), + ({"a": "1"}, {"a": "2"}, {}, {"a": "12"}), + ({"a": "1"}, {"b": "2"}, {}, {"a": "1", "b": "2"}), + ({"a": "1"}, {"b": {}}, {}, {"a": "1", "b": {}}), + ({"a": {}}, {"b": {}}, {}, {"a": {}, "b": {}}), + ({"a": {}}, {"a": {}}, {}, {"a": {}}), + ({"a": {"k1": 1}}, {"a": {}}, {}, {"a": {"k1": 1}}), + ({"a": {"k1": 1}}, {"a": {"k2": 1}}, {}, {"a": {"k1": 1, "k2": 1}}), + ({"a": {"k1": 1}}, {"a": {"k1": 1}}, {}, {"a": {"k1": 2}}), + ( + {"a": {"k1": 1}}, + {"a": {"k1": 1}}, + { + "exclude": {"a": True}, + }, + {"a": {"k1": 1}}, + ), + ( + {"a": {"k1": 1}}, + {"a": {"k1": 1}}, + { + "exclude": {"a": {}}, + }, + {"a": {"k1": 2}}, + ), + ( + {"a": {}}, + {"a": {"k1": 1}}, + { + "exclude": {"a": {}}, + }, + {"a": {"k1": 1}}, + ), + ( + {"a": {}}, + {"a": {"k1": 1}}, + { + "exclude": {"a": True}, + }, + {"a": {}}, + ), + ( + {"a": {"k1": 1}}, + {"a": {"k1": 1}}, + { + "exclude": {"a": {}}, + }, + {"a": {"k1": 2}}, + ), + ( + {"a": {"k1": 1}}, + {"a": {"k1": 1}}, + { + "exclude": {"b": True}, + }, + {"a": {"k1": 2}}, + ), + ( + {"a": {"k1": 1}}, + {"a": {"k1": 1}}, + { + "exclude": {"b": True}, + }, + {"a": {"k1": 2}}, + ), + ({"a": {"k1": 1}}, {"a": {"k1": 1}}, {"exclude": {"a": {"k1": True}}, "recursive": True}, {"a": {"k1": 1}}), + ({"a": {"k1": 1}}, {"a": {"k1": 1}}, {"exclude": {"a": {"k2": True}}, "recursive": True}, {"a": {"k1": 2}}), + ( + {"a": {"k1": 1}, "b": 1}, + {"a": {"k1": 1, "k2": 1}, "b": 1}, + {"recursive": False}, + {"a": {"k1": 1}, "b": 2}, + ), ) for a, b, kwargs, expected in cases: - dd('a:', a) - dd('b:', b) - dd('kwargs:', kwargs) - dd('expected:', expected) + dd("a:", a) + dd("b:", b) + dd("kwargs:", kwargs) + dd("expected:", expected) result = k3dict.add(a, b, **kwargs) - dd('rst:', result) + dd("rst:", result) self.assertIsNot(a, result) - self.assertDictEqual(expected, result, - repr([a, b, kwargs, result])) + self.assertDictEqual(expected, result, repr([a, b, kwargs, result])) result = k3dict.addto(a, b, **kwargs) self.assertIs(a, result) - self.assertDictEqual(expected, result, - repr([a, b, kwargs, result])) + self.assertDictEqual(expected, result, repr([a, b, kwargs, result])) class TestCombine(unittest.TestCase): - def test_combine(self): cases = ( - ({'a': 2}, - {'a': 3}, - operator.mul, - None, - True, - {'a': 6}), - - ({'a': 2}, - {'a': 3}, - operator.mul, - {'a': True}, - True, - {'a': 2}), - - ({'a': 2, 'b': {'c': 3}}, - {'a': 4, 'b': {'d': 5}}, - operator.add, - None, - True, - {'a': 6, 'b': {'c': 3, 'd': 5}}), - - ({'a': 2}, - {'a': 3, 'b': {'c': 4}}, - operator.add, - None, - True, - {'a': 5, 'b': {'c': 4}}), - - ({'a': 2}, - {'a': 3, 'b': {'c': 4}}, - operator.add, - None, - False, - {'a': 5}), - - ({'a': 2}, - {'b': {'c': {'d': 3}}}, - operator.add, - None, - True, - {'a': 2, 'b': {'c': {'d': 3}}}), + ({"a": 2}, {"a": 3}, operator.mul, None, True, {"a": 6}), + ({"a": 2}, {"a": 3}, operator.mul, {"a": True}, True, {"a": 2}), + ( + {"a": 2, "b": {"c": 3}}, + {"a": 4, "b": {"d": 5}}, + operator.add, + None, + True, + {"a": 6, "b": {"c": 3, "d": 5}}, + ), + ({"a": 2}, {"a": 3, "b": {"c": 4}}, operator.add, None, True, {"a": 5, "b": {"c": 4}}), + ({"a": 2}, {"a": 3, "b": {"c": 4}}, operator.add, None, False, {"a": 5}), + ({"a": 2}, {"b": {"c": {"d": 3}}}, operator.add, None, True, {"a": 2, "b": {"c": {"d": 3}}}), ) for a, b, op, exclude, recursive, expected in cases: result = k3dict.combine(a, b, op, exclude=exclude, recursive=recursive) self.assertIsNot(a, result) - self.assertDictEqual(expected, result, - repr([a, b, op, exclude, expected, result])) + self.assertDictEqual(expected, result, repr([a, b, op, exclude, expected, result])) result = k3dict.combineto(a, b, op, exclude=exclude, recursive=recursive) self.assertIs(a, result) - self.assertDictEqual(expected, result, - repr([a, b, op, exclude, expected, result])) + self.assertDictEqual(expected, result, repr([a, b, op, exclude, expected, result])) def test_deepcopy(self): - def add_value(dict_b): for k, v in list(dict_b.items()): if isinstance(v, dict): @@ -1436,23 +1561,14 @@ def add_value(dict_b): dict_b[k] = v + 1 cases = ( - - ({'a': 2}, - {'a': 3, 'b': {'c': 0}}, - operator.add, - {'a': 5, 'b': {'c': 0}}), - - ({'a': 2}, - {'a': 3, 'b': {'c': {'d': 0}}}, - operator.add, - {'a': 5, 'b': {'c': {'d': 0}}}), - - ({'a': 2, 'b': {'c': 0}}, - {'a': 3, 'b': {'c': 0}}, - operator.add, - {'a': 5, 'b': {'c': 0}},) - - + ({"a": 2}, {"a": 3, "b": {"c": 0}}, operator.add, {"a": 5, "b": {"c": 0}}), + ({"a": 2}, {"a": 3, "b": {"c": {"d": 0}}}, operator.add, {"a": 5, "b": {"c": {"d": 0}}}), + ( + {"a": 2, "b": {"c": 0}}, + {"a": 3, "b": {"c": 0}}, + operator.add, + {"a": 5, "b": {"c": 0}}, + ), ) for a, b, op, expected in cases: @@ -1471,195 +1587,189 @@ def add_value(dict_b): class TestDictutil(unittest.TestCase): - def test_subdict(self): + source_dict = {0: "0", "a": "a", "none": None} - source_dict = {0: '0', 'a': 'a', 'none': None} - - default_dict = {'b': 'b', 'c': 'c'} + default_dict = {"b": "b", "c": "c"} def iter_default(k): return default_dict.get(k) def iter_keys(): - for i in (0, 'a', 'b'): + for i in (0, "a", "b"): yield i cases = ( ( - ('a', 0), + ("a", 0), { - 'use_default': True, + "use_default": True, }, - {0: '0', 'a': 'a'}, - 'normal', + {0: "0", "a": "a"}, + "normal", ), ( - ('a', 'b'), + ("a", "b"), { - 'use_default': True, - 'default': 0, + "use_default": True, + "default": 0, }, - {'a': 'a', 'b': 0}, - 'use default', + {"a": "a", "b": 0}, + "use default", ), ( - ('b', 'c'), + ("b", "c"), { - 'use_default': True, - 'default': iter_default, + "use_default": True, + "default": iter_default, }, - {'b': 'b', 'c': 'c'}, - 'use default as callable', + {"b": "b", "c": "c"}, + "use default as callable", ), ( - ('b', 'c'), + ("b", "c"), { - 'use_default': False, - 'default': iter_default, + "use_default": False, + "default": iter_default, }, {}, - 'not use default', + "not use default", ), ( iter_keys(), { - 'use_default': True, + "use_default": True, }, - {0: '0', 'a': 'a', 'b': None}, - 'use keys as interable', + {0: "0", "a": "a", "b": None}, + "use keys as interable", ), ( - ('none',), + ("none",), { - 'use_default': True, - 'default': 'not None', + "use_default": True, + "default": "not None", }, - {'none': None}, - 'key exists and is None', + {"none": None}, + "key exists and is None", ), ) for flds, kwargs, expected, msg in cases: - dd('msg: ', msg) - dd('expected: ', expected) + dd("msg: ", msg) + dd("expected: ", expected) rst = k3dict.subdict(source_dict, flds, **kwargs) - dd('result: ', rst) + dd("result: ", rst) self.assertEqual(expected, rst) - test_dict = {'test': 0} - source_dict['copy'] = test_dict + test_dict = {"test": 0} + source_dict["copy"] = test_dict deepcopy_cases = ( ( - ('copy',), + ("copy",), { - 'deepcopy': True, + "deepcopy": True, }, - {'copy': {'test': 0}}, - 'test deepcopy value', + {"copy": {"test": 0}}, + "test deepcopy value", ), ( - ('copy',), + ("copy",), {}, - {'copy': test_dict}, - 'test reference value', + {"copy": test_dict}, + "test reference value", ), - ( - ('default',), + ("default",), { - 'use_default': True, - 'default': test_dict, - 'deepcopy_default': True, + "use_default": True, + "default": test_dict, + "deepcopy_default": True, }, - {'default': {'test': 0}}, - 'test deepcopy default', + {"default": {"test": 0}}, + "test deepcopy default", ), ( - ('default',), + ("default",), { - 'use_default': True, - 'default': test_dict, + "use_default": True, + "default": test_dict, }, - {'default': test_dict}, - 'test reference default', + {"default": test_dict}, + "test reference default", ), ( - ('copy', 'default'), + ("copy", "default"), { - 'use_default': True, - 'default': test_dict, - 'deepcopy': True, + "use_default": True, + "default": test_dict, + "deepcopy": True, }, { - 'copy': {'test': 0}, - 'default': test_dict, + "copy": {"test": 0}, + "default": test_dict, }, - 'test deepcopy value and reference default', + "test deepcopy value and reference default", ), ( - ('copy', 'default'), + ("copy", "default"), { - 'use_default': True, - 'default': test_dict, - 'deepcopy_default': True, + "use_default": True, + "default": test_dict, + "deepcopy_default": True, }, { - 'copy': test_dict, - 'default': {'test': 0}, + "copy": test_dict, + "default": {"test": 0}, }, - 'test reference value and deepcopy default', + "test reference value and deepcopy default", ), ) for flds, kwargs, expected, msg in deepcopy_cases: - dd('msg :', msg) - dd('expected: ', expected) + dd("msg :", msg) + dd("expected: ", expected) rst = k3dict.subdict(source_dict, flds, **kwargs) - dd('result: ', rst) + dd("result: ", rst) self.assertEqual(expected, rst) - if 'deepcopy' not in kwargs: + if "deepcopy" not in kwargs: for k in [x for x in flds if x in source_dict]: self.assertIs(expected[k], rst[k]) - if 'deepcopy_default' not in kwargs: + if "deepcopy_default" not in kwargs: for k in [x for x in flds if x not in source_dict]: self.assertIs(expected[k], rst[k]) class UserDefinedType(object): - def __init__(self, value=None): self.value = value class ForTestDict(k3dict.FixedKeysDict): - keys_default = { - 'key_1': str, - 'key_2': int, - 'key_3': dict, - 'key_4': UserDefinedType, + "key_1": str, + "key_2": int, + "key_3": dict, + "key_4": UserDefinedType, } - ident_keys = ('key_2', 'key_1') + ident_keys = ("key_2", "key_1") class StrictFixedKeysDictFoo(k3dict.FixedKeysDict): keys_default = { # Does not accept default value. # Must specified when init it. - 'foo': lambda a: a + "foo": lambda a: a } class TestFixedKeysDict(unittest.TestCase): - def setUp(self): pass @@ -1670,22 +1780,19 @@ def test_strict(self): StrictFixedKeysDictFoo(foo=1) def test_fixed_keys_dict(self): - cases = ( - (('10.1', 32, {'a': 1}, 'user_value'), (32, '10.1')), - ((10.1, '32', {'a': 1}, 'user_value'), (32, '10.1')), - ((10.1, '32', [('a', 1)], 'user_value'), (32, '10.1')), - - ((10.1, '32', [('a', 1)]), (32, '10.1')), - ((10.1,), (0, '10.1')), - ((), (0, '')), + (("10.1", 32, {"a": 1}, "user_value"), (32, "10.1")), + ((10.1, "32", {"a": 1}, "user_value"), (32, "10.1")), + ((10.1, "32", [("a", 1)], "user_value"), (32, "10.1")), + ((10.1, "32", [("a", 1)]), (32, "10.1")), + ((10.1,), (0, "10.1")), + ((), (0, "")), ) for case, ident in cases: - argkv = {} for i in range(len(case)): - argkv['key_'+str(i+1)] = case[i] + argkv["key_" + str(i + 1)] = case[i] d = ForTestDict(argkv) self.assertEqual(d.ident(), ident) @@ -1693,61 +1800,63 @@ def test_fixed_keys_dict(self): d = ForTestDict(**argkv) self.assertEqual(d.ident(), ident) - self.assertIsInstance(d['key_4'], UserDefinedType) + self.assertIsInstance(d["key_4"], UserDefinedType) def test_key_value_error(self): - case = { - 'key_0': 0, - 'key_1': 10.1, - 'key_2': 32, + "key_0": 0, + "key_1": 10.1, + "key_2": 32, } def _init(): return ForTestDict(**case) + self.assertRaises(KeyError, _init) case = { - 'key_1': 10.1, - 'key_2': 32, + "key_1": 10.1, + "key_2": 32, } d = ForTestDict(case) def _set(): - d['key_2'] = 10 - d['key_0'] = 10 + d["key_2"] = 10 + d["key_0"] = 10 + self.assertRaises(KeyError, _set) - self.assertEqual(d['key_2'], 10) + self.assertEqual(d["key_2"], 10) case = { - 'key_1': 10.1, - 'key_2': 32, + "key_1": 10.1, + "key_2": 32, } d = ForTestDict(case) def _set(): - d['key_2'] = '32.0' + d["key_2"] = "32.0" + self.assertRaises(ValueError, _set) def _set(): - d['key_3'] = UserDefinedType(10) + d["key_3"] = UserDefinedType(10) + self.assertRaises(ValueError, _set) def test_ident(self): d = { - 'key_1': '10.1', - 'key_2': '32', - 'key_3': {'a': 1}, + "key_1": "10.1", + "key_2": "32", + "key_3": {"a": 1}, } d = ForTestDict(d) cases = ( ((), ()), - (('key_1',), ('10.1',)), - (('key_1', 'key_2'), ('10.1', 32)), - (('key_1', 'key_2', 'key_3'), ('10.1', 32, {'a': 1})), - - (('key_3', 'key_1', 'key_2'), ({'a': 1}, '10.1', 32)), + (("key_1",), ("10.1",)), + (("key_1", "key_2"), ("10.1", 32)), + (("key_1", "key_2", "key_3"), ("10.1", 32, {"a": 1})), + (("key_3", "key_1", "key_2"), ({"a": 1}, "10.1", 32)), ) for ident_keys, ident in cases: @@ -1756,13 +1865,14 @@ def test_ident(self): def test_ident_keyerror(self): d = { - 'key_1': '10.1', - 'key_2': '32', + "key_1": "10.1", + "key_2": "32", } d = ForTestDict(d) - d.ident_keys = ('no_such_key',) + d.ident_keys = ("no_such_key",) def _ident(): return d.ident() + self.assertRaises(KeyError, _ident)