From be2050ef3202d20f80b1c555d6c7205058024f38 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Fri, 17 Dec 2021 14:41:33 +0800 Subject: [PATCH 1/4] Merge the changes from maintained fork --- .github/workflows/release.yml | 6 +- .github/workflows/tests.yml | 22 +- .pre-commit-config.yaml | 9 +- README.md | 9 +- poetry.lock | 955 ++++++++++----------------------- pyproject.toml | 23 +- tests/conftest.py | 9 +- tests/examples/example.toml | 1 - tests/test_api.py | 58 +- tests/test_build.py | 6 +- tests/test_items.py | 186 +++++-- tests/test_parser.py | 13 +- tests/test_toml_document.py | 221 +++++++- tests/test_toml_file.py | 5 +- tests/test_toml_spec_tests.py | 18 +- tests/test_toml_tests.py | 3 +- tests/test_utils.py | 2 +- tests/test_write.py | 6 +- tomlkit/__init__.py | 5 +- tomlkit/_compat.py | 166 +----- tomlkit/_utils.py | 41 +- tomlkit/api.py | 74 ++- tomlkit/container.py | 327 ++++++------ tomlkit/exceptions.py | 82 ++- tomlkit/items.py | 977 ++++++++++++++++++++-------------- tomlkit/parser.py | 154 +++--- tomlkit/source.py | 59 +- tomlkit/toml_char.py | 25 +- tomlkit/toml_file.py | 17 +- 29 files changed, 1713 insertions(+), 1766 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e48b906..22bd503 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Release on: push: tags: - - '*.*.*' + - "*.*.*" jobs: Release: @@ -13,7 +13,7 @@ jobs: - name: Checkout code uses: actions/checkout@v2 with: - submodules: 'recursive' + submodules: "recursive" - name: Get tag id: tag @@ -26,7 +26,7 @@ jobs: - name: Install and set up Poetry run: | - curl -fsS -o install-poetry.py https://raw.githubusercontent.com/sdispater/poetry/master/install-poetry.py + curl -fsSL -o install-poetry.py https://install.python-poetry.org python install-poetry.py -y - name: Update PATH diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b792e2a..ad890d2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ on: - develop pull_request: branches: - - '**' + - "**" jobs: Linting: @@ -15,14 +15,8 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Linting - run: | - pip install pre-commit - pre-commit run --all-files + - uses: actions/setup-python@v2 + - uses: pre-commit/action@v2.0.3 Tests: needs: Linting name: ${{ matrix.os }} / ${{ matrix.python-version }} @@ -30,15 +24,15 @@ jobs: strategy: matrix: os: [Ubuntu, MacOS, Windows] - python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] steps: - uses: actions/checkout@v2 with: - submodules: 'recursive' + submodules: "recursive" - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} @@ -51,8 +45,8 @@ jobs: - name: Install Poetry shell: bash run: | - curl -fsS -o get-poetry.py https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py - python get-poetry.py --preview -y + curl -fsSL -o install-poetry.py https://install.python-poetry.org + python install-poetry.py -y echo "$HOME/.poetry/bin" >> $GITHUB_PATH echo "%USERPROFILE%/.poetry/bin" >> $GITHUB_PATH - name: Setup Poetry diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3a1d64c..23c83af 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,19 +1,18 @@ repos: - repo: https://github.com/ambv/black - rev: stable + rev: 21.12b0 hooks: - - id: black - python_version: python3.6 + - id: black - repo: https://github.com/timothycrosley/isort - rev: 5.1.4 + rev: 5.10.1 hooks: - id: isort additional_dependencies: [toml] exclude: ^.*/?setup\.py$ - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.1.0 + rev: v4.0.1 hooks: - id: trailing-whitespace exclude: ^tests/(toml-test|toml-spec-tests)/.* diff --git a/README.md b/README.md index 5670d69..dfb0489 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,17 @@ [pypi_version]: https://img.shields.io/pypi/v/tomlkit.svg?logo=python&logoColor=white [python_versions]: https://img.shields.io/pypi/pyversions/tomlkit.svg?logo=python&logoColor=white [github_license]: https://img.shields.io/github/license/sdispater/tomlkit.svg?logo=github&logoColor=white -[travisci]: https://img.shields.io/travis/com/sdispater/tomlkit/master.svg?logo=travis&logoColor=white&label=Travis%20CI -[appveyor]: https://img.shields.io/appveyor/ci/sdispater/tomlkit/master.svg?logo=appveyor&logoColor=white&label=AppVeyor + + [codecov]: https://img.shields.io/codecov/c/github/sdispater/tomlkit/master.svg?logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CgogPGc+CiAgPHRpdGxlPmJhY2tncm91bmQ8L3RpdGxlPgogIDxyZWN0IGZpbGw9Im5vbmUiIGlkPSJjYW52YXNfYmFja2dyb3VuZCIgaGVpZ2h0PSI0MDIiIHdpZHRoPSI1ODIiIHk9Ii0xIiB4PSItMSIvPgogPC9nPgogPGc+CiAgPHRpdGxlPkxheWVyIDE8L3RpdGxlPgogIDxwYXRoIGlkPSJzdmdfMSIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjZmZmZmZmIiBkPSJtMjUuMDE0LDBjLTEzLjc4NCwwLjAxIC0yNS4wMDQsMTEuMTQ5IC0yNS4wMTQsMjQuODMybDAsMC4wNjJsNC4yNTQsMi40ODJsMC4wNTgsLTAuMDM5YTEyLjIzOCwxMi4yMzggMCAwIDEgOS4wNzgsLTEuOTI4YTExLjg0NCwxMS44NDQgMCAwIDEgNS45OCwyLjk3NWwwLjczLDAuNjhsMC40MTMsLTAuOTA0YzAuNCwtMC44NzQgMC44NjIsLTEuNjk2IDEuMzc0LC0yLjQ0M2MwLjIwNiwtMC4zIDAuNDMzLC0wLjYwNCAwLjY5MiwtMC45MjlsMC40MjcsLTAuNTM1bC0wLjUyNiwtMC40NGExNy40NSwxNy40NSAwIDAgMCAtOC4xLC0zLjc4MWExNy44NTMsMTcuODUzIDAgMCAwIC04LjM3NSwwLjQ5YzIuMDIzLC04Ljg2OCA5LjgyLC0xNS4wNSAxOS4wMjcsLTE1LjA1N2M1LjE5NSwwIDEwLjA3OCwyLjAwNyAxMy43NTIsNS42NTJjMi42MTksMi41OTggNC40MjIsNS44MzUgNS4yMjQsOS4zNzJhMTcuOTA4LDE3LjkwOCAwIDAgMCAtNS4yMDgsLTAuNzlsLTAuMzE4LC0wLjAwMWExOC4wOTYsMTguMDk2IDAgMCAwIC0yLjA2NywwLjE1M2wtMC4wODcsMC4wMTJjLTAuMzAzLDAuMDQgLTAuNTcsMC4wODEgLTAuODEzLDAuMTI2Yy0wLjExOSwwLjAyIC0wLjIzNywwLjA0NSAtMC4zNTUsMC4wNjhjLTAuMjgsMC4wNTcgLTAuNTU0LDAuMTE5IC0wLjgxNiwwLjE4NWwtMC4yODgsMC4wNzNjLTAuMzM2LDAuMDkgLTAuNjc1LDAuMTkxIC0xLjAwNiwwLjNsLTAuMDYxLDAuMDJjLTAuNzQsMC4yNTEgLTEuNDc4LDAuNTU4IC0yLjE5LDAuOTE0bC0wLjA1NywwLjAyOWMtMC4zMTYsMC4xNTggLTAuNjM2LDAuMzMzIC0wLjk3OCwwLjUzNGwtMC4wNzUsMC4wNDVhMTYuOTcsMTYuOTcgMCAwIDAgLTQuNDE0LDMuNzhsLTAuMTU3LDAuMTkxYy0wLjMxNywwLjM5NCAtMC41NjcsMC43MjcgLTAuNzg3LDEuMDQ4Yy0wLjE4NCwwLjI3IC0wLjM2OSwwLjU2IC0wLjYsMC45NDJsLTAuMTI2LDAuMjE3Yy0wLjE4NCwwLjMxOCAtMC4zNDgsMC42MjIgLTAuNDg3LDAuOWwtMC4wMzMsMC4wNjFjLTAuMzU0LDAuNzExIC0wLjY2MSwxLjQ1NSAtMC45MTcsMi4yMTRsLTAuMDM2LDAuMTExYTE3LjEzLDE3LjEzIDAgMCAwIC0wLjg1NSw1LjY0NGwwLjAwMywwLjIzNGEyMy41NjUsMjMuNTY1IDAgMCAwIDAuMDQzLDAuODIyYzAuMDEsMC4xMyAwLjAyMywwLjI1OSAwLjAzNiwwLjM4OGMwLjAxNSwwLjE1OCAwLjAzNCwwLjMxNiAwLjA1MywwLjQ3MWwwLjAxMSwwLjA4OGwwLjAyOCwwLjIxNGMwLjAzNywwLjI2NCAwLjA4LDAuNTI1IDAuMTMsMC43ODdjMC41MDMsMi42MzcgMS43Niw1LjI3NCAzLjYzNSw3LjYyNWwwLjA4NSwwLjEwNmwwLjA4NywtMC4xMDRjMC43NDgsLTAuODg0IDIuNjAzLC0zLjY4NyAyLjc2LC01LjM2OWwwLjAwMywtMC4wMzFsLTAuMDE1LC0wLjAyOGExMS43MzYsMTEuNzM2IDAgMCAxIC0xLjMzMywtNS40MDdjMCwtNi4yODQgNC45NCwtMTEuNTAyIDExLjI0MywtMTEuODhsMC40MTQsLTAuMDE1YzIuNTYxLC0wLjA1OCA1LjA2NCwwLjY3MyA3LjIzLDIuMTM2bDAuMDU4LDAuMDM5bDQuMTk3LC0yLjQ0bDAuMDU1LC0wLjAzM2wwLC0wLjA2MmMwLjAwNiwtNi42MzIgLTIuNTkyLC0xMi44NjUgLTcuMzE0LC0xNy41NTFjLTQuNzE2LC00LjY3OSAtMTAuOTkxLC03LjI1NSAtMTcuNjcyLC03LjI1NSIvPgogPC9nPgo8L3N2Zz4=&label=Codecov [![GitHub Release][github_release]](https://github.com/sdispater/tomlkit/releases/) [![PyPI Version][pypi_version]](https://pypi.python.org/pypi/tomlkit/) [![Python Versions][python_versions]](https://pypi.python.org/pypi/tomlkit/) [![License][github_license]](https://github.com/sdispater/tomlkit/blob/master/LICENSE) -
-[![Travis CI][travisci]](https://travis-ci.com/sdispater/tomlkit) -[![AppVeyor][appveyor]](https://ci.appveyor.com/project/sdispater/tomlkit) [![Codecov][codecov]](https://codecov.io/gh/sdispater/tomlkit) +
# TOML Kit - Style-preserving TOML library for Python @@ -152,7 +150,6 @@ It can be created with the following code: >>> doc["database"] = database ``` - ## Installation If you are using [Poetry](https://poetry.eustace.io), diff --git a/poetry.lock b/poetry.lock index 4683c0b..1c4e162 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,902 +1,525 @@ [[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -name = "appdirs" -optional = false -python-versions = "*" -version = "1.4.4" - -[[package]] -category = "dev" -description = "A few extensions to pyyaml." -name = "aspy.yaml" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.3.0" - -[package.dependencies] -pyyaml = "*" - -[[package]] -category = "dev" -description = "Atomic file writes." name = "atomicwrites" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.4.0" - -[[package]] +description = "Atomic file writes." category = "dev" -description = "Classes Without Boilerplate" -name = "attrs" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.3.0" - -[package.extras] -azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] -dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] -docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] [[package]] +name = "attrs" +version = "21.2.0" +description = "Classes Without Boilerplate" category = "dev" -description = "Backport of functools.lru_cache" -name = "backports.functools-lru-cache" optional = false -python-versions = ">=2.6" -version = "1.6.1" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov"] +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"] +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"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] [[package]] +name = "backports.entry-points-selectable" +version = "1.1.1" +description = "Compatibility shim providing selectable entry points for older implementations" category = "dev" -description = "The uncompromising code formatter." -name = "black" optional = false -python-versions = ">=3.6" -version = "19.10b0" +python-versions = ">=2.7" [package.dependencies] -appdirs = "*" -attrs = ">=18.1.0" -click = ">=6.5" -pathspec = ">=0.6,<1" -regex = "*" -toml = ">=0.9.4" -typed-ast = ">=1.4.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] [[package]] -category = "dev" -description = "Python package for providing Mozilla's CA Bundle." -name = "certifi" -optional = false -python-versions = "*" -version = "2020.6.20" - -[[package]] -category = "dev" -description = "Validate configuration and produce human readable error messages." name = "cfgv" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.0.1" - -[package.dependencies] -six = "*" - -[[package]] +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." category = "dev" -description = "Universal encoding detector for Python 2 and 3" -name = "chardet" optional = false -python-versions = "*" -version = "3.0.4" +python-versions = ">=3.6.1" [[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." category = "dev" -description = "Composable command line interface toolkit" -name = "click" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "7.1.2" [[package]] +name = "coverage" +version = "6.2" +description = "Code coverage measurement for Python" category = "dev" -description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab" -name = "codecov" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.1.8" +python-versions = ">=3.6" [package.dependencies] -coverage = "*" -requests = ">=2.7.9" - -[[package]] -category = "dev" -description = "Cross-platform colored terminal text." -name = "colorama" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.3" - -[[package]] -category = "dev" -description = "Updated configparser from Python 3.7 for Python 2.6+." -name = "configparser" -optional = false -python-versions = ">=2.6" -version = "4.0.2" +tomli = {version = "*", optional = true, markers = "extra == \"toml\""} [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2)", "pytest-flake8", "pytest-black-multipy"] - -[[package]] -category = "dev" -description = "Backports and enhancements for the contextlib module" -name = "contextlib2" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.6.0.post1" +toml = ["tomli"] [[package]] -category = "dev" -description = "Code coverage measurement for Python" -name = "coverage" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "5.2.1" - -[package.extras] -toml = ["toml"] - -[[package]] -category = "dev" -description = "Distribution utilities" name = "distlib" -optional = false -python-versions = "*" -version = "0.3.1" - -[[package]] -category = "main" -description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4" -name = "enum34" -optional = false -python-versions = "*" -version = "1.1.10" - -[[package]] +version = "0.3.4" +description = "Distribution utilities" category = "dev" -description = "A platform independent file lock." -name = "filelock" optional = false python-versions = "*" -version = "3.0.12" [[package]] +name = "filelock" +version = "3.4.0" +description = "A platform independent file lock." category = "dev" -description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+" -name = "funcsigs" optional = false -python-versions = "*" -version = "1.0.2" - -[[package]] -category = "main" -description = "Backport of the functools module from Python 3.2.3 for use on 2.7 and PyPy." -name = "functools32" -optional = false -python-versions = "*" -version = "3.2.3-2" +python-versions = ">=3.6" -[[package]] -category = "dev" -description = "Backport of the concurrent.futures package from Python 3" -name = "futures" -optional = false -python-versions = ">=2.6, <3" -version = "3.3.0" +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] [[package]] -category = "dev" -description = "File identification library for Python" name = "identify" +version = "2.4.0" +description = "File identification library for Python" +category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "1.4.25" +python-versions = ">=3.6.1" [package.extras] -license = ["editdistance"] +license = ["ukkonen"] [[package]] -category = "dev" -description = "Internationalized Domain Names in Applications (IDNA)" -name = "idna" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.10" - -[[package]] -category = "dev" -description = "Read metadata from Python packages" name = "importlib-metadata" +version = "4.8.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" -version = "1.7.0" +python-versions = ">=3.6" [package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" -configparser = {version = ">=3.5", markers = "python_version < \"3\""} -contextlib2 = {version = "*", markers = "python_version < \"3\""} -pathlib2 = {version = "*", markers = "python_version < \"3\""} [package.extras] -docs = ["sphinx", "rst.linker"] -testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] -category = "dev" -description = "Read resources from Python packages" name = "importlib-resources" +version = "5.2.3" +description = "Read resources from Python packages" +category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "3.0.0" +python-versions = ">=3.6" [package.dependencies] -contextlib2 = {version = "*", markers = "python_version < \"3\""} -pathlib2 = {version = "*", markers = "python_version < \"3\""} -singledispatch = {version = "*", markers = "python_version < \"3.4\""} -typing = {version = "*", markers = "python_version < \"3.5\""} -zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -docs = ["sphinx", "rst.linker", "jaraco.packaging"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] [[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" category = "dev" -description = "A Python utility / library to sort Python imports." -name = "isort" -optional = false -python-versions = ">=3.6,<4.0" -version = "5.2.1" - -[package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile_deprecated_finder = ["pipreqs", "requirementslib", "tomlkit (>=0.5.3)"] -requirements_deprecated_finder = ["pipreqs", "pip-api"] - -[[package]] -category = "dev" -description = "More routines for operating on iterables, beyond itertools" -name = "more-itertools" optional = false python-versions = "*" -version = "5.0.0" - -[package.dependencies] -six = ">=1.0.0,<2.0.0" [[package]] -category = "dev" -description = "More routines for operating on iterables, beyond itertools" -name = "more-itertools" -optional = false -python-versions = ">=3.5" -version = "8.4.0" - -[[package]] -category = "dev" -description = "Node.js virtual environment builder" name = "nodeenv" +version = "1.6.0" +description = "Node.js virtual environment builder" +category = "dev" optional = false python-versions = "*" -version = "1.4.0" [[package]] -category = "dev" -description = "Core utilities for Python packages" name = "packaging" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.4" - -[package.dependencies] -pyparsing = ">=2.0.2" -six = "*" - -[[package]] +version = "21.3" +description = "Core utilities for Python packages" category = "dev" -description = "Object-oriented filesystem paths" -name = "pathlib2" optional = false -python-versions = "*" -version = "2.3.5" +python-versions = ">=3.6" [package.dependencies] -six = "*" -scandir = {version = "*", markers = "python_version < \"3.5\""} +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] +name = "platformdirs" +version = "2.4.0" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" -description = "Utility library for gitignore style pattern matching of file paths." -name = "pathspec" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.8.0" +python-versions = ">=3.6" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] [[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.13.1" +python-versions = ">=3.6" [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] -category = "dev" -description = "A framework for managing and maintaining multi-language pre-commit hooks." name = "pre-commit" +version = "2.16.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.21.0" +python-versions = ">=3.6.1" [package.dependencies] -"aspy.yaml" = "*" cfgv = ">=2.0.0" identify = ">=1.0.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +importlib-resources = {version = "<5.3", markers = "python_version < \"3.7\""} nodeenv = ">=0.11.1" -pyyaml = "*" -six = "*" +pyyaml = ">=5.1" toml = "*" -virtualenv = ">=15.2" -futures = {version = "*", markers = "python_version < \"3.2\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} -importlib-resources = {version = "*", markers = "python_version < \"3.7\""} +virtualenv = ">=20.0.8" [[package]] -category = "dev" -description = "library with cross-python path, ini-parsing, io, code, log facilities" name = "py" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.9.0" - -[[package]] +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" -description = "Python parsing module" -name = "pyparsing" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.7" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] +name = "pyparsing" +version = "3.0.6" +description = "Python parsing module" category = "dev" -description = "pytest: simple powerful testing with Python" -name = "pytest" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "4.6.11" - -[package.dependencies] -atomicwrites = ">=1.0" -attrs = ">=17.4.0" -packaging = "*" -pluggy = ">=0.12,<1.0" -py = ">=1.5.0" -six = ">=1.10.0" -wcwidth = "*" -colorama = {version = "*", markers = "sys_platform == \"win32\" and python_version != \"3.4\""} -funcsigs = {version = ">=1.0", markers = "python_version < \"3.0\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -pathlib2 = {version = ">=2.2.0", markers = "python_version < \"3.6\""} - -[[package.dependencies.more-itertools]] -markers = "python_version <= \"2.7\"" -version = ">=4.0.0,<6.0.0" - -[[package.dependencies.more-itertools]] -markers = "python_version > \"2.7\"" -version = ">=4.0.0" +python-versions = ">=3.6" [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "nose", "requests", "mock"] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" category = "dev" -description = "Pytest plugin for measuring coverage." -name = "pytest-cov" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.10.0" +python-versions = ">=3.6" [package.dependencies] -coverage = ">=4.4" -pytest = ">=4.6" +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" +toml = "*" [package.extras] -testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] - -[[package]] -category = "dev" -description = "YAML parser and emitter for Python" -name = "pyyaml" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "5.3.1" - -[[package]] -category = "dev" -description = "Alternative regular expression module, to replace re." -name = "regex" -optional = false -python-versions = "*" -version = "2020.7.14" +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] +name = "pytest-cov" +version = "3.0.0" +description = "Pytest plugin for measuring coverage." category = "dev" -description = "Python HTTP for Humans." -name = "requests" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.24.0" +python-versions = ">=3.6" [package.dependencies] -certifi = ">=2017.4.17" -chardet = ">=3.0.2,<4" -idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] - -[[package]] -category = "dev" -description = "scandir, a better directory iterator and faster os.walk()" -name = "scandir" -optional = false -python-versions = "*" -version = "1.10.0" +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] [[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" category = "dev" -description = "This library brings functools.singledispatch from Python 3.4 to Python 2.6-3.3." -name = "singledispatch" optional = false -python-versions = "*" -version = "3.4.0.3" - -[package.dependencies] -six = "*" +python-versions = ">=3.6" [[package]] -category = "dev" -description = "Python 2 and 3 compatibility utilities" 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.*" -version = "1.15.0" [[package]] -category = "dev" -description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" -optional = false -python-versions = "*" -version = "0.10.1" - -[[package]] +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" category = "dev" -description = "tox is a generic virtualenv management and test command line tool" -name = "tox" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "3.18.1" - -[package.dependencies] -filelock = ">=3.0.0" -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" -colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = ">=0.12,<2", markers = "python_version < \"3.8\""} - -[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)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)"] +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] +name = "tomli" +version = "1.2.3" +description = "A lil' TOML parser" category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" -name = "typed-ast" -optional = false -python-versions = "*" -version = "1.4.1" - -[[package]] -category = "main" -description = "Type Hints for Python" -name = "typing" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.7.4.3" +python-versions = ">=3.6" [[package]] +name = "typing-extensions" +version = "4.0.1" +description = "Backported and Experimental Type Hints for Python 3.6+" category = "dev" -description = "HTTP library with thread-safe connection pooling, file post, and more." -name = "urllib3" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.25.10" - -[package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] +python-versions = ">=3.6" [[package]] -category = "dev" -description = "Virtual Python Environment builder" name = "virtualenv" +version = "20.10.0" +description = "Virtual Python Environment builder" +category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "20.0.28" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] -appdirs = ">=1.4.3,<2" +"backports.entry-points-selectable" = ">=1.0.4" distlib = ">=0.3.1,<1" -filelock = ">=3.0.0,<4" -six = ">=1.9.0,<2" -importlib-metadata = {version = ">=0.12,<2", markers = "python_version < \"3.8\""} +filelock = ">=3.2,<4" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""} -pathlib2 = {version = ">=2.3.3,<3", markers = "python_version < \"3.4\" and sys_platform != \"win32\""} +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 (>=19.9.0rc1)"] -testing = ["coverage (>=5)", "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)", "pytest-xdist (>=1.31.0)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] +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]] -category = "dev" -description = "Measures the displayed width of unicode strings in a terminal" -name = "wcwidth" -optional = false -python-versions = "*" -version = "0.2.5" - -[package.dependencies] -"backports.functools-lru-cache" = {version = ">=1.2.1", markers = "python_version < \"3.2\""} - -[[package]] -category = "dev" -description = "Backport of pathlib-compatible object wrapper for zip files" name = "zipp" +version = "3.6.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" optional = false -python-versions = ">=2.7" -version = "1.2.0" - -[package.dependencies] -contextlib2 = {version = "*", markers = "python_version < \"3.4\""} +python-versions = ">=3.6" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pathlib2", "unittest2", "jaraco.itertools", "func-timeout"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] -content-hash = "c9969acb3fec641f267adc9c8ea62b8a424b229c29834d0f5ce6427d71b114b1" lock-version = "1.1" -python-versions = "~2.7 || ^3.5" +python-versions = "^3.6" +content-hash = "913cebf3c94a1db4435a2efa7f9ce19fc58d167a90ee9b15abacec8a9ef77e0c" [metadata.files] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] -"aspy.yaml" = [ - {file = "aspy.yaml-1.3.0-py2.py3-none-any.whl", hash = "sha256:463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc"}, - {file = "aspy.yaml-1.3.0.tar.gz", hash = "sha256:e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"}, -] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, - {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, -] -"backports.functools-lru-cache" = [ - {file = "backports.functools_lru_cache-1.6.1-py2.py3-none-any.whl", hash = "sha256:0bada4c2f8a43d533e4ecb7a12214d9420e66eb206d54bf2d682581ca4b80848"}, - {file = "backports.functools_lru_cache-1.6.1.tar.gz", hash = "sha256:8fde5f188da2d593bd5bc0be98d9abc46c95bb8a9dde93429570192ee6cc2d4a"}, + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] -black = [ - {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, - {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, -] -certifi = [ - {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, - {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, +"backports.entry-points-selectable" = [ + {file = "backports.entry_points_selectable-1.1.1-py2.py3-none-any.whl", hash = "sha256:7fceed9532a7aa2bd888654a7314f864a3c16a4e710b34a58cfc0f08114c663b"}, + {file = "backports.entry_points_selectable-1.1.1.tar.gz", hash = "sha256:914b21a479fde881635f7af5adc7f6e38d6b274be32269070c53b698c60d5386"}, ] cfgv = [ - {file = "cfgv-2.0.1-py2.py3-none-any.whl", hash = "sha256:fbd93c9ab0a523bf7daec408f3be2ed99a980e20b2d19b50fc184ca6b820d289"}, - {file = "cfgv-2.0.1.tar.gz", hash = "sha256:edb387943b665bf9c434f717bf630fa78aecd53d5900d2e05da6ad6048553144"}, -] -chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, -] -click = [ - {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, - {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, -] -codecov = [ - {file = "codecov-2.1.8-py2.py3-none-any.whl", hash = "sha256:65e8a8008e43eb45a9404bf68f8d4a60d36de3827ef2287971c94940128eba1e"}, - {file = "codecov-2.1.8-py3.8.egg", hash = "sha256:fa7985ac6a3886cf68e3420ee1b5eb4ed30c4bdceec0f332d17ab69f545fbc90"}, - {file = "codecov-2.1.8.tar.gz", hash = "sha256:0be9cd6358cc6a3c01a1586134b0fb524dfa65ccbec3a40e9f28d5f976676ba2"}, + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] colorama = [ - {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, - {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, -] -configparser = [ - {file = "configparser-4.0.2-py2.py3-none-any.whl", hash = "sha256:254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c"}, - {file = "configparser-4.0.2.tar.gz", hash = "sha256:c7d282687a5308319bf3d2e7706e575c635b0a470342641c93bea0ea3b5331df"}, -] -contextlib2 = [ - {file = "contextlib2-0.6.0.post1-py2.py3-none-any.whl", hash = "sha256:3355078a159fbb44ee60ea80abd0d87b80b78c248643b49aa6d94673b413609b"}, - {file = "contextlib2-0.6.0.post1.tar.gz", hash = "sha256:01f490098c18b19d2bd5bb5dc445b2054d2fa97f09a4280ba2c5f3c394c8162e"}, + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-5.2.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4"}, - {file = "coverage-5.2.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01"}, - {file = "coverage-5.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8"}, - {file = "coverage-5.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59"}, - {file = "coverage-5.2.1-cp27-cp27m-win32.whl", hash = "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3"}, - {file = "coverage-5.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f"}, - {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd"}, - {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651"}, - {file = "coverage-5.2.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b"}, - {file = "coverage-5.2.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d"}, - {file = "coverage-5.2.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3"}, - {file = "coverage-5.2.1-cp35-cp35m-win32.whl", hash = "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"}, - {file = "coverage-5.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962"}, - {file = "coverage-5.2.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082"}, - {file = "coverage-5.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716"}, - {file = "coverage-5.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb"}, - {file = "coverage-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d"}, - {file = "coverage-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546"}, - {file = "coverage-5.2.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811"}, - {file = "coverage-5.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258"}, - {file = "coverage-5.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034"}, - {file = "coverage-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46"}, - {file = "coverage-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8"}, - {file = "coverage-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0"}, - {file = "coverage-5.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd"}, - {file = "coverage-5.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b"}, - {file = "coverage-5.2.1-cp38-cp38-win32.whl", hash = "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd"}, - {file = "coverage-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d"}, - {file = "coverage-5.2.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3"}, - {file = "coverage-5.2.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4"}, - {file = "coverage-5.2.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4"}, - {file = "coverage-5.2.1-cp39-cp39-win32.whl", hash = "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89"}, - {file = "coverage-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b"}, - {file = "coverage-5.2.1.tar.gz", hash = "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b"}, + {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"}, + {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"}, + {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"}, + {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"}, + {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"}, + {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"}, + {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"}, + {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"}, + {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"}, + {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"}, + {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"}, + {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"}, + {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"}, + {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"}, + {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"}, + {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"}, + {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"}, + {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"}, + {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"}, + {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, ] distlib = [ - {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, - {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, -] -enum34 = [ - {file = "enum34-1.1.10-py2-none-any.whl", hash = "sha256:a98a201d6de3f2ab3db284e70a33b0f896fbf35f8086594e8c9e74b909058d53"}, - {file = "enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328"}, - {file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"}, + {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, + {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, ] filelock = [ - {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, - {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, -] -funcsigs = [ - {file = "funcsigs-1.0.2-py2.py3-none-any.whl", hash = "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca"}, - {file = "funcsigs-1.0.2.tar.gz", hash = "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"}, -] -functools32 = [ - {file = "functools32-3.2.3-2.tar.gz", hash = "sha256:f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d"}, - {file = "functools32-3.2.3-2.zip", hash = "sha256:89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0"}, -] -futures = [ - {file = "futures-3.3.0-py2-none-any.whl", hash = "sha256:49b3f5b064b6e3afc3316421a3f25f66c137ae88f068abbf72830170033c5e16"}, - {file = "futures-3.3.0.tar.gz", hash = "sha256:7e033af76a5e35f58e56da7a91e687706faf4e7bdfb2cbc3f2cca6b9bcda9794"}, + {file = "filelock-3.4.0-py3-none-any.whl", hash = "sha256:2e139a228bcf56dd8b2274a65174d005c4a6b68540ee0bdbb92c76f43f29f7e8"}, + {file = "filelock-3.4.0.tar.gz", hash = "sha256:93d512b32a23baf4cac44ffd72ccf70732aeff7b8050fcaf6d3ec406d954baf4"}, ] identify = [ - {file = "identify-1.4.25-py2.py3-none-any.whl", hash = "sha256:ccd88716b890ecbe10920659450a635d2d25de499b9a638525a48b48261d989b"}, - {file = "identify-1.4.25.tar.gz", hash = "sha256:110ed090fec6bce1aabe3c72d9258a9de82207adeaa5a05cd75c635880312f9a"}, -] -idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "identify-2.4.0-py2.py3-none-any.whl", hash = "sha256:eba31ca80258de6bb51453084bff4a923187cd2193b9c13710f2516ab30732cc"}, + {file = "identify-2.4.0.tar.gz", hash = "sha256:a33ae873287e81651c7800ca309dc1f84679b763c9c8b30680e16fbfa82f0107"}, ] importlib-metadata = [ - {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"}, - {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"}, + {file = "importlib_metadata-4.8.3-py3-none-any.whl", hash = "sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e"}, + {file = "importlib_metadata-4.8.3.tar.gz", hash = "sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668"}, ] importlib-resources = [ - {file = "importlib_resources-3.0.0-py2.py3-none-any.whl", hash = "sha256:d028f66b66c0d5732dae86ba4276999855e162a749c92620a38c1d779ed138a7"}, - {file = "importlib_resources-3.0.0.tar.gz", hash = "sha256:19f745a6eca188b490b1428c8d1d4a0d2368759f32370ea8fb89cad2ab1106c3"}, + {file = "importlib_resources-5.2.3-py3-none-any.whl", hash = "sha256:ae35ed1cfe8c0d6c1a53ecd168167f01fa93b893d51a62cdf23aea044c67211b"}, + {file = "importlib_resources-5.2.3.tar.gz", hash = "sha256:203d70dda34cfbfbb42324a8d4211196e7d3e858de21a5eb68c6d1cdd99e4e98"}, ] -isort = [ - {file = "isort-5.2.1-py3-none-any.whl", hash = "sha256:a4401d357b0f7a9064781da345e6e2f075ebc09fbebf605740163140d5ac418c"}, - {file = "isort-5.2.1.tar.gz", hash = "sha256:761a8f490d8bbcd3549b5618ed423468bbdece603cce44b290ee274c9a360893"}, -] -more-itertools = [ - {file = "more-itertools-5.0.0.tar.gz", hash = "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4"}, - {file = "more_itertools-5.0.0-py2-none-any.whl", hash = "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc"}, - {file = "more_itertools-5.0.0-py3-none-any.whl", hash = "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"}, - {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"}, - {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"}, +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] nodeenv = [ - {file = "nodeenv-1.4.0-py2.py3-none-any.whl", hash = "sha256:4b0b77afa3ba9b54f4b6396e60b0c83f59eaeb2d63dc3cc7a70f7f4af96c82bc"}, + {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, + {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, ] packaging = [ - {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, - {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, -] -pathlib2 = [ - {file = "pathlib2-2.3.5-py2.py3-none-any.whl", hash = "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db"}, - {file = "pathlib2-2.3.5.tar.gz", hash = "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -pathspec = [ - {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, - {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, +platformdirs = [ + {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, + {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, ] pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] pre-commit = [ - {file = "pre_commit-1.21.0-py2.py3-none-any.whl", hash = "sha256:f92a359477f3252452ae2e8d3029de77aec59415c16ae4189bcfba40b757e029"}, - {file = "pre_commit-1.21.0.tar.gz", hash = "sha256:8f48d8637bdae6fa70cc97db9c1dd5aa7c5c8bf71968932a380628c25978b850"}, + {file = "pre_commit-2.16.0-py2.py3-none-any.whl", hash = "sha256:758d1dc9b62c2ed8881585c254976d66eae0889919ab9b859064fc2fe3c7743e"}, + {file = "pre_commit-2.16.0.tar.gz", hash = "sha256:fe9897cac830aa7164dbd02a4e7b90cae49630451ce88464bca73db486ba9f65"}, ] py = [ - {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, - {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, + {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, ] pytest = [ - {file = "pytest-4.6.11-py2.py3-none-any.whl", hash = "sha256:a00a7d79cbbdfa9d21e7d0298392a8dd4123316bfac545075e6f8f24c94d8c97"}, - {file = "pytest-4.6.11.tar.gz", hash = "sha256:50fa82392f2120cc3ec2ca0a75ee615be4c479e66669789771f1758332be4353"}, + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-cov = [ - {file = "pytest-cov-2.10.0.tar.gz", hash = "sha256:1a629dc9f48e53512fcbfda6b07de490c374b0c83c55ff7a1720b3fccff0ac87"}, - {file = "pytest_cov-2.10.0-py2.py3-none-any.whl", hash = "sha256:6e6d18092dce6fad667cd7020deed816f858ad3b49d5b5e2b1cc1c97a4dba65c"}, + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] pyyaml = [ - {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, - {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, - {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, - {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, - {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, - {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, - {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, - {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, - {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, - {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, - {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, -] -regex = [ - {file = "regex-2020.7.14-cp27-cp27m-win32.whl", hash = "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7"}, - {file = "regex-2020.7.14-cp27-cp27m-win_amd64.whl", hash = "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c50a724d136ec10d920661f1442e4a8b010a4fe5aebd65e0c2241ea41dbe93dc"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8a51f2c6d1f884e98846a0a9021ff6861bdb98457879f412fdc2b42d14494067"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9c568495e35599625f7b999774e29e8d6b01a6fb684d77dee1f56d41b11b40cd"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:51178c738d559a2d1071ce0b0f56e57eb315bcf8f7d4cf127674b533e3101f88"}, - {file = "regex-2020.7.14-cp36-cp36m-win32.whl", hash = "sha256:9eddaafb3c48e0900690c1727fba226c4804b8e6127ea409689c3bb492d06de4"}, - {file = "regex-2020.7.14-cp36-cp36m-win_amd64.whl", hash = "sha256:14a53646369157baa0499513f96091eb70382eb50b2c82393d17d7ec81b7b85f"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1269fef3167bb52631ad4fa7dd27bf635d5a0790b8e6222065d42e91bede4162"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d0a5095d52b90ff38592bbdc2644f17c6d495762edf47d876049cfd2968fbccf"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:4c037fd14c5f4e308b8370b447b469ca10e69427966527edcab07f52d88388f7"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bc3d98f621898b4a9bc7fecc00513eec8f40b5b83913d74ccb445f037d58cd89"}, - {file = "regex-2020.7.14-cp37-cp37m-win32.whl", hash = "sha256:46bac5ca10fb748d6c55843a931855e2727a7a22584f302dd9bb1506e69f83f6"}, - {file = "regex-2020.7.14-cp37-cp37m-win_amd64.whl", hash = "sha256:0dc64ee3f33cd7899f79a8d788abfbec168410be356ed9bd30bbd3f0a23a7204"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a"}, - {file = "regex-2020.7.14-cp38-cp38-win32.whl", hash = "sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341"}, - {file = "regex-2020.7.14-cp38-cp38-win_amd64.whl", hash = "sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840"}, - {file = "regex-2020.7.14.tar.gz", hash = "sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb"}, -] -requests = [ - {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, - {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, -] -scandir = [ - {file = "scandir-1.10.0-cp27-cp27m-win32.whl", hash = "sha256:92c85ac42f41ffdc35b6da57ed991575bdbe69db895507af88b9f499b701c188"}, - {file = "scandir-1.10.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cb925555f43060a1745d0a321cca94bcea927c50114b623d73179189a4e100ac"}, - {file = "scandir-1.10.0-cp34-cp34m-win32.whl", hash = "sha256:2c712840c2e2ee8dfaf36034080108d30060d759c7b73a01a52251cc8989f11f"}, - {file = "scandir-1.10.0-cp34-cp34m-win_amd64.whl", hash = "sha256:2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e"}, - {file = "scandir-1.10.0-cp35-cp35m-win32.whl", hash = "sha256:2b8e3888b11abb2217a32af0766bc06b65cc4a928d8727828ee68af5a967fa6f"}, - {file = "scandir-1.10.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8c5922863e44ffc00c5c693190648daa6d15e7c1207ed02d6f46a8dcc2869d32"}, - {file = "scandir-1.10.0-cp36-cp36m-win32.whl", hash = "sha256:2ae41f43797ca0c11591c0c35f2f5875fa99f8797cb1a1fd440497ec0ae4b022"}, - {file = "scandir-1.10.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7d2d7a06a252764061a020407b997dd036f7bd6a175a5ba2b345f0a357f0b3f4"}, - {file = "scandir-1.10.0-cp37-cp37m-win32.whl", hash = "sha256:67f15b6f83e6507fdc6fca22fedf6ef8b334b399ca27c6b568cbfaa82a364173"}, - {file = "scandir-1.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b24086f2375c4a094a6b51e78b4cf7ca16c721dcee2eddd7aa6494b42d6d519d"}, - {file = "scandir-1.10.0.tar.gz", hash = "sha256:4d4631f6062e658e9007ab3149a9b914f3548cb38bfb021c64f39a025ce578ae"}, -] -singledispatch = [ - {file = "singledispatch-3.4.0.3-py2.py3-none-any.whl", hash = "sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8"}, - {file = "singledispatch-3.4.0.3.tar.gz", hash = "sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c"}, + {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"}, ] six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] toml = [ - {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, - {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, -] -tox = [ - {file = "tox-3.18.1-py2.py3-none-any.whl", hash = "sha256:3d914480c46232c2d1a035482242535a26d76cc299e4fd28980c858463206f45"}, - {file = "tox-3.18.1.tar.gz", hash = "sha256:5c82e40046a91dbc80b6bd08321b13b4380d8ce3bcb5b62616cb17aaddefbb3a"}, + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] -typed-ast = [ - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, - {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, - {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, - {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, - {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, - {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, - {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, - {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, +tomli = [ + {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, + {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, ] -typing = [ - {file = "typing-3.7.4.3-py2-none-any.whl", hash = "sha256:283d868f5071ab9ad873e5e52268d611e851c870a2ba354193026f2dfb29d8b5"}, - {file = "typing-3.7.4.3.tar.gz", hash = "sha256:1187fb9c82fd670d10aa07bbb6cfcfe4bdda42d6fab8d5134f04e8c4d0b71cc9"}, -] -urllib3 = [ - {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, - {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, +typing-extensions = [ + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, ] virtualenv = [ - {file = "virtualenv-20.0.28-py2.py3-none-any.whl", hash = "sha256:8f582a030156282a9ee9d319984b759a232b07f86048c1d6a9e394afa44e78c8"}, - {file = "virtualenv-20.0.28.tar.gz", hash = "sha256:688a61d7976d82b92f7906c367e83bb4b3f0af96f8f75bfcd3da95608fe8ac6c"}, -] -wcwidth = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, + {file = "virtualenv-20.10.0-py2.py3-none-any.whl", hash = "sha256:4b02e52a624336eece99c96e3ab7111f469c24ba226a53ec474e8e787b365814"}, + {file = "virtualenv-20.10.0.tar.gz", hash = "sha256:576d05b46eace16a9c348085f7d0dc8ef28713a2cabaa1cf0aea41e8f12c9218"}, ] zipp = [ - {file = "zipp-1.2.0-py2.py3-none-any.whl", hash = "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"}, - {file = "zipp-1.2.0.tar.gz", hash = "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1"}, + {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, + {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, ] diff --git a/pyproject.toml b/pyproject.toml index b2bc115..ebd7048 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,26 +16,13 @@ include = [ ] [tool.poetry.dependencies] -python = "~2.7 || ^3.5" - -# enum34 is needed for Python 2.7 -enum34 = { version = "^1.1", python = "~2.7" } - -# functools32 is needed for Python 2.7 -functools32 = { version = "^3.2.3", python = "~2.7" } - -# The typing module is not in the stdlib in Python 2.7 and 3.4 -typing = { version = "^3.6", python = "~2.7 || ~3.4" } +python = "^3.6" [tool.poetry.dev-dependencies] -pytest = "^4.6" -pytest-cov = "^2.5" -black = { version = "^19.3b0", markers = "python_version >= '3.6' and python_version < '4.0' and implementation_name != 'pypy'" } -pre-commit = "^1.10" -tox = "^3.1" -codecov = "^2.0" -pyyaml = "~5.3.1" -isort = {version = "^5.2.0", python = "^3.6"} +pytest = "^6.2.5" +pytest-cov = "^3.0.0" +PyYAML = "^6.0" +pre-commit = {version = "^2.1.0", python = "^3.6.1"} [tool.black] line-length = 88 diff --git a/tests/conftest.py b/tests/conftest.py index 37183e9..0893f59 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,3 @@ -import io import os import pytest @@ -7,7 +6,7 @@ @pytest.fixture def example(): def _example(name): - with io.open( + with open( os.path.join(os.path.dirname(__file__), "examples", name + ".toml"), encoding="utf-8", ) as f: @@ -19,7 +18,7 @@ def _example(name): @pytest.fixture def json_example(): def _example(name): - with io.open( + with open( os.path.join(os.path.dirname(__file__), "examples", "json", name + ".json"), encoding="utf-8", ) as f: @@ -31,7 +30,7 @@ def _example(name): @pytest.fixture def invalid_example(): def _example(name): - with io.open( + with open( os.path.join( os.path.dirname(__file__), "examples", "invalid", name + ".toml" ), @@ -69,7 +68,7 @@ def get_tomltest_cases(): bn, ext = f.rsplit(".", 1) if bn not in rv[d]: rv[d][bn] = {} - with io.open(os.path.join(TEST_DIR, d, f), encoding="utf-8") as inp: + with open(os.path.join(TEST_DIR, d, f), encoding="utf-8") as inp: rv[d][bn][ext] = inp.read() return rv diff --git a/tests/examples/example.toml b/tests/examples/example.toml index 314768a..3628c5b 100644 --- a/tests/examples/example.toml +++ b/tests/examples/example.toml @@ -36,7 +36,6 @@ hosts = [ ] # Products - [[products]] name = "Hammer" sku = 738594937 diff --git a/tests/test_api.py b/tests/test_api.py index 9bd9c2e..5038fda 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,24 +1,27 @@ +import io import json +import os from datetime import date from datetime import datetime from datetime import time +from types import MappingProxyType import pytest import tomlkit +from tomlkit import dump from tomlkit import dumps +from tomlkit import load from tomlkit import loads from tomlkit import parse -from tomlkit.exceptions import EmptyKeyError from tomlkit.exceptions import InvalidCharInStringError from tomlkit.exceptions import InvalidControlChar from tomlkit.exceptions import InvalidDateError from tomlkit.exceptions import InvalidDateTimeError from tomlkit.exceptions import InvalidNumberError from tomlkit.exceptions import InvalidTimeError -from tomlkit.exceptions import MixedArrayTypesError from tomlkit.exceptions import UnexpectedCharError from tomlkit.items import AoT from tomlkit.items import Array @@ -39,7 +42,7 @@ def json_serial(obj): if isinstance(obj, (datetime, date, time)): return obj.isoformat() - raise TypeError("Type {} not serializable".format(type(obj))) + raise TypeError(f"Type {type(obj)} not serializable") @pytest.mark.parametrize( @@ -63,6 +66,30 @@ def test_parse_can_parse_valid_toml_files(example, example_name): assert isinstance(loads(example(example_name)), TOMLDocument) +@pytest.mark.parametrize( + "example_name", + [ + "example", + "fruit", + "hard", + "sections_with_same_start", + "pyproject", + "0.5.0", + "test", + "newline_in_strings", + "preserve_quotes_in_string", + "string_slash_whitespace_newline", + "table_names", + ], +) +def test_load_from_file_object(example_name): + with open( + os.path.join(os.path.dirname(__file__), "examples", example_name + ".toml"), + encoding="utf-8", + ) as fp: + assert isinstance(load(fp), TOMLDocument) + + @pytest.mark.parametrize("example_name", ["0.5.0", "pyproject", "table_names"]) def test_parsed_document_are_properly_json_representable( example, json_example, example_name @@ -128,6 +155,23 @@ def test_a_raw_dict_can_be_dumped(): assert s == 'foo = "bar"\n' +def test_mapping_types_can_be_dumped(): + x = MappingProxyType({"foo": "bar"}) + assert dumps(x) == 'foo = "bar"\n' + + +def test_dumps_weird_object(): + with pytest.raises(TypeError): + dumps(object()) + + +def test_dump_to_file_object(): + doc = {"foo": "bar"} + fp = io.StringIO() + dump(doc, fp) + assert fp.getvalue() == 'foo = "bar"\n' + + def test_integer(): i = tomlkit.integer("34") @@ -231,3 +275,11 @@ def test_item_dict_to_table(): bar = "baz" """ ) + + +def test_item_mixed_aray(): + example = [{"a": 3}, "b", 42] + expected = '[{a = 3}, "b", 42]' + t = tomlkit.item(example) + assert t.as_string().strip() == expected + assert dumps({"x": {"y": example}}).strip() == "[x]\ny = " + expected diff --git a/tests/test_build.py b/tests/test_build.py index 098b2d5..136abb8 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import datetime from tomlkit import aot @@ -79,7 +76,6 @@ def test_build_example(example): doc.add(nl()) doc.add(comment("Products")) - doc.add(nl()) products = aot() doc["products"] = products @@ -145,4 +141,4 @@ def test_top_level_keys_are_put_at_the_root_of_the_document(): name = "test" """ - assert doc.as_string() + assert doc.as_string() == expected diff --git a/tests/test_items.py b/tests/test_items.py index 5fc30de..40c299a 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import math import pickle @@ -13,10 +10,11 @@ from tomlkit import inline_table from tomlkit import parse -from tomlkit._compat import PY2 -from tomlkit._compat import OrderedDict +from tomlkit.api import array +from tomlkit.api import ws from tomlkit.exceptions import NonExistentKey from tomlkit.items import Bool +from tomlkit.items import Comment from tomlkit.items import InlineTable from tomlkit.items import Integer from tomlkit.items import Key @@ -133,6 +131,9 @@ def test_key_automatically_sets_proper_string_type_if_not_bare(): assert key.t == KeyType.Basic + key = Key("") + assert key.t == KeyType.Basic + def test_array_behaves_like_a_list(): a = item([1, 2]) @@ -148,20 +149,24 @@ def test_array_behaves_like_a_list(): assert a == [1, 2, 4] assert a.as_string() == "[1, 2, 4]" - del a[-1] + assert a.pop() == 4 assert a == [1, 2] assert a.as_string() == "[1, 2]" + a[0] = 4 + assert a == [4, 2] + a[-2] = 0 + assert a == [0, 2] + del a[-2] assert a == [2] assert a.as_string() == "[2]" - if not PY2: - a.clear() - assert a == [] - assert a.as_string() == "[]" + a.clear() + assert a == [] + assert a.as_string() == "[]" - content = """a = [1, 2] # Comment + content = """a = [1, 2,] # Comment """ doc = parse(content) @@ -198,6 +203,86 @@ def test_array_multiline(): assert "[]" == t.as_string() +def test_array_multiline_modify(): + doc = parse( + """\ +a = [ + "abc" +]""" + ) + doc["a"].append("def") + expected = """\ +a = [ + "abc", + "def" +]""" + assert expected == doc.as_string() + doc["a"].insert(1, "ghi") + expected = """\ +a = [ + "abc", + "ghi", + "def" +]""" + assert expected == doc.as_string() + + +def test_append_to_empty_array(): + doc = parse("x = [ ]") + doc["x"].append("a") + assert doc.as_string() == 'x = [ "a" ]' + doc = parse("x = [\n]") + doc["x"].append("a") + assert doc.as_string() == 'x = [\n "a"\n]' + + +def test_modify_array_with_comment(): + doc = parse("x = [ # comment\n]") + doc["x"].append("a") + assert doc.as_string() == 'x = [ # comment\n "a"\n]' + doc = parse( + """\ +x = [ + "a", + # comment + "b" +]""" + ) + doc["x"].insert(1, "c") + expected = """\ +x = [ + "a", + # comment + "c", + "b" +]""" + assert doc.as_string() == expected + doc = parse( + """\ +x = [ + 1 # comment +]""" + ) + doc["x"].append(2) + assert ( + doc.as_string() + == """\ +x = [ + 1, # comment + 2 +]""" + ) + + +def test_append_dict_to_array(): + doc = parse("x = [ ]") + doc["x"].append({"name": "John Doe", "email": "john@doe.com"}) + expected = 'x = [ {name = "John Doe",email = "john@doe.com"} ]' + assert doc.as_string() == expected + # Make sure the produced string is valid + assert parse(doc.as_string()) == doc + + def test_dicts_are_converted_to_tables(): t = item({"foo": {"bar": "baz"}}) @@ -209,22 +294,42 @@ def test_dicts_are_converted_to_tables(): ) +def test_array_add_line(): + t = array() + t.add_line(1, 2, 3, comment="Line 1") + t.add_line(4, 5, 6, comment="Line 2") + t.add_line(7, ws(","), ws(" "), 8, add_comma=False) + t.add_line(indent="") + assert len(t) == 8 + assert list(t) == [1, 2, 3, 4, 5, 6, 7, 8] + assert ( + t.as_string() + == """[ + 1, 2, 3, # Line 1 + 4, 5, 6, # Line 2 + 7, 8 +]""" + ) + + +def test_array_add_line_invalid_value(): + t = array() + with pytest.raises(ValueError, match="is not allowed"): + t.add_line(1, ws(" ")) + with pytest.raises(ValueError, match="is not allowed"): + t.add_line(Comment(Trivia(" ", comment="test"))) + assert len(t) == 0 + + def test_dicts_are_converted_to_tables_and_keep_order(): t = item( - OrderedDict( - [ - ( - "foo", - OrderedDict( - [ - ("bar", "baz"), - ("abc", 123), - ("baz", [OrderedDict([("c", 3), ("b", 2), ("a", 1)])]), - ] - ), - ) - ] - ) + { + "foo": { + "bar": "baz", + "abc": 123, + "baz": [{"c": 3, "b": 2, "a": 1}], + }, + } ) assert ( @@ -243,20 +348,13 @@ def test_dicts_are_converted_to_tables_and_keep_order(): def test_dicts_are_converted_to_tables_and_are_sorted_if_requested(): t = item( - OrderedDict( - [ - ( - "foo", - OrderedDict( - [ - ("bar", "baz"), - ("abc", 123), - ("baz", [OrderedDict([("c", 3), ("b", 2), ("a", 1)])]), - ] - ), - ) - ] - ), + { + "foo": { + "bar": "baz", + "abc": 123, + "baz": [{"c": 3, "b": 2, "a": 1}], + }, + }, _sort_keys=True, ) @@ -436,6 +534,16 @@ def test_tables_behave_like_dicts(): """ ) + assert t.get("bar") == "boom" + assert t.setdefault("foobar", "fuzz") == "fuzz" + assert ( + t.as_string() + == """foo = "bar" +bar = "boom" +foobar = "fuzz" +""" + ) + def test_items_are_pickable(): n = item(12) diff --git a/tests/test_parser.py b/tests/test_parser.py index 9f759a2..c293f82 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -2,6 +2,7 @@ from tomlkit.exceptions import EmptyTableNameError from tomlkit.exceptions import InternalParserError +from tomlkit.exceptions import UnexpectedCharError from tomlkit.items import StringType from tomlkit.parser import Parser @@ -19,7 +20,6 @@ def test_parser_should_raise_an_internal_error_if_parsing_wrong_type_of_string() def test_parser_should_raise_an_error_for_empty_tables(): content = """ [one] - [] """ @@ -28,5 +28,14 @@ def test_parser_should_raise_an_error_for_empty_tables(): with pytest.raises(EmptyTableNameError) as e: parser.parse() - assert e.value.line == 4 + assert e.value.line == 3 assert e.value.col == 1 + + +def test_parser_should_raise_an_error_if_equal_not_found(): + content = """[foo] +a {c = 1, d = 2} +""" + parser = Parser(content) + with pytest.raises(UnexpectedCharError): + parser.parse() diff --git a/tests/test_toml_document.py b/tests/test_toml_document.py index 59b42d7..5445134 100644 --- a/tests/test_toml_document.py +++ b/tests/test_toml_document.py @@ -1,18 +1,16 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import copy import json import pickle from datetime import datetime +from textwrap import dedent import pytest import tomlkit from tomlkit import parse -from tomlkit._compat import PY36 +from tomlkit import ws from tomlkit._utils import _utc from tomlkit.exceptions import NonExistentKey @@ -540,6 +538,22 @@ def test_values_can_still_be_set_for_out_of_order_tables(): del doc["a"]["a"]["key"] +def test_out_of_order_table_can_add_multiple_tables(): + content = """\ +[a.a.b] +x = 1 +[foo] +bar = 1 +[a.a.c] +y = 1 +[a.a.d] +z = 1 +""" + doc = parse(content) + assert doc.as_string() == content + assert doc["a"]["a"] == {"b": {"x": 1}, "c": {"y": 1}, "d": {"z": 1}} + + def test_out_of_order_tables_are_still_dicts(): content = """ [a.a] @@ -642,15 +656,12 @@ def test_updating_nested_value_keeps_correct_indent(): assert doc.as_string() == expected -@pytest.mark.skipif(not PY36, reason="Dict order is not deterministic on Python < 3.6") def test_repr(): content = """ namespace.key1 = "value1" namespace.key2 = "value2" - [tool.poetry.foo] option = "test" - [tool.poetry.bar] option = "test" inline = {"foo" = "bar", "bar" = "baz"} @@ -682,3 +693,199 @@ def test_deepcopy(): copied = copy.deepcopy(doc) assert copied == doc assert copied.as_string() == content + + +def test_move_table(): + content = """a = 1 +[x] +a = 1 + +[y] +b = 1 +""" + doc = parse(content) + doc["a"] = doc.pop("x") + doc["z"] = doc.pop("y") + assert ( + doc.as_string() + == """[a] +a = 1 + +[z] +b = 1 +""" + ) + + +def test_replace_with_table(): + content = """a = 1 +b = 2 +c = 3 +""" + doc = parse(content) + doc["b"] = {"foo": "bar"} + assert ( + doc.as_string() + == """a = 1 +c = 3 + +[b] +foo = "bar" +""" + ) + + +def test_replace_with_table_of_nested(): + example = """\ + [a] + x = 1 + + [a.b] + y = 2 + """ + doc = parse(dedent(example)) + doc["c"] = doc.pop("a") + expected = """\ + [c] + x = 1 + + [c.b] + y = 2 + """ + assert doc.as_string().strip() == dedent(expected).strip() + + +def test_replace_with_aot_of_nested(): + example = """\ + [a] + x = 1 + + [[a.b]] + y = 2 + + [[a.b]] + + [a.b.c] + z = 2 + + [[a.b.c.d]] + w = 2 + """ + doc = parse(dedent(example)) + doc["f"] = doc.pop("a") + expected = """\ + [f] + x = 1 + + [[f.b]] + y = 2 + + [[f.b]] + + [f.b.c] + z = 2 + + [[f.b.c.d]] + w = 2 + """ + assert doc.as_string().strip() == dedent(expected).strip() + + +def test_replace_with_comment(): + content = 'a = "1"' + doc = parse(content) + a = tomlkit.item(int(doc["a"])) + a.comment("`a` should be an int") + doc["a"] = a + expected = "a = 1 # `a` should be an int" + assert doc.as_string() == expected + + content = 'a = "1, 2, 3"' + doc = parse(content) + a = tomlkit.array() + a.comment("`a` should be an array") + for x in doc["a"].split(","): + a.append(int(x.strip())) + doc["a"] = a + expected = "a = [1, 2, 3] # `a` should be an array" + assert doc.as_string() == expected + + doc = parse(content) + a = tomlkit.inline_table() + a.comment("`a` should be an inline-table") + for x in doc["a"].split(","): + i = int(x.strip()) + a.append(chr(ord("a") + i - 1), i) + doc["a"] = a + expected = "a = {a = 1, b = 2, c = 3} # `a` should be an inline-table" + assert doc.as_string() == expected + + +def test_no_spurious_whitespaces(): + content = """\ + [x] + a = 1 + + [y] + b = 2 + """ + doc = parse(dedent(content)) + doc["z"] = doc.pop("y") + expected = """\ + [x] + a = 1 + + [z] + b = 2 + """ + assert doc.as_string() == dedent(expected) + doc["w"] = {"c": 3} + expected = """\ + [x] + a = 1 + + [z] + b = 2 + + [w] + c = 3 + """ + assert doc.as_string() == dedent(expected) + + doc = parse(dedent(content)) + del doc["x"] + doc["z"] = {"c": 3} + expected = """\ + [y] + b = 2 + + [z] + c = 3 + """ + assert doc.as_string() == dedent(expected) + + +def test_pop_add_whitespace_and_insert_table_work_togheter(): + content = """\ + a = 1 + b = 2 + c = 3 + d = 4 + """ + doc = parse(dedent(content)) + doc.pop("a") + doc.pop("b") + doc.add(ws("\n")) + doc["e"] = {"foo": "bar"} + expected = """\ + c = 3 + d = 4 + + [e] + foo = "bar" + """ + text = doc.as_string() + out = parse(text) + assert out["d"] == 4 + assert "d" not in out["e"] + assert text == dedent(expected) diff --git a/tests/test_toml_file.py b/tests/test_toml_file.py index c795965..ca50637 100644 --- a/tests/test_toml_file.py +++ b/tests/test_toml_file.py @@ -1,4 +1,3 @@ -import io import os from tomlkit.toml_document import TOMLDocument @@ -18,8 +17,8 @@ def test_toml_file(example): toml.write(content) try: - with io.open(toml_file, encoding="utf-8") as f: + with open(toml_file, encoding="utf-8") as f: assert original_content == f.read() finally: - with io.open(toml_file, "w", encoding="utf-8") as f: + with open(toml_file, "w", encoding="utf-8") as f: assert f.write(original_content) diff --git a/tests/test_toml_spec_tests.py b/tests/test_toml_spec_tests.py index b2d2549..57ad181 100644 --- a/tests/test_toml_spec_tests.py +++ b/tests/test_toml_spec_tests.py @@ -1,4 +1,3 @@ -import io import json import os import re @@ -8,7 +7,6 @@ from tomlkit import parse from tomlkit._compat import decode -from tomlkit._compat import unicode from tomlkit._utils import parse_rfc3339 from tomlkit.exceptions import TOMLKitError @@ -42,7 +40,7 @@ def to_bool(s): stypes = { - "string": unicode, + "string": str, "bool": to_bool, "integer": int, "float": float, @@ -54,9 +52,9 @@ def to_bool(s): loader = yaml.SafeLoader loader.add_implicit_resolver( - u"tag:yaml.org,2002:float", + "tag:yaml.org,2002:float", re.compile( - u"""^(?: + """^(?: [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)? |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+) |\\.[0-9_]+(?:[eE][-+][0-9]+)? @@ -65,7 +63,7 @@ def to_bool(s): |\\.(?:nan|NaN|NAN))$""", re.X, ), - list(u"-+0123456789."), + list("-+0123456789."), ) @@ -89,15 +87,15 @@ def untag(value): def test_valid_decode(test): toml_file = os.path.join(SPEC_TEST_DIR, "values", test + ".toml") yaml_file = os.path.join(SPEC_TEST_DIR, "values", test + ".yaml") - with io.open(toml_file, encoding="utf-8") as f: + with open(toml_file, encoding="utf-8") as f: toml_content = f.read() toml_val = parse(toml_content) if os.path.exists(yaml_file): - with io.open(yaml_file, encoding="utf-8") as f: + with open(yaml_file, encoding="utf-8") as f: yaml_val = yaml.load(f.read(), Loader=loader) else: - with io.open( + with open( os.path.join(SPEC_TEST_DIR, "values", test + ".json"), encoding="utf-8" ) as f: yaml_val = untag(json.loads(f.read())) @@ -110,5 +108,5 @@ def test_valid_decode(test): def test_invalid_decode(test): toml_file = os.path.join(SPEC_TEST_DIR, "errors", test + ".toml") with pytest.raises(TOMLKitError): - with io.open(toml_file, encoding="utf-8") as f: + with open(toml_file, encoding="utf-8") as f: parse(f.read()) diff --git a/tests/test_toml_tests.py b/tests/test_toml_tests.py index 8899e48..1247795 100644 --- a/tests/test_toml_tests.py +++ b/tests/test_toml_tests.py @@ -4,7 +4,6 @@ from tomlkit import parse from tomlkit._compat import decode -from tomlkit._compat import unicode from tomlkit._utils import parse_rfc3339 from tomlkit.exceptions import TOMLKitError @@ -16,7 +15,7 @@ def to_bool(s): stypes = { - "string": unicode, + "string": str, "bool": to_bool, "integer": int, "float": float, diff --git a/tests/test_utils.py b/tests/test_utils.py index 31dabf6..7dde452 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,10 +2,10 @@ from datetime import datetime as dt from datetime import time from datetime import timedelta as td +from datetime import timezone as tz import pytest -from tomlkit._compat import timezone as tz from tomlkit._utils import _utc from tomlkit._utils import parse_rfc3339 diff --git a/tests/test_write.py b/tests/test_write.py index 03a3031..b68de6e 100644 --- a/tests/test_write.py +++ b/tests/test_write.py @@ -1,14 +1,12 @@ -from __future__ import unicode_literals - from tomlkit import dumps from tomlkit import loads def test_write_backslash(): - d = {"foo": "\e\u25E6\r"} + d = {"foo": "\\e\u25E6\r"} expected = """foo = "\\\\e\u25E6\\r" """ assert expected == dumps(d) - assert loads(dumps(d))["foo"] == "\e\u25E6\r" + assert loads(dumps(d))["foo"] == "\\e\u25E6\r" diff --git a/tomlkit/__init__.py b/tomlkit/__init__.py index 110d6c1..a9ca698 100644 --- a/tomlkit/__init__.py +++ b/tomlkit/__init__.py @@ -1,3 +1,4 @@ +from .api import TOMLDocument from .api import aot from .api import array from .api import boolean @@ -5,6 +6,7 @@ from .api import date from .api import datetime from .api import document +from .api import dump from .api import dumps from .api import float_ from .api import inline_table @@ -12,6 +14,7 @@ from .api import item from .api import key from .api import key_value +from .api import load from .api import loads from .api import nl from .api import parse @@ -22,4 +25,4 @@ from .api import ws -__version__ = "0.7.2" +__version__ = "0.7.0" diff --git a/tomlkit/_compat.py b/tomlkit/_compat.py index 487ed99..1295df6 100644 --- a/tomlkit/_compat.py +++ b/tomlkit/_compat.py @@ -1,171 +1,15 @@ -import re import sys +from typing import Any +from typing import List +from typing import Optional -try: - from datetime import timezone -except ImportError: - from datetime import datetime - from datetime import timedelta - from datetime import tzinfo - class timezone(tzinfo): - __slots__ = "_offset", "_name" - - # Sentinel value to disallow None - _Omitted = object() - - def __new__(cls, offset, name=_Omitted): - if not isinstance(offset, timedelta): - raise TypeError("offset must be a timedelta") - if name is cls._Omitted: - if not offset: - return cls.utc - name = None - elif not isinstance(name, str): - raise TypeError("name must be a string") - if not cls._minoffset <= offset <= cls._maxoffset: - raise ValueError( - "offset must be a timedelta " - "strictly between -timedelta(hours=24) and " - "timedelta(hours=24)." - ) - return cls._create(offset, name) - - @classmethod - def _create(cls, offset, name=None): - self = tzinfo.__new__(cls) - self._offset = offset - self._name = name - return self - - def __getinitargs__(self): - """pickle support""" - if self._name is None: - return (self._offset,) - return (self._offset, self._name) - - def __eq__(self, other): - if type(other) != timezone: - return False - return self._offset == other._offset - - def __hash__(self): - return hash(self._offset) - - def __repr__(self): - """Convert to formal string, for repr(). - - >>> tz = timezone.utc - >>> repr(tz) - 'datetime.timezone.utc' - >>> tz = timezone(timedelta(hours=-5), 'EST') - >>> repr(tz) - "datetime.timezone(datetime.timedelta(-1, 68400), 'EST')" - """ - if self is self.utc: - return "datetime.timezone.utc" - if self._name is None: - return "%s.%s(%r)" % ( - self.__class__.__module__, - self.__class__.__name__, - self._offset, - ) - return "%s.%s(%r, %r)" % ( - self.__class__.__module__, - self.__class__.__name__, - self._offset, - self._name, - ) - - def __str__(self): - return self.tzname(None) - - def utcoffset(self, dt): - if isinstance(dt, datetime) or dt is None: - return self._offset - raise TypeError( - "utcoffset() argument must be a datetime instance" " or None" - ) - - def tzname(self, dt): - if isinstance(dt, datetime) or dt is None: - if self._name is None: - return self._name_from_offset(self._offset) - return self._name - raise TypeError("tzname() argument must be a datetime instance" " or None") - - def dst(self, dt): - if isinstance(dt, datetime) or dt is None: - return None - raise TypeError("dst() argument must be a datetime instance" " or None") - - def fromutc(self, dt): - if isinstance(dt, datetime): - if dt.tzinfo is not self: - raise ValueError("fromutc: dt.tzinfo " "is not self") - return dt + self._offset - raise TypeError("fromutc() argument must be a datetime instance" " or None") - - _maxoffset = timedelta(hours=23, minutes=59) - _minoffset = -_maxoffset - - @staticmethod - def _name_from_offset(delta): - if not delta: - return "UTC" - if delta < timedelta(0): - sign = "-" - delta = -delta - else: - sign = "+" - hours, rest = divmod(delta, timedelta(hours=1)) - minutes, rest = divmod(rest, timedelta(minutes=1)) - seconds = rest.seconds - microseconds = rest.microseconds - if microseconds: - return ("UTC{}{:02d}:{:02d}:{:02d}.{:06d}").format( - sign, hours, minutes, seconds, microseconds - ) - if seconds: - return "UTC{}{:02d}:{:02d}:{:02d}".format(sign, hours, minutes, seconds) - return "UTC{}{:02d}:{:02d}".format(sign, hours, minutes) - - timezone.utc = timezone._create(timedelta(0)) - timezone.min = timezone._create(timezone._minoffset) - timezone.max = timezone._create(timezone._maxoffset) - - -PY2 = sys.version_info[0] == 2 -PY36 = sys.version_info >= (3, 6) PY38 = sys.version_info >= (3, 8) -if PY2: - unicode = unicode - chr = unichr - long = long -else: - unicode = str - chr = chr - long = int - - -if PY36: - OrderedDict = dict -else: - from collections import OrderedDict - -try: - from collections.abc import MutableMapping -except ImportError: - from collections import MutableMapping - - -def decode(string, encodings=None): - if not PY2 and not isinstance(string, bytes): - return string - if PY2 and isinstance(string, unicode): +def decode(string: Any, encodings: Optional[List[str]] = None): + if not isinstance(string, bytes): return string encodings = encodings or ["utf-8", "latin1", "ascii"] diff --git a/tomlkit/_utils.py b/tomlkit/_utils.py index 2ae3e42..a5e1aca 100644 --- a/tomlkit/_utils.py +++ b/tomlkit/_utils.py @@ -1,19 +1,14 @@ import re +from collections.abc import Mapping from datetime import date from datetime import datetime from datetime import time from datetime import timedelta +from datetime import timezone from typing import Union from ._compat import decode -from ._compat import timezone - - -try: - from collections.abc import Mapping -except ImportError: - from collections import Mapping RFC_3339_LOOSE = re.compile( @@ -45,7 +40,7 @@ _utc = timezone(timedelta(), "UTC") -def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time] +def parse_rfc3339(string: str) -> Union[datetime, date, time]: m = RFC_3339_DATETIME.match(string) if m: year = int(m.group(1)) @@ -57,7 +52,7 @@ def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time] microsecond = 0 if m.group(7): - microsecond = int(("{:<06s}".format(m.group(8)))[:6]) + microsecond = int((f"{m.group(8):<06s}")[:6]) if m.group(9): # Timezone @@ -71,9 +66,7 @@ def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time] if sign == "-": offset = -offset - tzinfo = timezone( - offset, "{}{}:{}".format(sign, m.group(12), m.group(13)) - ) + tzinfo = timezone(offset, f"{sign}{m.group(12)}:{m.group(13)}") return datetime( year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo @@ -97,7 +90,7 @@ def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time] microsecond = 0 if m.group(4): - microsecond = int(("{:<06s}".format(m.group(5)))[:6]) + microsecond = int((f"{m.group(5):<06s}")[:6]) return time(hour, minute, second, microsecond) @@ -108,7 +101,7 @@ def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time] _escapes = {v: k for k, v in _escaped.items()} -def escape_string(s): +def escape_string(s: str) -> str: s = decode(s) res = [] @@ -136,9 +129,23 @@ def flush(): return "".join(res) -def merge_dicts(d1, d2): +def merge_dicts(d1: dict, d2: dict) -> dict: for k, v in d2.items(): - if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], Mapping): - merge_dicts(d1[k], d2[k]) + if k in d1 and isinstance(d1[k], dict) and isinstance(v, Mapping): + merge_dicts(d1[k], v) else: d1[k] = d2[k] + + +def escape_quotes(s: str, quote: str) -> str: + escaped = False + result = "" + for c in s: + if escaped: + escaped = False + elif c == "\\": + escaped = True + elif c == quote: + result += "\\" + result += c + return result diff --git a/tomlkit/api.py b/tomlkit/api.py index 3de4121..d254c8e 100644 --- a/tomlkit/api.py +++ b/tomlkit/api.py @@ -1,5 +1,7 @@ import datetime as _datetime +from collections.abc import Mapping +from typing import IO from typing import Tuple from ._utils import parse_rfc3339 @@ -22,10 +24,10 @@ from .items import Whitespace from .items import item from .parser import Parser -from .toml_document import TOMLDocument as _TOMLDocument +from .toml_document import TOMLDocument -def loads(string): # type: (str) -> _TOMLDocument +def loads(string: str) -> TOMLDocument: """ Parses a string into a TOMLDocument. @@ -34,48 +36,68 @@ def loads(string): # type: (str) -> _TOMLDocument return parse(string) -def dumps(data, sort_keys=False): # type: (_TOMLDocument, bool) -> str +def dumps(data: Mapping, sort_keys: bool = False) -> str: """ Dumps a TOMLDocument into a string. """ - if not isinstance(data, _TOMLDocument) and isinstance(data, dict): - data = item(data, _sort_keys=sort_keys) + if not isinstance(data, Container) and isinstance(data, Mapping): + data = item(dict(data), _sort_keys=sort_keys) - return data.as_string() + try: + # data should be a `Container` (and therefore implement `as_string`) + # for all type safe invocations of this function + return data.as_string() # type: ignore[attr-defined] + except AttributeError as ex: + msg = f"Expecting Mapping or TOML Container, {type(data)} given" + raise TypeError(msg) from ex -def parse(string): # type: (str) -> _TOMLDocument +def load(fp: IO) -> TOMLDocument: + """ + Load toml document from a file-like object. + """ + return parse(fp.read()) + + +def dump(data: Mapping, fp: IO[str], *, sort_keys: bool = False) -> None: + """ + Dump a TOMLDocument into a writable file stream. + """ + fp.write(dumps(data, sort_keys=sort_keys)) + + +def parse(string: str) -> TOMLDocument: """ Parses a string into a TOMLDocument. """ return Parser(string).parse() -def document(): # type: () -> _TOMLDocument +def document() -> TOMLDocument: """ Returns a new TOMLDocument instance. """ - return _TOMLDocument() + return TOMLDocument() # Items -def integer(raw): # type: (str) -> Integer +def integer(raw: str) -> Integer: return item(int(raw)) -def float_(raw): # type: (str) -> Float +def float_(raw: str) -> Float: return item(float(raw)) -def boolean(raw): # type: (str) -> Bool +def boolean(raw: str) -> Bool: return item(raw == "true") -def string(raw): # type: (str) -> String +def string(raw: str) -> String: return item(raw) -def date(raw): # type: (str) -> Date +def date(raw: str) -> Date: value = parse_rfc3339(raw) if not isinstance(value, _datetime.date): raise ValueError("date() only accepts date strings.") @@ -83,7 +105,7 @@ def date(raw): # type: (str) -> Date return item(value) -def time(raw): # type: (str) -> Time +def time(raw: str) -> Time: value = parse_rfc3339(raw) if not isinstance(value, _datetime.time): raise ValueError("time() only accepts time strings.") @@ -91,7 +113,7 @@ def time(raw): # type: (str) -> Time return item(value) -def datetime(raw): # type: (str) -> DateTime +def datetime(raw: str) -> DateTime: value = parse_rfc3339(raw) if not isinstance(value, _datetime.datetime): raise ValueError("datetime() only accepts datetime strings.") @@ -99,44 +121,44 @@ def datetime(raw): # type: (str) -> DateTime return item(value) -def array(raw=None): # type: (str) -> Array +def array(raw: str = None) -> Array: if raw is None: raw = "[]" return value(raw) -def table(): # type: () -> Table +def table() -> Table: return Table(Container(), Trivia(), False) -def inline_table(): # type: () -> InlineTable +def inline_table() -> InlineTable: return InlineTable(Container(), Trivia(), new=True) -def aot(): # type: () -> AoT +def aot() -> AoT: return AoT([]) -def key(k): # type: (str) -> Key +def key(k: str) -> Key: return Key(k) -def value(raw): # type: (str) -> _Item +def value(raw: str) -> _Item: return Parser(raw)._parse_value() -def key_value(src): # type: (str) -> Tuple[Key, _Item] +def key_value(src: str) -> Tuple[Key, _Item]: return Parser(src)._parse_key_value() -def ws(src): # type: (str) -> Whitespace +def ws(src: str) -> Whitespace: return Whitespace(src, fixed=True) -def nl(): # type: () -> Whitespace +def nl() -> Whitespace: return ws("\n") -def comment(string): # type: (str) -> Comment +def comment(string: str) -> Comment: return Comment(Trivia(comment_ws=" ", comment="# " + string)) diff --git a/tomlkit/container.py b/tomlkit/container.py index e097636..59ebddf 100644 --- a/tomlkit/container.py +++ b/tomlkit/container.py @@ -1,21 +1,17 @@ -from __future__ import unicode_literals - import copy from typing import Any from typing import Dict -from typing import Generator +from typing import Iterator from typing import List from typing import Optional from typing import Tuple from typing import Union -from ._compat import MutableMapping from ._compat import decode from ._utils import merge_dicts from .exceptions import KeyAlreadyPresent from .exceptions import NonExistentKey -from .exceptions import ParseError from .exceptions import TOMLKitError from .items import AoT from .items import Comment @@ -24,29 +20,30 @@ from .items import Null from .items import Table from .items import Whitespace +from .items import _CustomDict from .items import item as _item _NOT_SET = object() -class Container(MutableMapping, dict): +class Container(_CustomDict): """ A container for items within a TOMLDocument. """ - def __init__(self, parsed=False): # type: (bool) -> None - self._map = {} # type: Dict[Key, int] - self._body = [] # type: List[Tuple[Optional[Key], Item]] + def __init__(self, parsed: bool = False) -> None: + self._map: Dict[Key, int] = {} + self._body: List[Tuple[Optional[Key], Item]] = [] self._parsed = parsed self._table_keys = [] @property - def body(self): # type: () -> List[Tuple[Optional[Key], Item]] + def body(self) -> List[Tuple[Optional[Key], Item]]: return self._body @property - def value(self): # type: () -> Dict[Any, Any] + def value(self) -> Dict[Any, Any]: d = {} for k, v in self._body: if k is None: @@ -65,10 +62,10 @@ def value(self): # type: () -> Dict[Any, Any] return d - def parsing(self, parsing): # type: (bool) -> None + def parsing(self, parsing: bool) -> None: self._parsed = parsing - for k, v in self._body: + for _, v in self._body: if isinstance(v, Table): v.value.parsing(parsing) elif isinstance(v, AoT): @@ -76,8 +73,8 @@ def parsing(self, parsing): # type: (bool) -> None t.value.parsing(parsing) def add( - self, key, item=None - ): # type: (Union[Key, Item, str], Optional[Item]) -> Container + self, key: Union[Key, Item, str], item: Optional[Item] = None + ) -> "Container": """ Adds an item to the current Container. """ @@ -91,7 +88,7 @@ def add( return self.append(key, item) - def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container + def append(self, key: Union[Key, str, None], item: Item) -> "Container": if not isinstance(key, Key) and key is not None: key = Key(key) @@ -101,16 +98,17 @@ def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container if isinstance(item, (AoT, Table)) and item.name is None: item.name = key.key - if ( - isinstance(item, Table) - and self._body - and not self._parsed - and not item.trivia.indent - ): - item.trivia.indent = "\n" + prev = self._previous_item() + prev_ws = isinstance(prev, Whitespace) or ends_with_withespace(prev) + if isinstance(item, Table): + if item.name != key.key: + item.invalidate_display_name() + if self._body and not (self._parsed or item.trivia.indent or prev_ws): + item.trivia.indent = "\n" if isinstance(item, AoT) and self._body and not self._parsed: - if item and "\n" not in item[0].trivia.indent: + item.invalidate_display_name() + if item and not ("\n" in item[0].trivia.indent or prev_ws): item[0].trivia.indent = "\n" + item[0].trivia.indent if key is not None and key in self: @@ -165,8 +163,15 @@ def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container return self + # Create a new element to replace the old one + current = copy.deepcopy(current) for k, v in item.value.body: current.append(k, v) + self._body[ + current_idx[-1] + if isinstance(current_idx, tuple) + else current_idx + ] = (current_body_element[0], current) return self elif current_body_element[0].is_dotted(): @@ -192,11 +197,9 @@ def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container # item that is not a table and insert after it # If no such item exists, insert at the top of the table key_after = None - idx = 0 - for k, v in self._body: + for i, (k, v) in enumerate(self._body): if isinstance(v, Null): - # This happens only after deletion - continue + continue # Null elements are inserted after deletion if isinstance(v, Whitespace) and not v.is_fixed(): continue @@ -204,8 +207,7 @@ def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container if not is_table and isinstance(v, (Table, AoT)): break - key_after = k or idx - idx += 1 + key_after = k or i # last scalar, Array or InlineTable value if key_after is not None: if isinstance(key_after, int): @@ -213,10 +215,11 @@ def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container return self._insert_at(key_after + 1, key, item) else: previous_item = self._body[-1][1] - if ( - not isinstance(previous_item, Whitespace) - and not is_table - and "\n" not in previous_item.trivia.trail + if not ( + isinstance(previous_item, Whitespace) + or ends_with_withespace(previous_item) + or is_table + or "\n" in previous_item.trivia.trail ): previous_item.trivia.trail += "\n" else: @@ -250,7 +253,7 @@ def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container return self - def remove(self, key): # type: (Union[Key, str]) -> Container + def remove(self, key: Union[Key, str]) -> "Container": if not isinstance(key, Key): key = Key(key) @@ -269,8 +272,8 @@ def remove(self, key): # type: (Union[Key, str]) -> Container return self def _insert_after( - self, key, other_key, item - ): # type: (Union[str, Key], Union[str, Key], Union[Item, Any]) -> Container + self, key: Union[Key, str], other_key: Union[Key, str], item: Any + ) -> "Container": if key is None: raise ValueError("Key cannot be null in insert_after()") @@ -315,11 +318,9 @@ def _insert_after( return self - def _insert_at( - self, idx, key, item - ): # type: (int, Union[str, Key], Union[Item, Any]) -> Container + def _insert_at(self, idx: int, key: Union[Key, str], item: Any) -> "Container": if idx > len(self._body) - 1: - raise ValueError("Unable to insert at position {}".format(idx)) + raise ValueError(f"Unable to insert at position {idx}") if not isinstance(key, Key): key = Key(key) @@ -328,10 +329,11 @@ def _insert_at( if idx > 0: previous_item = self._body[idx - 1][1] - if ( - not isinstance(previous_item, Whitespace) - and not isinstance(item, (AoT, Table)) - and "\n" not in previous_item.trivia.trail + if not ( + isinstance(previous_item, Whitespace) + or ends_with_withespace(previous_item) + or isinstance(item, (AoT, Table)) + or "\n" in previous_item.trivia.trail ): previous_item.trivia.trail += "\n" @@ -357,7 +359,7 @@ def _insert_at( return self - def item(self, key): # type: (Union[Key, str]) -> Item + def item(self, key: Union[Key, str]) -> Item: if not isinstance(key, Key): key = Key(key) @@ -373,11 +375,11 @@ def item(self, key): # type: (Union[Key, str]) -> Item return self._body[idx][1] - def last_item(self): # type: () -> Optional[Item] + def last_item(self) -> Optional[Item]: if self._body: return self._body[-1][1] - def as_string(self): # type: () -> str + def as_string(self) -> str: s = "" for k, v in self._body: if k is not None: @@ -393,8 +395,8 @@ def as_string(self): # type: () -> str return s def _render_table( - self, key, table, prefix=None - ): # (Key, Table, Optional[str]) -> str + self, key: Key, table: Table, prefix: Optional[str] = None + ) -> str: cur = "" if table.display_name is not None: @@ -457,7 +459,7 @@ def _render_aot(self, key, aot, prefix=None): return cur - def _render_aot_table(self, table, prefix=None): # (Table, Optional[str]) -> str + def _render_aot_table(self, table: Table, prefix: Optional[str] = None) -> str: cur = "" _key = prefix or "" @@ -510,41 +512,14 @@ def _render_simple_item(self, key, item, prefix=None): item.trivia.trail, ) - # Dictionary methods - - def pop(self, key, default=_NOT_SET): - try: - value = self[key] - except KeyError: - if default is _NOT_SET: - raise - - return default - - del self[key] - - return value - - def setdefault( - self, key, default=None - ): # type: (Union[Key, str], Any) -> Union[Item, Container] - super(Container, self).setdefault(key, default=default) - - return self[key] - - def __contains__(self, key): # type: (Union[Key, str]) -> bool - if not isinstance(key, Key): - key = Key(key) - - return key in self._map + def __len__(self) -> int: + return dict.__len__(self) - def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None - if key is not None and key in self: - self._replace(key, key, value) - else: - self.append(key, value) + def __iter__(self) -> Iterator[str]: + return iter(dict.keys(self)) - def __getitem__(self, key): # type: (Union[Key, str]) -> Union[Item, Container] + # Dictionary methods + def __getitem__(self, key: Union[Key, str]) -> Union[Item, "Container"]: if not isinstance(key, Key): key = Key(key) @@ -564,24 +539,22 @@ def __getitem__(self, key): # type: (Union[Key, str]) -> Union[Item, Container] return item - def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None + def __setitem__(self, key: Union[Key, str], value: Any) -> None: if key is not None and key in self: self._replace(key, key, value) else: self.append(key, value) - def __delitem__(self, key): # type: (Union[Key, str]) -> None + def __delitem__(self, key: Union[Key, str]) -> None: self.remove(key) - def __len__(self): # type: () -> int - return dict.__len__(self) - - def __iter__(self): # type: () -> Iterator[str] - return iter(dict.keys(self)) + def setdefault(self, key: Union[Key, str], default: Any) -> Any: + super().setdefault(key, default=default) + return self[key] def _replace( - self, key, new_key, value - ): # type: (Union[Key, str], Union[Key, str], Item) -> None + self, key: Union[Key, str], new_key: Union[Key, str], value: Item + ) -> None: if not isinstance(key, Key): key = Key(key) @@ -595,8 +568,8 @@ def _replace( self._replace_at(idx, new_key, value) def _replace_at( - self, idx, new_key, value - ): # type: (Union[int, Tuple[int]], Union[Key, str], Item) -> None + self, idx: Union[int, Tuple[int]], new_key: Union[Key, str], value: Item + ) -> None: if not isinstance(new_key, Key): new_key = Key(new_key) @@ -617,28 +590,49 @@ def _replace_at( value = _item(value) - # Copying trivia - if not isinstance(value, (Whitespace, AoT)): - value.trivia.indent = v.trivia.indent - value.trivia.comment_ws = v.trivia.comment_ws - value.trivia.comment = v.trivia.comment - value.trivia.trail = v.trivia.trail - - if isinstance(value, Table): - # Insert a cosmetic new line for tables - value.append(None, Whitespace("\n")) - - self._body[idx] = (new_key, value) + if isinstance(value, (AoT, Table)) and not isinstance(v, (AoT, Table)): + # new tables should appear after all non-table values + self.remove(k) + for i in range(idx, len(self._body)): + if isinstance(self._body[i][1], (AoT, Table)): + self._insert_at(i, new_key, value) + idx = i + break + else: + idx = -1 + self.append(new_key, value) + else: + # Copying trivia + if not isinstance(value, (Whitespace, AoT)): + value.trivia.indent = v.trivia.indent + value.trivia.comment_ws = value.trivia.comment_ws or v.trivia.comment_ws + value.trivia.comment = value.trivia.comment or v.trivia.comment + value.trivia.trail = v.trivia.trail + self._body[idx] = (new_key, value) - dict.__setitem__(self, new_key.key, value.value) + if hasattr(value, "invalidate_display_name"): + value.invalidate_display_name() # type: ignore[attr-defined] - def __str__(self): # type: () -> str + if isinstance(value, Table): + # Insert a cosmetic new line for tables if: + # - it does not have it yet OR is not followed by one + # - it is not the last item + last, _ = self._previous_item_with_index() + idx = last if idx < 0 else idx + has_ws = ends_with_withespace(value) + next_ws = idx < last and isinstance(self._body[idx + 1][1], Whitespace) + if idx < last and not (next_ws or has_ws): + value.append(None, Whitespace("\n")) + + dict.__setitem__(self, new_key.key, value.value) + + def __str__(self) -> str: return str(self.value) - def __repr__(self): # type: () -> str + def __repr__(self) -> str: return repr(self.value) - def __eq__(self, other): # type: (Dict) -> bool + def __eq__(self, other: dict) -> bool: if not isinstance(other, dict): return NotImplemented @@ -667,10 +661,10 @@ def __setstate__(self, state): if key is not None: dict.__setitem__(self, key.key, item.value) - def copy(self): # type: () -> Container + def copy(self) -> "Container": return copy.copy(self) - def __copy__(self): # type: () -> Container + def __copy__(self) -> "Container": c = self.__class__(self._parsed) for k, v in dict.items(self): dict.__setitem__(c, k, v) @@ -680,14 +674,34 @@ def __copy__(self): # type: () -> Container return c + def _previous_item_with_index( + self, idx: Optional[int] = None, ignore=(Null,) + ) -> Optional[Tuple[int, Item]]: + """Find the immediate previous item before index ``idx``""" + if idx is None or idx > len(self._body): + idx = len(self._body) + for i in range(idx - 1, -1, -1): + v = self._body[i][-1] + if not isinstance(v, ignore): + return i, v + return None + + def _previous_item( + self, idx: Optional[int] = None, ignore=(Null,) + ) -> Optional[Item]: + """Find the immediate previous item before index ``idx``. + If ``idx`` is not given, the last item is returned. + """ + prev = self._previous_item_with_index(idx, ignore) + return prev[-1] if prev else None + -class OutOfOrderTableProxy(MutableMapping, dict): - def __init__(self, container, indices): # type: (Container, Tuple) -> None +class OutOfOrderTableProxy(_CustomDict): + def __init__(self, container: Container, indices: Tuple[int]) -> None: self._container = container - self._internal_container = Container(self._container.parsing) + self._internal_container = Container(True) self._tables = [] self._tables_map = {} - self._map = {} for i in indices: key, item = self._container._body[i] @@ -700,27 +714,19 @@ def __init__(self, container, indices): # type: (Container, Tuple) -> None self._tables_map[k] = table_idx if k is not None: dict.__setitem__(self, k.key, v) - else: - self._internal_container.append(key, item) - self._map[key] = i - if key is not None: - dict.__setitem__(self, key.key, item) @property def value(self): return self._internal_container.value - def __getitem__(self, key): # type: (Union[Key, str]) -> Any + def __getitem__(self, key: Union[Key, str]) -> Any: if key not in self._internal_container: raise NonExistentKey(key) return self._internal_container[key] - def __setitem__(self, key, item): # type: (Union[Key, str], Any) -> None - if key in self._map: - idx = self._map[key] - self._container._replace_at(idx, key, item) - elif key in self._tables_map: + def __setitem__(self, key: Union[Key, str], item: Any) -> None: + if key in self._tables_map: table = self._tables[self._tables_map[key]] table[key] = item elif self._tables: @@ -729,15 +735,12 @@ def __setitem__(self, key, item): # type: (Union[Key, str], Any) -> None else: self._container[key] = item + self._internal_container[key] = item if key is not None: dict.__setitem__(self, key, item) - def __delitem__(self, key): # type: (Union[Key, str]) -> None - if key in self._map: - idx = self._map[key] - del self._container[key] - del self._map[key] - elif key in self._tables_map: + def __delitem__(self, key: Union[Key, str]) -> None: + if key in self._tables_map: table = self._tables[self._tables_map[key]] del table[key] del self._tables_map[key] @@ -745,47 +748,27 @@ def __delitem__(self, key): # type: (Union[Key, str]) -> None raise NonExistentKey(key) del self._internal_container[key] + if key is not None: + dict.__delitem__(self, key) - def keys(self): - return self._internal_container.keys() - - def values(self): - return self._internal_container.values() - - def items(self): # type: () -> Generator[Item] - return self._internal_container.items() - - def update(self, other): # type: (Dict) -> None - self._internal_container.update(other) - - def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any - return self._internal_container.get(key, default=default) - - def pop(self, key, default=_NOT_SET): - return self._internal_container.pop(key, default=default) - - def setdefault( - self, key, default=None - ): # type: (Union[Key, str], Any) -> Union[Item, Container] - return self._internal_container.setdefault(key, default=default) - - def __contains__(self, key): - return key in self._internal_container - - def __iter__(self): # type: () -> Iterator[str] - return iter(self._internal_container) + def __iter__(self) -> Iterator[str]: + return iter(dict.keys(self)) - def __str__(self): - return str(self._internal_container) + def __len__(self) -> int: + return dict.__len__(self) - def __repr__(self): - return repr(self._internal_container) + def __getattr__(self, attribute): + return getattr(self._internal_container, attribute) - def __eq__(self, other): # type: (Dict) -> bool - if not isinstance(other, dict): - return NotImplemented + def setdefault(self, key: Union[Key, str], default: Any) -> Any: + super().setdefault(key, default=default) + return self[key] - return self._internal_container == other - def __getattr__(self, attribute): - return getattr(self._internal_container, attribute) +def ends_with_withespace(it: Any) -> bool: + """Returns ``True`` if the given item ``it`` is a ``Table`` or ``AoT`` object + ending with a ``Whitespace``. + """ + return ( + isinstance(it, Table) and isinstance(it.value._previous_item(), Whitespace) + ) or (isinstance(it, AoT) and len(it) > 0 and isinstance(it[-1], Whitespace)) diff --git a/tomlkit/exceptions.py b/tomlkit/exceptions.py index d0c7ab5..73f636e 100644 --- a/tomlkit/exceptions.py +++ b/tomlkit/exceptions.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from typing import Optional @@ -15,18 +13,14 @@ class ParseError(ValueError, TOMLKitError): location within the line where the error was encountered. """ - def __init__( - self, line, col, message=None - ): # type: (int, int, Optional[str]) -> None + def __init__(self, line: int, col: int, message: Optional[str] = None) -> None: self._line = line self._col = col if message is None: message = "TOML parse error" - super(ParseError, self).__init__( - "{} at line {} col {}".format(message, self._line, self._col) - ) + super().__init__(f"{message} at line {self._line} col {self._col}") @property def line(self): @@ -42,10 +36,10 @@ class MixedArrayTypesError(ParseError): An array was found that had two or more element types. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Mixed types found in array" - super(MixedArrayTypesError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InvalidNumberError(ParseError): @@ -53,10 +47,10 @@ class InvalidNumberError(ParseError): A numeric field was improperly specified. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Invalid number" - super(InvalidNumberError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InvalidDateTimeError(ParseError): @@ -64,10 +58,10 @@ class InvalidDateTimeError(ParseError): A datetime field was improperly specified. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Invalid datetime" - super(InvalidDateTimeError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InvalidDateError(ParseError): @@ -75,10 +69,10 @@ class InvalidDateError(ParseError): A date field was improperly specified. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Invalid date" - super(InvalidDateError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InvalidTimeError(ParseError): @@ -86,10 +80,10 @@ class InvalidTimeError(ParseError): A date field was improperly specified. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Invalid time" - super(InvalidTimeError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InvalidNumberOrDateError(ParseError): @@ -97,10 +91,10 @@ class InvalidNumberOrDateError(ParseError): A numeric or date field was improperly specified. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Invalid number or date format" - super(InvalidNumberOrDateError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InvalidUnicodeValueError(ParseError): @@ -108,10 +102,10 @@ class InvalidUnicodeValueError(ParseError): A unicode code was improperly specified. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Invalid unicode value" - super(InvalidUnicodeValueError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class UnexpectedCharError(ParseError): @@ -119,10 +113,10 @@ class UnexpectedCharError(ParseError): An unexpected character was found during parsing. """ - def __init__(self, line, col, char): # type: (int, int, str) -> None - message = "Unexpected character: {}".format(repr(char)) + def __init__(self, line: int, col: int, char: str) -> None: + message = f"Unexpected character: {repr(char)}" - super(UnexpectedCharError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class EmptyKeyError(ParseError): @@ -130,10 +124,10 @@ class EmptyKeyError(ParseError): An empty key was found during parsing. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Empty key" - super(EmptyKeyError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class EmptyTableNameError(ParseError): @@ -141,10 +135,10 @@ class EmptyTableNameError(ParseError): An empty table name was found during parsing. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Empty table name" - super(EmptyTableNameError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InvalidCharInStringError(ParseError): @@ -152,10 +146,10 @@ class InvalidCharInStringError(ParseError): The string being parsed contains an invalid character. """ - def __init__(self, line, col, char): # type: (int, int, str) -> None - message = "Invalid character {} in string".format(repr(char)) + def __init__(self, line: int, col: int, char: str) -> None: + message = f"Invalid character {repr(char)} in string" - super(InvalidCharInStringError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class UnexpectedEofError(ParseError): @@ -163,10 +157,10 @@ class UnexpectedEofError(ParseError): The TOML being parsed ended before the end of a statement. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Unexpected end of file" - super(UnexpectedEofError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InternalParserError(ParseError): @@ -174,14 +168,12 @@ class InternalParserError(ParseError): An error that indicates a bug in the parser. """ - def __init__( - self, line, col, message=None - ): # type: (int, int, Optional[str]) -> None + def __init__(self, line: int, col: int, message: Optional[str] = None) -> None: msg = "Internal parser error" if message: - msg += " ({})".format(message) + msg += f" ({message})" - super(InternalParserError, self).__init__(line, col, message=msg) + super().__init__(line, col, message=msg) class NonExistentKey(KeyError, TOMLKitError): @@ -190,9 +182,9 @@ class NonExistentKey(KeyError, TOMLKitError): """ def __init__(self, key): - message = 'Key "{}" does not exist.'.format(key) + message = f'Key "{key}" does not exist.' - super(NonExistentKey, self).__init__(message) + super().__init__(message) class KeyAlreadyPresent(TOMLKitError): @@ -201,13 +193,13 @@ class KeyAlreadyPresent(TOMLKitError): """ def __init__(self, key): - message = 'Key "{}" already exists.'.format(key) + message = f'Key "{key}" already exists.' - super(KeyAlreadyPresent, self).__init__(message) + super().__init__(message) class InvalidControlChar(ParseError): - def __init__(self, line, col, char, type): # type: (int, int, int, str) -> None + def __init__(self, line: int, col: int, char: int, type: str) -> None: display_code = "\\u00" if char < 16: @@ -220,4 +212,4 @@ def __init__(self, line, col, char, type): # type: (int, int, int, str) -> None "use {} instead".format(type, display_code) ) - super(InvalidControlChar, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) diff --git a/tomlkit/items.py b/tomlkit/items.py index f738e0b..863d9f3 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -1,32 +1,57 @@ -from __future__ import unicode_literals - import re import string from datetime import date from datetime import datetime from datetime import time +from datetime import tzinfo from enum import Enum +from functools import lru_cache +from typing import TYPE_CHECKING from typing import Any from typing import Dict -from typing import Generator +from typing import Iterable +from typing import Iterator from typing import List from typing import Optional +from typing import TypeVar from typing import Union +from typing import cast +from typing import overload -from ._compat import PY2 from ._compat import PY38 -from ._compat import MutableMapping from ._compat import decode -from ._compat import long -from ._compat import unicode +from ._utils import escape_quotes from ._utils import escape_string +from .toml_char import TOMLChar + + +if TYPE_CHECKING: # pragma: no cover + # Define _CustomList and _CustomDict as a workaround for: + # https://github.com/python/mypy/issues/11427 + # + # According to this issue, the typeshed contains a "lie" + # (it adds MutableSequence to the ancestry of list and MutableMapping to + # the ancestry of dict) which completely messes with the type inference for + # Table, InlineTable, Array and Container. + # + # Importing from builtins is preferred over simple assignment, see issues: + # https://github.com/python/mypy/issues/8715 + # https://github.com/python/mypy/issues/10068 + from builtins import dict as _CustomDict + from builtins import list as _CustomList + + # Allow type annotations but break circular imports + from . import container +else: + from collections.abc import MutableMapping + from collections.abc import MutableSequence + class _CustomList(MutableSequence, list): + """Adds MutableSequence mixin while pretending to be a builtin list""" -if PY2: - from functools32 import lru_cache -else: - from functools import lru_cache + class _CustomDict(MutableMapping, dict): + """Adds MutableMapping mixin while pretending to be a builtin dict""" def item(value, _parent=None, _sort_keys=False): @@ -42,7 +67,8 @@ def item(value, _parent=None, _sort_keys=False): elif isinstance(value, float): return Float(value, Trivia(), str(value)) elif isinstance(value, dict): - val = Table(Container(), Trivia(), False) + table_constructor = InlineTable if isinstance(_parent, Array) else Table + val = table_constructor(Container(), Trivia(), False) for k, v in sorted( value.items(), key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1), @@ -51,31 +77,33 @@ def item(value, _parent=None, _sort_keys=False): return val elif isinstance(value, list): - if value and isinstance(value[0], dict): + if value and all(isinstance(v, dict) for v in value): a = AoT([]) + table_constructor = Table else: a = Array([], Trivia()) + table_constructor = InlineTable for v in value: if isinstance(v, dict): - table = Table(Container(), Trivia(), True) + table = table_constructor(Container(), Trivia(), True) for k, _v in sorted( v.items(), key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1), ): - i = item(_v, _sort_keys=_sort_keys) + i = item(_v, _parent=a, _sort_keys=_sort_keys) if isinstance(table, InlineTable): i.trivia.trail = "" - table[k] = item(i, _sort_keys=_sort_keys) + table[k] = i v = table a.append(v) return a - elif isinstance(value, (str, unicode)): + elif isinstance(value, str): escaped = escape_string(value) return String(StringType.SLB, decode(value), escaped, Trivia()) @@ -105,7 +133,7 @@ def item(value, _parent=None, _sort_keys=False): value.isoformat(), ) - raise ValueError("Invalid type {}".format(type(value))) + raise ValueError(f"Invalid type {type(value)}") class StringType(Enum): @@ -120,27 +148,27 @@ class StringType(Enum): @property @lru_cache(maxsize=None) - def unit(self): # type: () -> str + def unit(self) -> str: return self.value[0] @lru_cache(maxsize=None) - def is_basic(self): # type: () -> bool + def is_basic(self) -> bool: return self in {StringType.SLB, StringType.MLB} @lru_cache(maxsize=None) - def is_literal(self): # type: () -> bool + def is_literal(self) -> bool: return self in {StringType.SLL, StringType.MLL} @lru_cache(maxsize=None) - def is_singleline(self): # type: () -> bool + def is_singleline(self) -> bool: return self in {StringType.SLB, StringType.SLL} @lru_cache(maxsize=None) - def is_multiline(self): # type: () -> bool + def is_multiline(self) -> bool: return self in {StringType.MLB, StringType.MLL} @lru_cache(maxsize=None) - def toggle(self): # type: () -> StringType + def toggle(self) -> "StringType": return { StringType.SLB: StringType.MLB, StringType.MLB: StringType.SLB, @@ -157,9 +185,6 @@ class BoolType(Enum): def __bool__(self): return {BoolType.TRUE: True, BoolType.FALSE: False}[self] - if PY2: - __nonzero__ = __bool__ # for PY2 - def __iter__(self): return iter(self.value) @@ -173,8 +198,12 @@ class Trivia: """ def __init__( - self, indent=None, comment_ws=None, comment=None, trail=None - ): # type: (str, str, str, str) -> None + self, + indent: str = None, + comment_ws: str = None, + comment: str = None, + trail: str = None, + ) -> None: # Whitespace before a value. self.indent = indent or "" # Whitespace after a value, but before a comment. @@ -207,11 +236,16 @@ class Key: """ def __init__( - self, k, t=None, sep=None, dotted=False, original=None - ): # type: (str, Optional[KeyType], Optional[str], bool, Optional[str]) -> None + self, + k: str, + t: Optional[KeyType] = None, + sep: Optional[str] = None, + dotted: bool = False, + original: Optional[str] = None, + ) -> None: if t is None: - if any( - [c not in string.ascii_letters + string.digits + "-" + "_" for c in k] + if not k or any( + c not in string.ascii_letters + string.digits + "-" + "_" for c in k ): t = KeyType.Basic else: @@ -224,63 +258,63 @@ def __init__( self.sep = sep self.key = k if original is None: - original = k + original = t.value + escape_quotes(k, t.value) + t.value self._original = original self._dotted = dotted @property - def delimiter(self): # type: () -> str + def delimiter(self) -> str: return self.t.value - def is_dotted(self): # type: () -> bool + def is_dotted(self) -> bool: return self._dotted - def is_bare(self): # type: () -> bool + def is_bare(self) -> bool: return self.t == KeyType.Bare - def as_string(self): # type: () -> str - return "{}{}{}".format(self.delimiter, self._original, self.delimiter) + def as_string(self) -> str: + return self._original - def __hash__(self): # type: () -> int + def __hash__(self) -> int: return hash(self.key) - def __eq__(self, other): # type: (Key) -> bool + def __eq__(self, other: Any) -> bool: if isinstance(other, Key): return self.key == other.key return self.key == other - def __str__(self): # type: () -> str + def __str__(self) -> str: return self.as_string() - def __repr__(self): # type: () -> str - return "".format(self.as_string()) + def __repr__(self) -> str: + return f"" -class Item(object): +class Item: """ An item within a TOML document. """ - def __init__(self, trivia): # type: (Trivia) -> None + def __init__(self, trivia: Trivia) -> None: self._trivia = trivia @property - def trivia(self): # type: () -> Trivia + def trivia(self) -> Trivia: return self._trivia @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: raise NotImplementedError() - def as_string(self): # type: () -> str + def as_string(self) -> str: raise NotImplementedError() # Helpers - def comment(self, comment): # type: (str) -> Item + def comment(self, comment: str) -> "Item": if not comment.strip().startswith("#"): comment = "# " + comment @@ -289,7 +323,7 @@ def comment(self, comment): # type: (str) -> Item return self - def indent(self, indent): # type: (int) -> Item + def indent(self, indent: int) -> "Item": if self._trivia.indent.startswith("\n"): self._trivia.indent = "\n" + " " * indent else: @@ -297,16 +331,16 @@ def indent(self, indent): # type: (int) -> Item return self - def is_boolean(self): # type: () -> bool + def is_boolean(self) -> bool: return isinstance(self, Bool) - def is_table(self): # type: () -> bool + def is_table(self) -> bool: return isinstance(self, Table) - def is_inline_table(self): # type: () -> bool + def is_inline_table(self) -> bool: return isinstance(self, InlineTable) - def is_aot(self): # type: () -> bool + def is_aot(self) -> bool: return isinstance(self, AoT) def _getstate(self, protocol=3): @@ -324,34 +358,34 @@ class Whitespace(Item): A whitespace literal. """ - def __init__(self, s, fixed=False): # type: (str, bool) -> None + def __init__(self, s: str, fixed: bool = False) -> None: self._s = s self._fixed = fixed @property - def s(self): # type: () -> str + def s(self) -> str: return self._s @property - def value(self): # type: () -> str + def value(self) -> str: return self._s @property - def trivia(self): # type: () -> Trivia + def trivia(self) -> Trivia: raise RuntimeError("Called trivia on a Whitespace variant.") @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 0 - def is_fixed(self): # type: () -> bool + def is_fixed(self) -> bool: return self._fixed - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._s - def __repr__(self): # type: () -> str - return "<{} {}>".format(self.__class__.__name__, repr(self._s)) + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {repr(self._s)}>" def _getstate(self, protocol=3): return self._s, self._fixed @@ -363,28 +397,28 @@ class Comment(Item): """ @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 1 - def as_string(self): # type: () -> str + def as_string(self) -> str: return "{}{}{}".format( self._trivia.indent, decode(self._trivia.comment), self._trivia.trail ) - def __str__(self): # type: () -> str - return "{}{}".format(self._trivia.indent, decode(self._trivia.comment)) + def __str__(self) -> str: + return f"{self._trivia.indent}{decode(self._trivia.comment)}" -class Integer(long, Item): +class Integer(int, Item): """ An integer literal. """ - def __new__(cls, value, trivia, raw): # type: (int, Trivia, str) -> Integer - return super(Integer, cls).__new__(cls, value) + def __new__(cls, value: int, trivia: Trivia, raw: str) -> "Integer": + return super().__new__(cls, value) - def __init__(self, _, trivia, raw): # type: (int, Trivia, str) -> None - super(Integer, self).__init__(trivia) + def __init__(self, _: int, trivia: Trivia, raw: str) -> None: + super().__init__(trivia) self._raw = raw self._sign = False @@ -393,23 +427,23 @@ def __init__(self, _, trivia, raw): # type: (int, Trivia, str) -> None self._sign = True @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 2 @property - def value(self): # type: () -> int + def value(self) -> int: return self - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._raw def __add__(self, other): - result = super(Integer, self).__add__(other) + result = super().__add__(other) return self._new(result) def __radd__(self, other): - result = super(Integer, self).__radd__(other) + result = super().__radd__(other) if isinstance(other, Integer): return self._new(result) @@ -417,12 +451,12 @@ def __radd__(self, other): return result def __sub__(self, other): - result = super(Integer, self).__sub__(other) + result = super().__sub__(other) return self._new(result) def __rsub__(self, other): - result = super(Integer, self).__rsub__(other) + result = super().__rsub__(other) if isinstance(other, Integer): return self._new(result) @@ -447,11 +481,11 @@ class Float(float, Item): A float literal. """ - def __new__(cls, value, trivia, raw): # type: (float, Trivia, str) -> Integer - return super(Float, cls).__new__(cls, value) + def __new__(cls, value: float, trivia: Trivia, raw: str) -> Integer: + return super().__new__(cls, value) - def __init__(self, _, trivia, raw): # type: (float, Trivia, str) -> None - super(Float, self).__init__(trivia) + def __init__(self, _: float, trivia: Trivia, raw: str) -> None: + super().__init__(trivia) self._raw = raw self._sign = False @@ -460,23 +494,23 @@ def __init__(self, _, trivia, raw): # type: (float, Trivia, str) -> None self._sign = True @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 3 @property - def value(self): # type: () -> float + def value(self) -> float: return self - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._raw def __add__(self, other): - result = super(Float, self).__add__(other) + result = super().__add__(other) return self._new(result) def __radd__(self, other): - result = super(Float, self).__radd__(other) + result = super().__radd__(other) if isinstance(other, Float): return self._new(result) @@ -484,12 +518,12 @@ def __radd__(self, other): return result def __sub__(self, other): - result = super(Float, self).__sub__(other) + result = super().__sub__(other) return self._new(result) def __rsub__(self, other): - result = super(Float, self).__rsub__(other) + result = super().__rsub__(other) if isinstance(other, Float): return self._new(result) @@ -514,20 +548,20 @@ class Bool(Item): A boolean literal. """ - def __init__(self, t, trivia): # type: (int, Trivia) -> None - super(Bool, self).__init__(trivia) + def __init__(self, t: int, trivia: Trivia) -> None: + super().__init__(trivia) self._value = bool(t) @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 4 @property - def value(self): # type: () -> bool + def value(self) -> bool: return self._value - def as_string(self): # type: () -> str + def as_string(self) -> str: return str(self._value).lower() def _getstate(self, protocol=3): @@ -558,18 +592,18 @@ class DateTime(Item, datetime): def __new__( cls, - year, - month, - day, - hour, - minute, - second, - microsecond, - tzinfo, - trivia, - raw, - **kwargs - ): # type: (int, int, int, int, int, int, int, Optional[datetime.tzinfo], Trivia, str, Any) -> datetime + year: int, + month: int, + day: int, + hour: int, + minute: int, + second: int, + microsecond: int, + tzinfo: Optional[tzinfo], + trivia: Trivia, + raw: str, + **kwargs: Any, + ) -> datetime: return datetime.__new__( cls, year, @@ -580,25 +614,35 @@ def __new__( second, microsecond, tzinfo=tzinfo, - **kwargs + **kwargs, ) def __init__( - self, year, month, day, hour, minute, second, microsecond, tzinfo, trivia, raw - ): # type: (int, int, int, int, int, int, int, Optional[datetime.tzinfo], Trivia, str) -> None - super(DateTime, self).__init__(trivia) + self, + year: int, + month: int, + day: int, + hour: int, + minute: int, + second: int, + microsecond: int, + tzinfo: Optional[tzinfo], + trivia: Trivia, + raw: str, + ) -> None: + super().__init__(trivia) self._raw = raw @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 5 @property - def value(self): # type: () -> datetime + def value(self) -> datetime: return self - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._raw def __add__(self, other): @@ -614,7 +658,7 @@ def __add__(self, other): self.tzinfo, ).__add__(other) else: - result = super(DateTime, self).__add__(other) + result = super().__add__(other) return self._new(result) @@ -631,7 +675,7 @@ def __sub__(self, other): self.tzinfo, ).__sub__(other) else: - result = super(DateTime, self).__sub__(other) + result = super().__sub__(other) if isinstance(result, datetime): result = self._new(result) @@ -674,32 +718,32 @@ class Date(Item, date): A date literal. """ - def __new__(cls, year, month, day, *_): # type: (int, int, int, Any) -> date + def __new__(cls, year: int, month: int, day: int, *_: Any) -> date: return date.__new__(cls, year, month, day) def __init__( - self, year, month, day, trivia, raw - ): # type: (int, int, int, Trivia, str) -> None - super(Date, self).__init__(trivia) + self, year: int, month: int, day: int, trivia: Trivia, raw: str + ) -> None: + super().__init__(trivia) self._raw = raw @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 6 @property - def value(self): # type: () -> date + def value(self) -> date: return self - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._raw def __add__(self, other): if PY38: result = date(self.year, self.month, self.day).__add__(other) else: - result = super(Date, self).__add__(other) + result = super().__add__(other) return self._new(result) @@ -707,7 +751,7 @@ def __sub__(self, other): if PY38: result = date(self.year, self.month, self.day).__sub__(other) else: - result = super(Date, self).__sub__(other) + result = super().__sub__(other) if isinstance(result, date): result = self._new(result) @@ -729,29 +773,42 @@ class Time(Item, time): """ def __new__( - cls, hour, minute, second, microsecond, tzinfo, *_ - ): # type: (int, int, int, int, Optional[datetime.tzinfo], Any) -> time + cls, + hour: int, + minute: int, + second: int, + microsecond: int, + tzinfo: Optional[tzinfo], + *_: Any, + ) -> time: return time.__new__(cls, hour, minute, second, microsecond, tzinfo) def __init__( - self, hour, minute, second, microsecond, tzinfo, trivia, raw - ): # type: (int, int, int, int, Optional[datetime.tzinfo], Trivia, str) -> None - super(Time, self).__init__(trivia) + self, + hour: int, + minute: int, + second: int, + microsecond: int, + tzinfo: Optional[tzinfo], + trivia: Trivia, + raw: str, + ) -> None: + super().__init__(trivia) self._raw = raw @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 7 @property - def value(self): # type: () -> time + def value(self) -> time: return self - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._raw - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple: return ( self.hour, self.minute, @@ -763,37 +820,36 @@ def _getstate(self, protocol=3): ) -class Array(Item, list): +class Array(Item, _CustomList): """ An array literal """ - def __init__( - self, value, trivia, multiline=False - ): # type: (list, Trivia, bool) -> None - super(Array, self).__init__(trivia) - + def __init__(self, value: list, trivia: Trivia, multiline: bool = False) -> None: + super().__init__(trivia) + self._index_map: Dict[int, int] = {} list.__init__( self, [v.value for v in value if not isinstance(v, (Whitespace, Comment))] ) self._value = value self._multiline = multiline + self._reindex() @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 8 @property - def value(self): # type: () -> list + def value(self) -> list: return self - def multiline(self, multiline): # type: (bool) -> self + def multiline(self, multiline: bool) -> "Array": self._multiline = multiline return self - def as_string(self): # type: () -> str + def as_string(self) -> str: if not self._multiline: return "[{}]".format("".join(v.as_string() for v in self._value)) @@ -806,113 +862,311 @@ def as_string(self): # type: () -> str return s - def append(self, _item): # type: (Any) -> None - if self._value: - self._value.append(Whitespace(", ")) + def _reindex(self) -> None: + self._index_map.clear() + index = 0 + for i, v in enumerate(self._value): + if isinstance(v, (Whitespace, Comment)): + continue + self._index_map[index] = i + index += 1 + + def add_line( + self, + *items: Any, + indent: str = " ", + comment: Optional[str] = None, + add_comma: bool = True, + newline: bool = True, + ) -> None: + """Add multiple items in a line. + When add_comma is True, only accept actual values and + ", " will be added between values automatically. + """ + values = self._value[:] + new_values = [] + + def append_item(el: Item) -> None: + if not values: + return values.append(el) + last_el = values[-1] + if ( + isinstance(el, Whitespace) + and "," not in el.s + and isinstance(last_el, Whitespace) + and "," not in last_el.s + ): + values[-1] = Whitespace(last_el.s + el.s) + else: + values.append(el) + + if newline: + append_item(Whitespace("\n")) + if indent: + append_item(Whitespace(indent)) + for i, el in enumerate(items): + el = item(el, _parent=self) + if isinstance(el, Comment) or add_comma and isinstance(el, Whitespace): + raise ValueError(f"item type {type(el)} is not allowed") + if not isinstance(el, Whitespace): + new_values.append(el.value) + append_item(el) + if add_comma: + append_item(Whitespace(",")) + if i != len(items) - 1: + append_item(Whitespace(" ")) + if comment: + indent = " " if items else "" + append_item( + Comment(Trivia(indent=indent, comment=f"# {comment}", trail="")) + ) + # Atomic manipulation + self._value[:] = values + list.extend(self, new_values) + self._reindex() + + def clear(self) -> None: + list.clear(self) + + self._value.clear() + self._index_map.clear() + + def __len__(self) -> int: + return list.__len__(self) + + def __getitem__(self, key: Union[int, slice]) -> Any: + return list.__getitem__(self, key) + + def __setitem__(self, key: Union[int, slice], value: Any) -> Any: + it = item(value, _parent=self) + list.__setitem__(self, key, it.value) + if isinstance(key, slice): + raise ValueError("slice assignment is not supported") + if key < 0: + key += len(self) + self._value[self._index_map[key]] = it + + def insert(self, pos: int, value: Any) -> None: + it = item(value, _parent=self) + length = len(self) + if not isinstance(it, (Comment, Whitespace)): + list.insert(self, pos, it.value) + if pos < 0: + pos += length + if pos < 0: + pos = 0 + + items = [it] + idx = 0 + if pos < length: + try: + idx = self._index_map[pos] + except KeyError: + raise IndexError("list index out of range") + if not isinstance(it, (Whitespace, Comment)): + items.append(Whitespace(",")) + else: + idx = len(self._value) + if idx > 0: + last_item = self._value[idx - 1] + if isinstance(last_item, Whitespace) and "," not in last_item.s: + # the item has an indent, copy that + idx -= 1 + ws = last_item.s + if isinstance(it, Whitespace) and "," not in it.s: + # merge the whitespace + self._value[idx] = Whitespace(ws + it.s) + return + else: + ws = "" + has_newline = bool(set(ws) & set(TOMLChar.NL)) + has_space = ws and ws[-1] in TOMLChar.SPACES + if not has_space: + # four spaces for multiline array and single space otherwise + ws += " " if has_newline else " " + items.insert(0, Whitespace(ws)) + self._value[idx:idx] = items + i = idx - 1 + if pos > 0: # Check if the last item ends with a comma + while i >= 0 and isinstance(self._value[i], (Whitespace, Comment)): + if isinstance(self._value[i], Whitespace) and "," in self._value[i].s: + break + i -= 1 + else: + self._value.insert(i + 1, Whitespace(",")) + + self._reindex() + + def __delitem__(self, key: Union[int, slice]): + length = len(self) + list.__delitem__(self, key) + + def get_indice_to_remove(idx: int) -> Iterable[int]: + try: + real_idx = self._index_map[idx] + except KeyError: + raise IndexError("list index out of range") + yield real_idx + for i in range(real_idx + 1, len(self._value)): + if isinstance(self._value[i], Whitespace): + yield i + else: + break + + indexes = set() + if isinstance(key, slice): + for idx in range(key.start or 0, key.stop or length, key.step or 1): + indexes.update(get_indice_to_remove(idx)) + else: + indexes.update(get_indice_to_remove(length + key if key < 0 else key)) + for i in sorted(indexes, reverse=True): + del self._value[i] + while self._value and isinstance(self._value[-1], Whitespace): + self._value.pop() + self._reindex() + + def __str__(self): + return str( + [v.value for v in self._value if not isinstance(v, (Whitespace, Comment))] + ) + + def _getstate(self, protocol=3): + return self._value, self._trivia - it = item(_item) - super(Array, self).append(it.value) - self._value.append(it) +AT = TypeVar("AT", bound="AbstractTable") - if not PY2: - def clear(self): - super(Array, self).clear() +class AbstractTable(Item, _CustomDict): + """Common behaviour of both :class:`Table` and :class:`InlineTable`""" - self._value.clear() + def __init__(self, value: "container.Container", trivia: Trivia): + Item.__init__(self, trivia) - def __iadd__(self, other): # type: (list) -> Array - if not isinstance(other, list): - return NotImplemented + self._value = value - for v in other: - self.append(v) + for k, v in self._value.body: + if k is not None: + dict.__setitem__(self, k.key, v) - return self + @property + def value(self) -> "container.Container": + return self._value - def __delitem__(self, key): - super(Array, self).__delitem__(key) + @overload + def append(self: AT, key: None, value: Union[Comment, Whitespace]) -> AT: + ... - j = 0 if key >= 0 else -1 - for i, v in enumerate(self._value if key >= 0 else reversed(self._value)): - if key < 0: - i = -i - 1 + @overload + def append(self: AT, key: Union[Key, str], value: Any) -> AT: + ... - if isinstance(v, (Comment, Whitespace)): - continue + def append(self, key, value): + raise NotImplementedError + + @overload + def add(self: AT, value: Union[Comment, Whitespace]) -> AT: + ... - if j == key: - del self._value[i] + @overload + def add(self: AT, key: Union[Key, str], value: Any) -> AT: + ... + + def add(self, key, value=None): + if value is None: + if not isinstance(key, (Comment, Whitespace)): + msg = "Non comment/whitespace items must have an associated key" + raise ValueError(msg) - if i < 0 and abs(i) > len(self._value): - i += 1 + key, value = None, key - if i < len(self._value) - 1 and isinstance(self._value[i], Whitespace): - del self._value[i] + return self.append(key, value) - break + def remove(self: AT, key: Union[Key, str]) -> AT: + self._value.remove(key) + + if isinstance(key, Key): + key = key.key + + if key is not None: + dict.__delitem__(self, key) + + return self - j += 1 if key >= 0 else -1 + def setdefault(self, key: Union[Key, str], default: Any) -> Any: + super().setdefault(key, default) + return self[key] def __str__(self): - return str( - [v.value for v in self._value if not isinstance(v, (Whitespace, Comment))] - ) + return str(self.value) - def __repr__(self): - return str(self) + def __repr__(self) -> str: + return repr(self.value) - def _getstate(self, protocol=3): - return self._value, self._trivia + def __iter__(self) -> Iterator[str]: + return iter(self._value) + + def __len__(self) -> int: + return len(self._value) + + def __delitem__(self, key: Union[Key, str]) -> None: + self.remove(key) + + def __getitem__(self, key: Union[Key, str]) -> Item: + return cast(Item, self._value[key]) + + def __setitem__(self, key: Union[Key, str], value: Any) -> None: + if not isinstance(value, Item): + value = item(value) + + is_replace = key in self + self._value[key] = value + + if key is not None: + dict.__setitem__(self, key, value) + + if is_replace: + return + m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) + if not m: + return + indent = m.group(1) -class Table(Item, MutableMapping, dict): + if not isinstance(value, Whitespace): + m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent) + if not m: + value.trivia.indent = indent + else: + value.trivia.indent = m.group(1) + indent + m.group(2) + + +class Table(AbstractTable): """ A table literal. """ def __init__( self, - value, - trivia, - is_aot_element, - is_super_table=False, - name=None, - display_name=None, - ): # type: (tomlkit.container.Container, Trivia, bool, bool, Optional[str], Optional[str]) -> None - super(Table, self).__init__(trivia) + value: "container.Container", + trivia: Trivia, + is_aot_element: bool, + is_super_table: bool = False, + name: Optional[str] = None, + display_name: Optional[str] = None, + ) -> None: + super().__init__(value, trivia) self.name = name self.display_name = display_name - self._value = value self._is_aot_element = is_aot_element self._is_super_table = is_super_table - for k, v in self._value.body: - if k is not None: - dict.__setitem__(self, k.key, v) - - @property - def value(self): # type: () -> tomlkit.container.Container - return self._value - @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 9 - def add(self, key, item=None): # type: (Union[Key, Item, str], Any) -> Item - if item is None: - if not isinstance(key, (Comment, Whitespace)): - raise ValueError( - "Non comment/whitespace items must have an associated key" - ) - - key, item = None, key - - return self.append(key, item) - - def append(self, key, _item): # type: (Union[Key, str], Any) -> Table + def append(self, key, _item): """ Appends a (key, item) to the table. """ @@ -942,7 +1196,7 @@ def append(self, key, _item): # type: (Union[Key, str], Any) -> Table return self - def raw_append(self, key, _item): # type: (Union[Key, str], Any) -> Table + def raw_append(self, key: Union[Key, str], _item: Any) -> "Table": if not isinstance(_item, Item): _item = item(_item) @@ -956,93 +1210,40 @@ def raw_append(self, key, _item): # type: (Union[Key, str], Any) -> Table return self - def remove(self, key): # type: (Union[Key, str]) -> Table - self._value.remove(key) - - if isinstance(key, Key): - key = key.key - - if key is not None: - dict.__delitem__(self, key) - - return self - - def is_aot_element(self): # type: () -> bool + def is_aot_element(self) -> bool: return self._is_aot_element - def is_super_table(self): # type: () -> bool + def is_super_table(self) -> bool: return self._is_super_table - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._value.as_string() # Helpers - def indent(self, indent): # type: (int) -> Table - super(Table, self).indent(indent) + def indent(self, indent: int) -> "Table": + super().indent(indent) m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) if not m: - indent = "" + indent_str = "" else: - indent = m.group(1) + indent_str = m.group(1) - for k, item in self._value.body: + for _, item in self._value.body: if not isinstance(item, Whitespace): - item.trivia.indent = indent + item.trivia.indent + item.trivia.indent = indent_str + item.trivia.indent return self - def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any - return self._value.get(key, default) - - def setdefault( - self, key, default=None - ): # type: (Union[Key, str], Any) -> Union[Item, Container] - super(Table, self).setdefault(key, default=default) - - return self[key] - - def __getitem__(self, key): # type: (Union[Key, str]) -> Item - return self._value[key] - - def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None - fix_indent = key not in self - - if not isinstance(value, Item): - value = item(value) - - self._value[key] = value - - if key is not None: - dict.__setitem__(self, key, value) - - m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) - if not m or not fix_indent: - return - - indent = m.group(1) - - if not isinstance(value, Whitespace): - m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent) - if not m: - value.trivia.indent = indent - else: - value.trivia.indent = m.group(1) + indent + m.group(2) - - def __delitem__(self, key): # type: (Union[Key, str]) -> None - self.remove(key) + def invalidate_display_name(self): + self.display_name = None - def __len__(self): # type: () -> int - return len(self._value) - - def __iter__(self): # type: () -> Iterator[str] - return iter(self._value) + for child in self.values(): + if hasattr(child, "invalidate_display_name"): + child.invalidate_display_name() - def __repr__(self): # type: () -> str - return repr(self._value) - - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple: return ( self._value, self._trivia, @@ -1053,32 +1254,23 @@ def _getstate(self, protocol=3): ) -class InlineTable(Item, MutableMapping, dict): +class InlineTable(AbstractTable): """ An inline table literal. """ def __init__( - self, value, trivia, new=False - ): # type: (tomlkit.container.Container, Trivia, bool) -> None - super(InlineTable, self).__init__(trivia) + self, value: "container.Container", trivia: Trivia, new: bool = False + ) -> None: + super().__init__(value, trivia) - self._value = value self._new = new - for k, v in self._value.body: - if k is not None: - dict.__setitem__(self, k.key, v) - @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 10 - @property - def value(self): # type: () -> Dict - return self._value - - def append(self, key, _item): # type: (Union[Key, str], Any) -> InlineTable + def append(self, key, _item): """ Appends a (key, item) to the table. """ @@ -1101,18 +1293,7 @@ def append(self, key, _item): # type: (Union[Key, str], Any) -> InlineTable return self - def remove(self, key): # type: (Union[Key, str]) -> InlineTable - self._value.remove(key) - - if isinstance(key, Key): - key = key.key - - if key is not None: - dict.__delitem__(self, key) - - return self - - def as_string(self): # type: () -> str + def as_string(self) -> str: buf = "{" for i, (k, v) in enumerate(self._value.body): if k is None: @@ -1144,97 +1325,47 @@ def as_string(self): # type: () -> str return buf - def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any - return self._value.get(key, default) - - def setdefault( - self, key, default=None - ): # type: (Union[Key, str], Any) -> Union[Item, Container] - super(InlineTable, self).setdefault(key, default=default) - - return self[key] - - def __contains__(self, key): # type: (Union[Key, str]) -> bool - return key in self._value - - def __getitem__(self, key): # type: (Union[Key, str]) -> Item - return self._value[key] - - def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None - if not isinstance(value, Item): - value = item(value) - - self._value[key] = value - - if key is not None: - dict.__setitem__(self, key, value) - - if value.trivia.comment: + def __setitem__(self, key: Union[Key, str], value: Any) -> None: + if hasattr(value, "trivia") and value.trivia.comment: value.trivia.comment = "" + super().__setitem__(key, value) - m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) - if not m: - return - - indent = m.group(1) - - if not isinstance(value, Whitespace): - m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent) - if not m: - value.trivia.indent = indent - else: - value.trivia.indent = m.group(1) + indent + m.group(2) - - def __delitem__(self, key): # type: (Union[Key, str]) -> None - self.remove(key) - - def __len__(self): # type: () -> int - return len(self._value) - - def __iter__(self): # type: () -> Iterator[str] - return iter(self._value) - - def __repr__(self): - return repr(self._value) - - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple: return (self._value, self._trivia) -class String(unicode, Item): +class String(str, Item): """ A string literal. """ def __new__(cls, t, value, original, trivia): - return super(String, cls).__new__(cls, value) + return super().__new__(cls, value) - def __init__( - self, t, _, original, trivia - ): # type: (StringType, str, original, Trivia) -> None - super(String, self).__init__(trivia) + def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None: + super().__init__(trivia) self._t = t self._original = original @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 11 @property - def value(self): # type: () -> str + def value(self) -> str: return self - def as_string(self): # type: () -> str - return "{}{}{}".format(self._t.value, decode(self._original), self._t.value) + def as_string(self) -> str: + return f"{self._t.value}{decode(self._original)}{self._t.value}" def __add__(self, other): - result = super(String, self).__add__(other) + result = super().__add__(other) return self._new(result) def __sub__(self, other): - result = super(String, self).__sub__(other) + result = super().__sub__(other) return self._new(result) @@ -1242,67 +1373,103 @@ def _new(self, result): return String(self._t, result, result, self._trivia) def _getstate(self, protocol=3): - return self._t, unicode(self), self._original, self._trivia + return self._t, str(self), self._original, self._trivia -class AoT(Item, list): +class AoT(Item, _CustomList): """ An array of table literal """ def __init__( - self, body, name=None, parsed=False - ): # type: (List[Table], Optional[str], bool) -> None + self, body: List[Table], name: Optional[str] = None, parsed: bool = False + ) -> None: self.name = name - self._body = [] + self._body: List[Table] = [] self._parsed = parsed - super(AoT, self).__init__(Trivia(trail="")) + super().__init__(Trivia(trail="")) for table in body: self.append(table) @property - def body(self): # type: () -> List[Table] + def body(self) -> List[Table]: return self._body @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 12 @property - def value(self): # type: () -> List[Dict[Any, Any]] + def value(self) -> List[Dict[Any, Any]]: return [v.value for v in self._body] - def append(self, table): # type: (Table) -> Table + def __len__(self) -> int: + return len(self._body) + + @overload + def __getitem__(self, key: slice) -> List[Table]: + ... + + @overload + def __getitem__(self, key: int) -> Table: + ... + + def __getitem__(self, key): + return self._body[key] + + def __setitem__(self, key: Union[slice, int], value: Any) -> None: + raise NotImplementedError + + def __delitem__(self, key: Union[slice, int]) -> None: + del self._body[key] + list.__delitem__(self, key) + + def insert(self, index: int, value: Table) -> None: + if not isinstance(value, Table): + raise ValueError(f"Unsupported insert value type: {type(value)}") + length = len(self) + if index < 0: + index += length + if index < 0: + index = 0 + elif index >= length: + index = length m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) if m: indent = m.group(1) - m = re.match("(?s)^([^ ]*)(.*)$", table.trivia.indent) + m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent) if not m: - table.trivia.indent = indent + value.trivia.indent = indent else: - table.trivia.indent = m.group(1) + indent + m.group(2) - - if not self._parsed and "\n" not in table.trivia.indent and self._body: - table.trivia.indent = "\n" + table.trivia.indent - - self._body.append(table) - - super(AoT, self).append(table) - - return table - - def as_string(self): # type: () -> str + value.trivia.indent = m.group(1) + indent + m.group(2) + prev_table = self._body[index - 1] if 0 < index and length else None + next_table = self._body[index + 1] if index < length - 1 else None + if not self._parsed: + if prev_table and "\n" not in value.trivia.indent: + value.trivia.indent = "\n" + value.trivia.indent + if next_table and "\n" not in next_table.trivia.indent: + next_table.trivia.indent = "\n" + next_table.trivia.indent + self._body.insert(index, value) + list.insert(self, index, value) + + def invalidate_display_name(self): + """Call ``invalidate_display_name`` on the contained tables""" + for child in self: + if hasattr(child, "invalidate_display_name"): + child.invalidate_display_name() + + def as_string(self) -> str: b = "" for table in self._body: b += table.as_string() return b - def __repr__(self): # type: () -> str - return "".format(self.value) + def __repr__(self) -> str: + return f"" def _getstate(self, protocol=3): return self._body, self.name, self._parsed @@ -1313,18 +1480,18 @@ class Null(Item): A null item. """ - def __init__(self): # type: () -> None + def __init__(self) -> None: pass @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return -1 @property - def value(self): # type: () -> None + def value(self) -> None: return None - def as_string(self): # type: () -> str + def as_string(self) -> str: return "" def _getstate(self, protocol=3): diff --git a/tomlkit/parser.py b/tomlkit/parser.py index b702088..f5a10f0 100644 --- a/tomlkit/parser.py +++ b/tomlkit/parser.py @@ -1,23 +1,19 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import re import string from typing import Any -from typing import Generator +from typing import Iterator from typing import List from typing import Optional from typing import Tuple +from typing import Type from typing import Union -from ._compat import chr from ._compat import decode from ._utils import RFC_3339_LOOSE from ._utils import _escaped from ._utils import parse_rfc3339 from .container import Container -from .exceptions import EmptyKeyError from .exceptions import EmptyTableNameError from .exceptions import InternalParserError from .exceptions import InvalidCharInStringError @@ -67,7 +63,7 @@ class Parser: Parser for TOML documents. """ - def __init__(self, string): # type: (str) -> None + def __init__(self, string: str) -> None: # Input to parse self._src = Source(decode(string)) @@ -89,20 +85,20 @@ def _current(self): def _marker(self): return self._src.marker - def extract(self): # type: () -> str + def extract(self) -> str: """ Extracts the value between marker and index """ return self._src.extract() - def inc(self, exception=None): # type: (Optional[ParseError.__class__]) -> bool + def inc(self, exception: Optional[Type[ParseError]] = None) -> bool: """ Increments the parser if the end of the input has not been reached. Returns whether or not it was able to advance. """ return self._src.inc(exception=exception) - def inc_n(self, n, exception=None): # type: (int, Optional[ParseError]) -> bool + def inc_n(self, n: int, exception: Optional[ParseError] = None) -> bool: """ Increments the parser by n characters if the end of the input has not been reached. @@ -115,13 +111,13 @@ def consume(self, chars, min=0, max=-1): """ return self._src.consume(chars=chars, min=min, max=max) - def end(self): # type: () -> bool + def end(self) -> bool: """ Returns True if the parser has reached the end of the input. """ return self._src.end() - def mark(self): # type: () -> None + def mark(self) -> None: """ Sets the marker to the index's current position """ @@ -133,7 +129,7 @@ def parse_error(self, exception=ParseError, *args): """ return self._src.parse_error(exception, *args) - def parse(self): # type: () -> TOMLDocument + def parse(self) -> TOMLDocument: body = TOMLDocument(True) # Take all keyvals outside of tables/AoT's. @@ -169,7 +165,7 @@ def parse(self): # type: () -> TOMLDocument return body - def _merge_ws(self, item, container): # type: (Item, Container) -> bool + def _merge_ws(self, item: Item, container: Container) -> bool: """ Merges the given Item with the last one currently in the given Container if both are whitespace items. @@ -191,7 +187,7 @@ def _merge_ws(self, item, container): # type: (Item, Container) -> bool return True - def _is_child(self, parent, child): # type: (str, str) -> bool + def _is_child(self, parent: str, child: str) -> bool: """ Returns whether a key is strictly a child of another key. AoT siblings are not considered children of one another. @@ -204,9 +200,10 @@ def _is_child(self, parent, child): # type: (str, str) -> bool return parent_parts == child_parts[: len(parent_parts)] - def _split_table_name(self, name): # type: (str) -> Generator[Key] + def _split_table_name(self, name: str) -> Iterator[Key]: in_name = False current = "" + original = "" t = KeyType.Bare parts = 0 for c in name: @@ -215,33 +212,24 @@ def _split_table_name(self, name): # type: (str) -> Generator[Key] if c == ".": if in_name: current += c + original += c continue if not current: raise self.parse_error() - yield Key(current.strip(), t=t, sep="", original=current) + yield Key(current.strip(), t=t, sep="", original=original) parts += 1 - current = "" + current = original = "" t = KeyType.Bare - continue elif c in {"'", '"'}: if in_name: - if ( - t == KeyType.Literal - and c == '"' - or t == KeyType.Basic - and c == "'" - ): + if c == t.value: + in_name = False + else: current += c - continue - - if c != t.value: - raise self.parse_error() - - in_name = False else: if ( current.strip() @@ -252,24 +240,24 @@ def _split_table_name(self, name): # type: (str) -> Generator[Key] in_name = True t = KeyType.Literal if c == "'" else KeyType.Basic - - continue + original += c elif in_name or c.is_bare_key_char(): current += c + original += c elif c.is_spaces(): # A space is only valid at this point # if it's in between parts. # We store it for now and will check # later if it's valid current += c - continue + original += c else: raise self.parse_error() if current.strip(): - yield Key(current.strip(), t=t, sep="", original=current) + yield Key(current.strip(), t=t, sep="", original=original) - def _parse_item(self): # type: () -> Optional[Tuple[Optional[Key], Item]] + def _parse_item(self) -> Optional[Tuple[Optional[Key], Item]]: """ Attempts to parse the next item and returns it, along with its key if the item is value-like. @@ -305,7 +293,7 @@ def _parse_item(self): # type: () -> Optional[Tuple[Optional[Key], Item]] return self._parse_key_value(True) - def _parse_comment_trail(self): # type: () -> Tuple[str, str, str] + def _parse_comment_trail(self, parse_trail: bool = True) -> Tuple[str, str, str]: """ Returns (comment_ws, comment, trail) If there is no comment, comment_ws and comment will @@ -350,22 +338,23 @@ def _parse_comment_trail(self): # type: () -> Tuple[str, str, str] if self.end(): break - while self._current.is_spaces() and self.inc(): - pass + trail = "" + if parse_trail: + while self._current.is_spaces() and self.inc(): + pass - if self._current == "\r": - self.inc() + if self._current == "\r": + self.inc() - if self._current == "\n": - self.inc() + if self._current == "\n": + self.inc() - trail = "" - if self._idx != self._marker or self._current.is_ws(): - trail = self.extract() + if self._idx != self._marker or self._current.is_ws(): + trail = self.extract() return comment_ws, comment, trail - def _parse_key_value(self, parse_comment=False): # type: (bool) -> (Key, Item) + def _parse_key_value(self, parse_comment: bool = False) -> Tuple[Key, Item]: # Leading indent self.mark() @@ -386,7 +375,8 @@ def _parse_key_value(self, parse_comment=False): # type: (bool) -> (Key, Item) raise self.parse_error(UnexpectedCharError, "=") else: found_equals = True - pass + if not found_equals: + raise self.parse_error(UnexpectedCharError, self._current) if not key.sep: key.sep = self.extract() @@ -411,7 +401,7 @@ def _parse_key_value(self, parse_comment=False): # type: (bool) -> (Key, Item) return key, val - def _parse_key(self): # type: () -> Key + def _parse_key(self) -> Key: """ Parses a Key at the current position; WS before the key must be exhausted first at the callsite. @@ -421,7 +411,7 @@ def _parse_key(self): # type: () -> Key else: return self._parse_bare_key() - def _parse_quoted_key(self): # type: () -> Key + def _parse_quoted_key(self) -> Key: """ Parses a key enclosed in either single or double quotes. """ @@ -454,7 +444,7 @@ def _parse_quoted_key(self): # type: () -> Key return Key(key, key_type, "", dotted) - def _parse_bare_key(self): # type: () -> Key + def _parse_bare_key(self) -> Key: """ Parses a bare key. """ @@ -475,7 +465,7 @@ def _parse_bare_key(self): # type: () -> Key if " " in key: # Bare key with spaces in it - raise self.parse_error(ParseError, 'Invalid key "{}"'.format(key)) + raise self.parse_error(ParseError, f'Invalid key "{key}"') if self._current == ".": self.inc() @@ -487,8 +477,8 @@ def _parse_bare_key(self): # type: () -> Key return Key(key, key_type, "", dotted, original=original) def _handle_dotted_key( - self, container, key, value - ): # type: (Union[Container, Table], Key, Any) -> None + self, container: Union[Container, Table], key: Key, value: Any + ) -> None: names = tuple(self._split_table_name(key.as_string())) name = names[0] name._dotted = True @@ -549,7 +539,7 @@ def _handle_dotted_key( table = table[_name] - def _parse_value(self): # type: () -> Item + def _parse_value(self) -> Item: """ Attempts to parse a value at the current position. """ @@ -674,7 +664,7 @@ def _parse_true(self): def _parse_false(self): return self._parse_bool(BoolType.FALSE) - def _parse_bool(self, style): # type: (BoolType) -> Bool + def _parse_bool(self, style: BoolType) -> Bool: with self._state: style = BoolType(style) @@ -685,25 +675,25 @@ def _parse_bool(self, style): # type: (BoolType) -> Bool return Bool(style, Trivia()) - def _parse_array(self): # type: () -> Array + def _parse_array(self) -> Array: # Consume opening bracket, EOF here is an issue (middle of array) self.inc(exception=UnexpectedEofError) - elems = [] # type: List[Item] + elems: List[Item] = [] prev_value = None while True: # consume whitespace mark = self._idx - self.consume(TOMLChar.SPACES) - newline = self.consume(TOMLChar.NL) + self.consume(TOMLChar.SPACES + TOMLChar.NL) indent = self._src[mark : self._idx] + newline = set(TOMLChar.NL) & set(indent) if newline: elems.append(Whitespace(indent)) continue # consume comment if self._current == "#": - cws, comment, trail = self._parse_comment_trail() + cws, comment, trail = self._parse_comment_trail(parse_trail=False) elems.append(Comment(Trivia(indent, cws, comment, trail))) continue @@ -743,7 +733,7 @@ def _parse_array(self): # type: () -> Array else: return res - def _parse_inline_table(self): # type: () -> InlineTable + def _parse_inline_table(self) -> InlineTable: # consume opening bracket, EOF here is an issue (middle of array) self.inc(exception=UnexpectedEofError) @@ -799,7 +789,7 @@ def _parse_inline_table(self): # type: () -> InlineTable return InlineTable(elems, Trivia()) - def _parse_number(self, raw, trivia): # type: (str, Trivia) -> Optional[Item] + def _parse_number(self, raw: str, trivia: Trivia) -> Optional[Item]: # Leading zeros are not allowed sign = "" if raw.startswith(("+", "-")): @@ -829,7 +819,7 @@ def _parse_number(self, raw, trivia): # type: (str, Trivia) -> Optional[Item] base = 16 # Underscores should be surrounded by digits - clean = re.sub("(?i)(?<={})_(?={})".format(digits, digits), "", raw) + clean = re.sub(f"(?i)(?<={digits})_(?={digits})", "", raw) if "_" in clean: return @@ -845,11 +835,11 @@ def _parse_number(self, raw, trivia): # type: (str, Trivia) -> Optional[Item] except ValueError: return - def _parse_literal_string(self): # type: () -> String + def _parse_literal_string(self) -> String: with self._state: return self._parse_string(StringType.SLL) - def _parse_basic_string(self): # type: () -> String + def _parse_basic_string(self) -> String: with self._state: return self._parse_string(StringType.SLB) @@ -898,12 +888,12 @@ def _parse_escaped_char(self, multiline): raise self.parse_error(InvalidCharInStringError, self._current) - def _parse_string(self, delim): # type: (StringType) -> String + def _parse_string(self, delim: StringType) -> String: # only keep parsing for string if the current character matches the delim if self._current != delim.unit: raise self.parse_error( InternalParserError, - "Invalid character for string type {}".format(delim), + f"Invalid character for string type {delim}", ) # consume the opening/first delim, EOF here is an issue @@ -1006,8 +996,8 @@ def _parse_string(self, delim): # type: (StringType) -> String self.inc(exception=UnexpectedEofError) def _parse_table( - self, parent_name=None, parent=None - ): # type: (Optional[str], Optional[Table]) -> Tuple[Key, Union[Table, AoT]] + self, parent_name: Optional[str] = None, parent: Optional[Table] = None + ) -> Tuple[Key, Union[Table, AoT]]: """ Parses a table element. """ @@ -1075,7 +1065,7 @@ def _parse_table( key = Key(name, sep="") name_parts = tuple(self._split_table_name(name)) if any(" " in part.key.strip() and part.is_bare() for part in name_parts): - raise self.parse_error(ParseError, 'Invalid table name "{}"'.format(name)) + raise self.parse_error(ParseError, f'Invalid table name "{name}"') missing_table = False if parent_name: @@ -1102,7 +1092,7 @@ def _parse_table( values, Trivia(indent, cws, comment, trail), is_aot, - name=name, + name=name_parts[0].key if name_parts else key.key, display_name=name, ) @@ -1131,13 +1121,13 @@ def _parse_table( child = Table( Container(True), Trivia(indent, cws, comment, trail), - is_aot and i == len(name_parts[1:]) - 1, - is_super_table=i < len(name_parts[1:]) - 1, + is_aot and i == len(name_parts) - 2, + is_super_table=i < len(name_parts) - 2, name=_name.key, - display_name=name if i == len(name_parts[1:]) - 1 else None, + display_name=name if i == len(name_parts) - 2 else None, ) - if is_aot and i == len(name_parts[1:]) - 1: + if is_aot and i == len(name_parts) - 2: table.raw_append( _name, AoT([child], name=table.name, parsed=True) ) @@ -1194,7 +1184,7 @@ def _parse_table( return key, result - def _peek_table(self): # type: () -> Tuple[bool, str] + def _peek_table(self) -> Tuple[bool, str]: """ Peeks ahead non-intrusively by cloning then restoring the initial state of the parser. @@ -1203,7 +1193,6 @@ def _peek_table(self): # type: () -> Tuple[bool, str] as well as whether it is part of an AoT. """ # we always want to restore after exiting this scope - table_name = "" with self._state(save_marker=True, restore=True): if self._current != "[": raise self.parse_error( @@ -1220,12 +1209,13 @@ def _peek_table(self): # type: () -> Tuple[bool, str] self.mark() + table_name = "" while self._current != "]" and self.inc(): table_name = self.extract() return is_aot, table_name - def _parse_aot(self, first, name_first): # type: (Table, str) -> AoT + def _parse_aot(self, first: Table, name_first: str) -> AoT: """ Parses all siblings of the provided table first and bundles them into an AoT. @@ -1244,7 +1234,7 @@ def _parse_aot(self, first, name_first): # type: (Table, str) -> AoT return AoT(payload, parsed=True) - def _peek(self, n): # type: (int) -> str + def _peek(self, n: int) -> str: """ Peeks ahead n characters. @@ -1262,9 +1252,7 @@ def _peek(self, n): # type: (int) -> str break return buf - def _peek_unicode( - self, is_long - ): # type: (bool) -> Tuple[Optional[str], Optional[str]] + def _peek_unicode(self, is_long: bool) -> Tuple[Optional[str], Optional[str]]: """ Peeks ahead non-intrusively by cloning then restoring the initial state of the parser. diff --git a/tomlkit/source.py b/tomlkit/source.py index 6a6a239..1685df6 100644 --- a/tomlkit/source.py +++ b/tomlkit/source.py @@ -1,39 +1,28 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import itertools - from copy import copy from typing import Any from typing import Optional from typing import Tuple from typing import Type -from ._compat import PY2 -from ._compat import unicode from .exceptions import ParseError from .exceptions import UnexpectedCharError -from .exceptions import UnexpectedEofError from .toml_char import TOMLChar class _State: def __init__( - self, source, save_marker=False, restore=False - ): # type: (_Source, Optional[bool], Optional[bool]) -> None + self, + source: "Source", + save_marker: Optional[str] = False, + restore: Optional[str] = False, + ) -> None: self._source = source self._save_marker = save_marker self.restore = restore - def __enter__(self): # type: () -> None + def __enter__(self) -> None: # Entering this context manager - save the state - if PY2: - # Python 2.7 does not allow to directly copy - # an iterator, so we have to make tees of the original - # chars iterator. - self._source._chars, self._chars = itertools.tee(self._source._chars) - else: - self._chars = copy(self._source._chars) + self._chars = copy(self._source._chars) self._idx = self._source._idx self._current = self._source._current self._marker = self._source._marker @@ -55,14 +44,14 @@ class _StateHandler: State preserver for the Parser. """ - def __init__(self, source): # type: (Source) -> None + def __init__(self, source: "Source") -> None: self._source = source self._states = [] def __call__(self, *args, **kwargs): return _State(self._source, *args, **kwargs) - def __enter__(self): # type: () -> None + def __enter__(self) -> None: state = self() self._states.append(state) return state.__enter__() @@ -72,11 +61,11 @@ def __exit__(self, exception_type, exception_val, trace): return state.__exit__(exception_type, exception_val, trace) -class Source(unicode): +class Source(str): EOF = TOMLChar("\0") - def __init__(self, _): # type: (unicode) -> None - super(Source, self).__init__() + def __init__(self, _: str) -> None: + super().__init__() # Collection of TOMLChars self._chars = iter([(i, TOMLChar(c)) for i, c in enumerate(self)]) @@ -97,28 +86,28 @@ def reset(self): self.mark() @property - def state(self): # type: () -> _StateHandler + def state(self) -> _StateHandler: return self._state @property - def idx(self): # type: () -> int + def idx(self) -> int: return self._idx @property - def current(self): # type: () -> TOMLChar + def current(self) -> TOMLChar: return self._current @property - def marker(self): # type: () -> int + def marker(self) -> int: return self._marker - def extract(self): # type: () -> unicode + def extract(self) -> str: """ Extracts the value between marker and index """ return self[self._marker : self._idx] - def inc(self, exception=None): # type: (Optional[Type[ParseError]]) -> bool + def inc(self, exception: Optional[Type[ParseError]] = None) -> bool: """ Increments the parser if the end of the input has not been reached. Returns whether or not it was able to advance. @@ -135,7 +124,7 @@ def inc(self, exception=None): # type: (Optional[Type[ParseError]]) -> bool return False - def inc_n(self, n, exception=None): # type: (int, Exception) -> bool + def inc_n(self, n: int, exception: Exception = None) -> bool: """ Increments the parser by n characters if the end of the input has not been reached. @@ -160,21 +149,21 @@ def consume(self, chars, min=0, max=-1): if min > 0: self.parse_error(UnexpectedCharError) - def end(self): # type: () -> bool + def end(self) -> bool: """ Returns True if the parser has reached the end of the input. """ return self._current is self.EOF - def mark(self): # type: () -> None + def mark(self) -> None: """ Sets the marker to the index's current position """ self._marker = self._idx def parse_error( - self, exception=ParseError, *args - ): # type: (Type[ParseError], Any) -> ParseError + self, exception: Type[ParseError] = ParseError, *args: Any + ) -> ParseError: """ Creates a generic "parse error" at the current position. """ @@ -182,7 +171,7 @@ def parse_error( return exception(line, col, *args) - def _to_linecol(self): # type: () -> Tuple[int, int] + def _to_linecol(self) -> Tuple[int, int]: cur = 0 for i, line in enumerate(self.splitlines()): if cur + len(line) + 1 > self.idx: diff --git a/tomlkit/toml_char.py b/tomlkit/toml_char.py index 079b16c..11e5385 100644 --- a/tomlkit/toml_char.py +++ b/tomlkit/toml_char.py @@ -1,18 +1,11 @@ import string -from ._compat import PY2 -from ._compat import unicode +from functools import lru_cache -if PY2: - from functools32 import lru_cache -else: - from functools import lru_cache - - -class TOMLChar(unicode): +class TOMLChar(str): def __init__(self, c): - super(TOMLChar, self).__init__() + super().__init__() if len(self) > 1: raise ValueError("A TOML character must be of length 1") @@ -25,42 +18,42 @@ def __init__(self, c): WS = SPACES + NL @lru_cache(maxsize=None) - def is_bare_key_char(self): # type: () -> bool + def is_bare_key_char(self) -> bool: """ Whether the character is a valid bare key name or not. """ return self in self.BARE @lru_cache(maxsize=None) - def is_kv_sep(self): # type: () -> bool + def is_kv_sep(self) -> bool: """ Whether the character is a valid key/value separator ot not. """ return self in self.KV @lru_cache(maxsize=None) - def is_int_float_char(self): # type: () -> bool + def is_int_float_char(self) -> bool: """ Whether the character if a valid integer or float value character or not. """ return self in self.NUMBER @lru_cache(maxsize=None) - def is_ws(self): # type: () -> bool + def is_ws(self) -> bool: """ Whether the character is a whitespace character or not. """ return self in self.WS @lru_cache(maxsize=None) - def is_nl(self): # type: () -> bool + def is_nl(self) -> bool: """ Whether the character is a new line character or not. """ return self in self.NL @lru_cache(maxsize=None) - def is_spaces(self): # type: () -> bool + def is_spaces(self) -> bool: """ Whether the character is a space or not """ diff --git a/tomlkit/toml_file.py b/tomlkit/toml_file.py index 3b41666..38de220 100644 --- a/tomlkit/toml_file.py +++ b/tomlkit/toml_file.py @@ -1,24 +1,19 @@ -import io - -from typing import Any -from typing import Dict - from .api import loads from .toml_document import TOMLDocument -class TOMLFile(object): +class TOMLFile: """ Represents a TOML file. """ - def __init__(self, path): # type: (str) -> None + def __init__(self, path: str) -> None: self._path = path - def read(self): # type: () -> TOMLDocument - with io.open(self._path, encoding="utf-8") as f: + def read(self) -> TOMLDocument: + with open(self._path, encoding="utf-8") as f: return loads(f.read()) - def write(self, data): # type: (TOMLDocument) -> None - with io.open(self._path, "w", encoding="utf-8") as f: + def write(self, data: TOMLDocument) -> None: + with open(self._path, "w", encoding="utf-8") as f: f.write(data.as_string()) From bdd910b4236f68ea8ec357437cdc1755c51b2960 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Fri, 17 Dec 2021 14:59:27 +0800 Subject: [PATCH 2/4] update badge url --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dfb0489..8685f33 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [pypi_version]: https://img.shields.io/pypi/v/tomlkit.svg?logo=python&logoColor=white [python_versions]: https://img.shields.io/pypi/pyversions/tomlkit.svg?logo=python&logoColor=white [github_license]: https://img.shields.io/github/license/sdispater/tomlkit.svg?logo=github&logoColor=white +[github_action]: https://github.com/sdispater/tomlkit/actions/workflows/tests.yml/badge.svg @@ -11,8 +12,9 @@ [![PyPI Version][pypi_version]](https://pypi.python.org/pypi/tomlkit/) [![Python Versions][python_versions]](https://pypi.python.org/pypi/tomlkit/) [![License][github_license]](https://github.com/sdispater/tomlkit/blob/master/LICENSE) -[![Codecov][codecov]](https://codecov.io/gh/sdispater/tomlkit)
+[![Codecov][codecov]](https://codecov.io/gh/sdispater/tomlkit) +[![Tests][github_action]](https://github.com/sdispater/tomlkit/actions/workflows/tests.yml) # TOML Kit - Style-preserving TOML library for Python From 1ea89b931b29edda491b8ac4a02949a8151749e9 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Fri, 17 Dec 2021 15:09:34 +0800 Subject: [PATCH 3/4] Fix the CI workflow --- .github/workflows/tests.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ad890d2..ae62eb5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -45,11 +45,16 @@ jobs: - name: Install Poetry shell: bash run: | - curl -fsSL -o install-poetry.py https://install.python-poetry.org - python install-poetry.py -y - echo "$HOME/.poetry/bin" >> $GITHUB_PATH - echo "%USERPROFILE%/.poetry/bin" >> $GITHUB_PATH - - name: Setup Poetry + curl -fsSL https://install.python-poetry.org | python - -y + - name: Update PATH + if: ${{ matrix.os != 'Windows' }} + run: echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Update Path for Windows + if: ${{ matrix.os == 'Windows' }} + shell: bash + run: echo "$APPDATA\Python\Scripts" >> $GITHUB_PATH + - name: Configure Poetry shell: bash run: | poetry config virtualenvs.in-project true From 459e452ba834bea621a9496e5409ab993ec0ea4b Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Fri, 17 Dec 2021 15:37:55 +0800 Subject: [PATCH 4/4] changelog --- CHANGELOG.md | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f1e88a..99dc90f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## [Unreleased] + +### Changed + +- Drop support for Python<3.6. ([#151](https://github.com/sdispater/tomlkit/pull/151)) + +### Fixed + +- Fix a bug that tomlkit accepts an invalid table with missing `=`. ([#141](https://github.com/sdispater/tomlkit/issues/141)) +- Fix the invalid dumping output when the key is empty. ([#143](https://github.com/sdispater/tomlkit/issues/143)) +- Fix incorrect string returned by dumps when moving/renaming table. ([#144](https://github.com/sdispater/tomlkit/issues/144)) +- Fix inconsistent dumps when replacing existing item with nested table. ([#145](https://github.com/sdispater/tomlkit/issues/145)) +- Fix invalid dumps output when appending to a multiline array. ([#146](https://github.com/sdispater/tomlkit/issues/146)) +- Fix the `KeyAlreadyExistError` when the table is separated into multiple parts. ([#148](https://github.com/sdispater/tomlkit/issues/148)) + ## [0.7.2] - 2021-05-20 ### Fixed @@ -7,7 +22,6 @@ - Fixed an error where container's data were lost when copying. ([#126](https://github.com/sdispater/tomlkit/pull/126)) - Fixed missing tests in the source distribution of the package. ([#127](https://github.com/sdispater/tomlkit/pull/127)) - ## [0.7.1] - 2021-05-19 ### Fixed @@ -19,7 +33,6 @@ - Fixed an error in top level keys handling when building documents programmatically. ([#122](https://github.com/sdispater/tomlkit/pull/122)) - Fixed compliance with mypy by adding a `py.typed` file. ([#109](https://github.com/sdispater/tomlkit/pull/109)) - ## [0.7.0] - 2020-07-31 ### Added @@ -34,28 +47,24 @@ - Fixed compliance with the 1.0.0rc1 TOML specification ([#102](https://github.com/sdispater/tomlkit/pull/102)). - ## [0.6.0] - 2020-04-15 ### Added - Added support for heterogeneous arrays ([#92](https://github.com/sdispater/tomlkit/pull/92)). - ## [0.5.11] - 2020-02-29 ### Fixed - Fix containers and our of order tables dictionary behavior ([#82](https://github.com/sdispater/tomlkit/pull/82))) - ## [0.5.10] - 2020-02-28 ### Fixed - Fixed out of order tables not behaving properly ([#79](https://github.com/sdispater/tomlkit/pull/79)) - ## [0.5.9] - 2020-02-28 ### Fixed @@ -64,21 +73,18 @@ - Fixed parsing errors when single quotes are present in a table name ([#71](https://github.com/sdispater/tomlkit/pull/71)). - Fixed parsing errors when parsing some table names ([#76](https://github.com/sdispater/tomlkit/pull/76)). - ## [0.5.8] - 2019-10-11 ### Added - Added support for producing multiline arrays - ## [0.5.7] - 2019-10-04 ### Fixed - Fixed handling of inline tables. - ## [0.5.6] - 2019-10-04 ### Fixed @@ -86,14 +92,12 @@ - Fixed boolean comparison. - Fixed appending inline tables to tables. - ## [0.5.5] - 2019-07-01 ### Fixed - Fixed display of inline tables after element deletion. - ## [0.5.4] - 2019-06-30 ### Fixed @@ -104,7 +108,6 @@ - Fixed behavior of `setdefault()` on containers (Thanks to [@AndyKluger](https://github.com/AndyKluger)). - Fixed tables string representation. - ## [0.5.3] - 2018-11-19 ### Fixed @@ -112,7 +115,6 @@ - Fixed copy of TOML documents. - Fixed behavior on PyPy3. - ## [0.5.2] - 2018-11-09 ### Fixed @@ -121,14 +123,12 @@ - Fixed comments being displayed in inline tables. - Fixed string with non-scalar unicode code points not raising an error. - ## [0.5.1] - 2018-11-08 ### Fixed - Fixed deletion and replacement of sub tables declared after other tables. - ## [0.5.0] - 2018-11-06 ### Changed @@ -141,14 +141,12 @@ - Fixed comma handling when parsing inline tables. (Thanks to [@njalerikson](https://github.com/njalerikson)) - Fixed a `KeyAlreadyPresent` error when declaring a sub table after other tables. - ## [0.4.6] - 2018-10-16 ### Fixed - Fixed string parsing behavior. - ## [0.4.5] - 2018-10-12 ### Fixed @@ -157,14 +155,12 @@ - Fixed key comparison. - Fixed an error when using pickle on TOML documents. - ## [0.4.4] - 2018-09-01 ### Fixed - Fixed performances issues while parsing on Python 2.7. - ## [0.4.3] - 2018-08-28 ### Fixed @@ -173,14 +169,12 @@ - Fixed missing newline after table header. - Fixed dict-like behavior for tables and documents. - ## [0.4.2] - 2018-08-06 ### Fixed - Fixed insertion of an element after deletion. - ## [0.4.1] - 2018-08-06 ### Fixed @@ -189,7 +183,6 @@ - Fixed parsing of dotted keys inside tables. - Fixed parsing of array of tables with same prefix. - ## [0.4.0] - 2018-07-23 ### Added @@ -205,7 +198,6 @@ - Fixed potential new lines inside an inline table. - ## [0.3.0] - 2018-07-20 ### Changed @@ -224,8 +216,7 @@ - Fixed handling of super tables with different sections. - Fixed raw strings escaping. - -[Unreleased]: https://github.com/sdispater/tomlkit/compare/0.7.2...master +[unreleased]: https://github.com/sdispater/tomlkit/compare/0.7.2...master [0.7.2]: https://github.com/sdispater/tomlkit/releases/tag/0.7.2 [0.7.1]: https://github.com/sdispater/tomlkit/releases/tag/0.7.1 [0.7.0]: https://github.com/sdispater/tomlkit/releases/tag/0.7.0