pax_global_header00006660000000000000000000000064142623444420014517gustar00rootroot0000000000000052 comment=74c527265e56a46a2485561fbc74646a063df5da pawelzny-dotty_dict-74c5272/000077500000000000000000000000001426234444200160135ustar00rootroot00000000000000pawelzny-dotty_dict-74c5272/.bumpversion.cfg000066400000000000000000000003271426234444200211250ustar00rootroot00000000000000[bumpversion] current_version = 1.3.1 commit = False tag = False [bumpversion:file:dotty_dict/__init__.py] [bumpversion:file:docs/conf.py] [bumpversion:file:pyproject.toml] search = version = "{current_version}" pawelzny-dotty_dict-74c5272/.circleci/000077500000000000000000000000001426234444200176465ustar00rootroot00000000000000pawelzny-dotty_dict-74c5272/.circleci/config.yml000066400000000000000000000064111426234444200216400ustar00rootroot00000000000000# Python CircleCI 2.1 configuration file version: 2.1 defaults: &defaults working_directory: ~/repo read_cache: &read_cache keys: - v1-dependencies-{{ checksum "poetry.lock" }} - v1-dependencies- store_cache: &store_cache paths: - ./repo key: v1-dependencies-{{ checksum "poetry.lock" }} run_tests: &run_tests name: run tests command: poetry run pytest install_deps: &install_deps name: install dependencies command: poetry install --no-root jobs: py35: <<: *defaults docker: - image: cimg/python:3.5.10 steps: - checkout - restore_cache: *read_cache - run: *install_deps - save_cache: *store_cache - run: *run_tests py36: <<: *defaults docker: - image: cimg/python:3.6.15 steps: - checkout - restore_cache: *read_cache - run: *install_deps - save_cache: *store_cache - run: *run_tests py37: <<: *defaults docker: - image: cimg/python:3.7.13 steps: - checkout - restore_cache: *read_cache - run: *install_deps - save_cache: *store_cache - run: *run_tests py38: <<: *defaults docker: - image: cimg/python:3.8.13 steps: - checkout - restore_cache: *read_cache - run: *install_deps - save_cache: *store_cache - run: *run_tests py39: <<: *defaults docker: - image: cimg/python:3.9.13 steps: - checkout - restore_cache: *read_cache - run: *install_deps - save_cache: *store_cache - run: *run_tests py310: <<: *defaults docker: - image: cimg/python:3.10.5 steps: - checkout - restore_cache: *read_cache - run: *install_deps - save_cache: *store_cache - run: *run_tests pypy38: docker: - image: pypy:3.8-7.3.9 <<: *defaults steps: - checkout - restore_cache: *read_cache - run: name: install dependencies command: pip install pytest - save_cache: *store_cache - run: name: run tests command: pytest pypy39: docker: - image: pypy:3.9-7.3.9 <<: *defaults steps: - checkout - restore_cache: *read_cache - run: name: install dependencies command: pip install pytest - save_cache: *store_cache - run: name: run tests command: pytest flake8: docker: - image: cimg/python:3.8.13 <<: *defaults steps: - checkout - restore_cache: *read_cache - run: *install_deps - save_cache: *store_cache - run: name: run flake8 command: poetry run flake8 docs: docker: - image: cimg/python:3.8.13 <<: *defaults steps: - checkout - restore_cache: *read_cache - run: *install_deps - run: name: Install Sphinx command: | sudo apt update sudo apt install -y python3-sphinx python3-sphinx-rtd-theme - save_cache: *store_cache - run: name: run doctest command: sphinx-build -aEW -b html -d docs/_build/doctrees docs docs/_build/html workflows: version: 2 linters: jobs: - flake8 unit-tests: jobs: - py35 - py36 - py37 - py38 - py39 - py310 - pypy38 - pypy39 release-dry-run: jobs: - docs pawelzny-dotty_dict-74c5272/.editorconfig000066400000000000000000000006221426234444200204700ustar00rootroot00000000000000# http://editorconfig.org root = true [*] indent_style = space indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true charset = utf-8 end_of_line = lf [{LICENSE,poetry.lock}] insert_final_newline = false [Makefile] indent_style = tab insert_final_newline = false [*.{yml,yaml}] indent_style = space indent_size = 2 trim_trailing_whitespace = true insert_final_newline = false pawelzny-dotty_dict-74c5272/.gitignore000066400000000000000000000014501426234444200200030ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ .venv*/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # 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/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ # pyenv python configuration file .python-version .idea/ /.pytest_cache/ pawelzny-dotty_dict-74c5272/AUTHORS.rst000066400000000000000000000004711426234444200176740ustar00rootroot00000000000000======= Credits ======= *********** Development *********** * Pawel Zadrozny @pawelzny ************ Contributors ************ * Linus Groh @linusg * Andreas Motl @amotl * Aneesh Devasthale @aneeshd16 * Szymon Piotr Krasuski @Dysproz Read more how to contribute on :ref:`Contributing`. pawelzny-dotty_dict-74c5272/CONTRIBUTING.rst000066400000000000000000000061611426234444200204600ustar00rootroot00000000000000============ Contributing ============ Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. You can contribute in many ways: ********************** Types of Contributions ********************** Report Bugs =========== Report bugs at https://github.com/pawelzny/dotty_dict/issues If you are reporting a bug, please include: * Your operating system name and version. * Any details about your local setup that might be helpful in troubleshooting. * Detailed steps to reproduce the bug. Fix Bugs ======== Look through the GitHub issues for bugs. Anything tagged with "bug" is open to whoever wants to implement it. Implement Features ================== Look through the GitHub issues for features. Anything tagged with "feature" is open to whoever wants to implement it. Write Documentation =================== authentication could always use more documentation, whether as part of the official authentication docs, in docstrings, or even on the web in blog posts, articles, and such. Submit Feedback =============== The best way to send feedback is to file an issue at https://github.com/pawelzny/dotty_dict/issues If you are proposing a feature: * Explain in detail how it would work. * Keep the scope as narrow as possible, to make it easier to implement. * Remember that this is a volunteer-driven project, and that contributions are welcome :) ************ Get Started! ************ Ready to contribute? Here's how to set up `dotty_dict` for local development. 1. Fork the `dotty_dict` repo on GitHub. 2. Clone your fork locally:: $ git clone git@github.com:your_name_here/dotty_dict.git 3. Install your local copy into a virtualenv. This is how you set up your fork for local development:: $ cd dotty_dict/ $ make install or if you don't have 'make', do it manually:: $ cd dotty_dict/ $ pip install poetry==1.1.14 $ poetry install --no-root 4. Create a branch for local development:: $ git checkout -b name-of-your-bugfix-or-feature Now you can introduce your changes locally. 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: $ make test-all or if you don't have 'make', run tox directly:: $ poetry run tox --skip-missing-interpreters 6. Commit your changes and push your branch to GitHub:: $ git add . $ git commit -m "Your detailed description of your changes." $ git push origin HEAD 7. Submit a pull request through the GitHub website. *********************** Pull Request Guidelines *********************** Before you submit a pull request, check that it meets these guidelines: 1. The pull request should include tests. 2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst. 3. The pull request should work for: **Python >=3.5,<4.0** and for **>=PyPy3.8-7.3.9**. Check https://circleci.com/gh/pawelzny/dotty_dict and make sure that the tests pass for all supported Python versions. pawelzny-dotty_dict-74c5272/LICENSE000066400000000000000000000020601426234444200170160ustar00rootroot00000000000000MIT License Copyright (c) 2017, Pawel Zadrozny Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pawelzny-dotty_dict-74c5272/MANIFEST.in000066400000000000000000000004541426234444200175540ustar00rootroot00000000000000include AUTHORS.rst include CONTRIBUTING.rst include LICENSE include README.rst include pyproject.toml include poetry.lock recursive-exclude * __pycache__ recursive-exclude * *.py[co] recursive-include tests * recursive-include vo * recursive-include docs *.rst conf.py Makefile *.jpg *.png *.gif pawelzny-dotty_dict-74c5272/Makefile000066400000000000000000000025571426234444200174640ustar00rootroot00000000000000.PHONY: clean-pyc clean-build docs help .DEFAULT_GOAL := help help: @perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}' install: ## install dependencies pip install poetry==1.1.14 poetry install --no-root clean: clean-build clean-pyc clean-cache clean-build: ## remove build artifacts rm -fr build/ rm -fr dist/ rm -fr *.egg-info clean-pyc: ## remove Python file artifacts find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + clean-cache: ## remove .cache and .pytest_cache rm -rf .cache rm -rf .pytest_cache lint: ## check style with flake8 poetry run flake8 test: ## run tests quickly with the default Python poetry run pytest test-all: ## run tests on every Python version with tox poetry run tox --skip-missing-interpreters coverage: ## check code coverage quickly with the default Python rm -rf htmlcov poetry run coverage erase poetry run coverage run -m pytest poetry run coverage report -m poetry run coverage html docs: ## generate Sphinx HTML documentation, including API docs $(MAKE) -C docs clean $(MAKE) -C docs html release: sdist ## package and upload a release pipenv run twine upload dist/* build: clean ## package poetry build # gpg --detach-sign -a dist/*.tar.gz ls -l dist pawelzny-dotty_dict-74c5272/README.rst000066400000000000000000000101431426234444200175010ustar00rootroot00000000000000********** Dotty-Dict ********** :Info: Dictionary wrapper for quick access to deeply nested keys. :Author: Pawel Zadrozny @pawelzny .. image:: https://circleci.com/gh/pawelzny/dotty_dict/tree/master.svg?style=shield&circle-token=77f51e87481f339d69ca502fdbb0c2b1a76c0369 :target: https://circleci.com/gh/pawelzny/dotty_dict/tree/master :alt: CI Status .. image:: https://readthedocs.org/projects/vo/badge/?version=latest :target: http://dotty-dict.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://img.shields.io/pypi/v/dotty_dict.svg :target: https://pypi.org/project/dotty_dict/ :alt: PyPI Repository Status .. image:: https://img.shields.io/github/release/pawelzny/dotty_dict.svg :target: https://github.com/pawelzny/dotty_dict :alt: Release Status .. image:: https://img.shields.io/pypi/status/dotty_dict.svg :target: https://pypi.org/project/dotty_dict/ :alt: Project Status .. image:: https://img.shields.io/pypi/pyversions/dotty_dict.svg :target: https://pypi.org/project/dotty_dict/ :alt: Supported python versions .. image:: https://img.shields.io/pypi/implementation/dotty_dict.svg :target: https://pypi.org/project/dotty_dict/ :alt: Supported interpreters .. image:: https://img.shields.io/pypi/l/dotty_dict.svg :target: https://github.com/pawelzny/dotty_dict/blob/master/LICENSE :alt: License Features ======== * Simple wrapper around python dictionary and dict like objects * Two wrappers with the same dict are considered equal * Access to deeply nested keys with dot notation: ``dot['deeply.nested.key']`` * Create, read, update and delete nested keys of any length * Expose all dictionary methods like ``.get``, ``.pop``, ``.keys`` and other * Access dicts in lists by index ``dot['parents.0.first_name']`` * key=value caching to speed up lookups and low down memory consumption * support for setting value in multidimensional lists * support for accessing lists with slices Installation ============ .. code:: bash pip install dotty-dict * **Package**: https://pypi.org/project/dotty-dict/ * **Source**: https://github.com/pawelzny/dotty_dict Documentation ============= * Full documentation: http://dotty-dict.readthedocs.io * Public API: http://dotty-dict.readthedocs.io/en/latest/api.html * Examples and usage ideas: http://dotty-dict.readthedocs.io/en/latest/examples.html TODO ==== Waiting for your feature requests ;) Quick Example ============= Create new dotty using factory function. .. code-block:: python >>> from dotty_dict import dotty >>> dot = dotty({'plain': {'old': {'python': 'dictionary'}}}) >>> dot['plain.old'] {'python': 'dictionary'} You can start with empty dotty .. code-block:: python >>> from dotty_dict import dotty >>> dot = dotty() >>> dot['very.deeply.nested.thing'] = 'spam' >>> dot Dotty(dictionary={'very': {'deeply': {'nested': {'thing': 'spam'}}}}, separator='.', esc_char='\\') >>> dot['very.deeply.spam'] = 'indeed' >>> dot Dotty(dictionary={'very': {'deeply': {'nested': {'thing': 'spam'}, 'spam': 'indeed'}}}, separator='.', esc_char='\\') >>> del dot['very.deeply.nested'] >>> dot Dotty(dictionary={'very': {'deeply': {'spam': 'indeed'}}}, separator='.', esc_char='\\') >>> dot.get('very.not_existing.key') None NOTE: Using integer in dictionary keys will be treated as embedded list index. Install for development ======================= Install dev dependencies .. code-block:: console $ make install Testing ======= .. code-block:: console $ make test Or full tests with TOX: .. code-block:: console $ make test-all Limitations =========== In some very rare cases dotty may not work properly. * When nested dictionary has two keys of different type, but with the same value. In that case dotty will return dict or list under random key with passed value. * Keys in dictionary may not contain dots. If you need to use dots, please specify dotty with custom separator. * Nested keys may not be bool type. Bool type keys are only supported when calling keys with type defined value (e.g. dot[True], dot[False]). pawelzny-dotty_dict-74c5272/docs/000077500000000000000000000000001426234444200167435ustar00rootroot00000000000000pawelzny-dotty_dict-74c5272/docs/Makefile000066400000000000000000000151771426234444200204160ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -aEW -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/dotty_dict.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/dotty_dict.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/dotty_dict" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/dotty_dict" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." pawelzny-dotty_dict-74c5272/docs/_static/000077500000000000000000000000001426234444200203715ustar00rootroot00000000000000pawelzny-dotty_dict-74c5272/docs/_static/.gitkeep000066400000000000000000000000001426234444200220100ustar00rootroot00000000000000pawelzny-dotty_dict-74c5272/docs/_templates/000077500000000000000000000000001426234444200211005ustar00rootroot00000000000000pawelzny-dotty_dict-74c5272/docs/_templates/layout.html000066400000000000000000000006131426234444200233030ustar00rootroot00000000000000{% extends "!layout.html" %} {% block extrabody %} {{ super() }} {% endblock %} pawelzny-dotty_dict-74c5272/docs/api.rst000066400000000000000000000012071426234444200202460ustar00rootroot00000000000000========== Public API ========== .. seealso:: Check out :ref:`examples` derived from real and fully tested source code. .. py:module:: dotty_dict.dotty_dict .. autofunction:: dotty .. autoclass:: Dotty :member-order: alphabetical :members: .. method:: clear() Removes all elements from dotty dict. .. method:: items() Returns generator of dotty dict's (key, value) tuple pairs. .. method:: keys() Returns generator of dotty dict's keys. .. method:: values() Returns generator of dotty dict's values. .. method:: update(dict2) Adds dictionary dict2's key-values pairs to dotty dict. pawelzny-dotty_dict-74c5272/docs/authors.rst000066400000000000000000000000341426234444200211570ustar00rootroot00000000000000.. include:: ../AUTHORS.rst pawelzny-dotty_dict-74c5272/docs/conf.py000077500000000000000000000126331426234444200202520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/stable/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys __version__ = '1.3.1' sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- project = 'Dotty Dict' copyright = u'2017, Pawel Zadrozny' author = u'Pawel Zadrozny' # The short X.Y version version = __version__ # The full version, including alpha/beta/rc tags release = __version__ # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # 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.doctest', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'sphinx.ext.githubpages', 'sphinx.ext.autosectionlabel', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # 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 = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- 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 = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # 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'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'DottyDictdoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'DottyDict.tex', 'Dotty Dict Documentation', u'Pawel Zadrozny', 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'DottyDict', 'Dotty Dict Documentation', [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'DottyDict', 'Dotty Dict Documentation', author, 'DottyDict', 'Dict-like object allowing access to keys with dot notation.', 'Miscellaneous'), ] # -- Options for Epub output ------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project epub_author = author epub_publisher = author epub_copyright = copyright # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html', '.nojekyll'] # -- Extension configuration ------------------------------------------------- pawelzny-dotty_dict-74c5272/docs/contributing.rst000066400000000000000000000000411426234444200221770ustar00rootroot00000000000000.. include:: ../CONTRIBUTING.rst pawelzny-dotty_dict-74c5272/docs/dotty_dict.rst000066400000000000000000000000331426234444200216370ustar00rootroot00000000000000.. include:: ../README.rst pawelzny-dotty_dict-74c5272/docs/examples.rst000066400000000000000000000112401426234444200213110ustar00rootroot00000000000000======== Examples ======== Yes, I know it's dangerous to follow code examples. Usually examples aren't in sync with real source code. But I found a solution ... I hope! .. note:: | All examples are derived from real code hooked to Pytest. | Every change in source code enforce change in examples. | **Outdated examples == failed build**. | | You can check at https://github.com/pawelzny/dotty_dict/blob/master/tests/test_examples.py .. seealso:: Look at :ref:`Public API` for more details. ****** Basics ****** The easiest way to use Dotty dict is with function factory. Factory takes only one, optional dictionary as argument. If leaved empty, factory function will create new, empty dictionary. Wrap existing dict ================== .. literalinclude:: ../example/basics.py :language: python :dedent: 4 :start-after: wrap_existing_dict :end-before: # end of wrap_existing_dict Create new dotty ================ .. literalinclude:: ../example/basics.py :language: python :dedent: 4 :start-after: create_new_dotty :end-before: # end of create_new_dotty Builtin methods =============== Dotty exposes all native to dict, builtin methods. Only change is made to method which uses key as input to accept dot notation. .. literalinclude:: ../example/basics.py :language: python :dedent: 4 :start-after: builtin_methods :end-before: # end of builtin_methods ******** Advanced ******** Lets simulate more real scenario. API requests and responses are often very complex with many deeply nested keys. And when you need to check one of them it may looks like: ``res.get('data', {}).get('service', {}).get('status', {}).get('current', False)``. **It's awful!** All this empty dictionary fallback to dig in for current status! Make API request ================ In this scenario we will send post request to create new user with superuser privileges. Below there is example response as dictionary, and then the way to check granted privileges. .. literalinclude:: ../example/advanced.py :language: python :dedent: 4 :start-after: api_request :end-before: # end of api_request :emphasize-lines: 45 Access dict with embedded lists =============================== This scenario shows how to access subfield in a list. .. literalinclude:: ../example/advanced.py :language: python :dedent: 4 :start-after: list_embedded :end-before: # end of list_embedded :emphasize-lines: 4 Access multiple fields with list slices ======================================= This scenario shows how to access multiple subfields in a list of dicts. .. literalinclude:: ../example/advanced.py :language: python :dedent: 4 :start-after: list_slices :end-before: # end of list_slices :emphasize-lines: 4 Access numeric fields as dict keys ================================== This scenario shows how to access numeric keys which should not be treated as list indices. .. literalinclude:: ../example/advanced.py :language: python :dedent: 4 :start-after: no_list_flag :end-before: # end of no_list_flag :emphasize-lines: 4 Escape character ================ In some cases we want to preserve dot in key name and do not treat it as keys separator. It can by done with escape character. .. literalinclude:: ../example/advanced.py :language: python :dedent: 4 :start-after: escape_character :end-before: # end of escape_character Escape the escape character =========================== What if escape character should be preserved as integral key name, but it happens to be placed right before separator character? The answer is: Escape the escape character. .. warning:: Be careful because backslashes in Python require special treatment. .. literalinclude:: ../example/advanced.py :language: python :dedent: 4 :start-after: escape_the_escape_character :end-before: # end of escape_the_escape_character ************* Customization ************* By default Dotty uses dot as keys separator and backslash as escape character. In special occasions you may want to use different set of chars. Customization require using Dotty class directly instead of factory function. Custom separator ================ In fact any valid string can be used as separator. .. literalinclude:: ../example/customization.py :language: python :dedent: 4 :start-after: custom_separator :end-before: # end of custom_separator Custom escape char ================== As separator, escape character can be any valid string not only single character. .. literalinclude:: ../example/customization.py :language: python :dedent: 4 :start-after: custom_escape_char :end-before: # end of custom_escape_char pawelzny-dotty_dict-74c5272/docs/index.rst000066400000000000000000000012741426234444200206100ustar00rootroot00000000000000.. Dotty Dict documentation master file, created by sphinx-quickstart on Thu Mar 15 08:04:44 2018. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. ========== Dotty Dict ========== Dotty Dict is a wrapper around builtin dictionary. Provides quick access to deeply nested keys and values with dot notation. Dotty Dict expose dictionary public API as proxy to dict implemented underneath and should work with all dict-like objects which are instances of Mapping. .. toctree:: :maxdepth: 2 :caption: Contents: dotty_dict examples api authors contributing ******* LICENSE ******* .. include:: ../LICENSE pawelzny-dotty_dict-74c5272/dotty_dict/000077500000000000000000000000001426234444200201615ustar00rootroot00000000000000pawelzny-dotty_dict-74c5272/dotty_dict/__init__.py000066400000000000000000000003601426234444200222710ustar00rootroot00000000000000# -*- coding: utf-8 -*- from dotty_dict.dotty_dict import Dotty, dotty __author__ = 'Pawel Zadrozny' __copyright__ = 'Copyright (c) 2017, Pawel Zadrozny' __email__ = 'pawel.zny@gmail.com' __version__ = '1.3.1' __all__ = ['Dotty', 'dotty'] pawelzny-dotty_dict-74c5272/dotty_dict/dotty_dict.py000066400000000000000000000275211426234444200227100ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- try: from collections.abc import Mapping except ImportError: from collections import Mapping from functools import lru_cache import json __author__ = 'Pawel Zadrozny' __copyright__ = 'Copyright (c) 2017, Pawel Zadrozny' def dotty(dictionary=None, no_list=False): """Factory function for Dotty class. Create Dotty wrapper around existing or new dictionary. :param dict dictionary: Any dictionary or dict-like object :param bool no_list: If set to True then numeric keys will NOT be converted to list indices :return: Dotty instance """ if dictionary is None: dictionary = {} return Dotty(dictionary, separator='.', esc_char='\\', no_list=no_list) class Dotty: """Dictionary and dict-like objects wrapper. Dotty wraps dictionary and provides proxy for quick accessing to deeply nested keys and values using dot notation. Dot notation can be customize in special cases. Let's say dot character has special meaning, and you want to use other character for accessing deep keys. Dotty does not copy original dictionary but it operates on it. All changes made in original dictionary are reflected in dotty wrapped dict and vice versa. :param dict dictionary: Any dictionary or dict-like object :param str separator: Character used to chain deep access. :param str esc_char: Escape character for separator. :param bool no_list: If set to True then numeric keys will NOT be converted to list indices """ def __init__(self, dictionary, separator='.', esc_char='\\', no_list=False): if not isinstance(dictionary, (Mapping, dict)): raise AttributeError('Dictionary must be type of dict') else: self._data = dictionary self.separator = separator self.esc_char = esc_char self.no_list = no_list def __repr__(self): return 'Dotty(dictionary={}, separator={!r}, esc_char={!r})'.format( self._data, self.separator, self.esc_char) def __str__(self): return str(self._data) def __hash__(self): return hash(str(self)) def __eq__(self, other): try: return sorted(self._data.items()) == sorted(other.items()) except AttributeError: return False def __len__(self): return len(self._data) def __getattr__(self, item): return getattr(self._data, item) def __contains__(self, item): def search_in(items, data): """Recursively search for deep key in dict. :param list items: List of dictionary keys :param data: Portion of dictionary to operate on :return bool: Predicate of key existence """ it = items.pop(0) if it.isdigit(): idx = int(it) if idx < len(data): if items: return search_in(items, data[idx]) else: return data[idx] else: return False if items and it in data: return search_in(items, data[it]) return it in data return search_in(self._split(item), self._data) @staticmethod def _find_data_type(item, data): """This method returns item in datatype that exists in data dict. Method creates set of types present in dict keys and then iterates through them trying to convert item into one of types and check whether item under this type exists in dict keys. If yes then it'll return converted item. Otherwise item stays the same type as it was on entry. :param item: Item to convert to proper type :type item: any type :return: Converted or unchanged item :rtype: any type """ data_types = [type(i) for i in data.keys()] for t in set(data_types): try: if t(item) in data: item = t(item) return item except ValueError: pass return item @lru_cache(maxsize=32) # noqa: B019 # TODO: find a workaround for B019 def __getitem__(self, item): def get_from(items, data): """Recursively get value from dictionary deep key. :param list items: List of dictionary keys :param data: Portion of dictionary to operate on :return: Value from dictionary :raises KeyError: If key does not exist """ it = items.pop(0) if isinstance(data, list) and it.isdigit() and not self.no_list: it = int(it) elif it not in data and isinstance(data, dict): it = self._find_data_type(it, data) elif isinstance(data, list) and ':' in it and not self.no_list: # TODO: fix C417 Unnecessary use of map - use a generator expression instead. list_slice = slice(*map(lambda x: None if x == '' else int(x), it.split(':'))) # noqa: C417 if items: return [get_from(items.copy(), x) for x in data[list_slice]] else: return data[list_slice] try: data = data[it] except TypeError: raise KeyError("List index must be an integer, got {}".format(it)) if items and data is not None: return get_from(items, data) else: return data return get_from(self._split(item), self._data) def __setitem__(self, key, value): def set_to(items, data): """Recursively set value to dictionary deep key. :param list items: List of dictionary keys :param data: Portion of dictionary to operate on """ it = items.pop(0) if items: if items[0].isdigit(): next_item = [] else: next_item = {} if it.isdigit(): it = int(it) try: if not data[it]: data[it] = next_item except IndexError: self.set_list_index(data, it, next_item) set_to(items, data[it]) else: if not data.get(it): data[it] = next_item set_to(items, data[it]) else: if it.isdigit(): self.set_list_index(data, it, value) else: data[it] = value set_to(self._split(key), self._data) @staticmethod def set_list_index(data, index, value): """Set value in list at specified index. All the values before target index should stay unchanged or be filled with None. :param data: List where value should be set :param index: String or Int of target index :param value: Target value to put under index """ for _ in range(len(data), int(index) + 1): data.append(None) else: data[int(index)] = value def __delitem__(self, key): def del_key(items, data): """Recursively remove deep key from dict. :param list items: List of dictionary keys :param data: Portion of dictionary to operate on :raises KeyError: If key does not exist """ it = items.pop(0) if it.isdigit(): it = int(it) if items: del_key(items, data[it]) else: del data[it] del_key(self._split(key), self._data) def copy(self): """Returns a shallow copy of dictionary wrapped in Dotty. :return: Dotty instance """ return dotty(self._data.copy()) @staticmethod def fromkeys(seq, value=None): """Create a new dictionary with keys from seq and values set to value. New created dictionary is wrapped in Dotty. :param seq: Sequence of elements which is to be used as keys for the new dictionary :param value: Value which is set to each element of the dictionary :return: Dotty instance """ return dotty(dict.fromkeys(seq, value)) def get(self, key, default=None): """Get value from deep key or default if key does not exist. This method match 1:1 with dict .get method except that it accepts deeply nested key with dot notation. :param str key: Single key or chain of keys :param Any default: Default value if deep key does not exist :return: Any or default value """ try: return self.__getitem__(key) except (KeyError, IndexError): return default def pop(self, key, default=None): """Pop key from Dotty. This method match 1:1 with dict .pop method except that it accepts deeply nested key with dot notation. :param str key: Single key or chain of keys :param Any default: If default is provided will be returned :raises KeyError: If key does not exist and default has not been provided :return: Any or default value """ def pop_from(items, data): it = items.pop(0) if it not in data: return default if items: data = data[it] return pop_from(items, data) else: return data.pop(it, default) return pop_from(self._split(key), self._data) def setdefault(self, key, default=None): """Get key value if exist otherwise set default value under given key and return its value. This method match 1:1 with dict .setdefault method except that it accepts deeply nested key with dot notation. :param str key: Single key or chain of keys :param Any default: Default value for not existing key :return: Value under given key or default """ try: return self.__getitem__(key) except KeyError: self.__setitem__(key, default) return default def to_dict(self): """Return wrapped dictionary. This method does not copy wrapped dictionary. :return dict: Wrapped dictionary """ return json.loads(self.to_json()) def to_json(self): """Return wrapped dictionary as json string. This method does not copy wrapped dictionary. :return str: Wrapped dictionary as json string """ return json.dumps(self._data, cls=DottyEncoder) def _split(self, key): """Split dot notated chain of keys. Works with custom separators and escape characters. :param str key: Single key or chain of keys :return list: List of keys """ if not isinstance(key, str): return [key] esc_stamp = (self.esc_char + self.separator, '<#esc#>') skp_stamp = ('\\' + self.esc_char + self.separator, '<#skp#>' + self.separator) stamp_esc = ('<#esc#>', self.separator) stamp_skp = ('<#skp#>', self.esc_char) key = key.replace(*skp_stamp).replace(*esc_stamp) keys = key.split(self.separator) for i, k in enumerate(keys): keys[i] = k.replace(*stamp_esc).replace(*stamp_skp) return keys class DottyEncoder(json.JSONEncoder): """Helper class for encoding of nested Dotty dicts into standard dict """ def default(self, obj): """Return dict data of Dotty when possible or encode with standard format :param object: Input object :return: Serializable data """ if hasattr(obj, '_data'): return obj._data else: return json.JSONEncoder.default(self, obj) pawelzny-dotty_dict-74c5272/example/000077500000000000000000000000001426234444200174465ustar00rootroot00000000000000pawelzny-dotty_dict-74c5272/example/__init__.py000066400000000000000000000015541426234444200215640ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import glob import importlib import os from inspect import getmembers, isfunction __author__ = 'Pawel Zadrozny' __copyright__ = 'Copyright (c) 2018, Pawel Zadrozny' def fetch_all_examples_for_testing(): """Fetch all functions from every module in example package. This list of functions will be used in test_example module running by py.test. This helps to include all examples automatically. :return: List of example functions :rtype: list """ example_func = [] for f in glob.glob(os.path.dirname(__file__) + "/*.py"): if os.path.isfile(f) and not os.path.basename(f).startswith('_'): mod = importlib.import_module('example.{}'.format(os.path.basename(f)[:-3])) example_func.extend([o[1] for o in getmembers(mod) if isfunction(o[1])]) return example_func pawelzny-dotty_dict-74c5272/example/advanced.py000066400000000000000000000117561426234444200215770ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'Pawel Zadrozny' __copyright__ = 'Copyright (c) 2018, Pawel Zadrozny' def api_request(): def make_request(payload): """Fake request for example purpose. :param dict payload: Example payload :return dict: Example response """ return { 'status': { 'code': 200, 'msg': 'User created', }, 'data': { 'user': { 'id': 123, 'personal': { 'name': 'Arnold', 'email': 'arnold@dotty.dict', }, 'privileges': { 'granted': ['login', 'guest', 'superuser'], 'denied': ['admin'], 'history': { 'actions': [ ['superuser granted', '2018-04-29T17:08:48'], ['login granted', '2018-04-29T17:08:48'], ['guest granted', '2018-04-29T17:08:48'], ['created', '2018-04-29T17:08:48'], ['signup_submit', '2018-04-29T17:08:47'], ], }, }, }, }, } from dotty_dict import dotty request = dotty() request['request.data.payload'] = {'name': 'Arnold', 'email': 'arnold@dotty.dict', 'type': 'superuser'} request['request.data.headers'] = {'content_type': 'application/json'} request['request.url'] = 'http://127.0.0.1/api/user/create' response = dotty(make_request(request.to_dict())) assert response['status.code'] == 200 assert 'superuser' in response['data.user.privileges.granted'] # end of api_request def list_embedded(): from dotty_dict import dotty # dotty supports embedded lists # WARNING! # Dotty used to support lists only with dotty_l. # This feature is depreciated and was removed - now lists have native support. # If you need old functionality pass additional flag 'no_list' to dotty dot = dotty({ 'annotations': [ {'label': 'app', 'value': 'webapi'}, {'label': 'role', 'value': 'admin'}, ], 'spec': { 'containers': [ ['gpu', 'tensorflow', 'ML'], ['cpu', 'webserver', 'sql'], ] } }) assert dot['annotations.0.label'] == 'app' assert dot['annotations.0.value'] == 'webapi' assert dot['annotations.1.label'] == 'role' assert dot['annotations.1.value'] == 'admin' assert dot['spec.containers.0.0'] == 'gpu' assert dot['spec.containers.0.1'] == 'tensorflow' assert dot['spec.containers.0.2'] == 'ML' assert dot['spec.containers.1.0'] == 'cpu' assert dot['spec.containers.1.1'] == 'webserver' assert dot['spec.containers.1.2'] == 'sql' # end of list_embedded def list_slices(): from dotty_dict import dotty # dotty supports standard Python slices for lists dot = dotty({ 'annotations': [ {'label': 'app', 'value': 'webapi'}, {'label': 'role', 'value': 'admin'}, {'label': 'service', 'value': 'mail'}, {'label': 'database', 'value': 'postgres'} ], }) assert dot['annotations.:.label'] == ['app', 'role', 'service', 'database'] assert dot['annotations.:2.label'] == ['app', 'role'] assert dot['annotations.2:.label'] == ['service', 'database'] assert dot['annotations.::2.label'] == ['app', 'service'] # end of list_slices def no_list_flag(): from dotty_dict import dotty # For special use cases dotty supports dictionary key only access # With additional flag no_list passed to dotty # all digits and slices will be treated as string keys dot = dotty({ 'special': { '1': 'one', ':': 'colon', '2:': 'two colons' } }) assert dot['special.1'] == 'one' assert dot['special.:'] == 'colon' assert dot['special.2:'] == 'two colons' # end of no_list_flag def escape_character(): from dotty_dict import dotty dot = dotty({ 'deep': { 'key': 'value', }, 'key.with.dot': { 'deeper': 'other value', }, }) # how to access deeper value? assert dot[r'key\.with\.dot.deeper'] == 'other value' # end of escape_character def escape_the_escape_character(): from dotty_dict import dotty dot = dotty({ 'deep': { 'key': 'value', }, 'key.with_backslash\\': { # backslash at the end of key 'deeper': 'other value', }, }) # escape first dot and escape the escape character before second dot assert dot[r'key\.with_backslash\\.deeper'] == 'other value' # end of escape_the_escape_character pawelzny-dotty_dict-74c5272/example/basics.py000066400000000000000000000046051426234444200212710ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'Pawel Zadrozny' __copyright__ = 'Copyright (c) 2018, Pawel Zadrozny' def wrap_existing_dict(): from dotty_dict import dotty data = {'status': 'ok', 'code': 200, 'data': {'timestamp': 1525018224, 'payload': []}} data = dotty(data) assert data['data.timestamp'] == 1525018224 # end of wrap_existing_dict def create_new_dotty(): from dotty_dict import dotty data = dotty() data['status'] = 'ok' data['data.timestamp'] = 1525018224 data['data.fancy.deeply.nested.key.for'] = 'fun' assert data == {'status': 'ok', 'data': { 'timestamp': 1525018224, 'fancy': { 'deeply': { 'nested': { 'key': { 'for': 'fun', }, }, }, }, }} # end of create_new_dotty def builtin_methods(): from dotty_dict import dotty dot = dotty({'status': 'ok', 'data': { 'timestamp': 1525018224, 'fancy': { 'deeply': { 'nested': { 'key': { 'for': 'fun', }, }, }, }, }}) # get value, return None if not exist assert dot.get('data.payload') is None # pop key assert dot.pop('data.fancy.deeply.nested.key') == {'for': 'fun'} # get value and set new value if not exist assert dot.setdefault('data.payload', []) == [] assert 'payload' in dot['data'] # check what changed assert dot == {'status': 'ok', 'data': { 'timestamp': 1525018224, 'fancy': { 'deeply': { 'nested': {}, }, }, 'payload': [], }} # get keys assert sorted(dot.keys()) == ['data', 'status'] # end of builtin_methods pawelzny-dotty_dict-74c5272/example/customization.py000066400000000000000000000011151426234444200227260ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'Pawel Zadrozny' __copyright__ = 'Copyright (c) 2018, Pawel Zadrozny' def custom_separator(): from dotty_dict import Dotty dot = Dotty({'deep': {'deeper': {'harder': 'faster'}}}, separator='$', esc_char='\\') assert dot['deep$deeper$harder'] == 'faster' # end of custom_separator def custom_escape_char(): from dotty_dict import Dotty dot = Dotty({'deep.deeper': {'harder': 'faster'}}, separator='.', esc_char='#') assert dot['deep#.deeper.harder'] == 'faster' # end of custom_escape_char pawelzny-dotty_dict-74c5272/poetry.lock000066400000000000000000001570161426234444200202210ustar00rootroot00000000000000[[package]] name = "alabaster" version = "0.7.12" description = "A configurable sidebar-enabled Sphinx theme" category = "dev" optional = false python-versions = "*" [[package]] name = "atomicwrites" version = "1.4.1" description = "Atomic file writes." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" version = "21.4.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "babel" version = "2.10.3" description = "Internationalization utilities" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] pytz = ">=2015.7" [[package]] name = "bandit" version = "1.7.4" description = "Security oriented static analyser for python code." category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} GitPython = ">=1.0.1" PyYAML = ">=5.3.1" stevedore = ">=1.20.0" [package.extras] test = ["coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "toml", "beautifulsoup4 (>=4.8.0)", "pylint (==1.9.4)"] toml = ["toml"] yaml = ["pyyaml"] [[package]] name = "bump2version" version = "1.0.1" description = "Version-bump your software with a single command!" category = "dev" optional = false python-versions = ">=3.5" [[package]] name = "certifi" version = "2022.6.15" description = "Python package for providing Mozilla's CA Bundle." category = "dev" optional = false python-versions = ">=3.6" [[package]] name = "charset-normalizer" version = "2.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "dev" optional = false python-versions = ">=3.6.0" [package.extras] unicode_backport = ["unicodedata2"] [[package]] name = "colorama" version = "0.4.5" description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" version = "6.4.1" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" [package.extras] toml = ["tomli"] [[package]] name = "distlib" version = "0.3.4" description = "Distribution utilities" category = "dev" optional = false python-versions = "*" [[package]] name = "docutils" version = "0.17.1" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "filelock" version = "3.2.1" description = "A platform independent file lock." category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.extras] docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] testing = ["coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] [[package]] name = "flake8" version = "3.8.4" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.6.0a1,<2.7.0" pyflakes = ">=2.2.0,<2.3.0" [[package]] name = "flake8-bandit" version = "3.0.0" description = "Automated security testing with bandit and flake8." category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] bandit = ">=1.7.3" flake8 = "*" flake8-polyfill = "*" pycodestyle = "*" [[package]] name = "flake8-bugbear" version = "22.7.1" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] attrs = ">=19.2.0" flake8 = ">=3.0.0" [package.extras] dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit"] [[package]] name = "flake8-builtins" version = "1.5.3" description = "Check for python builtins being used as variables or parameters." category = "dev" optional = false python-versions = "*" [package.dependencies] flake8 = "*" [package.extras] test = ["coverage", "coveralls", "mock", "pytest", "pytest-cov"] [[package]] name = "flake8-comprehensions" version = "3.10.0" description = "A flake8 plugin to help you write better list/set/dict comprehensions." category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] flake8 = ">=3.0,<3.2.0 || >3.2.0" importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "flake8-polyfill" version = "1.0.2" description = "Polyfill package for Flake8 plugins" category = "dev" optional = false python-versions = "*" [package.dependencies] flake8 = "*" [[package]] name = "flake8-rst-docstrings" version = "0.2.6" description = "Python docstring reStructuredText (RST) validator" category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] flake8 = ">=3.0.0" pygments = "*" restructuredtext-lint = "*" [[package]] name = "flake9" version = "3.8.3.post2" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false python-versions = ">=3.4" [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.6.0a1,<2.7.0" pyflakes = ">=2.2.0,<2.3.0" [[package]] name = "gitdb" version = "4.0.9" description = "Git Object Database" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] smmap = ">=3.0.1,<6" [[package]] name = "gitpython" version = "3.1.27" description = "GitPython is a python library used to interact with Git repositories" category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] gitdb = ">=4.0.1,<5" typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""} [[package]] name = "idna" version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "dev" optional = false python-versions = ">=3.5" [[package]] name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" version = "2.1.3" description = "Read metadata from Python packages" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] zipp = ">=0.5" [package.extras] docs = ["sphinx", "rst.linker"] testing = ["packaging", "pep517", "unittest2", "importlib-resources (>=1.3)"] [[package]] name = "importlib-resources" version = "3.2.1" description = "Read resources from Python packages" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} [package.extras] docs = ["sphinx", "rst.linker", "jaraco.packaging"] [[package]] name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" category = "dev" optional = false python-versions = "*" [[package]] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] [[package]] name = "markupsafe" version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false python-versions = ">=3.7" [[package]] name = "mccabe" version = "0.6.1" description = "McCabe checker, plugin for flake8" category = "dev" optional = false python-versions = "*" [[package]] name = "more-itertools" version = "8.13.0" description = "More routines for operating on iterables, beyond itertools" category = "dev" optional = false python-versions = ">=3.5" [[package]] name = "packaging" version = "20.9" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pyparsing = ">=2.0.2" [[package]] name = "pathlib2" version = "2.3.7.post1" description = "Object-oriented filesystem paths" category = "dev" optional = false python-versions = "*" [package.dependencies] six = "*" [[package]] name = "pbr" version = "5.9.0" description = "Python Build Reasonableness" category = "dev" optional = false python-versions = ">=2.6" [[package]] name = "platformdirs" version = "2.0.2" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pluggy" version = "0.13.1" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] [[package]] name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pycodestyle" version = "2.6.0" description = "Python style guide checker" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyflakes" version = "2.2.0" description = "passive checker of Python programs" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" version = "2.12.0" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false python-versions = ">=3.6" [[package]] name = "pyparsing" version = "2.4.7" description = "Python parsing module" category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "pytest" version = "4.0.0" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] atomicwrites = ">=1.0" attrs = ">=17.4.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} more-itertools = ">=4.0.0" pathlib2 = {version = ">=2.2.0", markers = "python_version < \"3.6\""} pluggy = ">=0.7" py = ">=1.5.0" six = ">=1.10.0" [[package]] name = "pytest" version = "7.1.2" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" py = ">=1.8.2" tomli = ">=1.0.0" [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-flakefinder" version = "1.0.0" description = "Runs tests multiple times to expose flakiness." category = "dev" optional = false python-versions = ">=3.5" [package.dependencies] pytest = ">=2.7.1" [[package]] name = "pytz" version = "2022.1" description = "World timezone definitions, modern and historical" category = "dev" optional = false python-versions = "*" [[package]] name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" category = "dev" optional = false python-versions = ">=3.6" [[package]] name = "requests" version = "2.28.1" description = "Python HTTP for Humans." category = "dev" optional = false python-versions = ">=3.7, <4" [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<3" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "restructuredtext-lint" version = "1.4.0" description = "reStructuredText linter" category = "dev" optional = false python-versions = "*" [package.dependencies] docutils = ">=0.11,<1.0" [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "smmap" version = "5.0.0" description = "A pure Python implementation of a sliding window memory map manager" category = "dev" optional = false python-versions = ">=3.6" [[package]] name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." category = "dev" optional = false python-versions = "*" [[package]] name = "sphinx" version = "4.3.2" description = "Python documentation generator" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} docutils = ">=0.14,<0.18" imagesize = "*" Jinja2 = ">=2.3" packaging = "*" Pygments = ">=2.0" requests = ">=2.5.0" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.920)", "docutils-stubs", "types-typed-ast", "types-pkg-resources", "types-requests"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-rtd-theme" version = "1.0.0" description = "Read the Docs theme for Sphinx" category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" [package.dependencies] docutils = "<0.18" sphinx = ">=1.6" [package.extras] dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] [[package]] name = "sphinxcontrib-applehelp" version = "1.0.2" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" category = "dev" optional = false python-versions = ">=3.5" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." category = "dev" optional = false python-versions = ">=3.5" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" category = "dev" optional = false python-versions = ">=3.6" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest", "html5lib"] [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" category = "dev" optional = false python-versions = ">=3.5" [package.extras] test = ["pytest", "flake8", "mypy"] [[package]] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." category = "dev" optional = false python-versions = ">=3.5" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." category = "dev" optional = false python-versions = ">=3.5" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] name = "stevedore" version = "3.5.0" description = "Manage dynamic plugins for Python applications" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} pbr = ">=2.0.0,<2.1.0 || >2.1.0" [[package]] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" category = "dev" optional = false python-versions = ">=3.7" [[package]] name = "tox" version = "3.25.1" description = "tox is a generic virtualenv management and test command line tool" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} filelock = ">=3.0.0" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} packaging = ">=14" pluggy = ">=0.12.0" py = ">=1.4.17" six = ">=1.14.0" toml = ">=0.9.4" virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" [package.extras] docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] [[package]] name = "tox-pyenv" version = "1.1.0" description = "tox plugin that makes tox use `pyenv which` to find python executables" category = "dev" optional = false python-versions = "*" [package.dependencies] tox = ">=2.0" [[package]] name = "typing-extensions" version = "4.3.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "dev" optional = false python-versions = ">=3.7" [[package]] name = "urllib3" version = "1.26.10" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" [package.extras] brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" version = "20.15.1" description = "Virtual Python Environment builder" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] distlib = ">=0.3.1,<1" filelock = ">=3.2,<4" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""} platformdirs = ">=2,<3" six = ">=1.9.0,<2" [package.extras] docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] [[package]] name = "zipp" version = "1.2.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false python-versions = ">=2.7" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pathlib2", "unittest2", "jaraco.itertools", "func-timeout"] [metadata] lock-version = "1.1" python-versions = ">=3.5,<4.0" content-hash = "e9c6e328b6d1d143eae14fdb1e3f3e98567fc6e58cf97adf678833f5d82a7c31" [metadata.files] alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] atomicwrites = [ {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, ] attrs = [ {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] babel = [ {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, ] bandit = [ {file = "bandit-1.7.4-py3-none-any.whl", hash = "sha256:412d3f259dab4077d0e7f0c11f50f650cc7d10db905d98f6520a95a18049658a"}, {file = "bandit-1.7.4.tar.gz", hash = "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2"}, ] bump2version = [ {file = "bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410"}, {file = "bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6"}, ] certifi = [ {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, ] charset-normalizer = [ {file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"}, {file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"}, ] colorama = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] coverage = [ {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"}, {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"}, {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"}, {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"}, {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"}, {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"}, {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"}, {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"}, {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"}, {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"}, {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"}, {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"}, {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"}, {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"}, {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"}, {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"}, {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"}, {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"}, {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"}, {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"}, {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"}, {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"}, {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"}, {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"}, {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"}, {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"}, {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"}, {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"}, {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"}, {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"}, {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"}, {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"}, {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"}, {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"}, {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"}, {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"}, {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"}, {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"}, {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"}, {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"}, {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"}, ] distlib = [ {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, ] docutils = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] filelock = [ {file = "filelock-3.2.1-py2.py3-none-any.whl", hash = "sha256:7f07b08d731907441ff40d0c5b81f9512cd968842e0b6264c8bd18a8ce877760"}, {file = "filelock-3.2.1.tar.gz", hash = "sha256:9cdd29c411ab196cf4c35a1da684f7b9da723696cb356efa45bf5eb1ff313ee3"}, ] flake8 = [ {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, ] flake8-bandit = [ {file = "flake8_bandit-3.0.0-py2.py3-none-any.whl", hash = "sha256:61b617f4f7cdaa0e2b1e6bf7b68afb2b619a227bb3e3ae00dd36c213bd17900a"}, {file = "flake8_bandit-3.0.0.tar.gz", hash = "sha256:54d19427e6a8d50322a7b02e1841c0a7c22d856975f3459803320e0e18e2d6a1"}, ] flake8-bugbear = [ {file = "flake8-bugbear-22.7.1.tar.gz", hash = "sha256:e450976a07e4f9d6c043d4f72b17ec1baf717fe37f7997009c8ae58064f88305"}, {file = "flake8_bugbear-22.7.1-py3-none-any.whl", hash = "sha256:db5d7a831ef4412a224b26c708967ff816818cabae415e76b8c58df156c4b8e5"}, ] flake8-builtins = [ {file = "flake8-builtins-1.5.3.tar.gz", hash = "sha256:09998853b2405e98e61d2ff3027c47033adbdc17f9fe44ca58443d876eb00f3b"}, {file = "flake8_builtins-1.5.3-py2.py3-none-any.whl", hash = "sha256:7706babee43879320376861897e5d1468e396a40b8918ed7bccf70e5f90b8687"}, ] flake8-comprehensions = [ {file = "flake8-comprehensions-3.10.0.tar.gz", hash = "sha256:181158f7e7aa26a63a0a38e6017cef28c6adee71278ce56ce11f6ec9c4905058"}, {file = "flake8_comprehensions-3.10.0-py3-none-any.whl", hash = "sha256:dad454fd3d525039121e98fa1dd90c46bc138708196a4ebbc949ad3c859adedb"}, ] flake8-polyfill = [ {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, ] flake8-rst-docstrings = [ {file = "flake8-rst-docstrings-0.2.6.tar.gz", hash = "sha256:7d9526a264a1c2827b3408ea5f921f12ee0689ac123fecfa95744988aea02e6c"}, {file = "flake8_rst_docstrings-0.2.6-py3-none-any.whl", hash = "sha256:a7a9bd9008e763339b2e19dbece7767ac0887cfaeaf9a70b12eb4449d5df1849"}, ] flake9 = [ {file = "flake9-3.8.3.post2-py3-none-any.whl", hash = "sha256:47dced969a802a8892740bcaa35ae07232709b2ade803c45f48dd03ccb7f825f"}, {file = "flake9-3.8.3.post2.tar.gz", hash = "sha256:daefdbfb3d320eb215a4a52c62a4b4a027cbe11d39f5dab30df908b40fce5ba7"}, ] gitdb = [ {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, ] gitpython = [ {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] imagesize = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] importlib-metadata = [ {file = "importlib_metadata-2.1.3-py2.py3-none-any.whl", hash = "sha256:52e65a0856f9ba7ea8f2c4ced253fb6c88d1a8c352cb1e916cff4eb17d5a693d"}, {file = "importlib_metadata-2.1.3.tar.gz", hash = "sha256:02a9f62b02e9b1cc43871809ef99947e8f5d94771392d666ada2cafc4cd09d4f"}, ] importlib-resources = [ {file = "importlib_resources-3.2.1-py2.py3-none-any.whl", hash = "sha256:e2860cf0c4bc999947228d18be154fa3779c5dde0b882bd2d7b3f4d25e698bd6"}, {file = "importlib_resources-3.2.1.tar.gz", hash = "sha256:a9fe213ab6452708ec1b3f4ec6f2881b8ab3645cb4e5efb7fea2bbf05a91db3b"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] jinja2 = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] markupsafe = [ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] more-itertools = [ {file = "more-itertools-8.13.0.tar.gz", hash = "sha256:a42901a0a5b169d925f6f217cd5a190e32ef54360905b9c39ee7db5313bfec0f"}, {file = "more_itertools-8.13.0-py3-none-any.whl", hash = "sha256:c5122bffc5f104d37c1626b8615b511f3427aa5389b94d61e5ef8236bfbc3ddb"}, ] packaging = [ {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, ] pathlib2 = [ {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, ] pbr = [ {file = "pbr-5.9.0-py2.py3-none-any.whl", hash = "sha256:e547125940bcc052856ded43be8e101f63828c2d94239ffbe2b327ba3d5ccf0a"}, {file = "pbr-5.9.0.tar.gz", hash = "sha256:e8dca2f4b43560edef58813969f52a56cef023146cbb8931626db80e6c1c4308"}, ] platformdirs = [ {file = "platformdirs-2.0.2-py2.py3-none-any.whl", hash = "sha256:0b9547541f599d3d242078ae60b927b3e453f0ad52f58b4d4bc3be86aed3ec41"}, {file = "platformdirs-2.0.2.tar.gz", hash = "sha256:3b00d081227d9037bbbca521a5787796b5ef5000faea1e43fd76f1d44b06fcfa"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pycodestyle = [ {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, ] pyflakes = [ {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, ] pygments = [ {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytest = [ {file = "pytest-4.0.0-py2.py3-none-any.whl", hash = "sha256:c055690dfefa744992f563e8c3a654089a6aa5b8092dded9b6fafbd70b2e45a7"}, {file = "pytest-4.0.0.tar.gz", hash = "sha256:488c842647bbeb350029da10325cb40af0a9c7a2fdda45aeb1dda75b60048ffb"}, {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, ] pytest-flakefinder = [ {file = "pytest-flakefinder-1.0.0.tar.gz", hash = "sha256:1bdfaeaad4699cfb3b295e24c359e403e6261eabca9ba9bdffbaba8cabe87d63"}, {file = "pytest_flakefinder-1.0.0-py2.py3-none-any.whl", hash = "sha256:1d7761402e6e90d8c0a34b33b19799f8ce1c396623025528dccc1329a53d4e66"}, ] pytz = [ {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, ] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] requests = [ {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, ] restructuredtext-lint = [ {file = "restructuredtext_lint-1.4.0.tar.gz", hash = "sha256:1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] snowballstemmer = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] sphinx = [ {file = "Sphinx-4.3.2-py3-none-any.whl", hash = "sha256:6a11ea5dd0bdb197f9c2abc2e0ce73e01340464feaece525e64036546d24c851"}, {file = "Sphinx-4.3.2.tar.gz", hash = "sha256:0a8836751a68306b3fe97ecbe44db786f8479c3bf4b80e3a7f5c838657b4698c"}, ] sphinx-rtd-theme = [ {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, ] sphinxcontrib-devhelp = [ {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, ] sphinxcontrib-htmlhelp = [ {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, ] sphinxcontrib-jsmath = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, ] sphinxcontrib-qthelp = [ {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, ] sphinxcontrib-serializinghtml = [ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] stevedore = [ {file = "stevedore-3.5.0-py3-none-any.whl", hash = "sha256:a547de73308fd7e90075bb4d301405bebf705292fa90a90fc3bcf9133f58616c"}, {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] tox = [ {file = "tox-3.25.1-py2.py3-none-any.whl", hash = "sha256:c38e15f4733683a9cc0129fba078633e07eb0961f550a010ada879e95fb32632"}, {file = "tox-3.25.1.tar.gz", hash = "sha256:c138327815f53bc6da4fe56baec5f25f00622ae69ef3fe4e1e385720e22486f9"}, ] tox-pyenv = [ {file = "tox-pyenv-1.1.0.tar.gz", hash = "sha256:916c2213577aec0b3b5452c5bfb32fd077f3a3196f50a81ad57d7ef3fc2599e4"}, {file = "tox_pyenv-1.1.0-py2.py3-none-any.whl", hash = "sha256:e470c18af115fe52eeff95e7e3cdd0793613eca19709966fc2724b79d55246cb"}, ] typing-extensions = [ {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, ] urllib3 = [ {file = "urllib3-1.26.10-py2.py3-none-any.whl", hash = "sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec"}, {file = "urllib3-1.26.10.tar.gz", hash = "sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6"}, ] virtualenv = [ {file = "virtualenv-20.15.1-py2.py3-none-any.whl", hash = "sha256:b30aefac647e86af6d82bfc944c556f8f1a9c90427b2fb4e3bfbf338cb82becf"}, {file = "virtualenv-20.15.1.tar.gz", hash = "sha256:288171134a2ff3bfb1a2f54f119e77cd1b81c29fc1265a2356f3e8d14c7d58c4"}, ] zipp = [ {file = "zipp-1.2.0-py2.py3-none-any.whl", hash = "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"}, {file = "zipp-1.2.0.tar.gz", hash = "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1"}, ] pawelzny-dotty_dict-74c5272/pyproject.toml000066400000000000000000000042101426234444200207240ustar00rootroot00000000000000[build-system] requires = ["poetry_core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.poetry] name = "dotty_dict" version = "1.3.1" description = "Dictionary wrapper for quick access to deeply nested keys." readme= "README.rst" authors = ["Pawel Zadrozny "] license = "MIT" classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Natural Language :: English', 'Topic :: Software Development', 'Topic :: Software Development :: Libraries :: Python Modules', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'License :: OSI Approved :: MIT License', ] packages = [ {include = "dotty_dict"}, ] [tool.poetry.dependencies] python = ">=3.5,<4.0" [tool.poetry.dev-dependencies] pytest = [ {version = "7.1.2", python = ">=3.7"}, {version = "4.0.0", python = ">=3.5,<3.7"}, ] tox = "3.25.1" tox-pyenv = "1.1.0" coverage = [ {version = "6.4.1", python = ">=3.8"}, ] bump2version = "1.0.1" Sphinx = [ {version = "4.3.2", python = ">=3.8"}, ] sphinx-rtd-theme = [ {version = "1.0.0", python = ">=3.8"}, ] flake9 = "3.8.3.post2" flake8-bandit = [ {version = "3.0.0", python = ">=3.8"}, ] flake8-bugbear = [ {version = "22.7.1", python = ">=3.8"}, ] flake8-builtins = "1.5.3" flake8-comprehensions = [ {version = "3.10.0", python = ">=3.8"}, ] flake8-rst-docstrings = [ {version = "0.2.6", python = ">=3.8"}, ] pytest-flakefinder = "1.0.0" [tool.flake8] max-line-length = 100 max-complexity = 10 statistics = true ignore = ["B101", "E501"] select = ["B", "B902", "B950", "C", "E", "F", "G", "H", "I", "N", "RST", "W"] exclude = [".eggs", "docs", "setup.py", ".tox", ".venv"] [tool.pytest.ini_options] addopts = "-rx -s --verbose" testpaths = ["tests"] pawelzny-dotty_dict-74c5272/setup.py000066400000000000000000000053101426234444200175240ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import os import re import subprocess from setuptools import find_packages, setup from setuptools.command.develop import develop from setuptools.command.install import install from setuptools.command.test import test __author__ = 'Pawel Zadrozny' __copyright__ = 'Copyright (c) 2018, Pawel Zadrozny' with open('README.rst', 'r') as readme_file: readme = readme_file.read() def get_version(*file_paths): """Retrieves the version from project/__init__.py""" filename = os.path.join(os.path.dirname(__file__), *file_paths) version_file = open(filename).read() version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) if version_match: return version_match.group(1) raise RuntimeError('Unable to find version string.') class PostDevelopCommand(develop): """Post-installation for development mode.""" def run(self): subprocess.check_call(['pipenv', 'install', '--dev', '--deploy', '--system']) develop.run(self) class TestCommand(test): """Run tests""" def run(self): subprocess.check_call(['pytest']) test.run(self) setup( name='dotty_dict', version=get_version('dotty_dict', '__init__.py'), description="Dictionary wrapper for quick access to deeply nested keys.", long_description=readme, license="MIT license", author="Pawel Zadrozny @pawelzny", author_email='pawel.zny@gmail.com', url='https://github.com/pawelzny/dotty_dict', packages=find_packages(exclude=('tests', 'docs', 'bin', 'example')), package_dir={'dotty_dict': 'dotty_dict'}, include_package_data=True, use_scm_version=True, setup_requires=['setuptools_scm'], zip_safe=False, keywords='dot notation dict wrapper helper utils lib', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Natural Language :: English', 'Topic :: Software Development', 'Topic :: Software Development :: Libraries :: Python Modules', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'License :: OSI Approved :: MIT License', ], cmdclass={ 'develop': PostDevelopCommand, 'test': TestCommand, }, ) pawelzny-dotty_dict-74c5272/tests/000077500000000000000000000000001426234444200171555ustar00rootroot00000000000000pawelzny-dotty_dict-74c5272/tests/__init__.py000066400000000000000000000000301426234444200212570ustar00rootroot00000000000000# -*- coding: utf-8 -*- pawelzny-dotty_dict-74c5272/tests/test_dotty_api.py000066400000000000000000000161721426234444200225710ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import unittest from dotty_dict import Dotty, dotty class TestDottyPrivateMembers(unittest.TestCase): def test_split_separator(self): dot = dotty() result = dot._split('chain.of.keys') self.assertListEqual(result, ['chain', 'of', 'keys']) def test_split_with_custom_separator(self): dot = Dotty({}, separator='#', esc_char='\\') result = dot._split('chain#of#keys') self.assertListEqual(result, ['chain', 'of', 'keys']) class TestDottyPublicMembers(unittest.TestCase): def test_to_dict(self): plain_dict = {'very': {'deeply': {'nested': {'thing': 'spam'}}}} dot = dotty(plain_dict) self.assertIsInstance(dot.to_dict(), dict) self.assertEqual(sorted(dot.to_dict().items()), sorted(plain_dict.items())) def test_nested_dotty_object_to_dict(self): expected_dict = {'hello': {'world': 1}, 'nested': {'dotty': {'wazaa': 3}}} top_dot = dotty({'hello': {'world': 1}}) nested_dot = dotty({'wazaa': 3}) top_dot['nested.dotty'] = nested_dot self.assertDictEqual(top_dot.to_dict(), expected_dict) def test_nested_dotty_in_list_to_dict(self): expected_dict = {'testlist': [{'dot1': 1}, {'dot2': 2}]} dot_list = [dotty({'dot1': 1}), dotty({'dot2': 2})] top_dot = dotty({'testlist': dot_list}) self.assertDictEqual(top_dot.to_dict(), expected_dict) class TestDictSpecificMethods(unittest.TestCase): def setUp(self): self.dot = dotty({ 'flat_key': 'flat value', 'deep': { 'nested': 12, 'deeper': { 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, }) def test_access_keys(self): keys = sorted(self.dot.keys()) self.assertListEqual(keys, ['deep', 'flat_key']) def test_access_keys_from_deeply_nested_structure(self): keys = sorted(self.dot['deep.deeper'].keys()) self.assertListEqual(keys, ['ridiculous', 'secret']) def test_get_value_without_default(self): result = self.dot.get('deep.nested') self.assertEqual(result, 12) def test_get_value_with_default(self): result = self.dot.get('deep.other', False) self.assertFalse(result) def test_return_dotty_length(self): self.assertEqual(len(self.dot), 2) def test_pop_from_dotty_flat(self): result = self.dot.pop('flat_key') self.assertEqual(result, 'flat value') self.assertDictEqual(self.dot._data, { 'deep': { 'nested': 12, 'deeper': { 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, }) def test_pop_with_default_value(self): result = self.dot.pop('not_existing', 'abcd') self.assertEqual(result, 'abcd') self.assertDictEqual(self.dot._data, { 'flat_key': 'flat value', 'deep': { 'nested': 12, 'deeper': { 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, }) def test_pop_nested_key(self): result = self.dot.pop('deep.nested') self.assertEqual(result, 12) self.assertDictEqual(self.dot._data, { 'flat_key': 'flat value', 'deep': { 'deeper': { 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, }) def test_pop_nested_key_with_default_value(self): result = self.dot.pop('deep.deeper.not_existing', 'abcd') self.assertEqual(result, 'abcd') self.assertDictEqual(self.dot._data, { 'flat_key': 'flat value', 'deep': { 'nested': 12, 'deeper': { 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, }) def test_setdefault_flat_not_existing(self): result = self.dot.setdefault('next_flat', 'new default value') self.assertEqual(result, 'new default value') self.assertDictEqual(self.dot._data, { 'flat_key': 'flat value', 'next_flat': 'new default value', 'deep': { 'nested': 12, 'deeper': { 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, }) def test_setdefault_flat_existing(self): self.dot['next_flat'] = 'original value' result = self.dot.setdefault('next_flat', 'new default value') self.assertEqual(result, 'original value') self.assertDictEqual(self.dot._data, { 'flat_key': 'flat value', 'next_flat': 'original value', 'deep': { 'nested': 12, 'deeper': { 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, }) def test_setdefault_nested_key_not_existing(self): result = self.dot.setdefault('deep.deeper.next_key', 'new default value') self.assertEqual(result, 'new default value') self.assertDictEqual(self.dot._data, { 'flat_key': 'flat value', 'deep': { 'nested': 12, 'deeper': { 'next_key': 'new default value', 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, }) def test_setdefault_nested_key_existing(self): self.dot['deep.deeper.next_key'] = 'original value' result = self.dot.setdefault('deep.deeper.next_key', 'new default value') self.assertEqual(result, 'original value') self.assertDictEqual(self.dot._data, { 'flat_key': 'flat value', 'deep': { 'nested': 12, 'deeper': { 'next_key': 'original value', 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, }) def test_copy(self): first = dotty({'a': 1, 'b': 2}) second = first.copy() self.assertIsInstance(second, Dotty) self.assertEqual(first, second) self.assertIsNot(first, second) self.assertIsNot(first._data, second._data) def test_fromkeys(self): dot = dotty().fromkeys({'a', 'b', 'c'}, value=10) self.assertDictEqual(dot.to_dict(), {'a': 10, 'b': 10, 'c': 10}) self.assertIsInstance(dot, Dotty) pawelzny-dotty_dict-74c5272/tests/test_dotty_basics.py000066400000000000000000000033341426234444200232600ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import unittest from dotty_dict import dotty class TestDottyBasics(unittest.TestCase): def test_create_empty_instance(self): dot = dotty() self.assertEqual(dot, {}) def test_create_non_empty_instance(self): plain_dict = {'not': 'empty'} dot = dotty(plain_dict) self.assertEqual(dot, plain_dict) self.assertIsNot(dot, plain_dict) # noinspection PyTypeChecker def test_raise_attr_error_if_input_is_not_dict(self): with self.assertRaises(AttributeError): dotty(['not', 'valid']) def test_two_dotty_with_the_same_input_should_be_equal(self): first = dotty({'is': 'valid'}) second = dotty({'is': 'valid'}) self.assertEqual(first, second) def test_two_dotty_with_different_input_should_not_be_equal(self): first = dotty({'counter': 1}) second = dotty({'counter': 2}) self.assertNotEqual(first, second) def test_plain_dict_and_dotty_wrapper_should_be_equal(self): plain = {'a': 1, 'b': 2} dot = dotty(plain) self.assertEqual(dot, plain) def test_dotty_and_not_mapping_instance_should_not_be_equal(self): dot = dotty({'a': 1, 'b': 2}) self.assertNotEqual(dot, [('a', 1), ('b', 2)]) self.assertNotEqual(dot, ('a', 1)) self.assertNotEqual(dot, {1, 2, 3}) self.assertNotEqual(dot, 123) self.assertNotEqual(dot, 'a:1, b:2') def test_pop_with_default_value(self): dot = dotty() self.assertEqual(dot.pop('does.not.exist', None), None) self.assertEqual(dot.pop('does.not.exist', 55), 55) self.assertEqual(dot.pop('does.not.exist', 'my_value'), 'my_value') pawelzny-dotty_dict-74c5272/tests/test_dotty_cache.py000066400000000000000000000006321426234444200230550ustar00rootroot00000000000000import unittest from unittest.mock import MagicMock from dotty_dict import dotty class TestDottyCache(unittest.TestCase): def test_getitem_cache(self): dot = dotty() dot.__getitem__.cache_clear() dot._data = MagicMock() for _ in range(10): dot.get('x.y.z') self.assertEqual(dot.__getitem__.cache_info().hits, 9) dot.__getitem__.cache_clear() pawelzny-dotty_dict-74c5272/tests/test_dotty_value_access.py000066400000000000000000000164101426234444200244500ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import unittest from dotty_dict import Dotty, dotty class TestDottyValueAccess(unittest.TestCase): def setUp(self): self.dot = dotty({ 'flat_key': 'flat value', 'deep': { 'nested': 12, 'deeper': { 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, }) def test_access_flat_value(self): self.assertEqual(self.dot['flat_key'], 'flat value') # noinspection PyUnusedLocal def test_raise_key_error_if_key_does_not_exist(self): with self.assertRaises(KeyError): val = self.dot['not_existing'] # noqa def test_access_deep_nested_value(self): self.assertEqual(self.dot['deep.nested'], 12) def test_access_middle_nested_value(self): self.assertDictEqual(self.dot['deep.deeper.ridiculous'], {'hell': 'is here'}) def test_set_flat_value(self): self.dot['new_flat'] = 'super flat' self.assertIn('new_flat', self.dot) def test_set_deep_nested_value(self): self.dot['deep.new_key'] = 'new value' self.assertIn('new_key', self.dot['deep']) def test_set_new_deeply_nested_value(self): self.dot['other.chain.of.keys'] = True self.assertDictEqual(self.dot._data, { 'flat_key': 'flat value', 'deep': { 'nested': 12, 'deeper': { 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, 'other': { 'chain': { 'of': { 'keys': True, }, }, }, }) def test_dotty_has_flat_key(self): self.assertIn('flat_key', self.dot) def test_dotty_has_deeply_nested_key(self): self.assertIn('deep.nested', self.dot) def test_dotty_has_not_flat_key(self): self.assertNotIn('some_key', self.dot) def test_dotty_has_not_deeply_nested_key(self): self.assertNotIn('deep.other.chain', self.dot) def test_has_in(self): result = 'deep.deeper.secret' in self.dot self.assertTrue(result) def test_has_not_in(self): result = 'deep.other' in self.dot self.assertFalse(result) def test_delete_flat_key(self): del self.dot['flat_key'] self.assertDictEqual(self.dot._data, { 'deep': { 'nested': 12, 'deeper': { 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, }) def test_delete_nested_key(self): del self.dot['deep.deeper.secret'] self.assertDictEqual(self.dot._data, { 'flat_key': 'flat value', 'deep': { 'nested': 12, 'deeper': { 'ridiculous': { 'hell': 'is here', }, }, }, }) def test_raise_key_error_on_delete_not_existing_key(self): with self.assertRaises(KeyError): del self.dot['deep.deeper.key'] def test_set_value_with_escaped_separator(self): self.dot[r'deep.deeper.escaped\.dot_key'] = 'it works!' self.assertDictEqual(self.dot._data, { 'flat_key': 'flat value', 'deep': { 'nested': 12, 'deeper': { 'escaped.dot_key': 'it works!', 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, }) def test_get_value_with_escaped_separator(self): dot = dotty({ 'flat_key': 'flat value', 'deep': { 'nested': 12, 'deeper': { 'escaped.dot_key': 'it works!', 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, }) result = dot[r'deep.deeper.escaped\.dot_key'] self.assertEqual(result, 'it works!') def test_get_value_with_escaped_escape_separator(self): dot = dotty({ 'flat_key': 'flat value', 'deep': { 'nested': 12, 'deeper': { 'escaped\\': { 'dot_key': 'it works!', }, 'secret': 'abcd', 'ridiculous': { 'hell': 'is here', }, }, }, }) result = dot[r'deep.deeper.escaped\\.dot_key'] self.assertEqual(result, 'it works!') def test_use_custom_separator_and_custom_escape_char(self): sep = ',' esc = '$' dot = Dotty({}, separator=sep, esc_char=esc) dot['abcd,efg,hij'] = 'test' dot['abcd,efg$,hij'] = 'test2' dot[r'abcd,efg\$,hij'] = 'test3' self.assertDictEqual(dot._data, { 'abcd': { 'efg': { 'hij': 'test', }, 'efg,hij': 'test2', 'efg$': { 'hij': 'test3', }, }, }) def test_string_digit_key(self): dot = dotty({'field': { '1': 'one', '5': 'five' }}) dict_one = dot['field.1'] dict_five = dot['field.5'] self.assertEqual(dict_one, 'one') self.assertEqual(dict_five, 'five') def test_integer_keys(self): dot = dotty({'field': { 1: 'one', 5: 'five' }}) dict_one = dot['field.1'] dict_five = dot['field.5'] self.assertEqual(dict_one, 'one') self.assertEqual(dict_five, 'five') def test_data_gathering_with_int(self): dot = dotty({ '2': 'string_value', 2: 'int_value', 'nested': { '2': 'nested_string_value', 3: 'nested_int_value' } }) dict_string = dot['2'] dict_int = dot[2] nested_dict_string = dot['nested.2'] nested_dict_int = dot['nested.3'] self.assertEqual(dict_string, 'string_value') self.assertEqual(dict_int, 'int_value') self.assertEqual(nested_dict_string, 'nested_string_value') self.assertEqual(nested_dict_int, 'nested_int_value') def test_non_standard_key_types(self): dot = Dotty({ 3.3: 'float', True: 'bool', None: 'None', 'nested': { 4.4: 'nested_float' } }, separator=',') dict_float = dot[3.3] dict_bool = dot[True] dict_none = dot[None] nested_dict_float = dot['nested,4.4'] self.assertEqual(dict_float, 'float') self.assertEqual(dict_bool, 'bool') self.assertEqual(dict_none, 'None') self.assertEqual(nested_dict_float, 'nested_float') pawelzny-dotty_dict-74c5272/tests/test_examples.py000066400000000000000000000007211426234444200224040ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from example import fetch_all_examples_for_testing __author__ = 'Pawel Zadrozny' __copyright__ = 'Copyright (c) 2018, Pawel Zadrozny' def test_examples(): """Entry point for examples functions. All examples are stores in example module in form of functions with assertions. Any exception raised in example will break tests. """ for func in fetch_all_examples_for_testing(): func() pawelzny-dotty_dict-74c5272/tests/test_list_in_dotty.py000066400000000000000000000204171426234444200234560ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import unittest from dotty_dict import dotty class TestListInDotty(unittest.TestCase): def setUp(self): self.dot = dotty({ 'field1': 'Value of F1', 'field2': 'Value of F2', 'field3': [ { 'subfield1': 'Value of subfield1 (item 0)', 'subfield2': 'Value of subfield2 (item 0)' }, { 'subfield1': 'Value of subfield1 (item 1)', 'subfield2': 'Value of subfield2 (item 1)' }, ], 'field4': 'Not wanted', 'field5': [ { 'subfield1': [{'subsubfield': 'Value of sub subfield (item 0)'}] } ], 'field6': ['a', 'b'] }) def test_root_level_list_element(self): self.assertEqual(self.dot['field6.0'], 'a') def test_access_subfield1_of_field3(self): self.assertEqual(self.dot['field3.0.subfield1'], 'Value of subfield1 (item 0)') self.assertEqual(self.dot['field3.1.subfield1'], 'Value of subfield1 (item 1)') def test_access_sub_sub_field(self): self.assertEqual(self.dot['field5.0.subfield1.0.subsubfield'], 'Value of sub subfield (item 0)') def test_access_multidimensional_lists(self): dot = dotty({ 'field': [ [{'subfield': 'Value of subfield (item 0,0)'}], [{'subfield': 'Value of subfield (item 0,1)'}] ] }) self.assertEqual(dot['field.1.0.subfield'], 'Value of subfield (item 0,1)') def test_dotty_contains_subfield_of_field(self): self.assertIn('field3.0.subfield2', self.dot) def test_dotty_not_contains_out_of_range_subfield(self): self.assertNotIn('field3.3.subfield1', self.dot) def test_assert_key_error_if_index_is_not_integer(self): with self.assertRaises(KeyError): val = self.dot['field3.subfield1'] # noqa def test_assert_index_error_if_index_is_out_of_range(self): with self.assertRaises(IndexError): val = self.dot['field3.4.subfield1'] # noqa def test_assert_get_returns_default_if_index_is_out_of_range(self): val = self.dot.get('field3.4.subfield1') assert val is None def test_set_subfield_in_list(self): dot = dotty() dot['field.0.subfield'] = 'Value of subfield (item 0)' dot['field.1.subfield'] = 'Value of subfield (item 1)' dot['field.1.subfield2'] = 'Value of subfield2 (item 1)' self.assertDictEqual(dot.to_dict(), { 'field': [ {'subfield': 'Value of subfield (item 0)'}, {'subfield': 'Value of subfield (item 1)', 'subfield2': 'Value of subfield2 (item 1)'} ], }) def test_update_subfield_in_list(self): dot = dotty({ 'field': [ {'subfield': 'Value of subfield (item 0)'}, {'subfield': 'Value of subfield (item 1)', 'subfield2': 'Value of subfield2 (item 1)'} ], }) dot['field.0.subfield'] = 'updated value' self.assertDictEqual(dot.to_dict(), { 'field': [ {'subfield': 'updated value'}, {'subfield': 'Value of subfield (item 1)', 'subfield2': 'Value of subfield2 (item 1)'} ], }) def test_delete_subfield(self): dot = dotty({'field': [ { 'subfield1': 'Value of subfield1 (item 0)', 'subfield2': 'Value of subfield2 (item 0)' }, ]}) del dot['field.0.subfield2'] self.assertDictEqual(dot.to_dict(), {'field': [ {'subfield1': 'Value of subfield1 (item 0)'}, ]}) def test_list_as_return_value(self): dot = dotty({ 'field': [ 'list_field0', 'list_field1' ] }) self.assertEqual(dot['field.0'], 'list_field0') self.assertEqual(dot['field.1'], 'list_field1') self.assertTrue('field.0' in dot) self.assertTrue('field.1' in dot) self.assertFalse('field.2' in dot) def test_root_level_field_is_none(self): dot = dotty({ 'field': None, }) self.assertIsNone(dot['field.0']) class TestMultipleSelectList(unittest.TestCase): def setUp(self): self.dot = dotty({ 'field1': [ { "subfield1": "value01", "subfield2": "value02" }, { "subfield1": "value11", "subfield2": "value12" }, { "subfield1": "value21", "subfield2": "value22" }, { "subfield1": "value31", "subfield2": "value32" } ], "field2": [ { "subfield1": [ { "nestedsubfield1": "nestedvalue001", "nestedsubfield2": "nestedvalue002" }, { "nestedsubfield1": "nestedvalue011", "nestedsubfield2": "nestedvalue012" } ] }, { "subfield1": [ { "nestedsubfield1": "nestedvalue101", "nestedsubfield2": "nestedvalue102" }, { "nestedsubfield1": "nestedvalue111", "nestedsubfield2": "nestedvalue112" } ] } ] }) def test_whole_shallow_multiple_list(self): expected_list = ['value01', 'value11', 'value21', 'value31'] self.assertListEqual(self.dot['field1.:.subfield1'], expected_list) def test_whole_nested_multiple_list(self): expected_list = [['nestedvalue001', 'nestedvalue011'], ['nestedvalue101', 'nestedvalue111']] self.assertListEqual(self.dot['field2.:.subfield1.:.nestedsubfield1'], expected_list) def test_left_side_slice(self): expected_list = ['value21', 'value31'] self.assertListEqual(self.dot['field1.2:.subfield1'], expected_list) def test_right_side_slice(self): expected_list = ['value01', 'value11'] self.assertListEqual(self.dot['field1.:2.subfield1'], expected_list) def test_both_side_slice(self): expected_list = ['value11', 'value21'] self.assertListEqual(self.dot['field1.1:3.subfield1'], expected_list) def test_step_slice(self): expected_list = ['value01', 'value21'] self.assertListEqual(self.dot['field1.::2.subfield1'], expected_list) def test_return_whole_list(self): expected_list = [ { "subfield1": "value01", "subfield2": "value02" }, { "subfield1": "value11", "subfield2": "value12" }, { "subfield1": "value21", "subfield2": "value22" }, { "subfield1": "value31", "subfield2": "value32" } ] self.assertListEqual(self.dot['field1.:'], expected_list) class TestNoList(unittest.TestCase): def setUp(self): self.dot = dotty({ 'field1': { '1': 'value1', '2': { 'subfield1': 'value2', 'subfield2': 'value3' }, ':': 'value4', '2:': 'value5', 'key': 'value6' }, }, no_list=True) def test_simple_index(self): self.assertEqual(self.dot['field1.1'], 'value1') def test_whole_slice_index(self): self.assertEqual(self.dot['field1.:'], 'value4') def test_limit_slice_index(self): self.assertEqual(self.dot['field1.2:'], 'value5') def test_normal_key(self): self.assertEqual(self.dot['field1.key'], 'value6') pawelzny-dotty_dict-74c5272/tox.ini000066400000000000000000000017051426234444200173310ustar00rootroot00000000000000[tox] envlist = py35,py36,py37,py38,py39,py310,pypy3,flake8,docs [testenv:flake8] basepython = python3.8 commands = pip install poetry==1.1.14 poetry install flake8 dotty_dict exclude = .git .idea .tox .venv .eggs __pycache__ dotty_dict.egg-info [testenv:docs] basepython = python3.8 commands = pip install poetry==1.1.14 poetry install poetry run make docs [testenv:py35] basepython = python3.5 [testenv:py36] basepython = python3.6 [testenv:py37] basepython = python3.7 [testenv:py38] basepython = python3.8 [testenv:py39] basepython = python3.9 [testenv:py310] basepython = python3.10 [testenv:pypy3] basepython = pypy3 commands = pip install poetry==1.1.14 poetry install poetry run make test [testenv] changedir = {toxinidir} setenv = PYTHONPATH = {toxinidir}:{toxinidir}/vo passenv = PYTHONPATH commands = pip install poetry==1.1.14 poetry install poetry run make test