diff options
| author | Anderson Bravalheri <andersonbravalheri@gmail.com> | 2022-02-08 11:39:25 +0000 |
|---|---|---|
| committer | Anderson Bravalheri <andersonbravalheri@gmail.com> | 2022-03-05 09:58:49 +0000 |
| commit | e0d61d45eaf94b55a57a68c6cd65b3e508aee5a9 (patch) | |
| tree | 110828190a062fd7fc84b557752fefc13db018b0 /setuptools/_vendor | |
| parent | ccd2f073171065ad8fe65215ff837644689c6d85 (diff) | |
| download | python-setuptools-git-e0d61d45eaf94b55a57a68c6cd65b3e508aee5a9.tar.gz | |
Update vendored tomli to 2.0.1
Enforcing local imports is no longer needed.
Diffstat (limited to 'setuptools/_vendor')
| -rw-r--r-- | setuptools/_vendor/_validate_pyproject/fastjsonschema_validations.py | 2 | ||||
| -rw-r--r-- | setuptools/_vendor/tomli-2.0.1.dist-info/INSTALLER | 1 | ||||
| -rw-r--r-- | setuptools/_vendor/tomli-2.0.1.dist-info/LICENSE (renamed from setuptools/_vendor/tomli/LICENSE) | 0 | ||||
| -rw-r--r-- | setuptools/_vendor/tomli-2.0.1.dist-info/METADATA | 206 | ||||
| -rw-r--r-- | setuptools/_vendor/tomli-2.0.1.dist-info/RECORD | 15 | ||||
| -rw-r--r-- | setuptools/_vendor/tomli-2.0.1.dist-info/REQUESTED | 0 | ||||
| -rw-r--r-- | setuptools/_vendor/tomli-2.0.1.dist-info/WHEEL | 4 | ||||
| -rw-r--r-- | setuptools/_vendor/tomli/__init__.py | 8 | ||||
| -rw-r--r-- | setuptools/_vendor/tomli/_parser.py | 190 | ||||
| -rw-r--r-- | setuptools/_vendor/tomli/_re.py | 18 | ||||
| -rw-r--r-- | setuptools/_vendor/tomli/_types.py | 4 | ||||
| -rw-r--r-- | setuptools/_vendor/vendored.txt | 2 |
12 files changed, 358 insertions, 92 deletions
diff --git a/setuptools/_vendor/_validate_pyproject/fastjsonschema_validations.py b/setuptools/_vendor/_validate_pyproject/fastjsonschema_validations.py index d409b2a5..8bfd8809 100644 --- a/setuptools/_vendor/_validate_pyproject/fastjsonschema_validations.py +++ b/setuptools/_vendor/_validate_pyproject/fastjsonschema_validations.py @@ -10,7 +10,7 @@ # *** PLEASE DO NOT MODIFY DIRECTLY: Automatically generated code *** -VERSION = "2.15.2" +VERSION = "2.15.3" import re from .fastjsonschema_exceptions import JsonSchemaValueException diff --git a/setuptools/_vendor/tomli-2.0.1.dist-info/INSTALLER b/setuptools/_vendor/tomli-2.0.1.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/setuptools/_vendor/tomli-2.0.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/setuptools/_vendor/tomli/LICENSE b/setuptools/_vendor/tomli-2.0.1.dist-info/LICENSE index e859590f..e859590f 100644 --- a/setuptools/_vendor/tomli/LICENSE +++ b/setuptools/_vendor/tomli-2.0.1.dist-info/LICENSE diff --git a/setuptools/_vendor/tomli-2.0.1.dist-info/METADATA b/setuptools/_vendor/tomli-2.0.1.dist-info/METADATA new file mode 100644 index 00000000..efd87ecc --- /dev/null +++ b/setuptools/_vendor/tomli-2.0.1.dist-info/METADATA @@ -0,0 +1,206 @@ +Metadata-Version: 2.1 +Name: tomli +Version: 2.0.1 +Summary: A lil' TOML parser +Keywords: toml +Author-email: Taneli Hukkinen <hukkin@users.noreply.github.com> +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: MacOS +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX :: Linux +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Typing :: Typed +Project-URL: Changelog, https://github.com/hukkin/tomli/blob/master/CHANGELOG.md +Project-URL: Homepage, https://github.com/hukkin/tomli + +[](https://github.com/hukkin/tomli/actions?query=workflow%3ATests+branch%3Amaster+event%3Apush) +[](https://codecov.io/gh/hukkin/tomli) +[](https://pypi.org/project/tomli) + +# Tomli + +> A lil' TOML parser + +**Table of Contents** *generated with [mdformat-toc](https://github.com/hukkin/mdformat-toc)* + +<!-- mdformat-toc start --slug=github --maxlevel=6 --minlevel=2 --> + +- [Intro](#intro) +- [Installation](#installation) +- [Usage](#usage) + - [Parse a TOML string](#parse-a-toml-string) + - [Parse a TOML file](#parse-a-toml-file) + - [Handle invalid TOML](#handle-invalid-toml) + - [Construct `decimal.Decimal`s from TOML floats](#construct-decimaldecimals-from-toml-floats) +- [FAQ](#faq) + - [Why this parser?](#why-this-parser) + - [Is comment preserving round-trip parsing supported?](#is-comment-preserving-round-trip-parsing-supported) + - [Is there a `dumps`, `write` or `encode` function?](#is-there-a-dumps-write-or-encode-function) + - [How do TOML types map into Python types?](#how-do-toml-types-map-into-python-types) +- [Performance](#performance) + +<!-- mdformat-toc end --> + +## Intro<a name="intro"></a> + +Tomli is a Python library for parsing [TOML](https://toml.io). +Tomli is fully compatible with [TOML v1.0.0](https://toml.io/en/v1.0.0). + +## Installation<a name="installation"></a> + +```bash +pip install tomli +``` + +## Usage<a name="usage"></a> + +### Parse a TOML string<a name="parse-a-toml-string"></a> + +```python +import tomli + +toml_str = """ + gretzky = 99 + + [kurri] + jari = 17 + """ + +toml_dict = tomli.loads(toml_str) +assert toml_dict == {"gretzky": 99, "kurri": {"jari": 17}} +``` + +### Parse a TOML file<a name="parse-a-toml-file"></a> + +```python +import tomli + +with open("path_to_file/conf.toml", "rb") as f: + toml_dict = tomli.load(f) +``` + +The file must be opened in binary mode (with the `"rb"` flag). +Binary mode will enforce decoding the file as UTF-8 with universal newlines disabled, +both of which are required to correctly parse TOML. + +### Handle invalid TOML<a name="handle-invalid-toml"></a> + +```python +import tomli + +try: + toml_dict = tomli.loads("]] this is invalid TOML [[") +except tomli.TOMLDecodeError: + print("Yep, definitely not valid.") +``` + +Note that error messages are considered informational only. +They should not be assumed to stay constant across Tomli versions. + +### Construct `decimal.Decimal`s from TOML floats<a name="construct-decimaldecimals-from-toml-floats"></a> + +```python +from decimal import Decimal +import tomli + +toml_dict = tomli.loads("precision-matters = 0.982492", parse_float=Decimal) +assert toml_dict["precision-matters"] == Decimal("0.982492") +``` + +Note that `decimal.Decimal` can be replaced with another callable that converts a TOML float from string to a Python type. +The `decimal.Decimal` is, however, a practical choice for use cases where float inaccuracies can not be tolerated. + +Illegal types are `dict` and `list`, and their subtypes. +A `ValueError` will be raised if `parse_float` produces illegal types. + +## FAQ<a name="faq"></a> + +### Why this parser?<a name="why-this-parser"></a> + +- it's lil' +- pure Python with zero dependencies +- the fastest pure Python parser [\*](#performance): + 15x as fast as [tomlkit](https://pypi.org/project/tomlkit/), + 2.4x as fast as [toml](https://pypi.org/project/toml/) +- outputs [basic data types](#how-do-toml-types-map-into-python-types) only +- 100% spec compliant: passes all tests in + [a test set](https://github.com/toml-lang/compliance/pull/8) + soon to be merged to the official + [compliance tests for TOML](https://github.com/toml-lang/compliance) + repository +- thoroughly tested: 100% branch coverage + +### Is comment preserving round-trip parsing supported?<a name="is-comment-preserving-round-trip-parsing-supported"></a> + +No. + +The `tomli.loads` function returns a plain `dict` that is populated with builtin types and types from the standard library only. +Preserving comments requires a custom type to be returned so will not be supported, +at least not by the `tomli.loads` and `tomli.load` functions. + +Look into [TOML Kit](https://github.com/sdispater/tomlkit) if preservation of style is what you need. + +### Is there a `dumps`, `write` or `encode` function?<a name="is-there-a-dumps-write-or-encode-function"></a> + +[Tomli-W](https://github.com/hukkin/tomli-w) is the write-only counterpart of Tomli, providing `dump` and `dumps` functions. + +The core library does not include write capability, as most TOML use cases are read-only, and Tomli intends to be minimal. + +### How do TOML types map into Python types?<a name="how-do-toml-types-map-into-python-types"></a> + +| TOML type | Python type | Details | +| ---------------- | ------------------- | ------------------------------------------------------------ | +| Document Root | `dict` | | +| Key | `str` | | +| String | `str` | | +| Integer | `int` | | +| Float | `float` | | +| Boolean | `bool` | | +| Offset Date-Time | `datetime.datetime` | `tzinfo` attribute set to an instance of `datetime.timezone` | +| Local Date-Time | `datetime.datetime` | `tzinfo` attribute set to `None` | +| Local Date | `datetime.date` | | +| Local Time | `datetime.time` | | +| Array | `list` | | +| Table | `dict` | | +| Inline Table | `dict` | | + +## Performance<a name="performance"></a> + +The `benchmark/` folder in this repository contains a performance benchmark for comparing the various Python TOML parsers. +The benchmark can be run with `tox -e benchmark-pypi`. +Running the benchmark on my personal computer output the following: + +```console +foo@bar:~/dev/tomli$ tox -e benchmark-pypi +benchmark-pypi installed: attrs==19.3.0,click==7.1.2,pytomlpp==1.0.2,qtoml==0.3.0,rtoml==0.7.0,toml==0.10.2,tomli==1.1.0,tomlkit==0.7.2 +benchmark-pypi run-test-pre: PYTHONHASHSEED='2658546909' +benchmark-pypi run-test: commands[0] | python -c 'import datetime; print(datetime.date.today())' +2021-07-23 +benchmark-pypi run-test: commands[1] | python --version +Python 3.8.10 +benchmark-pypi run-test: commands[2] | python benchmark/run.py +Parsing data.toml 5000 times: +------------------------------------------------------ + parser | exec time | performance (more is better) +-----------+------------+----------------------------- + rtoml | 0.901 s | baseline (100%) + pytomlpp | 1.08 s | 83.15% + tomli | 3.89 s | 23.15% + toml | 9.36 s | 9.63% + qtoml | 11.5 s | 7.82% + tomlkit | 56.8 s | 1.59% +``` + +The parsers are ordered from fastest to slowest, using the fastest parser as baseline. +Tomli performed the best out of all pure Python TOML parsers, +losing only to pytomlpp (wraps C++) and rtoml (wraps Rust). + diff --git a/setuptools/_vendor/tomli-2.0.1.dist-info/RECORD b/setuptools/_vendor/tomli-2.0.1.dist-info/RECORD new file mode 100644 index 00000000..2d93fa2c --- /dev/null +++ b/setuptools/_vendor/tomli-2.0.1.dist-info/RECORD @@ -0,0 +1,15 @@ +tomli-2.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +tomli-2.0.1.dist-info/LICENSE,sha256=uAgWsNUwuKzLTCIReDeQmEpuO2GSLCte6S8zcqsnQv4,1072 +tomli-2.0.1.dist-info/METADATA,sha256=zPDceKmPwJGLWtZykrHixL7WVXWmJGzZ1jyRT5lCoPI,8875 +tomli-2.0.1.dist-info/RECORD,, +tomli-2.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +tomli-2.0.1.dist-info/WHEEL,sha256=jPMR_Dzkc4X4icQtmz81lnNY_kAsfog7ry7qoRvYLXw,81 +tomli/__init__.py,sha256=JhUwV66DB1g4Hvt1UQCVMdfCu-IgAV8FXmvDU9onxd4,396 +tomli/__pycache__/__init__.cpython-38.pyc,, +tomli/__pycache__/_parser.cpython-38.pyc,, +tomli/__pycache__/_re.cpython-38.pyc,, +tomli/__pycache__/_types.cpython-38.pyc,, +tomli/_parser.py,sha256=g9-ENaALS-B8dokYpCuzUFalWlog7T-SIYMjLZSWrtM,22633 +tomli/_re.py,sha256=dbjg5ChZT23Ka9z9DHOXfdtSpPwUfdgMXnj8NOoly-w,2943 +tomli/_types.py,sha256=-GTG2VUqkpxwMqzmVO4F7ybKddIbAnuAHXfmWQcTi3Q,254 +tomli/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26 diff --git a/setuptools/_vendor/tomli-2.0.1.dist-info/REQUESTED b/setuptools/_vendor/tomli-2.0.1.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/setuptools/_vendor/tomli-2.0.1.dist-info/REQUESTED diff --git a/setuptools/_vendor/tomli-2.0.1.dist-info/WHEEL b/setuptools/_vendor/tomli-2.0.1.dist-info/WHEEL new file mode 100644 index 00000000..c727d148 --- /dev/null +++ b/setuptools/_vendor/tomli-2.0.1.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.6.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/setuptools/_vendor/tomli/__init__.py b/setuptools/_vendor/tomli/__init__.py index 0ac89c82..4c6ec97e 100644 --- a/setuptools/_vendor/tomli/__init__.py +++ b/setuptools/_vendor/tomli/__init__.py @@ -1,9 +1,11 @@ -"""A lil' TOML parser.""" +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2021 Taneli Hukkinen +# Licensed to PSF under a Contributor Agreement. __all__ = ("loads", "load", "TOMLDecodeError") -__version__ = "1.2.3" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT +__version__ = "2.0.1" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT from ._parser import TOMLDecodeError, load, loads # Pretend this exception was created here. -TOMLDecodeError.__module__ = "setuptools.extern.tomli" +TOMLDecodeError.__module__ = __name__ diff --git a/setuptools/_vendor/tomli/_parser.py b/setuptools/_vendor/tomli/_parser.py index 093afe50..f1bb0aa1 100644 --- a/setuptools/_vendor/tomli/_parser.py +++ b/setuptools/_vendor/tomli/_parser.py @@ -1,7 +1,13 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2021 Taneli Hukkinen +# Licensed to PSF under a Contributor Agreement. + +from __future__ import annotations + +from collections.abc import Iterable import string from types import MappingProxyType -from typing import Any, BinaryIO, Dict, FrozenSet, Iterable, NamedTuple, Optional, Tuple -import warnings +from typing import Any, BinaryIO, NamedTuple from ._re import ( RE_DATETIME, @@ -48,31 +54,28 @@ class TOMLDecodeError(ValueError): """An error raised if a document is not valid TOML.""" -def load(fp: BinaryIO, *, parse_float: ParseFloat = float) -> Dict[str, Any]: +def load(__fp: BinaryIO, *, parse_float: ParseFloat = float) -> dict[str, Any]: """Parse TOML from a binary file object.""" - s_bytes = fp.read() + b = __fp.read() try: - s = s_bytes.decode() + s = b.decode() except AttributeError: - warnings.warn( - "Text file object support is deprecated in favor of binary file objects." - ' Use `open("foo.toml", "rb")` to open the file in binary mode.', - DeprecationWarning, - stacklevel=2, - ) - s = s_bytes # type: ignore[assignment] + raise TypeError( + "File must be opened in binary mode, e.g. use `open('foo.toml', 'rb')`" + ) from None return loads(s, parse_float=parse_float) -def loads(s: str, *, parse_float: ParseFloat = float) -> Dict[str, Any]: # noqa: C901 +def loads(__s: str, *, parse_float: ParseFloat = float) -> dict[str, Any]: # noqa: C901 """Parse TOML from a string.""" # The spec allows converting "\r\n" to "\n", even in string # literals. Let's do so to simplify parsing. - src = s.replace("\r\n", "\n") + src = __s.replace("\r\n", "\n") pos = 0 out = Output(NestedDict(), Flags()) header: Key = () + parse_float = make_safe_parse_float(parse_float) # Parse one statement at a time # (typically means one line in TOML source) @@ -100,9 +103,10 @@ def loads(s: str, *, parse_float: ParseFloat = float) -> Dict[str, Any]: # noqa pos = skip_chars(src, pos, TOML_WS) elif char == "[": try: - second_char: Optional[str] = src[pos + 1] + second_char: str | None = src[pos + 1] except IndexError: second_char = None + out.flags.finalize_pending() if second_char == "[": pos, header = create_list_rule(src, pos, out) else: @@ -138,7 +142,16 @@ class Flags: EXPLICIT_NEST = 1 def __init__(self) -> None: - self._flags: Dict[str, dict] = {} + self._flags: dict[str, dict] = {} + self._pending_flags: set[tuple[Key, int]] = set() + + def add_pending(self, key: Key, flag: int) -> None: + self._pending_flags.add((key, flag)) + + def finalize_pending(self) -> None: + for key, flag in self._pending_flags: + self.set(key, flag, recursive=False) + self._pending_flags.clear() def unset_all(self, key: Key) -> None: cont = self._flags @@ -148,19 +161,6 @@ class Flags: cont = cont[k]["nested"] cont.pop(key[-1], None) - def set_for_relative_key(self, head_key: Key, rel_key: Key, flag: int) -> None: - cont = self._flags - for k in head_key: - if k not in cont: - cont[k] = {"flags": set(), "recursive_flags": set(), "nested": {}} - cont = cont[k]["nested"] - for k in rel_key: - if k in cont: - cont[k]["flags"].add(flag) - else: - cont[k] = {"flags": {flag}, "recursive_flags": set(), "nested": {}} - cont = cont[k]["nested"] - def set(self, key: Key, flag: int, *, recursive: bool) -> None: # noqa: A003 cont = self._flags key_parent, key_stem = key[:-1], key[-1] @@ -193,7 +193,7 @@ class Flags: class NestedDict: def __init__(self) -> None: # The parsed content of the TOML document - self.dict: Dict[str, Any] = {} + self.dict: dict[str, Any] = {} def get_or_create_nest( self, @@ -217,10 +217,9 @@ class NestedDict: last_key = key[-1] if last_key in cont: list_ = cont[last_key] - try: - list_.append({}) - except AttributeError: + if not isinstance(list_, list): raise KeyError("An object other than list found behind this key") + list_.append({}) else: cont[last_key] = [{}] @@ -244,7 +243,7 @@ def skip_until( pos: Pos, expect: str, *, - error_on: FrozenSet[str], + error_on: frozenset[str], error_on_eof: bool, ) -> Pos: try: @@ -263,7 +262,7 @@ def skip_until( def skip_comment(src: str, pos: Pos) -> Pos: try: - char: Optional[str] = src[pos] + char: str | None = src[pos] except IndexError: char = None if char == "#": @@ -282,31 +281,31 @@ def skip_comments_and_array_ws(src: str, pos: Pos) -> Pos: return pos -def create_dict_rule(src: str, pos: Pos, out: Output) -> Tuple[Pos, Key]: +def create_dict_rule(src: str, pos: Pos, out: Output) -> tuple[Pos, Key]: pos += 1 # Skip "[" pos = skip_chars(src, pos, TOML_WS) pos, key = parse_key(src, pos) if out.flags.is_(key, Flags.EXPLICIT_NEST) or out.flags.is_(key, Flags.FROZEN): - raise suffixed_err(src, pos, f"Can not declare {key} twice") + raise suffixed_err(src, pos, f"Cannot declare {key} twice") out.flags.set(key, Flags.EXPLICIT_NEST, recursive=False) try: out.data.get_or_create_nest(key) except KeyError: - raise suffixed_err(src, pos, "Can not overwrite a value") from None + raise suffixed_err(src, pos, "Cannot overwrite a value") from None if not src.startswith("]", pos): - raise suffixed_err(src, pos, 'Expected "]" at the end of a table declaration') + raise suffixed_err(src, pos, "Expected ']' at the end of a table declaration") return pos + 1, key -def create_list_rule(src: str, pos: Pos, out: Output) -> Tuple[Pos, Key]: +def create_list_rule(src: str, pos: Pos, out: Output) -> tuple[Pos, Key]: pos += 2 # Skip "[[" pos = skip_chars(src, pos, TOML_WS) pos, key = parse_key(src, pos) if out.flags.is_(key, Flags.FROZEN): - raise suffixed_err(src, pos, f"Can not mutate immutable namespace {key}") + raise suffixed_err(src, pos, f"Cannot mutate immutable namespace {key}") # Free the namespace now that it points to another empty list item... out.flags.unset_all(key) # ...but this key precisely is still prohibited from table declaration @@ -314,10 +313,10 @@ def create_list_rule(src: str, pos: Pos, out: Output) -> Tuple[Pos, Key]: try: out.data.append_nest_to_list(key) except KeyError: - raise suffixed_err(src, pos, "Can not overwrite a value") from None + raise suffixed_err(src, pos, "Cannot overwrite a value") from None if not src.startswith("]]", pos): - raise suffixed_err(src, pos, 'Expected "]]" at the end of an array declaration') + raise suffixed_err(src, pos, "Expected ']]' at the end of an array declaration") return pos + 2, key @@ -328,18 +327,26 @@ def key_value_rule( key_parent, key_stem = key[:-1], key[-1] abs_key_parent = header + key_parent + relative_path_cont_keys = (header + key[:i] for i in range(1, len(key))) + for cont_key in relative_path_cont_keys: + # Check that dotted key syntax does not redefine an existing table + if out.flags.is_(cont_key, Flags.EXPLICIT_NEST): + raise suffixed_err(src, pos, f"Cannot redefine namespace {cont_key}") + # Containers in the relative path can't be opened with the table syntax or + # dotted key/value syntax in following table sections. + out.flags.add_pending(cont_key, Flags.EXPLICIT_NEST) + if out.flags.is_(abs_key_parent, Flags.FROZEN): raise suffixed_err( - src, pos, f"Can not mutate immutable namespace {abs_key_parent}" + src, pos, f"Cannot mutate immutable namespace {abs_key_parent}" ) - # Containers in the relative path can't be opened with the table syntax after this - out.flags.set_for_relative_key(header, key, Flags.EXPLICIT_NEST) + try: nest = out.data.get_or_create_nest(abs_key_parent) except KeyError: - raise suffixed_err(src, pos, "Can not overwrite a value") from None + raise suffixed_err(src, pos, "Cannot overwrite a value") from None if key_stem in nest: - raise suffixed_err(src, pos, "Can not overwrite a value") + raise suffixed_err(src, pos, "Cannot overwrite a value") # Mark inline table and array namespaces recursively immutable if isinstance(value, (dict, list)): out.flags.set(header + key, Flags.FROZEN, recursive=True) @@ -349,27 +356,27 @@ def key_value_rule( def parse_key_value_pair( src: str, pos: Pos, parse_float: ParseFloat -) -> Tuple[Pos, Key, Any]: +) -> tuple[Pos, Key, Any]: pos, key = parse_key(src, pos) try: - char: Optional[str] = src[pos] + char: str | None = src[pos] except IndexError: char = None if char != "=": - raise suffixed_err(src, pos, 'Expected "=" after a key in a key/value pair') + raise suffixed_err(src, pos, "Expected '=' after a key in a key/value pair") pos += 1 pos = skip_chars(src, pos, TOML_WS) pos, value = parse_value(src, pos, parse_float) return pos, key, value -def parse_key(src: str, pos: Pos) -> Tuple[Pos, Key]: +def parse_key(src: str, pos: Pos) -> tuple[Pos, Key]: pos, key_part = parse_key_part(src, pos) key: Key = (key_part,) pos = skip_chars(src, pos, TOML_WS) while True: try: - char: Optional[str] = src[pos] + char: str | None = src[pos] except IndexError: char = None if char != ".": @@ -381,9 +388,9 @@ def parse_key(src: str, pos: Pos) -> Tuple[Pos, Key]: pos = skip_chars(src, pos, TOML_WS) -def parse_key_part(src: str, pos: Pos) -> Tuple[Pos, str]: +def parse_key_part(src: str, pos: Pos) -> tuple[Pos, str]: try: - char: Optional[str] = src[pos] + char: str | None = src[pos] except IndexError: char = None if char in BARE_KEY_CHARS: @@ -397,12 +404,12 @@ def parse_key_part(src: str, pos: Pos) -> Tuple[Pos, str]: raise suffixed_err(src, pos, "Invalid initial character for a key part") -def parse_one_line_basic_str(src: str, pos: Pos) -> Tuple[Pos, str]: +def parse_one_line_basic_str(src: str, pos: Pos) -> tuple[Pos, str]: pos += 1 return parse_basic_str(src, pos, multiline=False) -def parse_array(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, list]: +def parse_array(src: str, pos: Pos, parse_float: ParseFloat) -> tuple[Pos, list]: pos += 1 array: list = [] @@ -426,7 +433,7 @@ def parse_array(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, list] return pos + 1, array -def parse_inline_table(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, dict]: +def parse_inline_table(src: str, pos: Pos, parse_float: ParseFloat) -> tuple[Pos, dict]: pos += 1 nested_dict = NestedDict() flags = Flags() @@ -438,11 +445,11 @@ def parse_inline_table(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos pos, key, value = parse_key_value_pair(src, pos, parse_float) key_parent, key_stem = key[:-1], key[-1] if flags.is_(key, Flags.FROZEN): - raise suffixed_err(src, pos, f"Can not mutate immutable namespace {key}") + raise suffixed_err(src, pos, f"Cannot mutate immutable namespace {key}") try: nest = nested_dict.get_or_create_nest(key_parent, access_lists=False) except KeyError: - raise suffixed_err(src, pos, "Can not overwrite a value") from None + raise suffixed_err(src, pos, "Cannot overwrite a value") from None if key_stem in nest: raise suffixed_err(src, pos, f"Duplicate inline table key {key_stem!r}") nest[key_stem] = value @@ -458,9 +465,9 @@ def parse_inline_table(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos pos = skip_chars(src, pos, TOML_WS) -def parse_basic_str_escape( # noqa: C901 +def parse_basic_str_escape( src: str, pos: Pos, *, multiline: bool = False -) -> Tuple[Pos, str]: +) -> tuple[Pos, str]: escape_id = src[pos : pos + 2] pos += 2 if multiline and escape_id in {"\\ ", "\\\t", "\\\n"}: @@ -473,7 +480,7 @@ def parse_basic_str_escape( # noqa: C901 except IndexError: return pos, "" if char != "\n": - raise suffixed_err(src, pos, 'Unescaped "\\" in a string') + raise suffixed_err(src, pos, "Unescaped '\\' in a string") pos += 1 pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE) return pos, "" @@ -484,16 +491,14 @@ def parse_basic_str_escape( # noqa: C901 try: return pos, BASIC_STR_ESCAPE_REPLACEMENTS[escape_id] except KeyError: - if len(escape_id) != 2: - raise suffixed_err(src, pos, "Unterminated string") from None - raise suffixed_err(src, pos, 'Unescaped "\\" in a string') from None + raise suffixed_err(src, pos, "Unescaped '\\' in a string") from None -def parse_basic_str_escape_multiline(src: str, pos: Pos) -> Tuple[Pos, str]: +def parse_basic_str_escape_multiline(src: str, pos: Pos) -> tuple[Pos, str]: return parse_basic_str_escape(src, pos, multiline=True) -def parse_hex_char(src: str, pos: Pos, hex_len: int) -> Tuple[Pos, str]: +def parse_hex_char(src: str, pos: Pos, hex_len: int) -> tuple[Pos, str]: hex_str = src[pos : pos + hex_len] if len(hex_str) != hex_len or not HEXDIGIT_CHARS.issuperset(hex_str): raise suffixed_err(src, pos, "Invalid hex value") @@ -504,7 +509,7 @@ def parse_hex_char(src: str, pos: Pos, hex_len: int) -> Tuple[Pos, str]: return pos, chr(hex_int) -def parse_literal_str(src: str, pos: Pos) -> Tuple[Pos, str]: +def parse_literal_str(src: str, pos: Pos) -> tuple[Pos, str]: pos += 1 # Skip starting apostrophe start_pos = pos pos = skip_until( @@ -513,7 +518,7 @@ def parse_literal_str(src: str, pos: Pos) -> Tuple[Pos, str]: return pos + 1, src[start_pos:pos] # Skip ending apostrophe -def parse_multiline_str(src: str, pos: Pos, *, literal: bool) -> Tuple[Pos, str]: +def parse_multiline_str(src: str, pos: Pos, *, literal: bool) -> tuple[Pos, str]: pos += 3 if src.startswith("\n", pos): pos += 1 @@ -544,7 +549,7 @@ def parse_multiline_str(src: str, pos: Pos, *, literal: bool) -> Tuple[Pos, str] return pos, result + (delim * 2) -def parse_basic_str(src: str, pos: Pos, *, multiline: bool) -> Tuple[Pos, str]: +def parse_basic_str(src: str, pos: Pos, *, multiline: bool) -> tuple[Pos, str]: if multiline: error_on = ILLEGAL_MULTILINE_BASIC_STR_CHARS parse_escapes = parse_basic_str_escape_multiline @@ -578,12 +583,14 @@ def parse_basic_str(src: str, pos: Pos, *, multiline: bool) -> Tuple[Pos, str]: def parse_value( # noqa: C901 src: str, pos: Pos, parse_float: ParseFloat -) -> Tuple[Pos, Any]: +) -> tuple[Pos, Any]: try: - char: Optional[str] = src[pos] + char: str | None = src[pos] except IndexError: char = None + # IMPORTANT: order conditions based on speed of checking and likelihood + # Basic strings if char == '"': if src.startswith('"""', pos): @@ -604,6 +611,14 @@ def parse_value( # noqa: C901 if src.startswith("false", pos): return pos + 5, False + # Arrays + if char == "[": + return parse_array(src, pos, parse_float) + + # Inline tables + if char == "{": + return parse_inline_table(src, pos, parse_float) + # Dates and times datetime_match = RE_DATETIME.match(src, pos) if datetime_match: @@ -623,14 +638,6 @@ def parse_value( # noqa: C901 if number_match: return number_match.end(), match_to_number(number_match, parse_float) - # Arrays - if char == "[": - return parse_array(src, pos, parse_float) - - # Inline tables - if char == "{": - return parse_inline_table(src, pos, parse_float) - # Special floats first_three = src[pos : pos + 3] if first_three in {"inf", "nan"}: @@ -661,3 +668,24 @@ def suffixed_err(src: str, pos: Pos, msg: str) -> TOMLDecodeError: def is_unicode_scalar_value(codepoint: int) -> bool: return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111) + + +def make_safe_parse_float(parse_float: ParseFloat) -> ParseFloat: + """A decorator to make `parse_float` safe. + + `parse_float` must not return dicts or lists, because these types + would be mixed with parsed TOML tables and arrays, thus confusing + the parser. The returned decorated callable raises `ValueError` + instead of returning illegal types. + """ + # The default `float` callable never returns illegal types. Optimize it. + if parse_float is float: # type: ignore[comparison-overlap] + return float + + def safe_parse_float(float_str: str) -> Any: + float_value = parse_float(float_str) + if isinstance(float_value, (dict, list)): + raise ValueError("parse_float must not return dicts or lists") + return float_value + + return safe_parse_float diff --git a/setuptools/_vendor/tomli/_re.py b/setuptools/_vendor/tomli/_re.py index 45e17e2c..994bb749 100644 --- a/setuptools/_vendor/tomli/_re.py +++ b/setuptools/_vendor/tomli/_re.py @@ -1,7 +1,13 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2021 Taneli Hukkinen +# Licensed to PSF under a Contributor Agreement. + +from __future__ import annotations + from datetime import date, datetime, time, timedelta, timezone, tzinfo from functools import lru_cache import re -from typing import Any, Optional, Union +from typing import Any from ._types import ParseFloat @@ -31,7 +37,7 @@ RE_NUMBER = re.compile( ) RE_LOCALTIME = re.compile(_TIME_RE_STR) RE_DATETIME = re.compile( - fr""" + rf""" ([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27 (?: [Tt ] @@ -43,7 +49,7 @@ RE_DATETIME = re.compile( ) -def match_to_datetime(match: "re.Match") -> Union[datetime, date]: +def match_to_datetime(match: re.Match) -> datetime | date: """Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`. Raises ValueError if the match does not correspond to a valid date @@ -68,7 +74,7 @@ def match_to_datetime(match: "re.Match") -> Union[datetime, date]: hour, minute, sec = int(hour_str), int(minute_str), int(sec_str) micros = int(micros_str.ljust(6, "0")) if micros_str else 0 if offset_sign_str: - tz: Optional[tzinfo] = cached_tz( + tz: tzinfo | None = cached_tz( offset_hour_str, offset_minute_str, offset_sign_str ) elif zulu_time: @@ -89,13 +95,13 @@ def cached_tz(hour_str: str, minute_str: str, sign_str: str) -> timezone: ) -def match_to_localtime(match: "re.Match") -> time: +def match_to_localtime(match: re.Match) -> time: hour_str, minute_str, sec_str, micros_str = match.groups() micros = int(micros_str.ljust(6, "0")) if micros_str else 0 return time(int(hour_str), int(minute_str), int(sec_str), micros) -def match_to_number(match: "re.Match", parse_float: "ParseFloat") -> Any: +def match_to_number(match: re.Match, parse_float: ParseFloat) -> Any: if match.group("floatpart"): return parse_float(match.group()) return int(match.group(), 0) diff --git a/setuptools/_vendor/tomli/_types.py b/setuptools/_vendor/tomli/_types.py index e37cc808..d949412e 100644 --- a/setuptools/_vendor/tomli/_types.py +++ b/setuptools/_vendor/tomli/_types.py @@ -1,3 +1,7 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2021 Taneli Hukkinen +# Licensed to PSF under a Contributor Agreement. + from typing import Any, Callable, Tuple # Type annotations diff --git a/setuptools/_vendor/vendored.txt b/setuptools/_vendor/vendored.txt index fe05dc1a..38d1f70f 100644 --- a/setuptools/_vendor/vendored.txt +++ b/setuptools/_vendor/vendored.txt @@ -9,5 +9,5 @@ importlib_metadata==4.11.1 typing_extensions==4.0.1 # required for importlib_resources and _metadata on older Pythons zipp==3.7.0 -tomli==1.2.3 +tomli==2.0.1 # validate-pyproject[all]==0.3.2 # Special handling in tools/vendored, don't uncomment or remove |
