diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e48b906e..22bd5034 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 b792e2a7..ae62eb5d 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,11 +45,16 @@ 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
- 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
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3a1d64ce..23c83af8 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/CHANGELOG.md b/CHANGELOG.md
index 0f1e88a2..99dc90fe 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
diff --git a/README.md b/README.md
index 5670d693..8685f331 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,10 @@
[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
+[github_action]: https://github.com/sdispater/tomlkit/actions/workflows/tests.yml/badge.svg
+
+
[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/)
@@ -12,9 +13,8 @@
[![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)
+[![Tests][github_action]](https://github.com/sdispater/tomlkit/actions/workflows/tests.yml)
# TOML Kit - Style-preserving TOML library for Python
@@ -152,7 +152,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 4683c0b8..1c4e162a 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 b2bc1154..ebd70487 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 37183e93..0893f59a 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 314768ac..3628c5b6 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 9bd9c2e9..5038fda7 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 098b2d52..136abb80 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 5fc30de8..40c299ac 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 9f759a2e..c293f828 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 59b42d7c..54451347 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 c7959654..ca506370 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 b2d2549e..57ad1812 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 8899e48a..12477951 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 31dabf62..7dde452c 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 03a30316..b68de6e4 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 110d6c19..a9ca698e 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 487ed990..1295df69 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 2ae3e424..a5e1aca4 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 3de41219..d254c8e8 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 e0976368..59ebddf4 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 d0c7ab5a..73f636ea 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 f738e0be..863d9f32 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 b702088d..f5a10f0c 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 6a6a2391..1685df6b 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 079b16cc..11e5385d 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 3b416664..38de2200 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())