From 0449c53058349b8c407f2e2b3d89ee0d036afbf1 Mon Sep 17 00:00:00 2001 From: Bartosz Sokorski Date: Thu, 28 Jul 2022 05:39:35 +0200 Subject: [PATCH] chore: Multiple code quality improvements (#212) * Add pre-commit config * First pre-commit swipe (isort, flake8-bugbear, black, pyupgrade) * Flake8-broken-line and flake8-comprehensions * Flake8-eradicate, flake8-simplify, pep8-naming * flake8-use-fstring * flake8-tidy-imports, flake8-quotes * flake8-typing-imports, flake8-type-checking * pycln * Remove unnecessary comments * Update versions of plugins * Code review changes - removed `exclude` from `pycln` pre-commit config - removed adding of futture-annotations import from isort config - added `TOMLDocument` to `__all__` list in tomlkit/__init__.py * Revert "First pre-commit swipe (isort, flake8-bugbear, black, pyupgrade)" This reverts commit 60abdac8b3b54dede22ef9f6c3e365c890062e91. * Remove unnecessary option * Remove unnecessary isort config option * Fix tests --- .flake8 | 19 ++++++- .pre-commit-config.yaml | 44 +++++++++++++-- tests/test_items.py | 37 ++++++------- tests/test_toml_document.py | 23 ++++---- tests/test_toml_spec_tests.py | 7 +-- tests/test_toml_tests.py | 9 +-- tests/util.py | 12 ++-- tomlkit/__init__.py | 51 ++++++++--------- tomlkit/_compat.py | 5 +- tomlkit/_utils.py | 5 +- tomlkit/api.py | 50 ++++++++--------- tomlkit/container.py | 85 +++++++++++++++-------------- tomlkit/exceptions.py | 5 +- tomlkit/items.py | 78 ++++++++++++++------------ tomlkit/parser.py | 100 +++++++++++++++++----------------- tomlkit/source.py | 12 ++-- tomlkit/toml_char.py | 24 ++++---- tomlkit/toml_document.py | 2 +- tomlkit/toml_file.py | 4 +- 19 files changed, 313 insertions(+), 259 deletions(-) diff --git a/.flake8 b/.flake8 index dc51aa9..a6b4d12 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,14 @@ [flake8] +min_python_version = 3.6.0 max-line-length = 88 ignore = E501, E203, W503 -per-file-ignores = __init__.py:F401 +per-file-ignores = + # N818: error suffix in exception names (API-breaking change) + tomlkit/exceptions.py: N818, + # FS003: f-string missing prefix + tests/test_items.py: FS003, + tests/test_api.py: FS003, + tests/test_toml_document.py: FS003, exclude = .git __pycache__ @@ -15,3 +22,13 @@ exclude = .pytest_cache .vscode .github +ban-relative-imports = true +# flake8-use-fstring: https://github.com/MichaelKim0407/flake8-use-fstring#--percent-greedy-and---format-greedy +format-greedy = 1 +inline-quotes = double +eradicate-whitelist-extend = ^-.*; +extend-ignore = + # E203: Whitespace before ':' (pycqa/pycodestyle#373) + E203, + # SIM106: Handle error-cases first + SIM106, diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d922f86..7d25336 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,15 +1,14 @@ repos: - - repo: https://github.com/ambv/black - rev: 22.3.0 + - repo: https://github.com/psf/black + rev: 22.6.0 hooks: - id: black - - repo: https://github.com/timothycrosley/isort + - repo: https://github.com/pycqa/isort rev: 5.10.1 hooks: - id: isort - additional_dependencies: [toml] - exclude: ^.*/?setup\.py$ + exclude: docs/.* - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.1.0 @@ -19,3 +18,38 @@ repos: - id: end-of-file-fixer exclude: ^tests/(toml-test|toml-spec-tests)/.* - id: debug-statements + + - repo: https://github.com/asottile/yesqa + rev: v1.3.0 + hooks: + - id: yesqa + additional_dependencies: &flake8_deps + - flake8-broken-line==0.4.0 + - flake8-bugbear==22.7.1 + - flake8-comprehensions==3.10.0 + - flake8-eradicate==1.2.1 + - flake8-quotes==3.3.1 + - flake8-simplify==0.19.2 + - flake8-tidy-imports==4.8.0 + - flake8-typing-imports==1.12.0 + - flake8-use-fstring==1.3 + - pep8-naming==0.13.0 + exclude: ^tomlkit/items\.py + + - repo: https://github.com/asottile/pyupgrade + rev: v2.37.1 + hooks: + - id: pyupgrade + args: [--py36-plus] + + - repo: https://github.com/hadialqattan/pycln + rev: v2.0.1 + hooks: + - id: pycln + args: [--all] + + - repo: https://github.com/pycqa/flake8 + rev: 4.0.1 + hooks: + - id: flake8 + additional_dependencies: *flake8_deps diff --git a/tests/test_items.py b/tests/test_items.py index f71bd60..3dcd509 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -8,6 +8,8 @@ import pytest +from tests.util import assert_is_ppo +from tests.util import elementary_test from tomlkit import api from tomlkit import parse from tomlkit.exceptions import NonExistentKey @@ -27,9 +29,6 @@ from tomlkit.items import item from tomlkit.parser import Parser -from .util import assert_is_ppo -from .util import elementary_test - @pytest.fixture() def tz_pst(): @@ -120,7 +119,7 @@ def test_aot_unwrap(): d = item([{"a": "A"}, {"b": "B"}]) unwrapped = d.unwrap() assert_is_ppo(unwrapped, list) - for du, dw in zip(unwrapped, d): + for du, _ in zip(unwrapped, d): assert_is_ppo(du, dict) for ku in du: vu = du[ku] @@ -182,26 +181,26 @@ def test_items_can_be_appended_to_and_removed_from_a_table(): _, table = parser._parse_table() assert isinstance(table, Table) - assert "" == table.as_string() + assert table.as_string() == "" table.append(Key("foo"), String(StringType.SLB, "bar", "bar", Trivia(trail="\n"))) - assert 'foo = "bar"\n' == table.as_string() + assert table.as_string() == 'foo = "bar"\n' table.append( Key("baz"), Integer(34, Trivia(comment_ws=" ", comment="# Integer", trail=""), "34"), ) - assert 'foo = "bar"\nbaz = 34 # Integer' == table.as_string() + assert table.as_string() == 'foo = "bar"\nbaz = 34 # Integer' table.remove(Key("baz")) - assert 'foo = "bar"\n' == table.as_string() + assert table.as_string() == 'foo = "bar"\n' table.remove(Key("foo")) - assert "" == table.as_string() + assert table.as_string() == "" with pytest.raises(NonExistentKey): table.remove(Key("foo")) @@ -215,23 +214,23 @@ def test_items_can_be_appended_to_and_removed_from_an_inline_table(): _, table = parser._parse_item() assert isinstance(table, InlineTable) - assert "{}" == table.as_string() + assert table.as_string() == "{}" table.append(Key("foo"), String(StringType.SLB, "bar", "bar", Trivia(trail=""))) - assert '{foo = "bar"}' == table.as_string() + assert table.as_string() == '{foo = "bar"}' table.append(Key("baz"), Integer(34, Trivia(trail=""), "34")) - assert '{foo = "bar", baz = 34}' == table.as_string() + assert table.as_string() == '{foo = "bar", baz = 34}' table.remove(Key("baz")) - assert '{foo = "bar"}' == table.as_string() + assert table.as_string() == '{foo = "bar"}' table.remove(Key("foo")) - assert "{}" == table.as_string() + assert table.as_string() == "{}" with pytest.raises(NonExistentKey): table.remove(Key("foo")) @@ -339,7 +338,7 @@ def test_array_multiline(): t = item([]) t.multiline(True) - assert "[]" == t.as_string() + assert t.as_string() == "[]" def test_array_multiline_modify(): @@ -791,7 +790,7 @@ def test_trim_comments_when_building_inline_table(): value.comment("Another comment") table.append("baz", value) assert "# Another comment" not in table.as_string() - assert '{foo = "bar", baz = "foobaz"}' == table.as_string() + assert table.as_string() == '{foo = "bar", baz = "foobaz"}' def test_deleting_inline_table_elemeent_does_not_leave_trailing_separator(): @@ -799,11 +798,11 @@ def test_deleting_inline_table_elemeent_does_not_leave_trailing_separator(): table["foo"] = "bar" table["baz"] = "boom" - assert '{foo = "bar", baz = "boom"}' == table.as_string() + assert table.as_string() == '{foo = "bar", baz = "boom"}' del table["baz"] - assert '{foo = "bar"}' == table.as_string() + assert table.as_string() == '{foo = "bar"}' table = api.inline_table() table["foo"] = "bar" @@ -812,7 +811,7 @@ def test_deleting_inline_table_elemeent_does_not_leave_trailing_separator(): table["baz"] = "boom" - assert '{baz = "boom"}' == table.as_string() + assert table.as_string() == '{baz = "boom"}' def test_booleans_comparison(): diff --git a/tests/test_toml_document.py b/tests/test_toml_document.py index f5a5e8c..811c90c 100644 --- a/tests/test_toml_document.py +++ b/tests/test_toml_document.py @@ -9,14 +9,13 @@ import tomlkit +from tests.util import assert_is_ppo from tomlkit import parse from tomlkit import ws from tomlkit._utils import _utc from tomlkit.api import document from tomlkit.exceptions import NonExistentKey -from .util import assert_is_ppo - def test_document_is_a_dict(example): content = example("example") @@ -461,7 +460,8 @@ def test_getting_inline_table_is_still_an_inline_table(): dev_dependencies["baz"]["source"] = "other" assert ( - """\ + doc.as_string() + == """\ [tool.poetry] name = "foo" @@ -472,7 +472,6 @@ def test_getting_inline_table_is_still_an_inline_table(): [tool.poetry.dev-dependencies] baz = {version = "^4.0", source = "other"} """ - == doc.as_string() ) @@ -508,7 +507,7 @@ def test_values_can_still_be_set_for_out_of_order_tables(): doc = parse(content) doc["a"]["a"]["key"] = "new_value" - assert "new_value" == doc["a"]["a"]["key"] + assert doc["a"]["a"]["key"] == "new_value" expected = """ [a.a] @@ -588,18 +587,18 @@ def test_out_of_order_tables_are_still_dicts(): table = doc["a"]["a"] assert "key" in table assert "c" in table - assert "value" == table.get("key") + assert table.get("key") == "value" assert {} == table.get("c") assert table.get("d") is None - assert "foo" == table.get("d", "foo") + assert table.get("d", "foo") == "foo" - assert "bar" == table.setdefault("d", "bar") - assert "bar" == table["d"] + assert table.setdefault("d", "bar") == "bar" + assert table["d"] == "bar" - assert "value" == table.pop("key") + assert table.pop("key") == "value" assert "key" not in table - assert "baz" == table.pop("missing", default="baz") + assert table.pop("missing", default="baz") == "baz" with pytest.raises(KeyError): table.pop("missing") @@ -629,7 +628,7 @@ def test_string_output_order_is_preserved_for_out_of_order_tables(): constraint["version"] = "^1.0" doc["tool"]["poetry"]["dependencies"]["bar"] = constraint - assert "^1.0" == doc["tool"]["poetry"]["dependencies"]["bar"]["version"] + assert doc["tool"]["poetry"]["dependencies"]["bar"]["version"] == "^1.0" expected = """ [tool.poetry] diff --git a/tests/test_toml_spec_tests.py b/tests/test_toml_spec_tests.py index 57ad181..fdfb735 100644 --- a/tests/test_toml_spec_tests.py +++ b/tests/test_toml_spec_tests.py @@ -78,7 +78,7 @@ def untag(value): elif value["type"] == "array": return [untag(i) for i in value["value"]] else: - raise Exception("Unsupported type {}".format(value["type"])) + raise Exception(f'Unsupported type {value["type"]}') else: return {k: untag(v) for k, v in value.items()} @@ -107,6 +107,5 @@ def test_valid_decode(test): @pytest.mark.parametrize("test", ERROR_TESTS) def test_invalid_decode(test): toml_file = os.path.join(SPEC_TEST_DIR, "errors", test + ".toml") - with pytest.raises(TOMLKitError): - with open(toml_file, encoding="utf-8") as f: - parse(f.read()) + with pytest.raises(TOMLKitError), 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 88a1de3..61e1281 100644 --- a/tests/test_toml_tests.py +++ b/tests/test_toml_tests.py @@ -38,7 +38,7 @@ def untag(value): elif value["type"] == "array": return [untag(i) for i in value["value"]] else: - raise Exception("Unsupported type {}".format(value["type"])) + raise Exception(f'Unsupported type {value["type"]}') else: return {k: untag(v) for k, v in value.items()} @@ -57,6 +57,7 @@ def test_invalid_decode(invalid_decode_case): def test_invalid_encode(invalid_encode_case): - with pytest.raises((TOMLKitError, UnicodeDecodeError)): - with open(invalid_encode_case, encoding="utf-8") as f: - load(f) + with pytest.raises((TOMLKitError, UnicodeDecodeError)), open( + invalid_encode_case, encoding="utf-8" + ) as f: + load(f) diff --git a/tests/util.py b/tests/util.py index 3a4c758..fbe6df2 100644 --- a/tests/util.py +++ b/tests/util.py @@ -43,15 +43,15 @@ def assert_not_tomlkit_type(v): - for i, T in enumerate(TOMLKIT_TYPES): - assert not isinstance(v, T) + for _, tomlkit_type in enumerate(TOMLKIT_TYPES): + assert not isinstance(v, tomlkit_type) -def assert_is_ppo(v_unwrapped, unwrappedType): +def assert_is_ppo(v_unwrapped, unwrapped_type): assert_not_tomlkit_type(v_unwrapped) - assert isinstance(v_unwrapped, unwrappedType) + assert isinstance(v_unwrapped, unwrapped_type) -def elementary_test(v, unwrappedType): +def elementary_test(v, unwrapped_type): v_unwrapped = v.unwrap() - assert_is_ppo(v_unwrapped, unwrappedType) + assert_is_ppo(v_unwrapped, unwrapped_type) diff --git a/tomlkit/__init__.py b/tomlkit/__init__.py index 1abb1eb..dc3f0af 100644 --- a/tomlkit/__init__.py +++ b/tomlkit/__init__.py @@ -1,28 +1,28 @@ -from .api import TOMLDocument -from .api import aot -from .api import array -from .api import boolean -from .api import comment -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 -from .api import integer -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 -from .api import string -from .api import table -from .api import time -from .api import value -from .api import ws +from tomlkit.api import TOMLDocument +from tomlkit.api import aot +from tomlkit.api import array +from tomlkit.api import boolean +from tomlkit.api import comment +from tomlkit.api import date +from tomlkit.api import datetime +from tomlkit.api import document +from tomlkit.api import dump +from tomlkit.api import dumps +from tomlkit.api import float_ +from tomlkit.api import inline_table +from tomlkit.api import integer +from tomlkit.api import item +from tomlkit.api import key +from tomlkit.api import key_value +from tomlkit.api import load +from tomlkit.api import loads +from tomlkit.api import nl +from tomlkit.api import parse +from tomlkit.api import string +from tomlkit.api import table +from tomlkit.api import time +from tomlkit.api import value +from tomlkit.api import ws __version__ = "0.11.1" @@ -49,6 +49,7 @@ "string", "table", "time", + "TOMLDocument", "value", "ws", ] diff --git a/tomlkit/_compat.py b/tomlkit/_compat.py index 1295df6..f1d3bcc 100644 --- a/tomlkit/_compat.py +++ b/tomlkit/_compat.py @@ -1,3 +1,4 @@ +import contextlib import sys from typing import Any @@ -15,9 +16,7 @@ def decode(string: Any, encodings: Optional[List[str]] = None): encodings = encodings or ["utf-8", "latin1", "ascii"] for encoding in encodings: - try: + with contextlib.suppress(UnicodeEncodeError, UnicodeDecodeError): return string.decode(encoding) - except (UnicodeEncodeError, UnicodeDecodeError): - pass return string.decode(encodings[0], errors="ignore") diff --git a/tomlkit/_utils.py b/tomlkit/_utils.py index 5c8113f..07ed7ba 100644 --- a/tomlkit/_utils.py +++ b/tomlkit/_utils.py @@ -9,7 +9,7 @@ from typing import Collection from typing import Union -from ._compat import decode +from tomlkit._compat import decode RFC_3339_LOOSE = re.compile( @@ -125,7 +125,6 @@ def escape_string(s: str, escape_sequences: Collection[str] = _basic_escapes) -> res = [] start = 0 - l = len(s) def flush(inc=1): if start != i: @@ -134,7 +133,7 @@ def flush(inc=1): return i + inc i = 0 - while i < l: + while i < len(s): for seq in escape_sequences: seq_len = len(seq) if s[i:].startswith(seq): diff --git a/tomlkit/api.py b/tomlkit/api.py index 273efc5..49573b5 100644 --- a/tomlkit/api.py +++ b/tomlkit/api.py @@ -6,31 +6,31 @@ from typing import Tuple from typing import Union -from ._utils import parse_rfc3339 -from .container import Container -from .exceptions import UnexpectedCharError -from .items import AoT -from .items import Array -from .items import Bool -from .items import Comment -from .items import Date -from .items import DateTime -from .items import DottedKey -from .items import Float -from .items import InlineTable -from .items import Integer -from .items import Item as _Item -from .items import Key -from .items import SingleKey -from .items import String -from .items import StringType as _StringType -from .items import Table -from .items import Time -from .items import Trivia -from .items import Whitespace -from .items import item -from .parser import Parser -from .toml_document import TOMLDocument +from tomlkit._utils import parse_rfc3339 +from tomlkit.container import Container +from tomlkit.exceptions import UnexpectedCharError +from tomlkit.items import AoT +from tomlkit.items import Array +from tomlkit.items import Bool +from tomlkit.items import Comment +from tomlkit.items import Date +from tomlkit.items import DateTime +from tomlkit.items import DottedKey +from tomlkit.items import Float +from tomlkit.items import InlineTable +from tomlkit.items import Integer +from tomlkit.items import Item as _Item +from tomlkit.items import Key +from tomlkit.items import SingleKey +from tomlkit.items import String +from tomlkit.items import StringType as _StringType +from tomlkit.items import Table +from tomlkit.items import Time +from tomlkit.items import Trivia +from tomlkit.items import Whitespace +from tomlkit.items import item +from tomlkit.parser import Parser +from tomlkit.toml_document import TOMLDocument def loads(string: Union[str, bytes]) -> TOMLDocument: diff --git a/tomlkit/container.py b/tomlkit/container.py index 070c583..606d728 100644 --- a/tomlkit/container.py +++ b/tomlkit/container.py @@ -8,22 +8,22 @@ from typing import Tuple from typing import Union -from ._compat import decode -from ._utils import merge_dicts -from .exceptions import KeyAlreadyPresent -from .exceptions import NonExistentKey -from .exceptions import TOMLKitError -from .items import AoT -from .items import Comment -from .items import Item -from .items import Key -from .items import Null -from .items import SingleKey -from .items import Table -from .items import Trivia -from .items import Whitespace -from .items import _CustomDict -from .items import item as _item +from tomlkit._compat import decode +from tomlkit._utils import merge_dicts +from tomlkit.exceptions import KeyAlreadyPresent +from tomlkit.exceptions import NonExistentKey +from tomlkit.exceptions import TOMLKitError +from tomlkit.items import AoT +from tomlkit.items import Comment +from tomlkit.items import Item +from tomlkit.items import Key +from tomlkit.items import Null +from tomlkit.items import SingleKey +from tomlkit.items import Table +from tomlkit.items import Trivia +from tomlkit.items import Whitespace +from tomlkit.items import _CustomDict +from tomlkit.items import item as _item _NOT_SET = object() @@ -533,15 +533,18 @@ def _render_table( if table.is_aot_element(): open_, close = "[[", "]]" - cur += "{}{}{}{}{}{}{}{}".format( - table.trivia.indent, - open_, - decode(_key), - close, - table.trivia.comment_ws, - decode(table.trivia.comment), - table.trivia.trail, - "\n" if "\n" not in table.trivia.trail and len(table.value) > 0 else "", + newline_in_table_trivia = ( + "\n" if "\n" not in table.trivia.trail and len(table.value) > 0 else "" + ) + cur += ( + f"{table.trivia.indent}" + f"{open_}" + f"{decode(_key)}" + f"{close}" + f"{table.trivia.comment_ws}" + f"{decode(table.trivia.comment)}" + f"{table.trivia.trail}" + f"{newline_in_table_trivia}" ) elif table.trivia.indent == "\n": cur += table.trivia.indent @@ -585,14 +588,14 @@ def _render_aot_table(self, table: Table, prefix: Optional[str] = None) -> str: if not table.is_super_table(): open_, close = "[[", "]]" - cur += "{}{}{}{}{}{}{}".format( - table.trivia.indent, - open_, - decode(_key), - close, - table.trivia.comment_ws, - decode(table.trivia.comment), - table.trivia.trail, + cur += ( + f"{table.trivia.indent}" + f"{open_}" + f"{decode(_key)}" + f"{close}" + f"{table.trivia.comment_ws}" + f"{decode(table.trivia.comment)}" + f"{table.trivia.trail}" ) for k, v in table.value.body: @@ -620,14 +623,14 @@ def _render_simple_item(self, key, item, prefix=None): if prefix is not None: _key = prefix + "." + _key - return "{}{}{}{}{}{}{}".format( - item.trivia.indent, - decode(_key), - key.sep, - decode(item.as_string()), - item.trivia.comment_ws, - decode(item.trivia.comment), - item.trivia.trail, + return ( + f"{item.trivia.indent}" + f"{decode(_key)}" + f"{key.sep}" + f"{decode(item.as_string())}" + f"{item.trivia.comment_ws}" + f"{decode(item.trivia.comment)}" + f"{item.trivia.trail}" ) def __len__(self) -> int: diff --git a/tomlkit/exceptions.py b/tomlkit/exceptions.py index d43abd1..3147ca2 100644 --- a/tomlkit/exceptions.py +++ b/tomlkit/exceptions.py @@ -210,8 +210,9 @@ def __init__(self, line: int, col: int, char: int, type: str) -> None: display_code += hex(char)[2:] message = ( - "Control characters (codes less than 0x1f and 0x7f) are not allowed in {}, " - "use {} instead".format(type, display_code) + "Control characters (codes less than 0x1f and 0x7f)" + f" are not allowed in {type}, " + f"use {display_code} instead" ) super().__init__(line, col, message=message) diff --git a/tomlkit/items.py b/tomlkit/items.py index 0e36b88..c956407 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -23,12 +23,12 @@ from typing import cast from typing import overload -from ._compat import PY38 -from ._compat import decode -from ._utils import CONTROL_CHARS -from ._utils import escape_string -from .exceptions import InvalidStringError -from .toml_char import TOMLChar +from tomlkit._compat import PY38 +from tomlkit._compat import decode +from tomlkit._utils import CONTROL_CHARS +from tomlkit._utils import escape_string +from tomlkit.exceptions import InvalidStringError +from tomlkit.toml_char import TOMLChar if TYPE_CHECKING: # pragma: no cover @@ -43,11 +43,11 @@ # 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 + from builtins import dict as _CustomDict # noqa: N812, TC004 + from builtins import list as _CustomList # noqa: N812, TC004 # Allow type annotations but break circular imports - from . import container + from tomlkit import container else: from collections.abc import MutableMapping from collections.abc import MutableSequence @@ -140,7 +140,7 @@ def item( b = 2 """ - from .container import Container + from tomlkit.container import Container if isinstance(value, Item): return value @@ -223,6 +223,12 @@ def item( raise ValueError(f"Invalid type {type(value)}") +# This code is only valid for Python < 3.8, when @cached_property was introduced +# it replaces chained @property and @lru_cache decorators +def lazy_property(f): + return property(lru_cache(maxsize=None)(f)) + + class StringType(Enum): # Single Line Basic SLB = '"' @@ -233,6 +239,13 @@ class StringType(Enum): # Multi Line Literal MLL = "'''" + def __init__(self, value): + self.is_basic = lru_cache(maxsize=None)(self._is_basic) + self.is_literal = lru_cache(maxsize=None)(self._is_literal) + self.is_singleline = lru_cache(maxsize=None)(self._is_singleline) + self.is_multiline = lru_cache(maxsize=None)(self._is_multiline) + self.toggle = lru_cache(maxsize=None)(self._toggle) + @classmethod def select(cls, literal=False, multiline=False) -> "StringType": return { @@ -266,29 +279,23 @@ def invalid_sequences(self) -> Collection[str]: StringType.MLL: (forbidden_in_literal | {"'''"}) - allowed_in_multiline, }[self] - @property - @lru_cache(maxsize=None) + @lazy_property def unit(self) -> str: return self.value[0] - @lru_cache(maxsize=None) - def is_basic(self) -> bool: + def _is_basic(self) -> bool: return self in {StringType.SLB, StringType.MLB} - @lru_cache(maxsize=None) - def is_literal(self) -> bool: + def _is_literal(self) -> bool: return self in {StringType.SLL, StringType.MLL} - @lru_cache(maxsize=None) - def is_singleline(self) -> bool: + def _is_singleline(self) -> bool: return self in {StringType.SLB, StringType.SLL} - @lru_cache(maxsize=None) - def is_multiline(self) -> bool: + def _is_multiline(self) -> bool: return self in {StringType.MLB, StringType.MLL} - @lru_cache(maxsize=None) - def toggle(self) -> "StringType": + def _toggle(self) -> "StringType": return { StringType.SLB: StringType.MLB, StringType.MLB: StringType.SLB, @@ -301,7 +308,7 @@ class BoolType(Enum): TRUE = "true" FALSE = "false" - @lru_cache(maxsize=None) + @lru_cache(maxsize=None) # noqa: B019 def __bool__(self): return {BoolType.TRUE: True, BoolType.FALSE: False}[self] @@ -589,8 +596,8 @@ def discriminant(self) -> int: return 1 def as_string(self) -> str: - return "{}{}{}".format( - self._trivia.indent, decode(self._trivia.comment), self._trivia.trail + return ( + f"{self._trivia.indent}{decode(self._trivia.comment)}{self._trivia.trail}" ) def __str__(self) -> str: @@ -1125,7 +1132,7 @@ def multiline(self, multiline: bool) -> "Array": def as_string(self) -> str: if not self._multiline or not self._value: - return "[{}]".format("".join(v.as_string() for v in self._value)) + return f'[{"".join(v.as_string() for v in self._value)}]' s = "[\n" s += "".join( @@ -1629,13 +1636,14 @@ def as_string(self) -> str: continue - buf += "{}{}{}{}{}{}".format( - v.trivia.indent, - k.as_string() + ("." if k.is_dotted() else ""), - k.sep, - v.as_string(), - v.trivia.comment, - v.trivia.trail.replace("\n", ""), + v_trivia_trail = v.trivia.trail.replace("\n", "") + buf += ( + f"{v.trivia.indent}" + f'{k.as_string() + ("." if k.is_dotted() else "")}' + f"{k.sep}" + f"{v.as_string()}" + f"{v.trivia.comment}" + f"{v_trivia_trail}" ) if i != len(self._value.body) - 1: @@ -1848,5 +1856,5 @@ def value(self) -> None: def as_string(self) -> str: return "" - def _getstate(self, protocol=3): - return tuple() + def _getstate(self, protocol=3) -> tuple: + return () diff --git a/tomlkit/parser.py b/tomlkit/parser.py index 2f94daa..e390579 100644 --- a/tomlkit/parser.py +++ b/tomlkit/parser.py @@ -7,48 +7,48 @@ from typing import Type from typing import Union -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 -from .exceptions import InvalidControlChar -from .exceptions import InvalidDateError -from .exceptions import InvalidDateTimeError -from .exceptions import InvalidNumberError -from .exceptions import InvalidTimeError -from .exceptions import InvalidUnicodeValueError -from .exceptions import ParseError -from .exceptions import UnexpectedCharError -from .exceptions import UnexpectedEofError -from .items import AoT -from .items import Array -from .items import Bool -from .items import BoolType -from .items import Comment -from .items import Date -from .items import DateTime -from .items import Float -from .items import InlineTable -from .items import Integer -from .items import Item -from .items import Key -from .items import KeyType -from .items import Null -from .items import SingleKey -from .items import String -from .items import StringType -from .items import Table -from .items import Time -from .items import Trivia -from .items import Whitespace -from .source import Source -from .toml_char import TOMLChar -from .toml_document import TOMLDocument +from tomlkit._compat import decode +from tomlkit._utils import RFC_3339_LOOSE +from tomlkit._utils import _escaped +from tomlkit._utils import parse_rfc3339 +from tomlkit.container import Container +from tomlkit.exceptions import EmptyKeyError +from tomlkit.exceptions import EmptyTableNameError +from tomlkit.exceptions import InternalParserError +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 InvalidUnicodeValueError +from tomlkit.exceptions import ParseError +from tomlkit.exceptions import UnexpectedCharError +from tomlkit.exceptions import UnexpectedEofError +from tomlkit.items import AoT +from tomlkit.items import Array +from tomlkit.items import Bool +from tomlkit.items import BoolType +from tomlkit.items import Comment +from tomlkit.items import Date +from tomlkit.items import DateTime +from tomlkit.items import Float +from tomlkit.items import InlineTable +from tomlkit.items import Integer +from tomlkit.items import Item +from tomlkit.items import Key +from tomlkit.items import KeyType +from tomlkit.items import Null +from tomlkit.items import SingleKey +from tomlkit.items import String +from tomlkit.items import StringType +from tomlkit.items import Table +from tomlkit.items import Time +from tomlkit.items import Trivia +from tomlkit.items import Whitespace +from tomlkit.source import Source +from tomlkit.toml_char import TOMLChar +from tomlkit.toml_document import TOMLDocument CTRL_I = 0x09 # Tab @@ -808,9 +808,7 @@ def _parse_string(self, delim: StringType) -> String: delim.is_singleline() and not escaped and (code == CHR_DEL or code <= CTRL_CHAR_LIMIT and code != CTRL_I) - ): - raise self.parse_error(InvalidControlChar, code, "strings") - elif ( + ) or ( delim.is_multiline() and not escaped and ( @@ -922,7 +920,7 @@ def _parse_table( if parent_name: parent_name_parts = tuple(parent_name) else: - parent_name_parts = tuple() + parent_name_parts = () if len(name_parts) > len(parent_name_parts) + 1: missing_table = True @@ -966,10 +964,9 @@ def _parse_table( key = name_parts[0] for i, _name in enumerate(name_parts[1:]): - if _name in table: - child = table[_name] - else: - child = Table( + child = table.get( + _name, + Table( Container(True), Trivia(indent, cws, comment, trail), is_aot and i == len(name_parts) - 2, @@ -978,7 +975,8 @@ def _parse_table( display_name=full_key.as_string() if i == len(name_parts) - 2 else None, - ) + ), + ) if is_aot and i == len(name_parts) - 2: table.raw_append(_name, AoT([child], name=table.name, parsed=True)) diff --git a/tomlkit/source.py b/tomlkit/source.py index 6f82d94..d1a53cd 100644 --- a/tomlkit/source.py +++ b/tomlkit/source.py @@ -4,9 +4,9 @@ from typing import Tuple from typing import Type -from .exceptions import ParseError -from .exceptions import UnexpectedCharError -from .toml_char import TOMLChar +from tomlkit.exceptions import ParseError +from tomlkit.exceptions import UnexpectedCharError +from tomlkit.toml_char import TOMLChar class _State: @@ -129,11 +129,7 @@ def inc_n(self, n: int, exception: Optional[Type[ParseError]] = None) -> bool: Increments the parser by n characters if the end of the input has not been reached. """ - for _ in range(n): - if not self.inc(exception=exception): - return False - - return True + return all(self.inc(exception=exception) for _ in range(n)) def consume(self, chars, min=0, max=-1): """ diff --git a/tomlkit/toml_char.py b/tomlkit/toml_char.py index 487657b..b6260e9 100644 --- a/tomlkit/toml_char.py +++ b/tomlkit/toml_char.py @@ -5,6 +5,12 @@ class TOMLChar(str): def __init__(self, c): + self.is_bare_key_char = lru_cache(maxsize=None)(self._is_bare_key_char) + self.is_kv_sep = lru_cache(maxsize=None)(self._is_kv_sep) + self.is_int_float_char = lru_cache(maxsize=None)(self._is_int_float_char) + self.is_ws = lru_cache(maxsize=None)(self._is_ws) + self.is_nl = lru_cache(maxsize=None)(self._is_nl) + self.is_spaces = lru_cache(maxsize=None)(self._is_spaces) super().__init__() if len(self) > 1: @@ -17,43 +23,37 @@ def __init__(self, c): NL = "\n\r" WS = SPACES + NL - @lru_cache(maxsize=None) - def is_bare_key_char(self) -> 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) -> bool: + def _is_kv_sep(self) -> bool: """ Whether the character is a valid key/value separator or not. """ return self in self.KV - @lru_cache(maxsize=None) - def is_int_float_char(self) -> 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) -> 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) -> 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) -> bool: + def _is_spaces(self) -> bool: """ Whether the character is a space or not """ diff --git a/tomlkit/toml_document.py b/tomlkit/toml_document.py index b485e30..71fac2e 100644 --- a/tomlkit/toml_document.py +++ b/tomlkit/toml_document.py @@ -1,4 +1,4 @@ -from .container import Container +from tomlkit.container import Container class TOMLDocument(Container): diff --git a/tomlkit/toml_file.py b/tomlkit/toml_file.py index 5b28cb0..d05a62f 100644 --- a/tomlkit/toml_file.py +++ b/tomlkit/toml_file.py @@ -1,8 +1,8 @@ import os import re -from .api import loads -from .toml_document import TOMLDocument +from tomlkit.api import loads +from tomlkit.toml_document import TOMLDocument class TOMLFile: