From de25c04866e15f27ced066a0b27a0f6da91e4241 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 22 Mar 2026 13:16:43 +0000 Subject: [PATCH 1/6] round 1: clean, but lots of ignore --- pyproject.toml | 5 + tests/conftest.py | 13 +- tests/test_api.py | 95 +++---- tests/test_build.py | 17 +- tests/test_items.py | 342 +++++++++++------------ tests/test_parser.py | 8 +- tests/test_toml_document.py | 206 +++++++------- tests/test_toml_file.py | 109 ++++---- tests/test_toml_tests.py | 18 +- tests/test_utils.py | 7 +- tests/test_write.py | 19 +- tests/util.py | 6 +- tomlkit/_compat.py | 5 +- tomlkit/_types.py | 10 +- tomlkit/_utils.py | 7 +- tomlkit/api.py | 18 +- tomlkit/container.py | 202 ++++++++------ tomlkit/exceptions.py | 8 +- tomlkit/items.py | 521 ++++++++++++++++++++++++------------ tomlkit/parser.py | 69 +++-- tomlkit/source.py | 30 ++- tomlkit/toml_char.py | 2 +- tomlkit/toml_file.py | 2 +- 23 files changed, 1000 insertions(+), 719 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c30819e6..0fc50bc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,11 @@ force-single-line = true lines-after-imports = 2 lines-between-types = 1 +[tool.mypy] +strict = true +warn_return_any = true +warn_unused_configs = true + [build-system] requires = ["poetry-core>=1.0.0a9"] build-backend = "poetry.core.masonry.api" diff --git a/tests/conftest.py b/tests/conftest.py index ec7956c0..e665d68f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,12 @@ import os +from collections.abc import Callable import pytest @pytest.fixture -def example(): - def _example(name): +def example() -> Callable[[str], str]: + def _example(name: str) -> str: with open( os.path.join(os.path.dirname(__file__), "examples", name + ".toml"), encoding="utf-8", @@ -16,8 +17,8 @@ def _example(name): @pytest.fixture -def json_example(): - def _example(name): +def json_example() -> Callable[[str], str]: + def _example(name: str) -> str: with open( os.path.join(os.path.dirname(__file__), "examples", "json", name + ".json"), encoding="utf-8", @@ -28,8 +29,8 @@ def _example(name): @pytest.fixture -def invalid_example(): - def _example(name): +def invalid_example() -> Callable[[str], str]: + def _example(name: str) -> str: with open( os.path.join( os.path.dirname(__file__), "examples", "invalid", name + ".toml" diff --git a/tests/test_api.py b/tests/test_api.py index 3d6d8ae8..f4093971 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -6,6 +6,9 @@ from datetime import datetime from datetime import time from types import MappingProxyType +from typing import Any +from typing import Callable +from typing import Type import pytest @@ -38,7 +41,7 @@ from tomlkit.toml_document import TOMLDocument -def json_serial(obj): +def json_serial(obj: Any) -> str: """JSON serializer for objects not serializable by default json code""" if isinstance(obj, (datetime, date, time)): return obj.isoformat() @@ -62,7 +65,7 @@ def json_serial(obj): "table_names", ], ) -def test_parse_can_parse_valid_toml_files(example, example_name): +def test_parse_can_parse_valid_toml_files(example: Callable[[str], str], example_name: str) -> None: assert isinstance(parse(example(example_name)), TOMLDocument) assert isinstance(loads(example(example_name)), TOMLDocument) @@ -83,7 +86,7 @@ def test_parse_can_parse_valid_toml_files(example, example_name): "table_names", ], ) -def test_load_from_file_object(example_name): +def test_load_from_file_object(example_name: str) -> None: with open( os.path.join(os.path.dirname(__file__), "examples", example_name + ".toml"), encoding="utf-8", @@ -93,8 +96,8 @@ def test_load_from_file_object(example_name): @pytest.mark.parametrize("example_name", ["0.5.0", "pyproject", "table_names"]) def test_parsed_document_are_properly_json_representable( - example, json_example, example_name -): + example: Callable[[str], str], json_example: Callable[[str], str], example_name: str +) -> None: doc = json.loads(json.dumps(parse(example(example_name)), default=json_serial)) json_doc = json.loads(json_example(example_name)) @@ -123,8 +126,8 @@ def test_parsed_document_are_properly_json_representable( ], ) def test_parse_raises_errors_for_invalid_toml_files( - invalid_example, error, example_name -): + invalid_example: Callable[[str], str], error: Type[Exception], example_name: str +) -> None: with pytest.raises(error): parse(invalid_example(example_name)) @@ -142,69 +145,69 @@ def test_parse_raises_errors_for_invalid_toml_files( "table_names", ], ) -def test_original_string_and_dumped_string_are_equal(example, example_name): +def test_original_string_and_dumped_string_are_equal(example: Callable[[str], str], example_name: str) -> None: content = example(example_name) parsed = parse(content) assert content == dumps(parsed) -def test_a_raw_dict_can_be_dumped(): +def test_a_raw_dict_can_be_dumped() -> None: s = dumps({"foo": "bar"}) assert s == 'foo = "bar"\n' -def test_mapping_types_can_be_dumped(): +def test_mapping_types_can_be_dumped() -> None: x = MappingProxyType({"foo": "bar"}) assert dumps(x) == 'foo = "bar"\n' -def test_dumps_weird_object(): +def test_dumps_weird_object() -> None: with pytest.raises(TypeError): - dumps(object()) + dumps(object()) # type: ignore[arg-type] -def test_dump_tuple_value_as_array(): - x = {"foo": (1, 2)} +def test_dump_tuple_value_as_array() -> None: + x: dict[str, Any] = {"foo": (1, 2)} assert dumps(x) == "foo = [1, 2]\n" x = {"foo": ({"a": 1}, {"a": 2})} assert dumps(x) == "[[foo]]\na = 1\n\n[[foo]]\na = 2\n" -def test_dump_to_file_object(): +def test_dump_to_file_object() -> None: doc = {"foo": "bar"} fp = io.StringIO() dump(doc, fp) assert fp.getvalue() == 'foo = "bar"\n' -def test_dump_nested_dotted_table(): - a = tomlkit.parse("a.b.c.d='e'")["a"] +def test_dump_nested_dotted_table() -> None: + a: Any = tomlkit.parse("a.b.c.d='e'")["a"] assert a == {"b": {"c": {"d": "e"}}} assert dumps(a) == "b.c.d='e'" -def test_integer(): +def test_integer() -> None: i = tomlkit.integer("34") assert isinstance(i, Integer) -def test_float(): +def test_float() -> None: i = tomlkit.float_("34.56") assert isinstance(i, Float) -def test_boolean(): +def test_boolean() -> None: i = tomlkit.boolean("true") assert isinstance(i, Bool) -def test_date(): +def test_date() -> None: dt = tomlkit.date("1979-05-13") assert isinstance(dt, Date) @@ -213,7 +216,7 @@ def test_date(): tomlkit.date("12:34:56") -def test_time(): +def test_time() -> None: dt = tomlkit.time("12:34:56") assert isinstance(dt, Time) @@ -222,7 +225,7 @@ def test_time(): tomlkit.time("1979-05-13") -def test_datetime(): +def test_datetime() -> None: dt = tomlkit.datetime("1979-05-13T12:34:56") assert isinstance(dt, DateTime) @@ -231,7 +234,7 @@ def test_datetime(): tomlkit.time("1979-05-13") -def test_array(): +def test_array() -> None: a = tomlkit.array() assert isinstance(a, Array) @@ -241,45 +244,45 @@ def test_array(): assert isinstance(a, Array) -def test_table(): +def test_table() -> None: t = tomlkit.table() assert isinstance(t, Table) -def test_inline_table(): +def test_inline_table() -> None: t = tomlkit.inline_table() assert isinstance(t, InlineTable) -def test_aot(): +def test_aot() -> None: t = tomlkit.aot() assert isinstance(t, AoT) -def test_key(): +def test_key() -> None: k = tomlkit.key("foo") assert isinstance(k, Key) -def test_key_value(): +def test_key_value() -> None: k, i = tomlkit.key_value("foo = 12") assert isinstance(k, Key) assert isinstance(i, Integer) -def test_string(): +def test_string() -> None: s = tomlkit.string('foo "') assert s.value == 'foo "' assert s.as_string() == '"foo \\""' -def test_item_dict_to_table(): +def test_item_dict_to_table() -> None: t = tomlkit.item({"foo": {"bar": "baz"}}) assert t.value == {"foo": {"bar": "baz"}} @@ -291,7 +294,7 @@ def test_item_dict_to_table(): ) -def test_item_mixed_aray(): +def test_item_mixed_aray() -> None: example = [{"a": 3}, "b", 42] expected = '[{a = 3}, "b", 42]' t = tomlkit.item(example) @@ -299,7 +302,7 @@ def test_item_mixed_aray(): assert dumps({"x": {"y": example}}).strip() == "[x]\ny = " + expected -def test_build_super_table(): +def test_build_super_table() -> None: doc = tomlkit.document() table = tomlkit.table(True) table.add("bar", {"x": 1}) @@ -307,9 +310,9 @@ def test_build_super_table(): assert doc.as_string() == "[foo.bar]\nx = 1\n" -def test_add_dotted_key(): +def test_add_dotted_key() -> None: doc = tomlkit.document() - doc.add(tomlkit.key(["foo", "bar"]), 1) + doc.add(tomlkit.key(["foo", "bar"]), 1) # type: ignore[arg-type] assert doc.as_string() == "foo.bar = 1\n" table = tomlkit.table() @@ -324,15 +327,15 @@ def test_add_dotted_key(): ("false", False), ], ) -def test_value_parses_boolean(raw, expected): +def test_value_parses_boolean(raw: str, expected: bool) -> None: parsed = tomlkit.value(raw) - assert parsed == expected + assert parsed == expected # type: ignore[comparison-overlap] @pytest.mark.parametrize( "raw", ["t", "f", "tru", "fals", "test", "friend", "truthy", "falsify"] ) -def test_value_rejects_values_looking_like_bool_at_start(raw): +def test_value_rejects_values_looking_like_bool_at_start(raw: str) -> None: """Reproduces https://github.com/sdispater/tomlkit/issues/165""" with pytest.raises(tomlkit.exceptions.ParseError): tomlkit.value(raw) @@ -347,7 +350,7 @@ def test_value_rejects_values_looking_like_bool_at_start(raw): "true_hip_hop", ], ) -def test_value_rejects_values_having_true_prefix(raw): +def test_value_rejects_values_having_true_prefix(raw: str) -> None: """Values that have ``true`` or ``false`` as prefix but then have additional chars are rejected.""" with pytest.raises(tomlkit.exceptions.ParseError): tomlkit.value(raw) @@ -362,7 +365,7 @@ def test_value_rejects_values_having_true_prefix(raw): "false_prophet", ], ) -def test_value_rejects_values_having_false_prefix(raw): +def test_value_rejects_values_having_false_prefix(raw: str) -> None: """Values that have ``true`` or ``false`` as prefix but then have additional chars are rejected.""" with pytest.raises(tomlkit.exceptions.ParseError): tomlkit.value(raw) @@ -384,18 +387,18 @@ def test_value_rejects_values_having_false_prefix(raw): "false{a=1}", ], ) -def test_value_rejects_values_with_appendage(raw): +def test_value_rejects_values_with_appendage(raw: str) -> None: """Values that appear valid at the beginning but leave chars unparsed are rejected.""" with pytest.raises(tomlkit.exceptions.ParseError): tomlkit.value(raw) -def test_create_super_table_with_table(): +def test_create_super_table_with_table() -> None: data = {"foo": {"bar": {"a": 1}}} assert dumps(data) == "[foo.bar]\na = 1\n" -def test_create_super_table_with_aot(): +def test_create_super_table_with_aot() -> None: data = {"foo": {"bar": [{"a": 1}]}} assert dumps(data) == "[[foo.bar]]\na = 1\n" @@ -442,7 +445,7 @@ def test_create_super_table_with_aot(): ), ], ) -def test_create_string(kwargs, example, expected): +def test_create_string(kwargs: dict[str, Any], example: str, expected: str) -> None: value = tomlkit.string(example, **kwargs) assert value.as_string() == expected @@ -460,12 +463,12 @@ def test_create_string(kwargs, example, expected): ({"multiline": True, "literal": True}, "My'''String"), ], ) -def test_create_string_with_invalid_characters(kwargs, example): +def test_create_string_with_invalid_characters(kwargs: dict[str, Any], example: str) -> None: with pytest.raises(InvalidStringError): tomlkit.string(example, **kwargs) -def test_parse_empty_quoted_table_name(): +def test_parse_empty_quoted_table_name() -> None: content = "['']\nx = 1\n" parsed = loads(content) assert parsed == {"": {"x": 1}} diff --git a/tests/test_build.py b/tests/test_build.py index 136abb80..2ea35f89 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -1,4 +1,5 @@ import datetime +from collections.abc import Callable from tomlkit import aot from tomlkit import array @@ -11,13 +12,13 @@ from tomlkit._utils import _utc -def test_build_example(example): +def test_build_example(example: Callable[[str], str]) -> None: content = example("example") doc = document() doc.add(comment("This is a TOML document. Boom.")) doc.add(nl()) - doc.add("title", "TOML Example") + doc.add("title", "TOML Example") # type: ignore[arg-type] owner = table() owner.add("name", "Tom Preston-Werner") @@ -42,7 +43,7 @@ def test_build_example(example): "You can indent as you please. Tabs or spaces. TOML don't care." ).indent(2) c.trivia.trail = "" - servers.add(c) + servers.add(c) # type: ignore[call-overload] alpha = table() servers.append("alpha", alpha) alpha.indent(2) @@ -95,11 +96,11 @@ def test_build_example(example): assert content == doc.as_string() -def test_add_remove(): +def test_add_remove() -> None: content = "" doc = parse(content) - doc.append("foo", "bar") + doc.append("foo", "bar") # type: ignore[arg-type] assert ( doc.as_string() @@ -112,7 +113,7 @@ def test_add_remove(): assert doc.as_string() == "" -def test_append_table_after_multiple_indices(): +def test_append_table_after_multiple_indices() -> None: content = """ [packages] foo = "*" @@ -124,10 +125,10 @@ def test_append_table_after_multiple_indices(): version = "*" """ doc = parse(content) - doc.append("foobar", {"name": "John"}) + doc.append("foobar", {"name": "John"}) # type: ignore[arg-type] -def test_top_level_keys_are_put_at_the_root_of_the_document(): +def test_top_level_keys_are_put_at_the_root_of_the_document() -> None: doc = document() doc.add(comment("Comment")) doc["foo"] = {"name": "test"} diff --git a/tests/test_items.py b/tests/test_items.py index c4658988..d094b8d1 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -1,12 +1,14 @@ import copy import math import pickle - +from collections.abc import Callable from datetime import date from datetime import datetime from datetime import time from datetime import timedelta from datetime import timezone +from datetime import tzinfo +from typing import Any import pytest @@ -33,50 +35,50 @@ @pytest.fixture() -def tz_pst(): +def tz_pst() -> tzinfo: try: from datetime import timezone return timezone(timedelta(hours=-8), "PST") except ImportError: - from datetime import tzinfo + from datetime import tzinfo as _tzinfo - class PST(tzinfo): - def utcoffset(self, dt): + class PST(_tzinfo): + def utcoffset(self, dt: datetime | None) -> timedelta: return timedelta(hours=-8) - def tzname(self, dt): + def tzname(self, dt: datetime | None) -> str: return "PST" - def dst(self, dt): + def dst(self, dt: datetime | None) -> timedelta: return timedelta(0) return PST() @pytest.fixture() -def tz_utc(): +def tz_utc() -> tzinfo: try: from datetime import timezone return timezone.utc except ImportError: - from datetime import tzinfo + from datetime import tzinfo as _tzinfo - class UTC(tzinfo): - def utcoffset(self, dt): + class UTC(_tzinfo): + def utcoffset(self, dt: datetime | None) -> timedelta: return timedelta(hours=0) - def tzname(self, dt): + def tzname(self, dt: datetime | None) -> str: return "UTC" - def dst(self, dt): + def dst(self, dt: datetime | None) -> timedelta: return timedelta(0) return UTC() -def test_item_base_has_no_unwrap(): +def test_item_base_has_no_unwrap() -> None: trivia = Trivia(indent="\t", comment_ws=" ", comment="For unit test") item = Item(trivia) try: @@ -87,37 +89,37 @@ def test_item_base_has_no_unwrap(): raise AssertionError("`items.Item` should not implement `unwrap`") -def test_integer_unwrap(): +def test_integer_unwrap() -> None: elementary_test(item(666), int) -def test_float_unwrap(): +def test_float_unwrap() -> None: elementary_test(item(2.78), float) -def test_false_unwrap(): +def test_false_unwrap() -> None: elementary_test(item(False), bool) -def test_true_unwrap(): +def test_true_unwrap() -> None: elementary_test(item(True), bool) -def test_datetime_unwrap(): +def test_datetime_unwrap() -> None: dt = datetime.now(tz=timezone.utc) elementary_test(item(dt), datetime) -def test_string_unwrap(): +def test_string_unwrap() -> None: elementary_test(item("hello"), str) -def test_null_unwrap(): +def test_null_unwrap() -> None: n = Null() elementary_test(n, type(None)) -def test_aot_unwrap(): +def test_aot_unwrap() -> None: d = item([{"a": "A"}, {"b": "B"}]) unwrapped = d.unwrap() assert_is_ppo(unwrapped, list) @@ -129,30 +131,30 @@ def test_aot_unwrap(): assert_is_ppo(vu, str) -def test_aot_set_item(): +def test_aot_set_item() -> None: d = item(["A", {"b": "B"}, ["c", "D"]]) d[0] = "C" assert isinstance(d[0], String) assert d[0] == "C" d[1]["b"] = "D" assert isinstance(d[1], InlineTable) - assert d[1]["b"] == "D" + assert d[1]["b"] == "D" # type: ignore[comparison-overlap] d[0] = ["c", "C"] assert isinstance(d[0], Array) assert d[0][1] == "C" -def test_time_unwrap(): +def test_time_unwrap() -> None: t = time(3, 8, 14) elementary_test(item(t), time) -def test_date_unwrap(): +def test_date_unwrap() -> None: d = date.today() elementary_test(item(d), date) -def test_array_unwrap(): +def test_array_unwrap() -> None: trivia = Trivia(indent="\t", comment_ws=" ", comment="For unit test") i = item(666) f = item(2.78) @@ -165,7 +167,7 @@ def test_array_unwrap(): assert_is_ppo(a_unwrapped[2], bool) -def test_abstract_table_unwrap(): +def test_abstract_table_unwrap() -> None: table = item({"foo": "bar"}) super_table = item({"table": table, "baz": "borg"}) @@ -179,7 +181,7 @@ def test_abstract_table_unwrap(): assert_is_ppo(vu, str) -def test_key_comparison(): +def test_key_comparison() -> None: k = Key("foo") assert k == Key("foo") @@ -188,7 +190,7 @@ def test_key_comparison(): assert k != 5 -def test_items_can_be_appended_to_and_removed_from_a_table(): +def test_items_can_be_appended_to_and_removed_from_a_table() -> None: string = """[table] """ @@ -221,12 +223,12 @@ def test_items_can_be_appended_to_and_removed_from_a_table(): table.remove(Key("foo")) -def test_items_can_be_appended_to_and_removed_from_an_inline_table(): +def test_items_can_be_appended_to_and_removed_from_an_inline_table() -> None: string = """table = {} """ parser = Parser(string) - _, table = parser._parse_item() + _, table = parser._parse_item() # type: ignore[misc] assert isinstance(table, InlineTable) assert table.as_string() == "{}" @@ -251,34 +253,34 @@ def test_items_can_be_appended_to_and_removed_from_an_inline_table(): table.remove(Key("foo")) -def test_inf_and_nan_are_supported(example): +def test_inf_and_nan_are_supported(example: Callable[[str], str]) -> None: content = example("0.5.0") doc = parse(content) - assert doc["sf1"] == float("inf") - assert doc["sf2"] == float("inf") - assert doc["sf3"] == float("-inf") + assert doc["sf1"] == float("inf") # type: ignore[comparison-overlap] + assert doc["sf2"] == float("inf") # type: ignore[comparison-overlap] + assert doc["sf3"] == float("-inf") # type: ignore[comparison-overlap] - assert math.isnan(doc["sf4"]) - assert math.isnan(doc["sf5"]) - assert math.isnan(doc["sf6"]) + assert math.isnan(doc["sf4"]) # type: ignore[arg-type] + assert math.isnan(doc["sf5"]) # type: ignore[arg-type] + assert math.isnan(doc["sf6"]) # type: ignore[arg-type] -def test_hex_octal_and_bin_integers_are_supported(example): +def test_hex_octal_and_bin_integers_are_supported(example: Callable[[str], str]) -> None: content = example("0.5.0") doc = parse(content) - assert doc["hex1"] == 3735928559 - assert doc["hex2"] == 3735928559 - assert doc["hex3"] == 3735928559 + assert doc["hex1"] == 3735928559 # type: ignore[comparison-overlap] + assert doc["hex2"] == 3735928559 # type: ignore[comparison-overlap] + assert doc["hex3"] == 3735928559 # type: ignore[comparison-overlap] - assert doc["oct1"] == 342391 - assert doc["oct2"] == 493 + assert doc["oct1"] == 342391 # type: ignore[comparison-overlap] + assert doc["oct2"] == 493 # type: ignore[comparison-overlap] - assert doc["bin1"] == 214 + assert doc["bin1"] == 214 # type: ignore[comparison-overlap] -def test_key_automatically_sets_proper_string_type_if_not_bare(): +def test_key_automatically_sets_proper_string_type_if_not_bare() -> None: key = Key("foo.bar") assert key.t == KeyType.Basic @@ -287,7 +289,7 @@ def test_key_automatically_sets_proper_string_type_if_not_bare(): assert key.t == KeyType.Basic -def test_array_behaves_like_a_list(): +def test_array_behaves_like_a_list() -> None: a = item([1, 2]) assert a == [1, 2] @@ -323,9 +325,9 @@ def test_array_behaves_like_a_list(): doc = parse(content) assert str(doc["a"]) == "[1, 2]" - assert doc["a"] == [1, 2] - doc["a"] += [3, 4] - assert doc["a"] == [1, 2, 3, 4] + assert doc["a"] == [1, 2] # type: ignore[comparison-overlap] + doc["a"] += [3, 4] # type: ignore[operator] + assert doc["a"] == [1, 2, 3, 4] # type: ignore[comparison-overlap] assert ( doc.as_string() == """a = [1, 2, 3, 4] # Comment @@ -333,7 +335,7 @@ def test_array_behaves_like_a_list(): ) -def test_array_multiline(): +def test_array_multiline() -> None: t = item([1, 2, 3, 4, 5, 6, 7, 8]) t.multiline(True) @@ -351,27 +353,27 @@ def test_array_multiline(): assert expected == t.as_string() - t = item([]) + t = item([]) # type: ignore[assignment] t.multiline(True) assert t.as_string() == "[]" -def test_array_multiline_modify(): +def test_array_multiline_modify() -> None: doc = parse( """\ a = [ "abc" ]""" ) - doc["a"].append("def") + doc["a"].append("def") # type: ignore[attr-defined] expected = """\ a = [ "abc", "def" ]""" assert expected == doc.as_string() - doc["a"].insert(1, "ghi") + doc["a"].insert(1, "ghi") # type: ignore[attr-defined] expected = """\ a = [ "abc", @@ -381,18 +383,18 @@ def test_array_multiline_modify(): assert expected == doc.as_string() -def test_append_to_empty_array(): +def test_append_to_empty_array() -> None: doc = parse("x = [ ]") - doc["x"].append("a") + doc["x"].append("a") # type: ignore[attr-defined] assert doc.as_string() == 'x = ["a" ]' doc = parse("x = [\n]") - doc["x"].append("a") + doc["x"].append("a") # type: ignore[attr-defined] assert doc.as_string() == 'x = [\n "a"\n]' -def test_modify_array_with_comment(): +def test_modify_array_with_comment() -> None: doc = parse("x = [ # comment\n]") - doc["x"].append("a") + doc["x"].append("a") # type: ignore[attr-defined] assert doc.as_string() == 'x = [ # comment\n "a"\n]' doc = parse( """\ @@ -402,7 +404,7 @@ def test_modify_array_with_comment(): "b" ]""" ) - doc["x"].insert(1, "c") + doc["x"].insert(1, "c") # type: ignore[attr-defined] expected = """\ x = [ "a", @@ -417,7 +419,7 @@ def test_modify_array_with_comment(): 1 # comment ]""" ) - doc["x"].append(2) + doc["x"].append(2) # type: ignore[attr-defined] assert ( doc.as_string() == """\ @@ -426,11 +428,11 @@ def test_modify_array_with_comment(): 2 ]""" ) - doc["x"].pop(0) + doc["x"].pop(0) # type: ignore[attr-defined] assert doc.as_string() == "x = [\n 2\n]" -def test_append_to_multiline_array_with_comment(): +def test_append_to_multiline_array_with_comment() -> None: doc = parse( """\ x = [ @@ -440,7 +442,7 @@ def test_append_to_multiline_array_with_comment(): ] """ ) - doc["x"].multiline(True).append(3) + doc["x"].multiline(True).append(3) # type: ignore[attr-defined] assert ( doc.as_string() == """\ @@ -452,7 +454,7 @@ def test_append_to_multiline_array_with_comment(): ] """ ) - assert doc["x"].pop() == 3 + assert doc["x"].pop() == 3 # type: ignore[attr-defined] assert ( doc.as_string() == """\ @@ -465,16 +467,16 @@ def test_append_to_multiline_array_with_comment(): ) -def test_append_dict_to_array(): +def test_append_dict_to_array() -> None: doc = parse("x = []") - doc["x"].append({"name": "John Doe", "email": "john@doe.com"}) + doc["x"].append({"name": "John Doe", "email": "john@doe.com"}) # type: ignore[attr-defined] 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(): +def test_dicts_are_converted_to_tables() -> None: t = item({"foo": {"bar": "baz"}}) assert ( @@ -485,7 +487,7 @@ def test_dicts_are_converted_to_tables(): ) -def test_array_add_line(): +def test_array_add_line() -> None: t = api.array() t.add_line(1, 2, 3, comment="Line 1") t.add_line(4, 5, 6, comment="Line 2") @@ -505,7 +507,7 @@ def test_array_add_line(): ) -def test_array_add_line_invalid_value(): +def test_array_add_line_invalid_value() -> None: t = api.array() with pytest.raises(ValueError, match="is not allowed"): t.add_line(1, api.ws(" ")) @@ -514,7 +516,7 @@ def test_array_add_line_invalid_value(): assert len(t) == 0 -def test_dicts_are_converted_to_tables_and_keep_order(): +def test_dicts_are_converted_to_tables_and_keep_order() -> None: t = item( { "foo": { @@ -539,7 +541,7 @@ def test_dicts_are_converted_to_tables_and_keep_order(): ) -def test_dicts_are_converted_to_tables_and_are_sorted_if_requested(): +def test_dicts_are_converted_to_tables_and_are_sorted_if_requested() -> None: t = item( { "foo": { @@ -565,7 +567,7 @@ def test_dicts_are_converted_to_tables_and_are_sorted_if_requested(): ) -def test_dicts_with_sub_dicts_are_properly_converted(): +def test_dicts_with_sub_dicts_are_properly_converted() -> None: t = item( {"foo": {"bar": {"string": "baz"}, "int": 34, "float": 3.14}}, _sort_keys=True ) @@ -582,7 +584,7 @@ def test_dicts_with_sub_dicts_are_properly_converted(): ) -def test_item_array_of_dicts_converted_to_aot(): +def test_item_array_of_dicts_converted_to_aot() -> None: a = item({"foo": [{"bar": "baz"}]}) assert ( @@ -593,37 +595,37 @@ def test_item_array_of_dicts_converted_to_aot(): ) -def test_add_float_to_int(): +def test_add_float_to_int() -> None: content = "[table]\nmy_int = 2043" doc = parse(content) - doc["table"]["my_int"] += 5.0 - assert doc["table"]["my_int"] == 2048.0 + doc["table"]["my_int"] += 5.0 # type: ignore[operator] + assert doc["table"]["my_int"] == 2048.0 # type: ignore[comparison-overlap] assert isinstance(doc["table"]["my_int"], float) -def test_sub_float_from_int(): +def test_sub_float_from_int() -> None: content = "[table]\nmy_int = 2048" doc = parse(content) - doc["table"]["my_int"] -= 5.0 - assert doc["table"]["my_int"] == 2043.0 + doc["table"]["my_int"] -= 5.0 # type: ignore[operator] + assert doc["table"]["my_int"] == 2043.0 # type: ignore[comparison-overlap] assert isinstance(doc["table"]["my_int"], float) -def test_sub_int_from_float(): +def test_sub_int_from_float() -> None: content = "[table]\nmy_int = 2048.0" doc = parse(content) - doc["table"]["my_int"] -= 5 - assert doc["table"]["my_int"] == 2043.0 + doc["table"]["my_int"] -= 5 # type: ignore[operator] + assert doc["table"]["my_int"] == 2043.0 # type: ignore[comparison-overlap] -def test_add_sum_int_with_float(): +def test_add_sum_int_with_float() -> None: content = "[table]\nmy_int = 2048.3" doc = parse(content) - doc["table"]["my_int"] += 5 - assert doc["table"]["my_int"] == 2053.3 + doc["table"]["my_int"] += 5 # type: ignore[operator] + assert doc["table"]["my_int"] == 2053.3 # type: ignore[comparison-overlap] -def test_integers_behave_like_ints(): +def test_integers_behave_like_ints() -> None: i = item(34) assert i == 34 @@ -633,21 +635,21 @@ def test_integers_behave_like_ints(): assert i == 35 assert i.as_string() == "35" - i -= 2 + i -= 2 # type: ignore[assignment] assert i == 33 assert i.as_string() == "33" - i /= 2 + i /= 2 # type: ignore[assignment] assert i == 16.5 assert i.as_string() == "16.5" doc = parse("int = +34") - doc["int"] += 1 + doc["int"] += 1 # type: ignore[operator] assert doc.as_string() == "int = +35" -def test_floats_behave_like_floats(): +def test_floats_behave_like_floats() -> None: i = item(34.12) assert i == 34.12 @@ -657,17 +659,17 @@ def test_floats_behave_like_floats(): assert i == 35.12 assert i.as_string() == "35.12" - i -= 2 + i -= 2 # type: ignore[assignment] assert i == 33.12 assert i.as_string() == "33.12" doc = parse("float = +34.12") - doc["float"] += 1 + doc["float"] += 1 # type: ignore[operator] assert doc.as_string() == "float = +35.12" -def test_datetimes_behave_like_datetimes(tz_utc, tz_pst): +def test_datetimes_behave_like_datetimes(tz_utc: tzinfo, tz_pst: tzinfo) -> None: i = item(datetime(2018, 7, 22, 12, 34, 56)) assert i == datetime(2018, 7, 22, 12, 34, 56) @@ -677,25 +679,25 @@ def test_datetimes_behave_like_datetimes(tz_utc, tz_pst): assert i == datetime(2018, 7, 23, 12, 34, 56) assert i.as_string() == "2018-07-23T12:34:56" - i -= timedelta(days=2) + i -= timedelta(days=2) # type: ignore[assignment] assert i == datetime(2018, 7, 21, 12, 34, 56) assert i.as_string() == "2018-07-21T12:34:56" - i = i.replace(year=2019, tzinfo=tz_utc) + i = i.replace(year=2019, tzinfo=tz_utc) # type: ignore[assignment] assert i == datetime(2019, 7, 21, 12, 34, 56, tzinfo=tz_utc) assert i.as_string() == "2019-07-21T12:34:56+00:00" - i = i.astimezone(tz_pst) + i = i.astimezone(tz_pst) # type: ignore[assignment] assert i == datetime(2019, 7, 21, 4, 34, 56, tzinfo=tz_pst) assert i.as_string() == "2019-07-21T04:34:56-08:00" doc = parse("dt = 2018-07-22T12:34:56-05:00") - doc["dt"] += timedelta(days=1) + doc["dt"] += timedelta(days=1) # type: ignore[operator] assert doc.as_string() == "dt = 2018-07-23T12:34:56-05:00" -def test_dates_behave_like_dates(): +def test_dates_behave_like_dates() -> None: i = item(date(2018, 7, 22)) assert i == date(2018, 7, 22) @@ -705,21 +707,21 @@ def test_dates_behave_like_dates(): assert i == date(2018, 7, 23) assert i.as_string() == "2018-07-23" - i -= timedelta(days=2) + i -= timedelta(days=2) # type: ignore[assignment] assert i == date(2018, 7, 21) assert i.as_string() == "2018-07-21" - i = i.replace(year=2019) + i = i.replace(year=2019) # type: ignore[assignment] assert i == date(2019, 7, 21) assert i.as_string() == "2019-07-21" doc = parse("dt = 2018-07-22 # Comment") - doc["dt"] += timedelta(days=1) + doc["dt"] += timedelta(days=1) # type: ignore[operator] assert doc.as_string() == "dt = 2018-07-23 # Comment" -def test_parse_datetime_followed_by_space(): +def test_parse_datetime_followed_by_space() -> None: # issue #260 doc = parse("dt = 2018-07-22 ") assert doc["dt"] == date(2018, 7, 22) @@ -730,18 +732,18 @@ def test_parse_datetime_followed_by_space(): assert doc.as_string() == "dt = 2013-01-24 13:48:01.123456 " -def test_times_behave_like_times(): +def test_times_behave_like_times() -> None: i = item(time(12, 34, 56)) assert i == time(12, 34, 56) assert i.as_string() == "12:34:56" - i = i.replace(hour=13) + i = i.replace(hour=13) # type: ignore[assignment] assert i == time(13, 34, 56) assert i.as_string() == "13:34:56" -def test_strings_behave_like_strs(): +def test_strings_behave_like_strs() -> None: i = item("foo") assert i == "foo" @@ -756,20 +758,20 @@ def test_strings_behave_like_strs(): assert i.as_string() == '"foo bar é"' doc = parse('str = "foo" # Comment') - doc["str"] += " bar" + doc["str"] += " bar" # type: ignore[operator] assert doc.as_string() == 'str = "foo bar" # Comment' -def test_string_add_preserve_escapes(): +def test_string_add_preserve_escapes() -> None: i = api.value('"foo\\"bar"') - i += " baz" + i += " baz" # type: ignore[operator] assert i == 'foo"bar baz' assert i.as_string() == '"foo\\"bar baz"' -def test_tables_behave_like_dicts(): +def test_tables_behave_like_dicts() -> None: t = item({"foo": "bar"}) assert ( @@ -797,7 +799,7 @@ def test_tables_behave_like_dicts(): ) assert t.get("bar") == "boom" - assert t.setdefault("foobar", "fuzz") == "fuzz" + assert t.setdefault("foobar", "fuzz") == "fuzz" # type: ignore[comparison-overlap] assert ( t.as_string() == """foo = "bar" @@ -807,65 +809,65 @@ def test_tables_behave_like_dicts(): ) -def test_items_are_pickable(): +def test_items_are_pickable() -> None: n = item(12) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "12" - n = item(12.34) + n = item(12.34) # type: ignore[assignment] s = pickle.dumps(n) assert pickle.loads(s).as_string() == "12.34" - n = item(True) + n = item(True) # type: ignore[assignment] s = pickle.dumps(n) assert pickle.loads(s).as_string() == "true" - n = item(datetime(2018, 10, 11, 12, 34, 56, 123456)) + n = item(datetime(2018, 10, 11, 12, 34, 56, 123456)) # type: ignore[assignment] s = pickle.dumps(n) assert pickle.loads(s).as_string() == "2018-10-11T12:34:56.123456" - n = item(date(2018, 10, 11)) + n = item(date(2018, 10, 11)) # type: ignore[assignment] s = pickle.dumps(n) assert pickle.loads(s).as_string() == "2018-10-11" - n = item(time(12, 34, 56, 123456)) + n = item(time(12, 34, 56, 123456)) # type: ignore[assignment] s = pickle.dumps(n) assert pickle.loads(s).as_string() == "12:34:56.123456" - n = item([1, 2, 3]) + n = item([1, 2, 3]) # type: ignore[assignment] s = pickle.dumps(n) assert pickle.loads(s).as_string() == "[1, 2, 3]" - n = item({"foo": "bar"}) + n = item({"foo": "bar"}) # type: ignore[assignment] s = pickle.dumps(n) assert pickle.loads(s).as_string() == 'foo = "bar"\n' - n = api.inline_table() + n = api.inline_table() # type: ignore[assignment] n["foo"] = "bar" s = pickle.dumps(n) assert pickle.loads(s).as_string() == '{foo = "bar"}' - n = item("foo") + n = item("foo") # type: ignore[assignment] s = pickle.dumps(n) assert pickle.loads(s).as_string() == '"foo"' - n = item([{"foo": "bar"}]) + n = item([{"foo": "bar"}]) # type: ignore[assignment] s = pickle.dumps(n) assert pickle.loads(s).as_string() == 'foo = "bar"\n' -def test_trim_comments_when_building_inline_table(): +def test_trim_comments_when_building_inline_table() -> None: table = api.inline_table() row = parse('foo = "bar" # Comment') table.update(row) @@ -877,7 +879,7 @@ def test_trim_comments_when_building_inline_table(): assert table.as_string() == '{foo = "bar", baz = "foobaz"}' -def test_deleting_inline_table_element_does_not_leave_trailing_separator(): +def test_deleting_inline_table_element_does_not_leave_trailing_separator() -> None: table = api.inline_table() table["foo"] = "bar" table["baz"] = "boom" @@ -898,7 +900,7 @@ def test_deleting_inline_table_element_does_not_leave_trailing_separator(): assert table.as_string() == '{baz = "boom"}' -def test_deleting_inline_table_element_does_not_leave_trailing_separator2(): +def test_deleting_inline_table_element_does_not_leave_trailing_separator2() -> None: doc = parse('a = {foo = "bar", baz = "boom"}') table = doc["a"] assert table.as_string() == '{foo = "bar", baz = "boom"}' @@ -914,7 +916,7 @@ def test_deleting_inline_table_element_does_not_leave_trailing_separator2(): assert table.as_string() == '{ baz = "boom"}' -def test_booleans_comparison(): +def test_booleans_comparison() -> None: boolean = Bool(True, Trivia()) assert boolean @@ -928,14 +930,14 @@ def test_booleans_comparison(): """ content = parse(s) - assert content["foo"]["value"] is False + assert content["foo"]["value"] is False # type: ignore[comparison-overlap] assert isinstance(content["foo"].item("value"), Bool) assert {"foo": {"value": False}} == content assert {"value": False} == content["foo"] -def test_table_copy(): +def test_table_copy() -> None: table = item({"foo": "bar"}) table_copy = table.copy() assert isinstance(table_copy, Table) @@ -944,7 +946,7 @@ def test_table_copy(): assert table_copy.as_string() == 'foo = "bar"\n' -def test_copy_copy(): +def test_copy_copy() -> None: result = parse( """ [tool.poetry] @@ -964,29 +966,29 @@ def test_copy_copy(): "key_str,escaped", [("\\", '"\\\\"'), ('"', '"\\""'), ("\t", '"\\t"'), ("\x10", '"\\u0010"')], ) -def test_escape_key(key_str, escaped): +def test_escape_key(key_str: str, escaped: str) -> None: assert api.key(key_str).as_string() == escaped -def test_custom_encoders(): +def test_custom_encoders() -> None: import decimal - @api.register_encoder - def encode_decimal(obj): + @api.register_encoder # type: ignore[type-var] + def encode_decimal(obj: Any) -> Item: if isinstance(obj, decimal.Decimal): return api.float_(str(obj)) raise TypeError - assert api.item(decimal.Decimal("1.23")).as_string() == "1.23" + assert api.item(decimal.Decimal("1.23")).as_string() == "1.23" # type: ignore[call-overload] with pytest.raises(TypeError): - api.item(object()) + api.item(object()) # type: ignore[call-overload] assert api.dumps({"foo": decimal.Decimal("1.23")}) == "foo = 1.23\n" - api.unregister_encoder(encode_decimal) + api.unregister_encoder(encode_decimal) # type: ignore[arg-type] -def test_custom_encoders_with_parent_and_sort_keys(): +def test_custom_encoders_with_parent_and_sort_keys() -> None: """Test that custom encoders can receive _parent and _sort_keys parameters.""" import decimal @@ -994,7 +996,7 @@ def test_custom_encoders_with_parent_and_sort_keys(): sort_keys_captured = None @api.register_encoder - def encode_decimal_with_context(obj, _parent=None, _sort_keys=False): + def encode_decimal_with_context(obj: Any, _parent: Item | None = None, _sort_keys: bool = False) -> Item: nonlocal parent_captured, sort_keys_captured if isinstance(obj, decimal.Decimal): parent_captured = _parent @@ -1003,7 +1005,7 @@ def encode_decimal_with_context(obj, _parent=None, _sort_keys=False): raise TypeError # Test with default parameters - result = api.item(decimal.Decimal("1.23")) + result = api.item(decimal.Decimal("1.23")) # type: ignore[call-overload] assert result.as_string() == "1.23" assert parent_captured is None assert sort_keys_captured is False @@ -1012,7 +1014,7 @@ def encode_decimal_with_context(obj, _parent=None, _sort_keys=False): parent_captured = None sort_keys_captured = None table = api.table() - result = item(decimal.Decimal("4.56"), _parent=table, _sort_keys=True) + result = item(decimal.Decimal("4.56"), _parent=table, _sort_keys=True) # type: ignore[call-overload] assert result.as_string() == "4.56" assert parent_captured is table assert sort_keys_captured is True @@ -1020,37 +1022,37 @@ def encode_decimal_with_context(obj, _parent=None, _sort_keys=False): api.unregister_encoder(encode_decimal_with_context) -def test_custom_encoders_backward_compatibility(): +def test_custom_encoders_backward_compatibility() -> None: """Test that old-style custom encoders still work without modification.""" import decimal - @api.register_encoder - def encode_decimal_old_style(obj): + @api.register_encoder # type: ignore[type-var] + def encode_decimal_old_style(obj: Any) -> Item: # Old style encoder - only accepts obj parameter if isinstance(obj, decimal.Decimal): return api.float_(str(obj)) raise TypeError # Should work exactly as before - result = api.item(decimal.Decimal("2.34")) + result = api.item(decimal.Decimal("2.34")) # type: ignore[call-overload] assert result.as_string() == "2.34" # Should work when called from item() with extra parameters table = api.table() - result = item(decimal.Decimal("5.67"), _parent=table, _sort_keys=True) + result = item(decimal.Decimal("5.67"), _parent=table, _sort_keys=True) # type: ignore[call-overload] assert result.as_string() == "5.67" - api.unregister_encoder(encode_decimal_old_style) + api.unregister_encoder(encode_decimal_old_style) # type: ignore[arg-type] -def test_custom_encoders_with_kwargs(): +def test_custom_encoders_with_kwargs() -> None: """Test that custom encoders can use **kwargs to accept additional parameters.""" import decimal kwargs_captured = None - @api.register_encoder - def encode_decimal_with_kwargs(obj, **kwargs): + @api.register_encoder # type: ignore[type-var] + def encode_decimal_with_kwargs(obj: Any, **kwargs: Any) -> Item: nonlocal kwargs_captured if isinstance(obj, decimal.Decimal): kwargs_captured = kwargs @@ -1059,22 +1061,22 @@ def encode_decimal_with_kwargs(obj, **kwargs): # Test with parent and sort_keys passed as kwargs table = api.table() - result = item(decimal.Decimal("7.89"), _parent=table, _sort_keys=True) + result = item(decimal.Decimal("7.89"), _parent=table, _sort_keys=True) # type: ignore[call-overload] assert result.as_string() == "7.89" assert kwargs_captured == {"_parent": table, "_sort_keys": True} - api.unregister_encoder(encode_decimal_with_kwargs) + api.unregister_encoder(encode_decimal_with_kwargs) # type: ignore[arg-type] -def test_custom_encoders_for_complex_objects(): +def test_custom_encoders_for_complex_objects() -> None: """Test custom encoders that need to encode nested structures.""" class CustomDict: - def __init__(self, data): + def __init__(self, data: dict[str, Any]) -> None: self.data = data @api.register_encoder - def encode_custom_dict(obj, _parent=None, _sort_keys=False): + def encode_custom_dict(obj: Any, _parent: Item | None = None, _sort_keys: bool = False) -> Item: if isinstance(obj, CustomDict): # Create a table and use item() to convert nested values table = api.table() @@ -1086,7 +1088,7 @@ def encode_custom_dict(obj, _parent=None, _sort_keys=False): # Test with nested structure custom_obj = CustomDict({"a": 1, "b": {"c": 2, "d": 3}}) - result = item(custom_obj, _sort_keys=True) + result = item(custom_obj, _sort_keys=True) # type: ignore[call-overload] # Should properly format as a table with sorted keys expected = """a = 1 @@ -1100,23 +1102,23 @@ def encode_custom_dict(obj, _parent=None, _sort_keys=False): api.unregister_encoder(encode_custom_dict) -def test_no_extra_minus_sign(): +def test_no_extra_minus_sign() -> None: doc = parse("a = -1") assert doc.as_string() == "a = -1" - doc["a"] *= -1 + doc["a"] *= -1 # type: ignore[operator] assert doc.as_string() == "a = +1" - doc["a"] *= -1 + doc["a"] *= -1 # type: ignore[operator] assert doc.as_string() == "a = -1" doc = parse("a = -1.5") assert doc.as_string() == "a = -1.5" - doc["a"] *= -1 + doc["a"] *= -1 # type: ignore[operator] assert doc.as_string() == "a = +1.5" - doc["a"] *= -1 + doc["a"] *= -1 # type: ignore[operator] assert doc.as_string() == "a = -1.5" -def test_serialize_table_with_dotted_key(): +def test_serialize_table_with_dotted_key() -> None: child = api.table() child.add(api.key(("b", "c")), 1) parent = api.table() @@ -1124,10 +1126,10 @@ def test_serialize_table_with_dotted_key(): assert parent.as_string() == "[a]\nb.c = 1\n" -def test_not_showing_parent_header_for_super_table(): +def test_not_showing_parent_header_for_super_table() -> None: doc = api.document() - def add_table(parent, name): + def add_table(parent: Any, name: str) -> Any: parent.add(name, api.table()) return parent[name] @@ -1137,7 +1139,7 @@ def add_table(parent, name): assert doc.as_string() == "[root.first]\n\n[root.second]\n" -def test_removal_of_arrayitem_with_extra_whitespace(): +def test_removal_of_arrayitem_with_extra_whitespace() -> None: expected = 'x = [\n "bar",\n]' doc = parse('x = [\n "foo" ,#spam\n "bar",\n]') x = doc["x"] @@ -1148,7 +1150,7 @@ def test_removal_of_arrayitem_with_extra_whitespace(): assert docstr == expected -def test_badly_formatted_array_and_item_removal(): +def test_badly_formatted_array_and_item_removal() -> None: expected = """ x = [ '0'#a @@ -1185,17 +1187,17 @@ def test_badly_formatted_array_and_item_removal(): parse(doc.as_string()) -def test_array_item_removal_newline_restore_next(): +def test_array_item_removal_newline_restore_next() -> None: expected = "x = [\n '0',\n '2'\n]" docstr = "x = [\n '0',\n '1','2'\n]" doc = parse(docstr) - doc["x"].remove("1") + doc["x"].remove("1") # type: ignore[attr-defined] assert doc.as_string() == expected parse(doc.as_string()) docstr = "x = [\n '0',\n '1', '2'\n]" doc = parse(docstr) - doc["x"].remove("1") + doc["x"].remove("1") # type: ignore[attr-defined] assert doc.as_string() == expected parse(doc.as_string()) diff --git a/tests/test_parser.py b/tests/test_parser.py index 9ec5aa70..daa529e0 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -7,7 +7,7 @@ from tomlkit.parser import Parser -def test_parser_should_raise_an_internal_error_if_parsing_wrong_type_of_string(): +def test_parser_should_raise_an_internal_error_if_parsing_wrong_type_of_string() -> None: parser = Parser('"foo"') with pytest.raises(InternalParserError) as e: @@ -17,7 +17,7 @@ def test_parser_should_raise_an_internal_error_if_parsing_wrong_type_of_string() assert e.value.col == 0 -def test_parser_should_raise_an_error_for_empty_tables(): +def test_parser_should_raise_an_error_for_empty_tables() -> None: content = """ [one] [] @@ -32,7 +32,7 @@ def test_parser_should_raise_an_error_for_empty_tables(): assert e.value.col == 1 -def test_parser_should_raise_an_error_if_equal_not_found(): +def test_parser_should_raise_an_error_if_equal_not_found() -> None: content = """[foo] a {c = 1, d = 2} """ @@ -41,7 +41,7 @@ def test_parser_should_raise_an_error_if_equal_not_found(): parser.parse() -def test_parse_multiline_string_ignore_the_first_newline(): +def test_parse_multiline_string_ignore_the_first_newline() -> None: content = 'a = """\nfoo\n"""' parser = Parser(content) assert parser.parse() == {"a": "foo\n"} diff --git a/tests/test_toml_document.py b/tests/test_toml_document.py index 4dea5527..c9c507cf 100644 --- a/tests/test_toml_document.py +++ b/tests/test_toml_document.py @@ -2,8 +2,10 @@ import json import pickle +from collections.abc import Callable from datetime import datetime from textwrap import dedent +from typing import Any import pytest @@ -18,7 +20,7 @@ from tomlkit.toml_document import TOMLDocument -def test_document_is_a_dict(example): +def test_document_is_a_dict(example: Callable[[str], str]) -> None: content = example("example") doc = parse(content) @@ -31,18 +33,18 @@ def test_document_is_a_dict(example): assert doc.get("owner") == owner assert isinstance(owner, dict) assert "name" in owner - assert owner["name"] == "Tom Preston-Werner" - assert owner["organization"] == "GitHub" - assert owner["bio"] == "GitHub Cofounder & CEO\nLikes tater tots and beer." + assert owner["name"] == "Tom Preston-Werner" # type: ignore[comparison-overlap] + assert owner["organization"] == "GitHub" # type: ignore[comparison-overlap] + assert owner["bio"] == "GitHub Cofounder & CEO\nLikes tater tots and beer." # type: ignore[comparison-overlap] assert owner["dob"] == datetime(1979, 5, 27, 7, 32, tzinfo=_utc) # database database = doc["database"] assert isinstance(database, dict) - assert database["server"] == "192.168.1.1" - assert database["ports"] == [8001, 8001, 8002] - assert database["connection_max"] == 5000 - assert database["enabled"] is True + assert database["server"] == "192.168.1.1" # type: ignore[comparison-overlap] + assert database["ports"] == [8001, 8001, 8002] # type: ignore[comparison-overlap] + assert database["connection_max"] == 5000 # type: ignore[comparison-overlap] + assert database["enabled"] is True # type: ignore[comparison-overlap] # servers servers = doc["servers"] @@ -130,16 +132,16 @@ def test_document_is_a_dict(example): ) -def test_toml_document_without_super_tables(): +def test_toml_document_without_super_tables() -> None: content = """[tool.poetry] name = "foo" """ doc = parse(content) assert "tool" in doc - assert "poetry" in doc["tool"] + assert "poetry" in doc["tool"] # type: ignore[operator] - assert doc["tool"]["poetry"]["name"] == "foo" + assert doc["tool"]["poetry"]["name"] == "foo" # type: ignore[comparison-overlap] doc["tool"]["poetry"]["name"] = "bar" @@ -150,13 +152,13 @@ def test_toml_document_without_super_tables(): """ ) - d = {} + d: dict[str, Any] = {} d.update(doc) assert "tool" in d -def test_toml_document_unwrap(): +def test_toml_document_unwrap() -> None: content = """[tool.poetry] name = "foo" """ @@ -170,36 +172,36 @@ def test_toml_document_unwrap(): assert_is_ppo(unwrapped["tool"]["poetry"]["name"], str) -def test_toml_document_with_dotted_keys(example): +def test_toml_document_with_dotted_keys(example: Callable[[str], str]) -> None: content = example("0.5.0") doc = parse(content) assert "physical" in doc - assert "color" in doc["physical"] - assert "shape" in doc["physical"] - assert doc["physical"]["color"] == "orange" - assert doc["physical"]["shape"] == "round" + assert "color" in doc["physical"] # type: ignore[operator] + assert "shape" in doc["physical"] # type: ignore[operator] + assert doc["physical"]["color"] == "orange" # type: ignore[comparison-overlap] + assert doc["physical"]["shape"] == "round" # type: ignore[comparison-overlap] assert "site" in doc - assert "google.com" in doc["site"] + assert "google.com" in doc["site"] # type: ignore[operator] assert doc["site"]["google.com"] - assert doc["a"]["b"]["c"] == 1 - assert doc["a"]["b"]["d"] == 2 + assert doc["a"]["b"]["c"] == 1 # type: ignore[comparison-overlap] + assert doc["a"]["b"]["d"] == 2 # type: ignore[comparison-overlap] -def test_toml_document_super_table_with_different_sub_sections(example): +def test_toml_document_super_table_with_different_sub_sections(example: Callable[[str], str]) -> None: content = example("pyproject") doc = parse(content) tool = doc["tool"] - assert "poetry" in tool - assert "black" in tool + assert "poetry" in tool # type: ignore[operator] + assert "black" in tool # type: ignore[operator] -def test_adding_an_element_to_existing_table_with_ws_remove_ws(): +def test_adding_an_element_to_existing_table_with_ws_remove_ws() -> None: content = """[foo] [foo.bar] @@ -219,7 +221,7 @@ def test_adding_an_element_to_existing_table_with_ws_remove_ws(): assert expected == doc.as_string() -def test_document_with_aot_after_sub_tables(): +def test_document_with_aot_after_sub_tables() -> None: content = """[foo.bar] name = "Bar" @@ -231,10 +233,10 @@ def test_document_with_aot_after_sub_tables(): """ doc = parse(content) - assert doc["foo"]["bar"]["tests"][0]["name"] == "Test 1" + assert doc["foo"]["bar"]["tests"][0]["name"] == "Test 1" # type: ignore[comparison-overlap] -def test_document_with_new_sub_table_after_other_table(): +def test_document_with_new_sub_table_after_other_table() -> None: content = """[foo] name = "Bar" @@ -246,14 +248,14 @@ def test_document_with_new_sub_table_after_other_table(): """ doc = parse(content) - assert doc["foo"]["name"] == "Bar" - assert doc["bar"]["name"] == "Baz" - assert doc["foo"]["baz"]["name"] == "Test 1" + assert doc["foo"]["name"] == "Bar" # type: ignore[comparison-overlap] + assert doc["bar"]["name"] == "Baz" # type: ignore[comparison-overlap] + assert doc["foo"]["baz"]["name"] == "Test 1" # type: ignore[comparison-overlap] assert doc.as_string() == content -def test_document_with_new_sub_table_after_other_table_delete(): +def test_document_with_new_sub_table_after_other_table_delete() -> None: content = """[foo] name = "Bar" @@ -277,7 +279,7 @@ def test_document_with_new_sub_table_after_other_table_delete(): ) -def test_document_with_new_sub_table_after_other_table_replace(): +def test_document_with_new_sub_table_after_other_table_replace() -> None: content = """[foo] name = "Bar" @@ -304,7 +306,7 @@ def test_document_with_new_sub_table_after_other_table_replace(): ) -def test_inserting_after_element_with_no_new_line_adds_a_new_line(): +def test_inserting_after_element_with_no_new_line_adds_a_new_line() -> None: doc = parse("foo = 10") doc["bar"] = 11 @@ -324,7 +326,7 @@ def test_inserting_after_element_with_no_new_line_adds_a_new_line(): assert expected == doc.as_string() -def test_inserting_after_deletion(): +def test_inserting_after_deletion() -> None: doc = parse("foo = 10\n") del doc["foo"] @@ -336,20 +338,20 @@ def test_inserting_after_deletion(): assert expected == doc.as_string() -def test_toml_document_with_dotted_keys_inside_table(example): +def test_toml_document_with_dotted_keys_inside_table(example: Callable[[str], str]) -> None: content = example("0.5.0") doc = parse(content) t = doc["table"] - assert "a" in t + assert "a" in t # type: ignore[operator] - assert t["a"]["b"]["c"] == 1 - assert t["a"]["b"]["d"] == 2 - assert t["a"]["c"] == 3 + assert t["a"]["b"]["c"] == 1 # type: ignore[comparison-overlap] + assert t["a"]["b"]["d"] == 2 # type: ignore[comparison-overlap] + assert t["a"]["c"] == 3 # type: ignore[comparison-overlap] -def test_toml_document_with_super_aot_after_super_table(example): +def test_toml_document_with_super_aot_after_super_table(example: Callable[[str], str]) -> None: content = example("pyproject") doc = parse(content) @@ -364,7 +366,7 @@ def test_toml_document_with_super_aot_after_super_table(example): assert second["name"] == "second" -def test_toml_document_has_always_a_new_line_after_table_header(): +def test_toml_document_has_always_a_new_line_after_table_header() -> None: content = """[section.sub]""" doc = parse(content) @@ -383,14 +385,14 @@ def test_toml_document_has_always_a_new_line_after_table_header(): assert doc.as_string() == """[section.sub]""" -def test_toml_document_is_pickable(example): +def test_toml_document_is_pickable(example: Callable[[str], str]) -> None: content = example("example") doc = parse(content) assert pickle.loads(pickle.dumps(doc)).as_string() == content -def test_toml_document_set_super_table_element(): +def test_toml_document_set_super_table_element() -> None: content = """[site.user] name = "John" """ @@ -406,7 +408,7 @@ def test_toml_document_set_super_table_element(): ) -def test_toml_document_can_be_copied(): +def test_toml_document_can_be_copied() -> None: content = "[foo]\nbar=1" doc = parse(content) @@ -419,11 +421,11 @@ def test_toml_document_can_be_copied(): ) assert doc == {"foo": {"bar": 1}} - assert doc["foo"]["bar"] == 1 + assert doc["foo"]["bar"] == 1 # type: ignore[comparison-overlap] assert json.loads(json.dumps(doc)) == {"foo": {"bar": 1}} doc = parse(content) - doc = doc.copy() + doc = doc.copy() # type: ignore[assignment] assert ( doc.as_string() @@ -432,11 +434,11 @@ def test_toml_document_can_be_copied(): ) assert doc == {"foo": {"bar": 1}} - assert doc["foo"]["bar"] == 1 + assert doc["foo"]["bar"] == 1 # type: ignore[comparison-overlap] assert json.loads(json.dumps(doc)) == {"foo": {"bar": 1}} -def test_getting_inline_table_is_still_an_inline_table(): +def test_getting_inline_table_is_still_an_inline_table() -> None: content = """\ [tool.poetry] name = "foo" @@ -476,7 +478,7 @@ def test_getting_inline_table_is_still_an_inline_table(): ) -def test_declare_sub_table_with_intermediate_table(): +def test_declare_sub_table_with_intermediate_table() -> None: content = """ [students] tommy = 87 @@ -491,11 +493,11 @@ def test_declare_sub_table_with_intermediate_table(): """ doc = parse(content) - assert {"tommy": 87, "mary": 66, "bob": {"score": 91}} == doc["students"] + assert {"tommy": 87, "mary": 66, "bob": {"score": 91}} == doc["students"] # type: ignore[comparison-overlap] assert {"tommy": 87, "mary": 66, "bob": {"score": 91}} == doc.get("students") -def test_values_can_still_be_set_for_out_of_order_tables(): +def test_values_can_still_be_set_for_out_of_order_tables() -> None: content = """ [a.a] key = "value" @@ -508,7 +510,7 @@ def test_values_can_still_be_set_for_out_of_order_tables(): doc = parse(content) doc["a"]["a"]["key"] = "new_value" - assert doc["a"]["a"]["key"] == "new_value" + assert doc["a"]["a"]["key"] == "new_value" # type: ignore[comparison-overlap] expected = """ [a.a] @@ -555,7 +557,7 @@ 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(): +def test_out_of_order_table_can_add_multiple_tables() -> None: content = """\ [a.a.b] x = 1 @@ -568,10 +570,10 @@ def test_out_of_order_table_can_add_multiple_tables(): """ doc = parse(content) assert doc.as_string() == content - assert doc["a"]["a"] == {"b": {"x": 1}, "c": {"y": 1}, "d": {"z": 1}} + assert doc["a"]["a"] == {"b": {"x": 1}, "c": {"y": 1}, "d": {"z": 1}} # type: ignore[comparison-overlap] -def test_out_of_order_tables_are_still_dicts(): +def test_out_of_order_tables_are_still_dicts() -> None: content = """ [a.a] key = "value" @@ -594,18 +596,18 @@ def test_out_of_order_tables_are_still_dicts(): assert table.get("d", "foo") == "foo" assert table.setdefault("d", "bar") == "bar" - assert table["d"] == "bar" + assert table["d"] == "bar" # type: ignore[comparison-overlap] assert table.pop("key") == "value" assert "key" not in table - assert table.pop("missing", default="baz") == "baz" + assert table.pop("missing", default="baz") == "baz" # type: ignore[call-overload] with pytest.raises(KeyError): table.pop("missing") -def test_string_output_order_is_preserved_for_out_of_order_tables(): +def test_string_output_order_is_preserved_for_out_of_order_tables() -> None: content = """ [tool.poetry] name = "foo" @@ -629,7 +631,7 @@ def test_string_output_order_is_preserved_for_out_of_order_tables(): constraint["version"] = "^1.0" doc["tool"]["poetry"]["dependencies"]["bar"] = constraint - assert doc["tool"]["poetry"]["dependencies"]["bar"]["version"] == "^1.0" + assert doc["tool"]["poetry"]["dependencies"]["bar"]["version"] == "^1.0" # type: ignore[comparison-overlap] expected = """ [tool.poetry] @@ -652,7 +654,7 @@ def test_string_output_order_is_preserved_for_out_of_order_tables(): assert expected == doc.as_string() -def test_remove_from_out_of_order_table(): +def test_remove_from_out_of_order_table() -> None: content = """[a] x = 1 @@ -677,7 +679,7 @@ def test_remove_from_out_of_order_table(): assert json.dumps(document) == '{"a": {"x": 1}, "c": {"z": 3}}' -def test_update_nested_out_of_order_table(): +def test_update_nested_out_of_order_table() -> None: doc = parse("""\ [root1.root2.a.b.c] value = 2 @@ -686,7 +688,7 @@ def test_update_nested_out_of_order_table(): [root1.root2.x] value = 4 """) - doc["root1"]["root2"]["a"].add("tmp", "hi") + doc["root1"]["root2"]["a"].add("tmp", "hi") # type: ignore[attr-defined] assert ( doc.as_string() == """\ @@ -703,7 +705,7 @@ def test_update_nested_out_of_order_table(): ) -def test_updating_nested_value_keeps_correct_indent(): +def test_updating_nested_value_keeps_correct_indent() -> None: content = """ [Key1] [key1.Key2] @@ -724,7 +726,7 @@ def test_updating_nested_value_keeps_correct_indent(): assert doc.as_string() == expected -def test_repr(): +def test_repr() -> None: content = """ namespace.key1 = "value1" namespace.key2 = "value2" @@ -750,7 +752,7 @@ def test_repr(): assert repr(doc["namespace"]) == "{'key1': 'value1', 'key2': 'value2'}" -def test_deepcopy(): +def test_deepcopy() -> None: content = """ [tool] name = "foo" @@ -763,7 +765,7 @@ def test_deepcopy(): assert copied.as_string() == content -def test_move_table(): +def test_move_table() -> None: content = """a = 1 [x] a = 1 @@ -785,7 +787,7 @@ def test_move_table(): ) -def test_replace_with_table(): +def test_replace_with_table() -> None: content = """a = 1 b = 2 c = 3 @@ -803,7 +805,7 @@ def test_replace_with_table(): ) -def test_replace_table_with_value(): +def test_replace_table_with_value() -> None: content = """[foo] a = 1 @@ -823,7 +825,7 @@ def test_replace_table_with_value(): ) -def test_replace_preserve_sep(): +def test_replace_preserve_sep() -> None: content = """a = 1 [foo] @@ -842,7 +844,7 @@ def test_replace_preserve_sep(): ) -def test_replace_with_table_of_nested(): +def test_replace_with_table_of_nested() -> None: example = """\ [a] x = 1 @@ -862,7 +864,7 @@ def test_replace_with_table_of_nested(): assert doc.as_string().strip() == dedent(expected).strip() -def test_replace_with_aot_of_nested(): +def test_replace_with_aot_of_nested() -> None: example = """\ [a] x = 1 @@ -898,10 +900,10 @@ def test_replace_with_aot_of_nested(): assert doc.as_string().strip() == dedent(expected).strip() -def test_replace_with_comment(): +def test_replace_with_comment() -> None: content = 'a = "1"' doc = parse(content) - a = tomlkit.item(int(doc["a"])) + a = tomlkit.item(int(doc["a"])) # type: ignore[call-overload] a.comment("`a` should be an int") doc["a"] = a expected = "a = 1 # `a` should be an int" @@ -909,26 +911,26 @@ def test_replace_with_comment(): content = 'a = "1, 2, 3"' doc = parse(content) - a = tomlkit.array() + a = tomlkit.array() # type: ignore[assignment] a.comment("`a` should be an array") - for x in doc["a"].split(","): - a.append(int(x.strip())) + for x in doc["a"].split(","): # type: ignore[attr-defined] + a.append(int(x.strip())) # type: ignore[attr-defined] 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 = tomlkit.inline_table() # type: ignore[assignment] a.comment("`a` should be an inline-table") - for x in doc["a"].split(","): + for x in doc["a"].split(","): # type: ignore[attr-defined] i = int(x.strip()) - a.append(chr(ord("a") + i - 1), i) + a.append(chr(ord("a") + i - 1), i) # type: ignore[attr-defined] 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(): +def test_no_spurious_whitespaces() -> None: content = """\ [x] a = 1 @@ -972,7 +974,7 @@ def test_no_spurious_whitespaces(): assert doc.as_string() == dedent(expected) -def test_pop_add_whitespace_and_insert_table_work_togheter(): +def test_pop_add_whitespace_and_insert_table_work_togheter() -> None: content = """\ a = 1 b = 2 @@ -993,12 +995,12 @@ def test_pop_add_whitespace_and_insert_table_work_togheter(): """ text = doc.as_string() out = parse(text) - assert out["d"] == 4 - assert "d" not in out["e"] + assert out["d"] == 4 # type: ignore[comparison-overlap] + assert "d" not in out["e"] # type: ignore[operator] assert text == dedent(expected) -def test_add_newline_before_super_table(): +def test_add_newline_before_super_table() -> None: doc = document() doc["a"] = 1 doc["b"] = {"c": {}} @@ -1013,7 +1015,7 @@ def test_add_newline_before_super_table(): assert doc.as_string() == dedent(expected) -def test_remove_item_from_super_table(): +def test_remove_item_from_super_table() -> None: content = """\ [hello.one] a = 1 @@ -1031,7 +1033,7 @@ def test_remove_item_from_super_table(): assert doc.as_string() == dedent(expected) -def test_nested_table_update_display_name(): +def test_nested_table_update_display_name() -> None: content = """\ [parent] @@ -1047,7 +1049,7 @@ def test_nested_table_update_display_name(): [bar] z = 3 """ - doc["parent"].update(parse(dedent(sub))) + doc["parent"].update(parse(dedent(sub))) # type: ignore[attr-defined] expected = """\ [parent] @@ -1060,7 +1062,7 @@ def test_nested_table_update_display_name(): assert doc.as_string() == dedent(expected) -def test_build_table_with_dotted_key(): +def test_build_table_with_dotted_key() -> None: doc = tomlkit.document() data = { "a.b.c": 1, @@ -1071,9 +1073,9 @@ def test_build_table_with_dotted_key(): for key, value in data.items(): if "." not in key: - doc.append(key, value) + doc.append(key, value) # type: ignore[arg-type] else: - doc.append(tomlkit.key(key.split(".")), value) + doc.append(tomlkit.key(key.split(".")), value) # type: ignore[arg-type] expected = """\ a.b.c = 1 @@ -1089,7 +1091,7 @@ def test_build_table_with_dotted_key(): } -def test_parse_subtables_no_extra_indent(): +def test_parse_subtables_no_extra_indent() -> None: expected = """\ [a] [a.b.c] @@ -1102,7 +1104,7 @@ def test_parse_subtables_no_extra_indent(): assert doc.as_string() == expected -def test_item_preserves_the_order(): +def test_item_preserves_the_order() -> None: t = tomlkit.inline_table() t.update({"a": 1, "b": 2}) doc = {"name": "foo", "table": t, "age": 42} @@ -1114,7 +1116,7 @@ def test_item_preserves_the_order(): assert tomlkit.dumps(doc) == expected -def test_delete_out_of_order_table_key(): +def test_delete_out_of_order_table_key() -> None: content = """\ [foo] name = "foo" @@ -1152,7 +1154,7 @@ def test_delete_out_of_order_table_key(): ) -def test_overwrite_out_of_order_table_key(): +def test_overwrite_out_of_order_table_key() -> None: content = """\ [foo] name = "foo" @@ -1192,12 +1194,12 @@ def test_overwrite_out_of_order_table_key(): ) -def test_set_default_int(): +def test_set_default_int() -> None: with pytest.raises(TypeError): - TOMLDocument().setdefault(4, 5) + TOMLDocument().setdefault(4, 5) # type: ignore[arg-type] -def test_overwriting_out_of_order_table(): +def test_overwriting_out_of_order_table() -> None: content = """\ [foo.bar] open = false @@ -1235,7 +1237,7 @@ def test_overwriting_out_of_order_table(): ) -def test_delete_key_from_out_of_order_table(): +def test_delete_key_from_out_of_order_table() -> None: content = """\ [foo.bar.baz] a = 1 @@ -1259,7 +1261,7 @@ def test_delete_key_from_out_of_order_table(): ) -def test_parse_aot_without_ending_newline(): +def test_parse_aot_without_ending_newline() -> None: content = '''\ [[products]] name = "Hammer" @@ -1295,7 +1297,7 @@ def test_parse_aot_without_ending_newline(): } -def test_appending_to_super_table(): +def test_appending_to_super_table() -> None: content = """\ [a.b] value = 5 @@ -1303,7 +1305,7 @@ def test_appending_to_super_table(): doc = parse(content) table_a = doc["a"] - table_a.append(tomlkit.key(["c", "d"]), "foo") + table_a.append(tomlkit.key(["c", "d"]), "foo") # type: ignore[attr-defined] expected = """\ [a] diff --git a/tests/test_toml_file.py b/tests/test_toml_file.py index c1cef3a4..28a0d790 100644 --- a/tests/test_toml_file.py +++ b/tests/test_toml_file.py @@ -1,10 +1,13 @@ import os +from collections.abc import Callable +from pathlib import Path +from typing import Any from tomlkit.toml_document import TOMLDocument from tomlkit.toml_file import TOMLFile -def test_toml_file(example): +def test_toml_file(example: Callable[[str], str]) -> None: original_content = example("example") toml_file = os.path.join(os.path.dirname(__file__), "examples", "example.toml") @@ -12,7 +15,7 @@ def test_toml_file(example): content = toml.read() assert isinstance(content, TOMLDocument) - assert content["owner"]["organization"] == "GitHub" + assert content["owner"]["organization"] == "GitHub" # type: ignore[comparison-overlap] toml.write(content) @@ -24,94 +27,94 @@ def test_toml_file(example): assert f.write(original_content) -def test_keep_old_eol(tmp_path): +def test_keep_old_eol(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" - with open(toml_path, "wb+") as f: - f.write(b"a = 1\r\nb = 2\r\n") + with open(toml_path, "wb+") as fh: + fh.write(b"a = 1\r\nb = 2\r\n") - f = TOMLFile(toml_path) - content = f.read() + toml_f = TOMLFile(toml_path) + content = toml_f.read() content["b"] = 3 - f.write(content) + toml_f.write(content) - with open(toml_path, "rb") as f: - assert f.read() == b"a = 1\r\nb = 3\r\n" + with open(toml_path, "rb") as fh: + assert fh.read() == b"a = 1\r\nb = 3\r\n" -def test_keep_old_eol_2(tmp_path): +def test_keep_old_eol_2(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" - with open(toml_path, "wb+") as f: - f.write(b"a = 1\nb = 2\n") + with open(toml_path, "wb+") as fh: + fh.write(b"a = 1\nb = 2\n") - f = TOMLFile(toml_path) - content = f.read() + toml_f = TOMLFile(toml_path) + content = toml_f.read() content["b"] = 3 - f.write(content) + toml_f.write(content) - with open(toml_path, "rb") as f: - assert f.read() == b"a = 1\nb = 3\n" + with open(toml_path, "rb") as fh: + assert fh.read() == b"a = 1\nb = 3\n" -def test_mixed_eol(tmp_path): +def test_mixed_eol(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" - with open(toml_path, "wb+") as f: - f.write(b"a = 1\r\nrb = 2\n") + with open(toml_path, "wb+") as fh: + fh.write(b"a = 1\r\nrb = 2\n") - f = TOMLFile(toml_path) - f.write(f.read()) + toml_f = TOMLFile(toml_path) + toml_f.write(toml_f.read()) - with open(toml_path, "rb") as f: - assert f.read() == b"a = 1\r\nrb = 2\n" + with open(toml_path, "rb") as fh: + assert fh.read() == b"a = 1\r\nrb = 2\n" -def test_consistent_eol(tmp_path): +def test_consistent_eol(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" - with open(toml_path, "wb+") as f: - f.write(b"a = 1\r\nb = 2\r\n") + with open(toml_path, "wb+") as fh: + fh.write(b"a = 1\r\nb = 2\r\n") - f = TOMLFile(toml_path) - content = f.read() + toml_f = TOMLFile(toml_path) + content = toml_f.read() content["c"] = 3 - f.write(content) + toml_f.write(content) - with open(toml_path, "rb") as f: - assert f.read() == b"a = 1\r\nb = 2\r\nc = 3\r\n" + with open(toml_path, "rb") as fh: + assert fh.read() == b"a = 1\r\nb = 2\r\nc = 3\r\n" -def test_consistent_eol_2(tmp_path): +def test_consistent_eol_2(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" - with open(toml_path, "wb+") as f: - f.write(b"a = 1\nb = 2\n") + with open(toml_path, "wb+") as fh: + fh.write(b"a = 1\nb = 2\n") - f = TOMLFile(toml_path) - content = f.read() + toml_f = TOMLFile(toml_path) + content = toml_f.read() content["c"] = 3 content["c"].trivia.trail = "\r\n" - f.write(content) + toml_f.write(content) - with open(toml_path, "rb") as f: - assert f.read() == b"a = 1\nb = 2\nc = 3\n" + with open(toml_path, "rb") as fh: + assert fh.read() == b"a = 1\nb = 2\nc = 3\n" -def test_default_eol_is_os_linesep(tmp_path): +def test_default_eol_is_os_linesep(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" - f = TOMLFile(toml_path) + toml_f = TOMLFile(toml_path) content = TOMLDocument() - content.append("a", 1) + content.append("a", 1) # type: ignore[arg-type] content["a"].trivia.trail = "\n" - content.append("b", 2) + content.append("b", 2) # type: ignore[arg-type] content["b"].trivia.trail = "\r\n" - f.write(content) + toml_f.write(content) linesep = os.linesep.encode() - with open(toml_path, "rb") as f: - assert f.read() == b"a = 1" + linesep + b"b = 2" + linesep + with open(toml_path, "rb") as fh: + assert fh.read() == b"a = 1" + linesep + b"b = 2" + linesep -def test_readwrite_eol_windows(tmp_path): +def test_readwrite_eol_windows(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" doc = TOMLDocument() - doc.add("a", 1) - f = TOMLFile(toml_path) - f.write(doc) - readback = f.read() + doc.add("a", 1) # type: ignore[arg-type] + toml_f = TOMLFile(toml_path) + toml_f.write(doc) + readback = toml_f.read() assert doc.as_string() == readback.as_string() diff --git a/tests/test_toml_tests.py b/tests/test_toml_tests.py index 957f7ebf..3d02d5b1 100644 --- a/tests/test_toml_tests.py +++ b/tests/test_toml_tests.py @@ -1,5 +1,7 @@ import json import os +from typing import Any +from typing import Callable import pytest @@ -14,13 +16,13 @@ FILES_LIST = os.path.join(TESTS_ROOT, "files-toml-1.1.0") -def to_bool(s): +def to_bool(s: str) -> bool: assert s in ["true", "false"] return s == "true" -stypes = { +stypes: dict[str, Callable[[str], Any]] = { "string": str, "bool": to_bool, "integer": int, @@ -32,7 +34,7 @@ def to_bool(s): } -def untag(value): +def untag(value: Any) -> Any: if isinstance(value, list): return [untag(i) for i in value] elif "type" in value and "value" in value and len(value) == 2: @@ -48,12 +50,12 @@ def untag(value): return {k: untag(v) for k, v in value.items()} -def _load_case_list(): +def _load_case_list() -> list[str]: with open(FILES_LIST, encoding="utf-8") as f: return [line.strip() for line in f if line.strip()] -def _build_cases(): +def _build_cases() -> tuple[list[dict[str, str]], list[str], list[dict[str, str]], list[str], list[str], list[str]]: valid_cases = [] valid_ids = [] invalid_decode_cases = [] @@ -109,7 +111,7 @@ def _build_cases(): @pytest.mark.parametrize("toml11_valid_case", VALID_CASES, ids=VALID_IDS) -def test_valid_decode(toml11_valid_case): +def test_valid_decode(toml11_valid_case: dict[str, str]) -> None: json_val = untag(json.loads(toml11_valid_case["json"])) toml_val = parse(toml11_valid_case["toml"]) @@ -120,7 +122,7 @@ def test_valid_decode(toml11_valid_case): @pytest.mark.parametrize( "toml11_invalid_decode_case", INVALID_DECODE_CASES, ids=INVALID_DECODE_IDS ) -def test_invalid_decode(toml11_invalid_decode_case): +def test_invalid_decode(toml11_invalid_decode_case: dict[str, str]) -> None: with pytest.raises(TOMLKitError): parse(toml11_invalid_decode_case["toml"]) @@ -128,7 +130,7 @@ def test_invalid_decode(toml11_invalid_decode_case): @pytest.mark.parametrize( "toml11_invalid_encode_case", INVALID_ENCODE_CASES, ids=INVALID_ENCODE_IDS ) -def test_invalid_encode(toml11_invalid_encode_case): +def test_invalid_encode(toml11_invalid_encode_case: str) -> None: with open(toml11_invalid_encode_case, encoding="utf-8") as f: with pytest.raises((TOMLKitError, UnicodeDecodeError)): load(f) diff --git a/tests/test_utils.py b/tests/test_utils.py index 7dde452c..c4f96cf1 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,6 +3,7 @@ from datetime import time from datetime import timedelta as td from datetime import timezone as tz +from typing import Union import pytest @@ -34,12 +35,12 @@ ), ], ) -def test_parse_rfc3339_datetime(string, expected): +def test_parse_rfc3339_datetime(string: str, expected: Union[dt, date, time]) -> None: assert parse_rfc3339(string) == expected @pytest.mark.parametrize("string, expected", [("1979-05-27", date(1979, 5, 27))]) -def test_parse_rfc3339_date(string, expected): +def test_parse_rfc3339_date(string: str, expected: Union[dt, date, time]) -> None: assert parse_rfc3339(string) == expected @@ -47,5 +48,5 @@ def test_parse_rfc3339_date(string, expected): "string, expected", [("12:34:56", time(12, 34, 56)), ("12:34:56.123456", time(12, 34, 56, 123456))], ) -def test_parse_rfc3339_time(string, expected): +def test_parse_rfc3339_time(string: str, expected: Union[dt, date, time]) -> None: assert parse_rfc3339(string) == expected diff --git a/tests/test_write.py b/tests/test_write.py index 19e68a37..c5b923af 100644 --- a/tests/test_write.py +++ b/tests/test_write.py @@ -1,32 +1,37 @@ +from typing import Any + from tomlkit import dumps from tomlkit import loads -def test_write_backslash(): +def test_write_backslash() -> None: d = {"foo": "\\e\u25e6\r"} expected = """foo = "\\\\e\u25e6\\r" """ assert expected == dumps(d) - assert loads(dumps(d))["foo"] == "\\e\u25e6\r" + result: Any = loads(dumps(d))["foo"] + assert result == "\\e\u25e6\r" -def test_escape_special_characters_in_key(): +def test_escape_special_characters_in_key() -> None: d = {"foo\nbar": "baz"} expected = '"foo\\nbar" = "baz"\n' assert expected == dumps(d) - assert loads(dumps(d))["foo\nbar"] == "baz" + result: Any = loads(dumps(d))["foo\nbar"] + assert result == "baz" -def test_write_inline_table_in_nested_arrays(): +def test_write_inline_table_in_nested_arrays() -> None: d = {"foo": [[{"a": 1}]]} expected = "foo = [[{a = 1}]]\n" assert expected == dumps(d) - assert loads(dumps(d))["foo"] == [[{"a": 1}]] + result: Any = loads(dumps(d))["foo"] + assert result == [[{"a": 1}]] -def test_serialize_aot_with_nested_tables(): +def test_serialize_aot_with_nested_tables() -> None: doc = {"a": [{"b": {"c": 1}}]} expected = """\ [[a]] diff --git a/tests/util.py b/tests/util.py index fbe6df2e..83cf5d9f 100644 --- a/tests/util.py +++ b/tests/util.py @@ -42,16 +42,16 @@ ] -def assert_not_tomlkit_type(v): +def assert_not_tomlkit_type(v: object) -> None: for _, tomlkit_type in enumerate(TOMLKIT_TYPES): assert not isinstance(v, tomlkit_type) -def assert_is_ppo(v_unwrapped, unwrapped_type): +def assert_is_ppo(v_unwrapped: object, unwrapped_type: type) -> None: assert_not_tomlkit_type(v_unwrapped) assert isinstance(v_unwrapped, unwrapped_type) -def elementary_test(v, unwrapped_type): +def elementary_test(v: Item, unwrapped_type: type) -> None: v_unwrapped = v.unwrap() assert_is_ppo(v_unwrapped, unwrapped_type) diff --git a/tomlkit/_compat.py b/tomlkit/_compat.py index 8e76b7fd..44dca7a8 100644 --- a/tomlkit/_compat.py +++ b/tomlkit/_compat.py @@ -3,13 +3,10 @@ import contextlib import sys -from typing import Any - - PY38 = sys.version_info >= (3, 8) -def decode(string: Any, encodings: list[str] | None = None): +def decode(string: str | bytes, encodings: list[str] | None = None) -> str: if not isinstance(string, bytes): return string diff --git a/tomlkit/_types.py b/tomlkit/_types.py index 501bf4dc..3585d56c 100644 --- a/tomlkit/_types.py +++ b/tomlkit/_types.py @@ -7,6 +7,14 @@ WT = TypeVar("WT", bound="WrapperType") +__all__ = [ + "_CustomDict", + "_CustomFloat", + "_CustomInt", + "_CustomList", + "wrap_method", +] + if TYPE_CHECKING: # pragma: no cover # Define _CustomList and _CustomDict as a workaround for: # https://github.com/python/mypy/issues/11427 @@ -73,7 +81,7 @@ class _CustomFloat(Real, float): def wrap_method( original_method: Callable[Concatenate[WT, P], Any], ) -> Callable[Concatenate[WT, P], Any]: - def wrapper(self: WT, *args: P.args, **kwargs: P.kwargs) -> Any: + def wrapper(self: WT, /, *args: P.args, **kwargs: P.kwargs) -> Any: result = original_method(self, *args, **kwargs) if result is NotImplemented: return result diff --git a/tomlkit/_utils.py b/tomlkit/_utils.py index 99887b67..3c205741 100644 --- a/tomlkit/_utils.py +++ b/tomlkit/_utils.py @@ -9,6 +9,7 @@ from datetime import time from datetime import timedelta from datetime import timezone +from typing import Any from tomlkit._compat import decode @@ -130,7 +131,7 @@ def escape_string(s: str, escape_sequences: Collection[str] = _basic_escapes) -> res = [] start = 0 - def flush(inc=1): + def flush(inc: int = 1) -> int: if start != i: res.append(s[start:i]) @@ -153,9 +154,9 @@ def flush(inc=1): return "".join(res) -def merge_dicts(d1: dict, d2: dict) -> dict: +def merge_dicts(d1: dict[str, Any], d2: dict[str, Any]) -> None: for k, v in d2.items(): if k in d1 and isinstance(d1[k], dict) and isinstance(v, Mapping): - merge_dicts(d1[k], v) + merge_dicts(d1[k], dict(v)) else: d1[k] = d2[k] diff --git a/tomlkit/api.py b/tomlkit/api.py index 3294a7af..da2ab504 100644 --- a/tomlkit/api.py +++ b/tomlkit/api.py @@ -7,6 +7,7 @@ from collections.abc import Mapping from typing import IO from typing import TYPE_CHECKING +from typing import Any from typing import TypeVar from tomlkit._utils import parse_rfc3339 @@ -32,9 +33,9 @@ from tomlkit.items import Time from tomlkit.items import Trivia from tomlkit.items import Whitespace -from tomlkit.items import item +from tomlkit.items import item as item from tomlkit.parser import Parser -from tomlkit.toml_document import TOMLDocument +from tomlkit.toml_document import TOMLDocument as TOMLDocument if TYPE_CHECKING: @@ -52,7 +53,7 @@ def loads(string: str | bytes) -> TOMLDocument: return parse(string) -def dumps(data: Mapping, sort_keys: bool = False) -> str: +def dumps(data: Mapping[str, Any], sort_keys: bool = False) -> str: """ Dumps a TOMLDocument into a string. """ @@ -64,7 +65,7 @@ def dumps(data: Mapping, sort_keys: bool = False) -> str: 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] + return data.as_string() except AttributeError as ex: msg = f"Expecting Mapping or TOML Table or Container, {type(data)} given" raise TypeError(msg) from ex @@ -77,7 +78,7 @@ def load(fp: IO[str] | IO[bytes]) -> TOMLDocument: return parse(fp.read()) -def dump(data: Mapping, fp: IO[str], *, sort_keys: bool = False) -> None: +def dump(data: Mapping[str, Any], fp: IO[str], *, sort_keys: bool = False) -> None: """ Dump a TOMLDocument into a writable file stream. @@ -185,7 +186,10 @@ def array(raw: str = "[]") -> Array: >>> a [1, 2, 3] """ - return value(raw) + v = value(raw) + if not isinstance(v, Array): + raise ValueError(f"Expected an array, got {type(v)}") + return v def table(is_super_table: bool | None = None) -> Table: @@ -252,7 +256,7 @@ def key(k: str | Iterable[str]) -> Key: """ if isinstance(k, str): return SingleKey(k) - return DottedKey([key(_k) for _k in k]) + return DottedKey([SingleKey(_k) for _k in k]) def value(raw: str) -> _Item: diff --git a/tomlkit/container.py b/tomlkit/container.py index 38e3b349..6e58b727 100644 --- a/tomlkit/container.py +++ b/tomlkit/container.py @@ -27,7 +27,7 @@ _NOT_SET = object() -class Container(_CustomDict): +class Container(_CustomDict): # type: ignore[type-arg] """ A container for items within a TOMLDocument. @@ -35,10 +35,10 @@ class Container(_CustomDict): """ def __init__(self, parsed: bool = False) -> None: - self._map: dict[SingleKey, int | tuple[int, ...]] = {} + self._map: dict[Key, int | tuple[int, ...]] = {} self._body: list[tuple[Key | None, Item]] = [] self._parsed = parsed - self._table_keys = [] + self._table_keys: list[Key] = [] @property def body(self) -> list[tuple[Key | None, Item]]: @@ -46,42 +46,39 @@ def body(self) -> list[tuple[Key | None, Item]]: def unwrap(self) -> dict[str, Any]: """Returns as pure python object (ppo)""" - unwrapped = {} + unwrapped: dict[str, Any] = {} for k, v in self.items(): if k is None: continue - if isinstance(k, Key): - k = k.key + key_str: str = k.key if isinstance(k, Key) else k + val: Any = v.unwrap() if hasattr(v, "unwrap") else v - if hasattr(v, "unwrap"): - v = v.unwrap() - - if k in unwrapped: - merge_dicts(unwrapped[k], v) + if key_str in unwrapped: + merge_dicts(unwrapped[key_str], val) else: - unwrapped[k] = v + unwrapped[key_str] = val return unwrapped @property def value(self) -> dict[str, Any]: """The wrapped dict value""" - d = {} + d: dict[str, Any] = {} for k, v in self._body: if k is None: continue - k = k.key - v = v.value + key_str = k.key + val: Any = v.value - if isinstance(v, Container): - v = v.value + if isinstance(val, Container): + val = val.value - if k in d: - merge_dicts(d[k], v) + if key_str in d: + merge_dicts(d[key_str], val) else: - d[k] = v + d[key_str] = val return d @@ -112,8 +109,9 @@ def add(self, key: Key | Item | str, item: Item | None = None) -> Container: "Non comment/whitespace items must have an associated key" ) - key, item = None, key + return self.append(None, key) + assert not isinstance(key, Item) return self.append(key, item) def _handle_dotted_key(self, key: Key, value: Item) -> None: @@ -143,20 +141,23 @@ def _get_last_index_before_table(self) -> int: if isinstance(v, Whitespace) and not v.is_fixed(): continue - if isinstance(v, (Table, AoT)) and not k.is_dotted(): + if isinstance(v, (Table, AoT)) and k is not None and not k.is_dotted(): break last_index = i return last_index + 1 - def _validate_out_of_order_table(self, key: SingleKey | None = None) -> None: + def _validate_out_of_order_table(self, key: Key | None = None) -> None: if key is None: for k in self._map: assert k is not None self._validate_out_of_order_table(k) return - if key not in self._map or not isinstance(self._map[key], tuple): + if key not in self._map: + return + current_idx = self._map[key] + if not isinstance(current_idx, tuple): return - OutOfOrderTableProxy.validate(self, self._map[key]) + OutOfOrderTableProxy.validate(self, current_idx) def append( self, key: Key | str | None, item: Item, validate: bool = True @@ -173,6 +174,7 @@ def append( return self if isinstance(item, (AoT, Table)) and item.name is None: + assert isinstance(key, Key) item.name = key.key prev = self._previous_item() @@ -183,6 +185,7 @@ def append( if ( self._body and not (self._parsed or item.trivia.indent or prev_ws) + and key is not None and not key.is_dotted() ): item.trivia.indent = "\n" @@ -216,7 +219,7 @@ def append( current.append(item) return self - elif current.is_aot(): + elif isinstance(current, AoT): if not item.is_aot_element(): # Tried to define a table after an AoT with the same name. raise KeyAlreadyPresent(key) @@ -229,7 +232,10 @@ def append( # We need to merge both super tables if ( key.is_dotted() - or current_body_element[0].is_dotted() + or ( + current_body_element[0] is not None + and current_body_element[0].is_dotted() + ) or self._table_keys[-1] != current_body_element[0] ): if key.is_dotted() and not self._parsed: @@ -260,12 +266,16 @@ def append( ] = (current_body_element[0], current) return self - elif current_body_element[0].is_dotted(): + elif ( + current_body_element[0] is not None + and current_body_element[0].is_dotted() + ): raise TOMLKitError("Redefinition of an existing table") else: # Merging a concrete table into an existing implicit/super # table is only valid if it does not redefine existing # subtrees via dotted keys and does not change prior types. + assert isinstance(current, Table) self._validate_table_candidate(current, item) elif not item.is_super_table(): raise KeyAlreadyPresent(key) @@ -335,13 +345,13 @@ def _validate_table_candidate(self, current: Table, candidate: Table) -> None: raise TOMLKitError("Redefinition of an existing table") def _raw_append(self, key: Key | None, item: Item) -> None: - if key in self._map: + if key is not None and key in self._map: current_idx = self._map[key] if not isinstance(current_idx, tuple): current_idx = (current_idx,) current = self._body[current_idx[-1]][1] - if key is not None and not isinstance(current, Table): + if not isinstance(current, Table): raise KeyAlreadyPresent(key) self._map[key] = (*current_idx, len(self._body)) @@ -349,7 +359,7 @@ def _raw_append(self, key: Key | None, item: Item) -> None: self._map[key] = len(self._body) self._body.append((key, item)) - if item.is_table(): + if item.is_table() and key is not None: self._table_keys.append(key) if key is not None: @@ -357,19 +367,19 @@ def _raw_append(self, key: Key | None, item: Item) -> None: def _remove_at(self, idx: int) -> None: key = self._body[idx][0] + assert key is not None index = self._map.get(key) if index is None: raise NonExistentKey(key) self._body[idx] = (None, Null()) if isinstance(index, tuple): - index = list(index) - index.remove(idx) - if len(index) == 1: - index = index.pop() + index_list = list(index) + index_list.remove(idx) + if len(index_list) == 1: + self._map[key] = index_list.pop() else: - index = tuple(index) - self._map[key] = index + self._map[key] = tuple(index_list) else: dict.__delitem__(self, key.key) self._map.pop(key) @@ -499,7 +509,7 @@ def item(self, key: Key | str) -> Item: # The item we are getting is an out of order table # so we need a proxy to retrieve the proper objects # from the parent container - return OutOfOrderTableProxy(self, idx) + return OutOfOrderTableProxy(self, idx) # type: ignore[return-value] return self._body[idx][1] @@ -507,6 +517,7 @@ def last_item(self) -> Item | None: """Get the last item.""" if self._body: return self._body[-1][1] + return None def as_string(self) -> str: """Render as TOML string.""" @@ -557,7 +568,11 @@ def _render_table(self, key: Key, table: Table, prefix: str | None = None) -> st and not key.is_dotted() ) or ( - any(k.is_dotted() for k, v in table.value.body if isinstance(v, Table)) + any( + k is not None and k.is_dotted() + for k, v in table.value.body + if isinstance(v, Table) + ) and not key.is_dotted() ) ): @@ -589,6 +604,7 @@ def _render_table(self, key: Key, table: Table, prefix: str | None = None) -> st and "\n" not in v.trivia.indent ): cur += "\n" + assert k is not None if v.is_super_table(): if k.is_dotted() and not key.is_dotted(): # Dotted key inside table @@ -604,6 +620,7 @@ def _render_table(self, key: Key, table: Table, prefix: str | None = None) -> st and "\n" not in v.trivia.indent ): cur += "\n" + assert k is not None cur += self._render_aot(k, v, prefix=_key) else: cur += self._render_simple_item( @@ -612,7 +629,7 @@ def _render_table(self, key: Key, table: Table, prefix: str | None = None) -> st return cur - def _render_aot(self, key, aot, prefix=None): + def _render_aot(self, key: Key, aot: AoT, prefix: str | None = None) -> str: _key = key.as_string() if prefix is not None: _key = prefix + "." + _key @@ -641,6 +658,7 @@ def _render_aot_table(self, table: Table, prefix: str | None = None) -> str: for k, v in table.value.body: if isinstance(v, Table): + assert k is not None if v.is_super_table(): if k.is_dotted(): # Dotted key inside table @@ -650,13 +668,16 @@ def _render_aot_table(self, table: Table, prefix: str | None = None) -> str: else: cur += self._render_table(k, v, prefix=_key) elif isinstance(v, AoT): + assert k is not None cur += self._render_aot(k, v, prefix=_key) else: cur += self._render_simple_item(k, v) return cur - def _render_simple_item(self, key, item, prefix=None): + def _render_simple_item( + self, key: Key | None, item: Item, prefix: str | None = None + ) -> str: if key is None: return item.as_string() @@ -681,10 +702,10 @@ def __iter__(self) -> Iterator[str]: return iter(dict.keys(self)) # Dictionary methods - def __getitem__(self, key: Key | str) -> Item | Container: + def __getitem__(self, key: Key | str) -> Item: item = self.item(key) if isinstance(item, Item) and item.is_boolean(): - return item.value + return item.value # type: ignore[no-any-return] return item @@ -698,8 +719,9 @@ def __setitem__(self, key: Key | str, value: Any) -> None: def __delitem__(self, key: Key | str) -> None: self.remove(key) - def setdefault(self, key: Key | str, default: Any) -> Any: - super().setdefault(key, default=default) + def setdefault(self, key: Key | str, default: Any = None) -> Item: + if key not in self: + self[key] = default return self[key] def _replace(self, key: Key | str, new_key: Key | str, value: Item) -> None: @@ -713,7 +735,7 @@ def _replace(self, key: Key | str, new_key: Key | str, value: Item) -> None: self._replace_at(idx, new_key, value) def _replace_at( - self, idx: int | tuple[int], new_key: Key | str, value: Item + self, idx: int | tuple[int, ...], new_key: Key | str, value: Item ) -> None: value = _item(value) @@ -724,6 +746,7 @@ def _replace_at( idx = idx[0] k, v = self._body[idx] + assert k is not None if not isinstance(new_key, Key): if ( isinstance(value, (AoT, Table)) != isinstance(v, (AoT, Table)) @@ -736,7 +759,7 @@ def _replace_at( del self._map[k] self._map[new_key] = idx if new_key != k: - dict.__delitem__(self, k) + dict.__delitem__(self, k.key) if isinstance(value, (AoT, Table)) != isinstance(v, (AoT, Table)): # new tables should appear after all non-table values @@ -759,14 +782,16 @@ def _replace_at( self._body[idx] = (new_key, value) if hasattr(value, "invalidate_display_name"): - value.invalidate_display_name() # type: ignore[attr-defined] + value.invalidate_display_name() 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, or # - The table being replaced has a newline - last, _ = self._previous_item_with_index() + result = self._previous_item_with_index() + assert result is not None + last, _ = result idx = last if idx < 0 else idx has_ws = ends_with_whitespace(value) replace_has_ws = ( @@ -778,6 +803,7 @@ def _replace_at( if (idx < last or replace_has_ws) and not (next_ws or has_ws): value.append(None, Whitespace("\n")) + assert isinstance(new_key, Key) dict.__setitem__(self, new_key.key, value.value) def __str__(self) -> str: @@ -786,26 +812,26 @@ def __str__(self) -> str: def __repr__(self) -> str: return repr(self.value) - def __eq__(self, other: dict) -> bool: + def __eq__(self, other: object) -> bool: if not isinstance(other, dict): return NotImplemented - return _equal_with_nan(self.value, other) + return bool(_equal_with_nan(self.value, other)) - def _getstate(self, protocol): + def _getstate(self, protocol: int) -> tuple[bool]: return (self._parsed,) - def __reduce__(self): + def __reduce__(self) -> tuple[type, tuple[bool], tuple[Any, ...]]: return self.__reduce_ex__(2) - def __reduce_ex__(self, protocol): + def __reduce_ex__(self, protocol: int) -> tuple[type, tuple[bool], tuple[Any, ...]]: # type: ignore[override] return ( self.__class__, self._getstate(protocol), (self._map, self._body, self._parsed, self._table_keys), ) - def __setstate__(self, state): + def __setstate__(self, state: tuple[Any, ...]) -> None: self._map = state[0] self._body = state[1] self._parsed = state[2] @@ -829,7 +855,7 @@ def __copy__(self) -> Container: return c def _previous_item_with_index( - self, idx: int | None = None, ignore=(Null,) + self, idx: int | None = None, ignore: tuple[type, ...] = (Null,) ) -> tuple[int, Item] | None: """Find the immediate previous item before index ``idx``""" if idx is None or idx > len(self._body): @@ -840,7 +866,9 @@ def _previous_item_with_index( return i, v return None - def _previous_item(self, idx: int | None = None, ignore=(Null,)) -> Item | None: + def _previous_item( + self, idx: int | None = None, ignore: tuple[type, ...] = (Null,) + ) -> Item | None: """Find the immediate previous item before index ``idx``. If ``idx`` is not given, the last item is returned. """ @@ -848,7 +876,7 @@ def _previous_item(self, idx: int | None = None, ignore=(Null,)) -> Item | None: return prev[-1] if prev else None -class OutOfOrderTableProxy(_CustomDict): +class OutOfOrderTableProxy(_CustomDict): # type: ignore[type-arg] @staticmethod def validate(container: Container, indices: tuple[int, ...]) -> None: """Validate out of order tables in the given container""" @@ -870,26 +898,26 @@ def __init__(self, container: Container, indices: tuple[int, ...]) -> None: self._tables_map: dict[Key, list[int]] = {} for i in indices: - _, item = self._container._body[i] + _, _item = self._container._body[i] - if isinstance(item, Table): - self._tables.append(item) + if isinstance(_item, Table): + self._tables.append(_item) table_idx = len(self._tables) - 1 - for k, v in item.value.body: + for k, v in _item.value.body: self._internal_container._raw_append(k, v) - indices = self._tables_map.setdefault(k, []) - if table_idx not in indices: - indices.append(table_idx) + key_indices = self._tables_map.setdefault(k, []) # type: ignore[arg-type] + if table_idx not in key_indices: + key_indices.append(table_idx) if k is not None: dict.__setitem__(self, k.key, v) self._internal_container._validate_out_of_order_table() - def unwrap(self) -> str: + def unwrap(self) -> dict[str, Any]: return self._internal_container.unwrap() @property - def value(self): + def value(self) -> dict[str, Any]: return self._internal_container.value def __getitem__(self, key: Key | str) -> Any: @@ -899,25 +927,27 @@ def __getitem__(self, key: Key | str) -> Any: return self._internal_container[key] def __setitem__(self, key: Key | str, value: Any) -> None: - from .items import item + from .items import item as _item_fn def _is_table_or_aot(it: Any) -> bool: - return isinstance(item(it), (Table, AoT)) + return isinstance(_item_fn(it), (Table, AoT)) - if key in self._tables_map: + _key: Key = key if isinstance(key, Key) else SingleKey(key) + + if _key in self._tables_map: # Overwrite the first table and remove others - indices = self._tables_map[key] - while len(indices) > 1: - table = self._tables[indices.pop()] + map_indices = self._tables_map[_key] + while len(map_indices) > 1: + table = self._tables[map_indices.pop()] self._remove_table(table) - old_value = self._tables[indices[0]][key] + old_value = self._tables[map_indices[0]][key] if _is_table_or_aot(old_value) and not _is_table_or_aot(value): # Remove the entry from the map and set value again. - del self._tables[indices[0]][key] - del self._tables_map[key] + del self._tables[map_indices[0]][key] + del self._tables_map[_key] self[key] = value return - self._tables[indices[0]][key] = value + self._tables[map_indices[0]][key] = value elif self._tables: if not _is_table_or_aot(value): # if the value is a plain value for table in self._tables: @@ -939,22 +969,23 @@ def _is_table_or_aot(it: Any) -> bool: def _remove_table(self, table: Table) -> None: """Remove table from the parent container""" self._tables.remove(table) - for idx, item in enumerate(self._container._body): - if item[1] is table: + for idx, body_item in enumerate(self._container._body): + if body_item[1] is table: self._container._remove_at(idx) break def __delitem__(self, key: Key | str) -> None: - if key not in self._tables_map: + _key: Key = key if isinstance(key, Key) else SingleKey(key) + if _key not in self._tables_map: raise NonExistentKey(key) - for i in reversed(self._tables_map[key]): + for i in reversed(self._tables_map[_key]): table = self._tables[i] del table[key] if not table and len(self._tables) > 1: self._remove_table(table) - del self._tables_map[key] + del self._tables_map[_key] del self._internal_container[key] if key is not None: dict.__delitem__(self, key) @@ -965,8 +996,9 @@ def __iter__(self) -> Iterator[str]: def __len__(self) -> int: return dict.__len__(self) - def setdefault(self, key: Key | str, default: Any) -> Any: - super().setdefault(key, default=default) + def setdefault(self, key: Key | str, default: Any = None) -> Any: + if key not in self: + self[key] = default return self[key] @@ -994,4 +1026,4 @@ def _equal_with_nan(left: Any, right: Any) -> bool: if math.isnan(left) and math.isnan(right): return True - return left == right + return bool(left == right) diff --git a/tomlkit/exceptions.py b/tomlkit/exceptions.py index e4e85a20..473357e8 100644 --- a/tomlkit/exceptions.py +++ b/tomlkit/exceptions.py @@ -24,11 +24,11 @@ def __init__(self, line: int, col: int, message: str | None = None) -> None: super().__init__(f"{message} at line {self._line} col {self._col}") @property - def line(self): + def line(self) -> int: return self._line @property - def col(self): + def col(self) -> int: return self._col @@ -182,7 +182,7 @@ class NonExistentKey(KeyError, TOMLKitError): A non-existent key was used. """ - def __init__(self, key): + def __init__(self, key: object) -> None: message = f'Key "{key}" does not exist.' super().__init__(message) @@ -193,7 +193,7 @@ class KeyAlreadyPresent(TOMLKitError): An already present key was used. """ - def __init__(self, key): + def __init__(self, key: object) -> None: key = getattr(key, "key", key) message = f'Key "{key}" already exists.' diff --git a/tomlkit/items.py b/tomlkit/items.py index 7b352e60..b1c9b383 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -14,12 +14,13 @@ from datetime import date from datetime import datetime from datetime import time +from datetime import timedelta from datetime import tzinfo from enum import Enum from typing import TYPE_CHECKING from typing import Any +from typing import Any from typing import TypeVar -from typing import cast from typing import overload from tomlkit._compat import PY38 @@ -28,7 +29,6 @@ from tomlkit._types import _CustomFloat from tomlkit._types import _CustomInt from tomlkit._types import _CustomList -from tomlkit._types import wrap_method from tomlkit._utils import CONTROL_CHARS from tomlkit._utils import escape_string from tomlkit.exceptions import ConvertError @@ -52,7 +52,7 @@ def __call__( @overload -def item(value: bool, _parent: Item | None = ..., _sort_keys: bool = ...) -> Bool: ... +def item(value: bool, _parent: Item | None = ..., _sort_keys: bool = ...) -> Bool: ... # type: ignore[overload-overlap] @overload @@ -68,9 +68,7 @@ def item(value: str, _parent: Item | None = ..., _sort_keys: bool = ...) -> Stri @overload -def item( - value: datetime, _parent: Item | None = ..., _sort_keys: bool = ... -) -> DateTime: ... +def item(value: datetime, _parent: Item | None = ..., _sort_keys: bool = ...) -> DateTime: ... # type: ignore[overload-overlap] @overload @@ -83,22 +81,22 @@ def item(value: time, _parent: Item | None = ..., _sort_keys: bool = ...) -> Tim @overload def item( - value: Sequence[dict], _parent: Item | None = ..., _sort_keys: bool = ... + value: Sequence[dict[str, Any]], _parent: Item | None = ..., _sort_keys: bool = ... ) -> AoT: ... @overload def item( - value: Sequence, _parent: Item | None = ..., _sort_keys: bool = ... + value: Sequence[Any], _parent: Item | None = ..., _sort_keys: bool = ... ) -> Array: ... @overload -def item(value: dict, _parent: Array = ..., _sort_keys: bool = ...) -> InlineTable: ... +def item(value: dict[str, Any], _parent: Array = ..., _sort_keys: bool = ...) -> InlineTable: ... @overload -def item(value: dict, _parent: Item | None = ..., _sort_keys: bool = ...) -> Table: ... +def item(value: dict[str, Any], _parent: Item | None = ..., _sort_keys: bool = ...) -> Table: ... @overload @@ -143,6 +141,7 @@ def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> I return val elif isinstance(value, (list, tuple)): + a: AoT | Array if ( value and all(isinstance(v, dict) for v in value) @@ -236,7 +235,7 @@ class StringType(Enum): MLL = "'''" @classmethod - def select(cls, literal=False, multiline=False) -> StringType: + def select(cls, literal: bool = False, multiline: bool = False) -> StringType: return { (False, False): cls.SLB, (False, True): cls.MLB, @@ -297,13 +296,13 @@ class BoolType(Enum): TRUE = "true" FALSE = "false" - def __bool__(self): + def __bool__(self) -> bool: return {BoolType.TRUE: True, BoolType.FALSE: False}[self] - def __iter__(self): + def __iter__(self) -> Iterator[str]: return iter(self.value) - def __len__(self): + def __len__(self) -> int: return len(self.value) @@ -434,7 +433,7 @@ def __eq__(self, other: Any) -> bool: if isinstance(other, Key): return isinstance(other, SingleKey) and self.key == other.key - return self.key == other + return bool(self.key == other) class DottedKey(Key): @@ -522,15 +521,24 @@ def is_inline_table(self) -> bool: def is_aot(self) -> bool: return isinstance(self, AoT) - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[object, ...]: return (self._trivia,) - def __reduce__(self): + def __reduce__(self) -> tuple[type, tuple[object, ...]]: return self.__reduce_ex__(2) - def __reduce_ex__(self, protocol): + def __reduce_ex__(self, protocol: int) -> tuple[type, tuple[object, ...]]: # type: ignore[override] return self.__class__, self._getstate(protocol) + def __getitem__(self, key: Key | str | int) -> Item: + raise TypeError(f"{type(self).__name__} does not support item access") + + def __setitem__(self, key: Key | str | int, value: Any) -> None: + raise TypeError(f"{type(self).__name__} does not support item assignment") + + def __delitem__(self, key: Key | str | int) -> None: + raise TypeError(f"{type(self).__name__} does not support item deletion") + class Whitespace(Item): """ @@ -568,7 +576,7 @@ def as_string(self) -> str: def __repr__(self) -> str: return f"<{self.__class__.__name__} {self._s!r}>" - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[str, bool]: return self._s, self._fixed @@ -627,60 +635,174 @@ def value(self) -> int: def as_string(self) -> str: return self._raw - def _new(self, result): + def _new(self, result: int) -> Integer: raw = str(result) if self._sign and result >= 0: raw = f"+{raw}" return Integer(result, self._trivia, raw) - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[int, Trivia, str]: return int(self), self._trivia, self._raw - # int methods - __abs__ = wrap_method(int.__abs__) - __add__ = wrap_method(int.__add__) - __and__ = wrap_method(int.__and__) - __ceil__ = wrap_method(int.__ceil__) + # int methods — explicit typed wrappers + def __abs__(self) -> Integer: + return self._new(int.__abs__(self)) + + def __add__(self, other: object) -> Integer: + result = int.__add__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __and__(self, other: object) -> Integer: + result = int.__and__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __ceil__(self) -> Integer: + return self._new(int.__ceil__(self)) + __eq__ = int.__eq__ - __floor__ = wrap_method(int.__floor__) - __floordiv__ = wrap_method(int.__floordiv__) - __invert__ = wrap_method(int.__invert__) + + def __floor__(self) -> Integer: + return self._new(int.__floor__(self)) + + def __floordiv__(self, other: object) -> Integer: + result = int.__floordiv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __invert__(self) -> Integer: + return self._new(int.__invert__(self)) + __le__ = int.__le__ - __lshift__ = wrap_method(int.__lshift__) + + def __lshift__(self, other: object) -> Integer: + result = int.__lshift__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + __lt__ = int.__lt__ - __mod__ = wrap_method(int.__mod__) - __mul__ = wrap_method(int.__mul__) - __neg__ = wrap_method(int.__neg__) - __or__ = wrap_method(int.__or__) - __pos__ = wrap_method(int.__pos__) - __pow__ = wrap_method(int.__pow__) - __radd__ = wrap_method(int.__radd__) - __rand__ = wrap_method(int.__rand__) - __rfloordiv__ = wrap_method(int.__rfloordiv__) - __rlshift__ = wrap_method(int.__rlshift__) - __rmod__ = wrap_method(int.__rmod__) - __rmul__ = wrap_method(int.__rmul__) - __ror__ = wrap_method(int.__ror__) - __round__ = wrap_method(int.__round__) - __rpow__ = wrap_method(int.__rpow__) - __rrshift__ = wrap_method(int.__rrshift__) - __rshift__ = wrap_method(int.__rshift__) - __rxor__ = wrap_method(int.__rxor__) - __trunc__ = wrap_method(int.__trunc__) - __xor__ = wrap_method(int.__xor__) - - def __rtruediv__(self, other): - result = int.__rtruediv__(self, other) + + def __mod__(self, other: object) -> Integer: + result = int.__mod__(self, other) # type: ignore[operator] if result is NotImplemented: - return result - return Float._new(self, result) + return result # type: ignore[return-value] + return self._new(result) - def __truediv__(self, other): - result = int.__truediv__(self, other) + def __mul__(self, other: object) -> Integer: + result = int.__mul__(self, other) # type: ignore[operator] if result is NotImplemented: - return result - return Float._new(self, result) + return result # type: ignore[return-value] + return self._new(result) + + def __neg__(self) -> Integer: + return self._new(int.__neg__(self)) + + def __or__(self, other: object) -> Integer: + result = int.__or__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __pos__(self) -> Integer: + return self._new(int.__pos__(self)) + + def __pow__(self, other: int, mod: int | None = None) -> Integer: # type: ignore[override] + result = int.__pow__(self, other) if mod is None else int.__pow__(self, other, mod) + return self._new(result) + + def __radd__(self, other: object) -> Integer: + result = int.__radd__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rand__(self, other: object) -> Integer: + result = int.__rand__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rfloordiv__(self, other: object) -> Integer: + result = int.__rfloordiv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rlshift__(self, other: object) -> Integer: + result = int.__rlshift__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rmod__(self, other: object) -> Integer: + result = int.__rmod__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rmul__(self, other: object) -> Integer: + result = int.__rmul__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __ror__(self, other: object) -> Integer: + result = int.__ror__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __round__(self, ndigits: int = 0) -> Integer: # type: ignore[override] + return self._new(int.__round__(self, ndigits)) + + def __rpow__(self, other: int, mod: int | None = None) -> Integer: # type: ignore[misc] + result = int.__rpow__(self, other) if mod is None else int.__rpow__(self, other, mod) + return self._new(result) + + def __rrshift__(self, other: object) -> Integer: + result = int.__rrshift__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rshift__(self, other: object) -> Integer: + result = int.__rshift__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rxor__(self, other: object) -> Integer: + result = int.__rxor__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __trunc__(self) -> Integer: + return self._new(int.__trunc__(self)) + + def __xor__(self, other: object) -> Integer: + result = int.__xor__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rtruediv__(self, other: object) -> Float: + result = int.__rtruediv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return Float._new(self, result) # type: ignore[arg-type] + + def __truediv__(self, other: object) -> Float: + result = int.__truediv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return Float._new(self, result) # type: ignore[arg-type] class Float(Item, _CustomFloat): @@ -720,7 +842,7 @@ def value(self) -> float: def as_string(self) -> str: return self._raw - def _new(self, result): + def _new(self, result: float) -> Float: raw = str(result) if self._sign and result >= 0: @@ -728,31 +850,100 @@ def _new(self, result): return Float(result, self._trivia, raw) - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[float, Trivia, str]: return float(self), self._trivia, self._raw - # float methods - __abs__ = wrap_method(float.__abs__) - __add__ = wrap_method(float.__add__) + # float methods — explicit typed wrappers + def __abs__(self) -> Float: + return self._new(float.__abs__(self)) + + def __add__(self, other: object) -> Float: + result = float.__add__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + __eq__ = float.__eq__ - __floordiv__ = wrap_method(float.__floordiv__) + + def __floordiv__(self, other: object) -> Float: + result = float.__floordiv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + __le__ = float.__le__ __lt__ = float.__lt__ - __mod__ = wrap_method(float.__mod__) - __mul__ = wrap_method(float.__mul__) - __neg__ = wrap_method(float.__neg__) - __pos__ = wrap_method(float.__pos__) - __pow__ = wrap_method(float.__pow__) - __radd__ = wrap_method(float.__radd__) - __rfloordiv__ = wrap_method(float.__rfloordiv__) - __rmod__ = wrap_method(float.__rmod__) - __rmul__ = wrap_method(float.__rmul__) - __round__ = wrap_method(float.__round__) - __rpow__ = wrap_method(float.__rpow__) - __rtruediv__ = wrap_method(float.__rtruediv__) - __truediv__ = wrap_method(float.__truediv__) - __trunc__ = float.__trunc__ + def __mod__(self, other: object) -> Float: + result = float.__mod__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __mul__(self, other: object) -> Float: + result = float.__mul__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __neg__(self) -> Float: + return self._new(float.__neg__(self)) + + def __pos__(self) -> Float: + return self._new(float.__pos__(self)) + + def __pow__(self, other: object, mod: None = None) -> Float: + result = float.__pow__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[no-any-return] + return self._new(result) + + def __radd__(self, other: object) -> Float: + result = float.__radd__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rfloordiv__(self, other: object) -> Float: + result = float.__rfloordiv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rmod__(self, other: object) -> Float: + result = float.__rmod__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rmul__(self, other: object) -> Float: + result = float.__rmul__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __round__(self, ndigits: int = 0) -> Float: # type: ignore[override] + return self._new(float.__round__(self, ndigits)) + + def __rpow__(self, other: object, mod: None = None) -> Float: + result = float.__rpow__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[no-any-return] + return self._new(result) + + def __rtruediv__(self, other: object) -> Float: + result = float.__rtruediv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __truediv__(self, other: object) -> Float: + result = float.__truediv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + __trunc__ = float.__trunc__ __ceil__ = float.__ceil__ __floor__ = float.__floor__ @@ -762,7 +953,7 @@ class Bool(Item): A boolean literal. """ - def __init__(self, t: int, trivia: Trivia) -> None: + def __init__(self, t: int | BoolType, trivia: Trivia) -> None: super().__init__(trivia) self._value = bool(t) @@ -782,24 +973,24 @@ def value(self) -> bool: def as_string(self) -> str: return str(self._value).lower() - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[bool, Trivia]: return self._value, self._trivia - def __bool__(self): + def __bool__(self) -> bool: return self._value __nonzero__ = __bool__ - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, bool): - return NotImplemented + return NotImplemented return other == self._value - def __hash__(self): + def __hash__(self) -> int: return hash(self._value) - def __repr__(self): + def __repr__(self) -> str: return repr(self._value) @@ -818,9 +1009,10 @@ def __new__( second: int, microsecond: int, tzinfo: tzinfo | None, - *_: Any, - **kwargs: Any, - ) -> datetime: + trivia: Trivia | None = None, + raw: str | None = None, + **kwargs: object, + ) -> DateTime: return datetime.__new__( cls, year, @@ -831,7 +1023,6 @@ def __new__( second, microsecond, tzinfo=tzinfo, - **kwargs, ) def __init__( @@ -846,7 +1037,7 @@ def __init__( tzinfo: tzinfo | None, trivia: Trivia | None = None, raw: str | None = None, - **kwargs: Any, + **kwargs: object, ) -> None: super().__init__(trivia or Trivia()) @@ -878,7 +1069,7 @@ def value(self) -> datetime: def as_string(self) -> str: return self._raw - def __add__(self, other): + def __add__(self, other: timedelta) -> DateTime: if PY38: result = datetime( self.year, @@ -895,7 +1086,7 @@ def __add__(self, other): return self._new(result) - def __sub__(self, other): + def __sub__(self, other: timedelta | datetime) -> DateTime | timedelta: # type: ignore[override] if PY38: result = datetime( self.year, @@ -908,23 +1099,23 @@ def __sub__(self, other): self.tzinfo, ).__sub__(other) else: - result = super().__sub__(other) + result = super().__sub__(other) # type: ignore[operator] if isinstance(result, datetime): result = self._new(result) return result - def replace(self, *args: Any, **kwargs: Any) -> datetime: - return self._new(super().replace(*args, **kwargs)) + def replace(self, *args: object, **kwargs: object) -> datetime: # type: ignore[override] + return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type] - def astimezone(self, tz: tzinfo) -> datetime: + def astimezone(self, tz: tzinfo) -> datetime: # type: ignore[override] result = super().astimezone(tz) if PY38: return result return self._new(result) - def _new(self, result) -> DateTime: + def _new(self, result: datetime) -> DateTime: raw = result.isoformat() return DateTime( @@ -940,7 +1131,7 @@ def _new(self, result) -> DateTime: raw, ) - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[int, int, int, int, int, int, int, tzinfo | None, Trivia, str]: return ( self.year, self.month, @@ -960,7 +1151,7 @@ class Date(Item, date): A date literal. """ - def __new__(cls, year: int, month: int, day: int, *_: Any) -> date: + def __new__(cls, year: int, month: int, day: int, trivia: Trivia | None = None, raw: str = "") -> Date: return date.__new__(cls, year, month, day) def __init__( @@ -990,7 +1181,7 @@ def value(self) -> date: def as_string(self) -> str: return self._raw - def __add__(self, other): + def __add__(self, other: timedelta) -> Date: if PY38: result = date(self.year, self.month, self.day).__add__(other) else: @@ -998,26 +1189,26 @@ def __add__(self, other): return self._new(result) - def __sub__(self, other): + def __sub__(self, other: timedelta | date) -> Date | timedelta: # type: ignore[override] if PY38: result = date(self.year, self.month, self.day).__sub__(other) else: - result = super().__sub__(other) + result = super().__sub__(other) # type: ignore[operator] if isinstance(result, date): result = self._new(result) return result - def replace(self, *args: Any, **kwargs: Any) -> date: - return self._new(super().replace(*args, **kwargs)) + def replace(self, *args: object, **kwargs: object) -> date: # type: ignore[override] + return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type] - def _new(self, result): + def _new(self, result: date) -> Date: raw = result.isoformat() return Date(result.year, result.month, result.day, self._trivia, raw) - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[int, int, int, Trivia, str]: return (self.year, self.month, self.day, self._trivia, self._raw) @@ -1033,8 +1224,9 @@ def __new__( second: int, microsecond: int, tzinfo: tzinfo | None, - *_: Any, - ) -> time: + trivia: Trivia | None = None, + raw: str = "", + ) -> Time: return time.__new__(cls, hour, minute, second, microsecond, tzinfo) def __init__( @@ -1066,10 +1258,10 @@ def value(self) -> time: def as_string(self) -> str: return self._raw - def replace(self, *args: Any, **kwargs: Any) -> time: - return self._new(super().replace(*args, **kwargs)) + def replace(self, *args: object, **kwargs: object) -> time: # type: ignore[override] + return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type] - def _new(self, result): + def _new(self, result: time) -> Time: raw = result.isoformat() return Time( @@ -1082,7 +1274,7 @@ def _new(self, result): raw, ) - def _getstate(self, protocol: int = 3) -> tuple: + def _getstate(self, protocol: int = 3) -> tuple[int, int, int, int, tzinfo | None, Trivia, str]: return ( self.hour, self.minute, @@ -1110,9 +1302,7 @@ def __init__( self.comment = comment def __iter__(self) -> Iterator[Item]: - return filter( - lambda x: x is not None, (self.indent, self.value, self.comma, self.comment) - ) + return (x for x in (self.indent, self.value, self.comma, self.comment) if x is not None) def __repr__(self) -> str: return repr(tuple(self)) @@ -1128,7 +1318,7 @@ def __bool__(self) -> bool: return True -class Array(Item, _CustomList): +class Array(Item, _CustomList): # type: ignore[type-arg] """ An array literal """ @@ -1190,7 +1380,7 @@ def discriminant(self) -> int: return 8 @property - def value(self) -> list: + def value(self) -> list[Item]: return self def _iter_items(self) -> Iterator[Item]: @@ -1330,16 +1520,12 @@ def __len__(self) -> int: return list.__len__(self) def item(self, index: int) -> Item: - rv = list.__getitem__(self, index) - return cast(Item, rv) + return list.__getitem__(self, index) # type: ignore[no-any-return] - def __getitem__(self, key: int | slice) -> Any: - rv = list.__getitem__(self, key) - if isinstance(rv, Bool): - return rv.value - return rv + def __getitem__(self, key: int | slice) -> Item: # type: ignore[override] + return list.__getitem__(self, key) # type: ignore[return-value] - def __setitem__(self, key: int | slice, value: Any) -> Any: + def __setitem__(self, key: int | slice, value: Any) -> None: # type: ignore[override] it = item(value, _parent=self) list.__setitem__(self, key, it) if isinstance(key, slice): @@ -1348,7 +1534,7 @@ def __setitem__(self, key: int | slice, value: Any) -> Any: key += len(self) self._value[self._index_map[key]].value = it - def insert(self, pos: int, value: Any) -> None: + def insert(self, pos: int, value: Any) -> None: # type: ignore[override] it = item(value, _parent=self) length = len(self) if not isinstance(it, (Comment, Whitespace)): @@ -1370,13 +1556,14 @@ def insert(self, pos: int, value: Any) -> None: if idx >= 1 and self._value[idx - 1].is_whitespace(): # The last item is a pure whitespace(\n ), insert before it idx -= 1 + _indent = self._value[idx].indent if ( - self._value[idx].indent is not None - and "\n" in self._value[idx].indent.s + _indent is not None + and "\n" in _indent.s ): default_indent = "\n " - indent: Item | None = None - comma: Item | None = Whitespace(",") if pos < length else None + indent: Whitespace | None = None + comma: Whitespace | None = Whitespace(",") if pos < length else None if idx < len(self._value) and not self._value[idx].is_whitespace(): # Prefer to copy the indentation from the item after indent = self._value[idx].indent @@ -1398,7 +1585,7 @@ def insert(self, pos: int, value: Any) -> None: self._value.insert(idx, new_item) self._reindex() - def __delitem__(self, key: int | slice): + def __delitem__(self, key: int | slice) -> None: # type: ignore[override] length = len(self) list.__delitem__(self, key) @@ -1420,8 +1607,8 @@ def __delitem__(self, key: int | slice): if ( idx == 0 and len(self._value) > 0 - and self._value[idx].indent - and "\n" not in self._value[idx].indent.s + and (ind := self._value[idx].indent) + and "\n" not in ind.s ): # Remove the indentation of the first item if not newline self._value[idx].indent = None @@ -1471,11 +1658,11 @@ def __delitem__(self, key: int | slice): self._reindex() - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[list[Item], Trivia, bool]: return list(self._iter_items()), self._trivia, self._multiline -class AbstractTable(Item, _CustomDict): +class AbstractTable(Item, _CustomDict): # type: ignore[type-arg] """Common behaviour of both :class:`Table` and :class:`InlineTable`""" def __init__(self, value: container.Container, trivia: Trivia): @@ -1508,7 +1695,7 @@ def append(self: AT, key: None, value: Comment | Whitespace) -> AT: ... @overload def append(self: AT, key: Key | str, value: Any) -> AT: ... - def append(self, key, value): + def append(self: AT, key: Key | str | None, value: Any) -> AT: raise NotImplementedError @overload @@ -1517,13 +1704,16 @@ def add(self: AT, key: Comment | Whitespace) -> AT: ... @overload def add(self: AT, key: Key | str, value: Any = ...) -> AT: ... - def add(self, key, value=None): + def add(self: AT, key: Key | str | Comment | Whitespace, value: Any | None = None) -> AT: if value is None: if not isinstance(key, (Comment, Whitespace)): msg = "Non comment/whitespace items must have an associated key" raise ValueError(msg) - key, value = None, key + return self.append(None, key) + + if isinstance(key, (Comment, Whitespace)): + raise ValueError("Comment/Whitespace keys must not have a value") return self.append(key, value) @@ -1541,12 +1731,11 @@ def remove(self: AT, key: Key | str) -> AT: def item(self, key: Key | str) -> Item: return self._value.item(key) - def setdefault(self, key: Key | str, default: Any) -> Any: + def setdefault(self, key: Key | str, default: Any) -> Item: # type: ignore[override] super().setdefault(key, default) return self[key] - def __str__(self): - return str(self.value) + def __str__(self) -> str: return str(self.value) def copy(self: AT) -> AT: return copy.copy(self) @@ -1560,13 +1749,13 @@ def __iter__(self) -> Iterator[str]: def __len__(self) -> int: return len(self._value) - def __delitem__(self, key: Key | str) -> None: + def __delitem__(self, key: Key | str) -> None: # type: ignore[override] self.remove(key) - def __getitem__(self, key: Key | str) -> Item: - return cast(Item, self._value[key]) + def __getitem__(self, key: Key | str) -> Item: # type: ignore[override] + return self._value[key] - def __setitem__(self, key: Key | str, value: Any) -> None: + def __setitem__(self, key: Key | str, value: Any) -> None: # type: ignore[override] if not isinstance(value, Item): value = item(value, _parent=self) @@ -1722,7 +1911,7 @@ def indent(self, indent: int) -> Table: return self - def invalidate_display_name(self): + def invalidate_display_name(self) -> None: """Call ``invalidate_display_name`` on the contained tables""" self.display_name = None @@ -1730,7 +1919,7 @@ def invalidate_display_name(self): if hasattr(child, "invalidate_display_name"): child.invalidate_display_name() - def _getstate(self, protocol: int = 3) -> tuple: + def _getstate(self, protocol: int = 3) -> tuple[container.Container, Trivia, bool, bool | None, str | None, str | None]: return ( self._value, self._trivia, @@ -1847,7 +2036,7 @@ def as_string(self) -> str: return buf - def __setitem__(self, key: Key | str, value: Any) -> None: + def __setitem__(self, key: Key | str, value: Any) -> None: # type: ignore[override] if hasattr(value, "trivia") and value.trivia.comment: value.trivia.comment = "" super().__setitem__(key, value) @@ -1855,16 +2044,16 @@ def __setitem__(self, key: Key | str, value: Any) -> None: def __copy__(self) -> InlineTable: return type(self)(self._value.copy(), self._trivia.copy(), self._new) - def _getstate(self, protocol: int = 3) -> tuple: + def _getstate(self, protocol: int = 3) -> tuple[container.Container, Trivia]: return (self._value, self._trivia) -class String(str, Item): +class String(str, Item): # type: ignore[misc] """ A string literal. """ - def __new__(cls, t, value, original, trivia): + def __new__(cls, t: StringType, value: str, original: str, trivia: Trivia) -> String: return super().__new__(cls, value) def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None: @@ -1891,9 +2080,7 @@ def as_string(self) -> str: def type(self) -> StringType: return self._t - def __add__(self: ItemT, other: str) -> ItemT: - if not isinstance(other, str): - return NotImplemented + def __add__(self, other: str) -> String: result = super().__add__(other) original = self._original + getattr(other, "_original", other) @@ -1902,11 +2089,11 @@ def __add__(self: ItemT, other: str) -> ItemT: def _new(self, result: str, original: str) -> String: return String(self._t, result, original, self._trivia) - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[StringType, str, str, Trivia]: return self._t, str(self), self._original, self._trivia @classmethod - def from_raw(cls, value: str, type_=StringType.SLB, escape=True) -> String: + def from_raw(cls, value: str, type_: StringType = StringType.SLB, escape: bool = True) -> String: value = decode(value) invalid = type_.invalid_sequences @@ -1919,7 +2106,7 @@ def from_raw(cls, value: str, type_=StringType.SLB, escape=True) -> String: return cls(type_, decode(value), string_value, Trivia()) -class AoT(Item, _CustomList): +class AoT(Item, _CustomList): # type: ignore[type-arg] """ An array of table literal """ @@ -1954,29 +2141,29 @@ def discriminant(self) -> int: return 12 @property - def value(self) -> list[dict[Any, Any]]: + def value(self) -> list[dict[str, Any]]: return [v.value for v in self._body] def __len__(self) -> int: return len(self._body) - @overload + @overload # type: ignore[override] def __getitem__(self, key: slice) -> list[Table]: ... @overload def __getitem__(self, key: int) -> Table: ... - def __getitem__(self, key): + def __getitem__(self, key: int | slice) -> Table | list[Table]: return self._body[key] - def __setitem__(self, key: slice | int, value: Any) -> None: + def __setitem__(self, key: slice | int, value: Any) -> None: # type: ignore[override] self._body[key] = item(value, _parent=self) - def __delitem__(self, key: slice | int) -> None: + def __delitem__(self, key: slice | int) -> None: # type: ignore[override] del self._body[key] list.__delitem__(self, key) - def insert(self, index: int, value: dict) -> None: + def insert(self, index: int, value: dict[str, Any]) -> None: # type: ignore[override] value = item(value, _parent=self) if not isinstance(value, Table): raise ValueError(f"Unsupported insert value type: {type(value)}") @@ -2006,7 +2193,7 @@ def insert(self, index: int, value: dict) -> None: self._body.insert(index, value) list.insert(self, index, value) - def invalidate_display_name(self): + def invalidate_display_name(self) -> None: """Call ``invalidate_display_name`` on the contained tables""" for child in self: if hasattr(child, "invalidate_display_name"): @@ -2022,7 +2209,7 @@ def as_string(self) -> str: def __repr__(self) -> str: return f"" - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[list[Table], str | None, bool]: return self._body, self.name, self._parsed @@ -2048,5 +2235,5 @@ def value(self) -> None: def as_string(self) -> str: return "" - def _getstate(self, protocol=3) -> tuple: + def _getstate(self, protocol: int = 3) -> tuple[()]: return () diff --git a/tomlkit/parser.py b/tomlkit/parser.py index 7321e0a9..538ed03e 100644 --- a/tomlkit/parser.py +++ b/tomlkit/parser.py @@ -4,6 +4,8 @@ import re import string +from typing import Any + from tomlkit._compat import decode from tomlkit._utils import RFC_3339_LOOSE from tomlkit._utils import _escaped @@ -44,6 +46,7 @@ from tomlkit.items import Trivia from tomlkit.items import Whitespace from tomlkit.source import Source +from tomlkit.source import _StateHandler from tomlkit.toml_char import TOMLChar from tomlkit.toml_document import TOMLDocument @@ -67,19 +70,19 @@ def __init__(self, string: str | bytes) -> None: self._aot_stack: list[Key] = [] @property - def _state(self): + def _state(self) -> _StateHandler: return self._src.state @property - def _idx(self): + def _idx(self) -> int: return self._src.idx @property - def _current(self): + def _current(self) -> TOMLChar: return self._src.current @property - def _marker(self): + def _marker(self) -> int: return self._src.marker def extract(self) -> str: @@ -102,7 +105,7 @@ def inc_n(self, n: int, exception: type[ParseError] | None = None) -> bool: """ return self._src.inc_n(n=n, exception=exception) - def consume(self, chars, min=0, max=-1): + def consume(self, chars: str, min: int = 0, max: int = -1) -> None: """ Consume chars until min/max is satisfied is valid. """ @@ -120,7 +123,12 @@ def mark(self) -> None: """ self._src.mark() - def parse_error(self, exception=ParseError, *args, **kwargs): + def parse_error( + self, + exception: type[ParseError] = ParseError, + *args: Any, + **kwargs: Any, + ) -> ParseError: """ Creates a generic "parse error" at the current position. """ @@ -233,7 +241,7 @@ def _parse_item(self) -> tuple[Key | None, Item] | None: return None, Comment(Trivia(indent, cws, comment, trail)) elif c == "[": # Found a table, delegate to the calling function. - return + return None else: # Beginning of a KV pair. # Return to beginning of whitespace so it gets included @@ -396,12 +404,12 @@ def _parse_quoted_key(self) -> Key: while self._current.is_spaces() and self.inc(): pass original += self.extract() - key = SingleKey(str(key_str), t=key_type, sep="", original=original) + result: Key = SingleKey(str(key_str), t=key_type, sep="", original=original) if self._current == ".": self.inc() - key = key.concat(self._parse_key()) + result = result.concat(self._parse_key()) - return key + return result def _parse_bare_key(self) -> Key: """ @@ -413,22 +421,22 @@ def _parse_bare_key(self) -> Key: pass original = self.extract() - key = original.strip() - if not key: + key_s = original.strip() + if not key_s: # Empty key raise self.parse_error(EmptyKeyError) - if " " in key: + if " " in key_s: # Bare key with spaces in it - raise self.parse_error(ParseError, f'Invalid key "{key}"') + raise self.parse_error(ParseError, f'Invalid key "{key_s}"') - key = SingleKey(key, KeyType.Bare, "", original) + result: Key = SingleKey(key_s, KeyType.Bare, "", original) if self._current == ".": self.inc() - key = key.concat(self._parse_key()) + result = result.concat(self._parse_key()) - return key + return result def _parse_value(self) -> Item: """ @@ -554,10 +562,10 @@ def _parse_value(self) -> Item: else: raise self.parse_error(UnexpectedCharError, c) - def _parse_true(self): + def _parse_true(self) -> Bool: return self._parse_bool(BoolType.TRUE) - def _parse_false(self): + def _parse_false(self) -> Bool: return self._parse_bool(BoolType.FALSE) def _parse_bool(self, style: BoolType) -> Bool: @@ -633,6 +641,8 @@ def _parse_array(self) -> Array: else: return res + raise self.parse_error(ParseError, "Failed to parse array") + def _parse_inline_table(self) -> InlineTable: # consume opening bracket, EOF here is an issue (middle of array) self.inc(exception=UnexpectedEofError) @@ -732,7 +742,7 @@ def _parse_basic_string(self) -> String: with self._state: return self._parse_string(StringType.SLB) - def _parse_escaped_char(self, multiline): + def _parse_escaped_char(self, multiline: bool) -> str: if multiline and self._current.is_ws(): # When the last non-whitespace character on a line is # a \, it will be trimmed along with all whitespace @@ -768,6 +778,7 @@ def _parse_escaped_char(self, multiline): # this needs to be a unicode u, ue = self._peek_unicode(self._current == "U") if u is not None: + assert ue is not None # consume the U char and the unicode value self.inc_n(len(ue) + 1) @@ -778,6 +789,7 @@ def _parse_escaped_char(self, multiline): if self._current == "x": h, he = self._peek_hex() if h is not None: + assert he is not None # consume the x char and the hex value self.inc_n(len(he) + 1) return h @@ -819,7 +831,7 @@ def _parse_string(self, delim: StringType) -> String: # consume the newline, EOF here is an issue (middle of string) self.inc(exception=UnexpectedEofError) else: - cur = self._current + cur: str = self._current with self._state(restore=True): if self.inc(): cur += self._current @@ -964,7 +976,7 @@ def _parse_table( cws, comment, trail = self._parse_comment_trail() - result = Null() + result: Table | AoT = Null() # type: ignore[assignment] table = Table( values, Trivia(indent, cws, comment, trail), @@ -1019,11 +1031,11 @@ def _parse_table( key = name_parts[0] while not self.end(): - item = self._parse_item() - if item: - _key, item = item - if not self._merge_ws(item, values): - table.raw_append(_key, item) + parsed = self._parse_item() + if parsed: + _key, _val = parsed + if not self._merge_ws(_val, values): + table.raw_append(_key, _val) else: if self._current == "[": _, key_next = self._peek_table() @@ -1091,12 +1103,13 @@ def _parse_aot(self, first: Table, name_first: Key) -> AoT: Parses all siblings of the provided table first and bundles them into an AoT. """ - payload = [first] + payload: list[Table] = [first] self._aot_stack.append(name_first) while not self.end(): is_aot_next, name_next = self._peek_table() if is_aot_next and name_next == name_first: _, table = self._parse_table(name_first) + assert isinstance(table, Table) payload.append(table) else: break diff --git a/tomlkit/source.py b/tomlkit/source.py index 8a8b2c3d..327c6279 100644 --- a/tomlkit/source.py +++ b/tomlkit/source.py @@ -28,7 +28,12 @@ def __enter__(self) -> _State: return self - def __exit__(self, exception_type, exception_val, trace): + def __exit__( + self, + exception_type: type[BaseException] | None, + exception_val: BaseException | None, + trace: Any, + ) -> None: # Exiting this context manager - restore the prior state if self.restore or exception_type: self._source._chars = self._chars @@ -45,19 +50,28 @@ class _StateHandler: def __init__(self, source: Source) -> None: self._source = source - self._states = [] + self._states: list[_State] = [] - def __call__(self, *args, **kwargs): - return _State(self._source, *args, **kwargs) + def __call__( + self, + save_marker: bool | None = False, + restore: bool | None = False, + ) -> _State: + return _State(self._source, save_marker, restore) def __enter__(self) -> _State: state = self() self._states.append(state) return state.__enter__() - def __exit__(self, exception_type, exception_val, trace): + def __exit__( + self, + exception_type: type[BaseException] | None, + exception_val: BaseException | None, + trace: Any, + ) -> None: state = self._states.pop() - return state.__exit__(exception_type, exception_val, trace) + state.__exit__(exception_type, exception_val, trace) class Source(str): @@ -77,7 +91,7 @@ def __init__(self, _: str) -> None: self.inc() - def reset(self): + def reset(self) -> None: # initialize both idx and current self.inc() @@ -130,7 +144,7 @@ def inc_n(self, n: int, exception: type[ParseError] | None = None) -> bool: """ return all(self.inc(exception=exception) for _ in range(n)) - def consume(self, chars, min=0, max=-1): + def consume(self, chars: str, min: int = 0, max: int = -1) -> None: """ Consume chars until min/max is satisfied is valid. """ diff --git a/tomlkit/toml_char.py b/tomlkit/toml_char.py index b4bb4110..970cbd4b 100644 --- a/tomlkit/toml_char.py +++ b/tomlkit/toml_char.py @@ -2,7 +2,7 @@ class TOMLChar(str): - def __init__(self, c): + def __init__(self, c: str) -> None: super().__init__() if len(self) > 1: diff --git a/tomlkit/toml_file.py b/tomlkit/toml_file.py index b8ffcbd8..324576bf 100644 --- a/tomlkit/toml_file.py +++ b/tomlkit/toml_file.py @@ -24,7 +24,7 @@ class TOMLFile: def __init__(self, path: _StrPath) -> None: self._path = path - self._linesep = os.linesep + self._linesep: str = os.linesep def read(self) -> TOMLDocument: """Read the file content as a :class:`tomlkit.toml_document.TOMLDocument`.""" From f20e3676619102c01eb8a3fc9cd48855502af613 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 22 Mar 2026 14:30:39 +0000 Subject: [PATCH 2/6] round 2: ignore less --- tests/test_api.py | 2 +- tests/test_build.py | 11 ++- tests/test_items.py | 182 ++++++++++++++++++------------------ tests/test_toml_document.py | 98 +++++++++---------- tests/test_toml_file.py | 8 +- tomlkit/api.py | 15 +-- tomlkit/container.py | 15 +-- tomlkit/items.py | 68 +++++++++++--- 8 files changed, 217 insertions(+), 182 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index f4093971..8038f5af 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -312,7 +312,7 @@ def test_build_super_table() -> None: def test_add_dotted_key() -> None: doc = tomlkit.document() - doc.add(tomlkit.key(["foo", "bar"]), 1) # type: ignore[arg-type] + doc.add(tomlkit.key(["foo", "bar"]), 1) assert doc.as_string() == "foo.bar = 1\n" table = tomlkit.table() diff --git a/tests/test_build.py b/tests/test_build.py index 2ea35f89..6b64fcc7 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -18,7 +18,7 @@ def test_build_example(example: Callable[[str], str]) -> None: doc = document() doc.add(comment("This is a TOML document. Boom.")) doc.add(nl()) - doc.add("title", "TOML Example") # type: ignore[arg-type] + doc.add("title", "TOML Example") owner = table() owner.add("name", "Tom Preston-Werner") @@ -41,9 +41,10 @@ def test_build_example(example: Callable[[str], str]) -> None: servers.add(nl()) c = comment( "You can indent as you please. Tabs or spaces. TOML don't care." - ).indent(2) + ) + c.indent(2) c.trivia.trail = "" - servers.add(c) # type: ignore[call-overload] + servers.add(c) alpha = table() servers.append("alpha", alpha) alpha.indent(2) @@ -100,7 +101,7 @@ def test_add_remove() -> None: content = "" doc = parse(content) - doc.append("foo", "bar") # type: ignore[arg-type] + doc.append("foo", "bar") assert ( doc.as_string() @@ -125,7 +126,7 @@ def test_append_table_after_multiple_indices() -> None: version = "*" """ doc = parse(content) - doc.append("foobar", {"name": "John"}) # type: ignore[arg-type] + doc.append("foobar", {"name": "John"}) def test_top_level_keys_are_put_at_the_root_of_the_document() -> None: diff --git a/tests/test_items.py b/tests/test_items.py index d094b8d1..a70b1cae 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -138,7 +138,7 @@ def test_aot_set_item() -> None: assert d[0] == "C" d[1]["b"] = "D" assert isinstance(d[1], InlineTable) - assert d[1]["b"] == "D" # type: ignore[comparison-overlap] + assert d[1]["b"] == "D" d[0] = ["c", "C"] assert isinstance(d[0], Array) assert d[0][1] == "C" @@ -228,7 +228,9 @@ def test_items_can_be_appended_to_and_removed_from_an_inline_table() -> None: """ parser = Parser(string) - _, table = parser._parse_item() # type: ignore[misc] + result = parser._parse_item() + assert result is not None + _, table = result assert isinstance(table, InlineTable) assert table.as_string() == "{}" @@ -257,27 +259,27 @@ def test_inf_and_nan_are_supported(example: Callable[[str], str]) -> None: content = example("0.5.0") doc = parse(content) - assert doc["sf1"] == float("inf") # type: ignore[comparison-overlap] - assert doc["sf2"] == float("inf") # type: ignore[comparison-overlap] - assert doc["sf3"] == float("-inf") # type: ignore[comparison-overlap] + assert doc["sf1"] == float("inf") + assert doc["sf2"] == float("inf") + assert doc["sf3"] == float("-inf") - assert math.isnan(doc["sf4"]) # type: ignore[arg-type] - assert math.isnan(doc["sf5"]) # type: ignore[arg-type] - assert math.isnan(doc["sf6"]) # type: ignore[arg-type] + assert math.isnan(doc["sf4"]) + assert math.isnan(doc["sf5"]) + assert math.isnan(doc["sf6"]) def test_hex_octal_and_bin_integers_are_supported(example: Callable[[str], str]) -> None: content = example("0.5.0") doc = parse(content) - assert doc["hex1"] == 3735928559 # type: ignore[comparison-overlap] - assert doc["hex2"] == 3735928559 # type: ignore[comparison-overlap] - assert doc["hex3"] == 3735928559 # type: ignore[comparison-overlap] + assert doc["hex1"] == 3735928559 + assert doc["hex2"] == 3735928559 + assert doc["hex3"] == 3735928559 - assert doc["oct1"] == 342391 # type: ignore[comparison-overlap] - assert doc["oct2"] == 493 # type: ignore[comparison-overlap] + assert doc["oct1"] == 342391 + assert doc["oct2"] == 493 - assert doc["bin1"] == 214 # type: ignore[comparison-overlap] + assert doc["bin1"] == 214 def test_key_automatically_sets_proper_string_type_if_not_bare() -> None: @@ -325,9 +327,9 @@ def test_array_behaves_like_a_list() -> None: doc = parse(content) assert str(doc["a"]) == "[1, 2]" - assert doc["a"] == [1, 2] # type: ignore[comparison-overlap] - doc["a"] += [3, 4] # type: ignore[operator] - assert doc["a"] == [1, 2, 3, 4] # type: ignore[comparison-overlap] + assert doc["a"] == [1, 2] + doc["a"] += [3, 4] + assert doc["a"] == [1, 2, 3, 4] assert ( doc.as_string() == """a = [1, 2, 3, 4] # Comment @@ -353,10 +355,10 @@ def test_array_multiline() -> None: assert expected == t.as_string() - t = item([]) # type: ignore[assignment] - t.multiline(True) + t2: Any = item([]) + t2.multiline(True) - assert t.as_string() == "[]" + assert t2.as_string() == "[]" def test_array_multiline_modify() -> None: @@ -366,14 +368,14 @@ def test_array_multiline_modify() -> None: "abc" ]""" ) - doc["a"].append("def") # type: ignore[attr-defined] + doc["a"].append("def") expected = """\ a = [ "abc", "def" ]""" assert expected == doc.as_string() - doc["a"].insert(1, "ghi") # type: ignore[attr-defined] + doc["a"].insert(1, "ghi") expected = """\ a = [ "abc", @@ -385,16 +387,16 @@ def test_array_multiline_modify() -> None: def test_append_to_empty_array() -> None: doc = parse("x = [ ]") - doc["x"].append("a") # type: ignore[attr-defined] + doc["x"].append("a") assert doc.as_string() == 'x = ["a" ]' doc = parse("x = [\n]") - doc["x"].append("a") # type: ignore[attr-defined] + doc["x"].append("a") assert doc.as_string() == 'x = [\n "a"\n]' def test_modify_array_with_comment() -> None: doc = parse("x = [ # comment\n]") - doc["x"].append("a") # type: ignore[attr-defined] + doc["x"].append("a") assert doc.as_string() == 'x = [ # comment\n "a"\n]' doc = parse( """\ @@ -404,7 +406,7 @@ def test_modify_array_with_comment() -> None: "b" ]""" ) - doc["x"].insert(1, "c") # type: ignore[attr-defined] + doc["x"].insert(1, "c") expected = """\ x = [ "a", @@ -419,7 +421,7 @@ def test_modify_array_with_comment() -> None: 1 # comment ]""" ) - doc["x"].append(2) # type: ignore[attr-defined] + doc["x"].append(2) assert ( doc.as_string() == """\ @@ -428,7 +430,7 @@ def test_modify_array_with_comment() -> None: 2 ]""" ) - doc["x"].pop(0) # type: ignore[attr-defined] + doc["x"].pop(0) assert doc.as_string() == "x = [\n 2\n]" @@ -442,7 +444,7 @@ def test_append_to_multiline_array_with_comment() -> None: ] """ ) - doc["x"].multiline(True).append(3) # type: ignore[attr-defined] + doc["x"].multiline(True).append(3) assert ( doc.as_string() == """\ @@ -454,7 +456,7 @@ def test_append_to_multiline_array_with_comment() -> None: ] """ ) - assert doc["x"].pop() == 3 # type: ignore[attr-defined] + assert doc["x"].pop() == 3 assert ( doc.as_string() == """\ @@ -469,7 +471,7 @@ def test_append_to_multiline_array_with_comment() -> None: def test_append_dict_to_array() -> None: doc = parse("x = []") - doc["x"].append({"name": "John Doe", "email": "john@doe.com"}) # type: ignore[attr-defined] + 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 @@ -598,31 +600,31 @@ def test_item_array_of_dicts_converted_to_aot() -> None: def test_add_float_to_int() -> None: content = "[table]\nmy_int = 2043" doc = parse(content) - doc["table"]["my_int"] += 5.0 # type: ignore[operator] - assert doc["table"]["my_int"] == 2048.0 # type: ignore[comparison-overlap] + doc["table"]["my_int"] += 5.0 + assert doc["table"]["my_int"] == 2048.0 assert isinstance(doc["table"]["my_int"], float) def test_sub_float_from_int() -> None: content = "[table]\nmy_int = 2048" doc = parse(content) - doc["table"]["my_int"] -= 5.0 # type: ignore[operator] - assert doc["table"]["my_int"] == 2043.0 # type: ignore[comparison-overlap] + doc["table"]["my_int"] -= 5.0 + assert doc["table"]["my_int"] == 2043.0 assert isinstance(doc["table"]["my_int"], float) def test_sub_int_from_float() -> None: content = "[table]\nmy_int = 2048.0" doc = parse(content) - doc["table"]["my_int"] -= 5 # type: ignore[operator] - assert doc["table"]["my_int"] == 2043.0 # type: ignore[comparison-overlap] + doc["table"]["my_int"] -= 5 + assert doc["table"]["my_int"] == 2043.0 def test_add_sum_int_with_float() -> None: content = "[table]\nmy_int = 2048.3" doc = parse(content) - doc["table"]["my_int"] += 5 # type: ignore[operator] - assert doc["table"]["my_int"] == 2053.3 # type: ignore[comparison-overlap] + doc["table"]["my_int"] += 5 + assert doc["table"]["my_int"] == 2053.3 def test_integers_behave_like_ints() -> None: @@ -635,16 +637,16 @@ def test_integers_behave_like_ints() -> None: assert i == 35 assert i.as_string() == "35" - i -= 2 # type: ignore[assignment] + i -= 2 assert i == 33 assert i.as_string() == "33" - i /= 2 # type: ignore[assignment] - assert i == 16.5 - assert i.as_string() == "16.5" + f = i / 2 + assert f == 16.5 + assert f.as_string() == "16.5" doc = parse("int = +34") - doc["int"] += 1 # type: ignore[operator] + doc["int"] += 1 assert doc.as_string() == "int = +35" @@ -659,12 +661,12 @@ def test_floats_behave_like_floats() -> None: assert i == 35.12 assert i.as_string() == "35.12" - i -= 2 # type: ignore[assignment] + i -= 2 assert i == 33.12 assert i.as_string() == "33.12" doc = parse("float = +34.12") - doc["float"] += 1 # type: ignore[operator] + doc["float"] += 1 assert doc.as_string() == "float = +35.12" @@ -679,20 +681,20 @@ def test_datetimes_behave_like_datetimes(tz_utc: tzinfo, tz_pst: tzinfo) -> None assert i == datetime(2018, 7, 23, 12, 34, 56) assert i.as_string() == "2018-07-23T12:34:56" - i -= timedelta(days=2) # type: ignore[assignment] + i -= timedelta(days=2) assert i == datetime(2018, 7, 21, 12, 34, 56) assert i.as_string() == "2018-07-21T12:34:56" - i = i.replace(year=2019, tzinfo=tz_utc) # type: ignore[assignment] + i = i.replace(year=2019, tzinfo=tz_utc) assert i == datetime(2019, 7, 21, 12, 34, 56, tzinfo=tz_utc) assert i.as_string() == "2019-07-21T12:34:56+00:00" - i = i.astimezone(tz_pst) # type: ignore[assignment] + i = i.astimezone(tz_pst) assert i == datetime(2019, 7, 21, 4, 34, 56, tzinfo=tz_pst) assert i.as_string() == "2019-07-21T04:34:56-08:00" doc = parse("dt = 2018-07-22T12:34:56-05:00") - doc["dt"] += timedelta(days=1) # type: ignore[operator] + doc["dt"] += timedelta(days=1) assert doc.as_string() == "dt = 2018-07-23T12:34:56-05:00" @@ -707,16 +709,16 @@ def test_dates_behave_like_dates() -> None: assert i == date(2018, 7, 23) assert i.as_string() == "2018-07-23" - i -= timedelta(days=2) # type: ignore[assignment] + i -= timedelta(days=2) assert i == date(2018, 7, 21) assert i.as_string() == "2018-07-21" - i = i.replace(year=2019) # type: ignore[assignment] + i = i.replace(year=2019) assert i == date(2019, 7, 21) assert i.as_string() == "2019-07-21" doc = parse("dt = 2018-07-22 # Comment") - doc["dt"] += timedelta(days=1) # type: ignore[operator] + doc["dt"] += timedelta(days=1) assert doc.as_string() == "dt = 2018-07-23 # Comment" @@ -738,7 +740,7 @@ def test_times_behave_like_times() -> None: assert i == time(12, 34, 56) assert i.as_string() == "12:34:56" - i = i.replace(hour=13) # type: ignore[assignment] + i = i.replace(hour=13) assert i == time(13, 34, 56) assert i.as_string() == "13:34:56" @@ -758,14 +760,14 @@ def test_strings_behave_like_strs() -> None: assert i.as_string() == '"foo bar é"' doc = parse('str = "foo" # Comment') - doc["str"] += " bar" # type: ignore[operator] + doc["str"] += " bar" assert doc.as_string() == 'str = "foo bar" # Comment' def test_string_add_preserve_escapes() -> None: - i = api.value('"foo\\"bar"') - i += " baz" # type: ignore[operator] + i: Any = api.value('"foo\\"bar"') + i += " baz" assert i == 'foo"bar baz' assert i.as_string() == '"foo\\"bar baz"' @@ -799,7 +801,7 @@ def test_tables_behave_like_dicts() -> None: ) assert t.get("bar") == "boom" - assert t.setdefault("foobar", "fuzz") == "fuzz" # type: ignore[comparison-overlap] + assert t.setdefault("foobar", "fuzz") == "fuzz" assert ( t.as_string() == """foo = "bar" @@ -810,58 +812,58 @@ def test_tables_behave_like_dicts() -> None: def test_items_are_pickable() -> None: - n = item(12) + n: Item = item(12) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "12" - n = item(12.34) # type: ignore[assignment] + n = item(12.34) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "12.34" - n = item(True) # type: ignore[assignment] + n = item(True) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "true" - n = item(datetime(2018, 10, 11, 12, 34, 56, 123456)) # type: ignore[assignment] + n = item(datetime(2018, 10, 11, 12, 34, 56, 123456)) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "2018-10-11T12:34:56.123456" - n = item(date(2018, 10, 11)) # type: ignore[assignment] + n = item(date(2018, 10, 11)) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "2018-10-11" - n = item(time(12, 34, 56, 123456)) # type: ignore[assignment] + n = item(time(12, 34, 56, 123456)) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "12:34:56.123456" - n = item([1, 2, 3]) # type: ignore[assignment] + n = item([1, 2, 3]) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "[1, 2, 3]" - n = item({"foo": "bar"}) # type: ignore[assignment] + n = item({"foo": "bar"}) s = pickle.dumps(n) assert pickle.loads(s).as_string() == 'foo = "bar"\n' - n = api.inline_table() # type: ignore[assignment] + n = api.inline_table() n["foo"] = "bar" s = pickle.dumps(n) assert pickle.loads(s).as_string() == '{foo = "bar"}' - n = item("foo") # type: ignore[assignment] + n = item("foo") s = pickle.dumps(n) assert pickle.loads(s).as_string() == '"foo"' - n = item([{"foo": "bar"}]) # type: ignore[assignment] + n = item([{"foo": "bar"}]) s = pickle.dumps(n) assert pickle.loads(s).as_string() == 'foo = "bar"\n' @@ -930,7 +932,7 @@ def test_booleans_comparison() -> None: """ content = parse(s) - assert content["foo"]["value"] is False # type: ignore[comparison-overlap] + assert content["foo"]["value"] is False assert isinstance(content["foo"].item("value"), Bool) assert {"foo": {"value": False}} == content @@ -973,19 +975,19 @@ def test_escape_key(key_str: str, escaped: str) -> None: def test_custom_encoders() -> None: import decimal - @api.register_encoder # type: ignore[type-var] + @api.register_encoder def encode_decimal(obj: Any) -> Item: if isinstance(obj, decimal.Decimal): return api.float_(str(obj)) raise TypeError - assert api.item(decimal.Decimal("1.23")).as_string() == "1.23" # type: ignore[call-overload] + assert api.item(decimal.Decimal("1.23")).as_string() == "1.23" with pytest.raises(TypeError): - api.item(object()) # type: ignore[call-overload] + api.item(object()) assert api.dumps({"foo": decimal.Decimal("1.23")}) == "foo = 1.23\n" - api.unregister_encoder(encode_decimal) # type: ignore[arg-type] + api.unregister_encoder(encode_decimal) def test_custom_encoders_with_parent_and_sort_keys() -> None: @@ -1005,7 +1007,7 @@ def encode_decimal_with_context(obj: Any, _parent: Item | None = None, _sort_key raise TypeError # Test with default parameters - result = api.item(decimal.Decimal("1.23")) # type: ignore[call-overload] + result = api.item(decimal.Decimal("1.23")) assert result.as_string() == "1.23" assert parent_captured is None assert sort_keys_captured is False @@ -1014,7 +1016,7 @@ def encode_decimal_with_context(obj: Any, _parent: Item | None = None, _sort_key parent_captured = None sort_keys_captured = None table = api.table() - result = item(decimal.Decimal("4.56"), _parent=table, _sort_keys=True) # type: ignore[call-overload] + result = item(decimal.Decimal("4.56"), _parent=table, _sort_keys=True) assert result.as_string() == "4.56" assert parent_captured is table assert sort_keys_captured is True @@ -1026,7 +1028,7 @@ def test_custom_encoders_backward_compatibility() -> None: """Test that old-style custom encoders still work without modification.""" import decimal - @api.register_encoder # type: ignore[type-var] + @api.register_encoder def encode_decimal_old_style(obj: Any) -> Item: # Old style encoder - only accepts obj parameter if isinstance(obj, decimal.Decimal): @@ -1034,15 +1036,15 @@ def encode_decimal_old_style(obj: Any) -> Item: raise TypeError # Should work exactly as before - result = api.item(decimal.Decimal("2.34")) # type: ignore[call-overload] + result = api.item(decimal.Decimal("2.34")) assert result.as_string() == "2.34" # Should work when called from item() with extra parameters table = api.table() - result = item(decimal.Decimal("5.67"), _parent=table, _sort_keys=True) # type: ignore[call-overload] + result = item(decimal.Decimal("5.67"), _parent=table, _sort_keys=True) assert result.as_string() == "5.67" - api.unregister_encoder(encode_decimal_old_style) # type: ignore[arg-type] + api.unregister_encoder(encode_decimal_old_style) def test_custom_encoders_with_kwargs() -> None: @@ -1051,7 +1053,7 @@ def test_custom_encoders_with_kwargs() -> None: kwargs_captured = None - @api.register_encoder # type: ignore[type-var] + @api.register_encoder def encode_decimal_with_kwargs(obj: Any, **kwargs: Any) -> Item: nonlocal kwargs_captured if isinstance(obj, decimal.Decimal): @@ -1061,11 +1063,11 @@ def encode_decimal_with_kwargs(obj: Any, **kwargs: Any) -> Item: # Test with parent and sort_keys passed as kwargs table = api.table() - result = item(decimal.Decimal("7.89"), _parent=table, _sort_keys=True) # type: ignore[call-overload] + result = item(decimal.Decimal("7.89"), _parent=table, _sort_keys=True) assert result.as_string() == "7.89" assert kwargs_captured == {"_parent": table, "_sort_keys": True} - api.unregister_encoder(encode_decimal_with_kwargs) # type: ignore[arg-type] + api.unregister_encoder(encode_decimal_with_kwargs) def test_custom_encoders_for_complex_objects() -> None: @@ -1088,7 +1090,7 @@ def encode_custom_dict(obj: Any, _parent: Item | None = None, _sort_keys: bool = # Test with nested structure custom_obj = CustomDict({"a": 1, "b": {"c": 2, "d": 3}}) - result = item(custom_obj, _sort_keys=True) # type: ignore[call-overload] + result = item(custom_obj, _sort_keys=True) # Should properly format as a table with sorted keys expected = """a = 1 @@ -1105,16 +1107,16 @@ def encode_custom_dict(obj: Any, _parent: Item | None = None, _sort_keys: bool = def test_no_extra_minus_sign() -> None: doc = parse("a = -1") assert doc.as_string() == "a = -1" - doc["a"] *= -1 # type: ignore[operator] + doc["a"] *= -1 assert doc.as_string() == "a = +1" - doc["a"] *= -1 # type: ignore[operator] + doc["a"] *= -1 assert doc.as_string() == "a = -1" doc = parse("a = -1.5") assert doc.as_string() == "a = -1.5" - doc["a"] *= -1 # type: ignore[operator] + doc["a"] *= -1 assert doc.as_string() == "a = +1.5" - doc["a"] *= -1 # type: ignore[operator] + doc["a"] *= -1 assert doc.as_string() == "a = -1.5" @@ -1192,12 +1194,12 @@ def test_array_item_removal_newline_restore_next() -> None: docstr = "x = [\n '0',\n '1','2'\n]" doc = parse(docstr) - doc["x"].remove("1") # type: ignore[attr-defined] + doc["x"].remove("1") assert doc.as_string() == expected parse(doc.as_string()) docstr = "x = [\n '0',\n '1', '2'\n]" doc = parse(docstr) - doc["x"].remove("1") # type: ignore[attr-defined] + doc["x"].remove("1") assert doc.as_string() == expected parse(doc.as_string()) diff --git a/tests/test_toml_document.py b/tests/test_toml_document.py index c9c507cf..a8066a82 100644 --- a/tests/test_toml_document.py +++ b/tests/test_toml_document.py @@ -33,18 +33,18 @@ def test_document_is_a_dict(example: Callable[[str], str]) -> None: assert doc.get("owner") == owner assert isinstance(owner, dict) assert "name" in owner - assert owner["name"] == "Tom Preston-Werner" # type: ignore[comparison-overlap] - assert owner["organization"] == "GitHub" # type: ignore[comparison-overlap] - assert owner["bio"] == "GitHub Cofounder & CEO\nLikes tater tots and beer." # type: ignore[comparison-overlap] + assert owner["name"] == "Tom Preston-Werner" + assert owner["organization"] == "GitHub" + assert owner["bio"] == "GitHub Cofounder & CEO\nLikes tater tots and beer." assert owner["dob"] == datetime(1979, 5, 27, 7, 32, tzinfo=_utc) # database database = doc["database"] assert isinstance(database, dict) - assert database["server"] == "192.168.1.1" # type: ignore[comparison-overlap] - assert database["ports"] == [8001, 8001, 8002] # type: ignore[comparison-overlap] - assert database["connection_max"] == 5000 # type: ignore[comparison-overlap] - assert database["enabled"] is True # type: ignore[comparison-overlap] + assert database["server"] == "192.168.1.1" + assert database["ports"] == [8001, 8001, 8002] + assert database["connection_max"] == 5000 + assert database["enabled"] is True # servers servers = doc["servers"] @@ -139,9 +139,9 @@ def test_toml_document_without_super_tables() -> None: doc = parse(content) assert "tool" in doc - assert "poetry" in doc["tool"] # type: ignore[operator] + assert "poetry" in doc["tool"] - assert doc["tool"]["poetry"]["name"] == "foo" # type: ignore[comparison-overlap] + assert doc["tool"]["poetry"]["name"] == "foo" doc["tool"]["poetry"]["name"] = "bar" @@ -178,17 +178,17 @@ def test_toml_document_with_dotted_keys(example: Callable[[str], str]) -> None: doc = parse(content) assert "physical" in doc - assert "color" in doc["physical"] # type: ignore[operator] - assert "shape" in doc["physical"] # type: ignore[operator] - assert doc["physical"]["color"] == "orange" # type: ignore[comparison-overlap] - assert doc["physical"]["shape"] == "round" # type: ignore[comparison-overlap] + assert "color" in doc["physical"] + assert "shape" in doc["physical"] + assert doc["physical"]["color"] == "orange" + assert doc["physical"]["shape"] == "round" assert "site" in doc - assert "google.com" in doc["site"] # type: ignore[operator] + assert "google.com" in doc["site"] assert doc["site"]["google.com"] - assert doc["a"]["b"]["c"] == 1 # type: ignore[comparison-overlap] - assert doc["a"]["b"]["d"] == 2 # type: ignore[comparison-overlap] + assert doc["a"]["b"]["c"] == 1 + assert doc["a"]["b"]["d"] == 2 def test_toml_document_super_table_with_different_sub_sections(example: Callable[[str], str]) -> None: @@ -197,8 +197,8 @@ def test_toml_document_super_table_with_different_sub_sections(example: Callable doc = parse(content) tool = doc["tool"] - assert "poetry" in tool # type: ignore[operator] - assert "black" in tool # type: ignore[operator] + assert "poetry" in tool + assert "black" in tool def test_adding_an_element_to_existing_table_with_ws_remove_ws() -> None: @@ -233,7 +233,7 @@ def test_document_with_aot_after_sub_tables() -> None: """ doc = parse(content) - assert doc["foo"]["bar"]["tests"][0]["name"] == "Test 1" # type: ignore[comparison-overlap] + assert doc["foo"]["bar"]["tests"][0]["name"] == "Test 1" def test_document_with_new_sub_table_after_other_table() -> None: @@ -248,9 +248,9 @@ def test_document_with_new_sub_table_after_other_table() -> None: """ doc = parse(content) - assert doc["foo"]["name"] == "Bar" # type: ignore[comparison-overlap] - assert doc["bar"]["name"] == "Baz" # type: ignore[comparison-overlap] - assert doc["foo"]["baz"]["name"] == "Test 1" # type: ignore[comparison-overlap] + assert doc["foo"]["name"] == "Bar" + assert doc["bar"]["name"] == "Baz" + assert doc["foo"]["baz"]["name"] == "Test 1" assert doc.as_string() == content @@ -344,11 +344,11 @@ def test_toml_document_with_dotted_keys_inside_table(example: Callable[[str], st doc = parse(content) t = doc["table"] - assert "a" in t # type: ignore[operator] + assert "a" in t - assert t["a"]["b"]["c"] == 1 # type: ignore[comparison-overlap] - assert t["a"]["b"]["d"] == 2 # type: ignore[comparison-overlap] - assert t["a"]["c"] == 3 # type: ignore[comparison-overlap] + assert t["a"]["b"]["c"] == 1 + assert t["a"]["b"]["d"] == 2 + assert t["a"]["c"] == 3 def test_toml_document_with_super_aot_after_super_table(example: Callable[[str], str]) -> None: @@ -421,11 +421,11 @@ def test_toml_document_can_be_copied() -> None: ) assert doc == {"foo": {"bar": 1}} - assert doc["foo"]["bar"] == 1 # type: ignore[comparison-overlap] + assert doc["foo"]["bar"] == 1 assert json.loads(json.dumps(doc)) == {"foo": {"bar": 1}} doc = parse(content) - doc = doc.copy() # type: ignore[assignment] + doc = doc.copy() assert ( doc.as_string() @@ -434,7 +434,7 @@ def test_toml_document_can_be_copied() -> None: ) assert doc == {"foo": {"bar": 1}} - assert doc["foo"]["bar"] == 1 # type: ignore[comparison-overlap] + assert doc["foo"]["bar"] == 1 assert json.loads(json.dumps(doc)) == {"foo": {"bar": 1}} @@ -493,7 +493,7 @@ def test_declare_sub_table_with_intermediate_table() -> None: """ doc = parse(content) - assert {"tommy": 87, "mary": 66, "bob": {"score": 91}} == doc["students"] # type: ignore[comparison-overlap] + assert {"tommy": 87, "mary": 66, "bob": {"score": 91}} == doc["students"] assert {"tommy": 87, "mary": 66, "bob": {"score": 91}} == doc.get("students") @@ -510,7 +510,7 @@ def test_values_can_still_be_set_for_out_of_order_tables() -> None: doc = parse(content) doc["a"]["a"]["key"] = "new_value" - assert doc["a"]["a"]["key"] == "new_value" # type: ignore[comparison-overlap] + assert doc["a"]["a"]["key"] == "new_value" expected = """ [a.a] @@ -570,7 +570,7 @@ def test_out_of_order_table_can_add_multiple_tables() -> None: """ doc = parse(content) assert doc.as_string() == content - assert doc["a"]["a"] == {"b": {"x": 1}, "c": {"y": 1}, "d": {"z": 1}} # type: ignore[comparison-overlap] + assert doc["a"]["a"] == {"b": {"x": 1}, "c": {"y": 1}, "d": {"z": 1}} def test_out_of_order_tables_are_still_dicts() -> None: @@ -596,12 +596,12 @@ def test_out_of_order_tables_are_still_dicts() -> None: assert table.get("d", "foo") == "foo" assert table.setdefault("d", "bar") == "bar" - assert table["d"] == "bar" # type: ignore[comparison-overlap] + assert table["d"] == "bar" assert table.pop("key") == "value" assert "key" not in table - assert table.pop("missing", default="baz") == "baz" # type: ignore[call-overload] + assert table.pop("missing", "baz") == "baz" with pytest.raises(KeyError): table.pop("missing") @@ -631,7 +631,7 @@ def test_string_output_order_is_preserved_for_out_of_order_tables() -> None: constraint["version"] = "^1.0" doc["tool"]["poetry"]["dependencies"]["bar"] = constraint - assert doc["tool"]["poetry"]["dependencies"]["bar"]["version"] == "^1.0" # type: ignore[comparison-overlap] + assert doc["tool"]["poetry"]["dependencies"]["bar"]["version"] == "^1.0" expected = """ [tool.poetry] @@ -688,7 +688,7 @@ def test_update_nested_out_of_order_table() -> None: [root1.root2.x] value = 4 """) - doc["root1"]["root2"]["a"].add("tmp", "hi") # type: ignore[attr-defined] + doc["root1"]["root2"]["a"].add("tmp", "hi") assert ( doc.as_string() == """\ @@ -903,7 +903,7 @@ def test_replace_with_aot_of_nested() -> None: def test_replace_with_comment() -> None: content = 'a = "1"' doc = parse(content) - a = tomlkit.item(int(doc["a"])) # type: ignore[call-overload] + a: Any = tomlkit.item(int(doc["a"])) a.comment("`a` should be an int") doc["a"] = a expected = "a = 1 # `a` should be an int" @@ -911,20 +911,20 @@ def test_replace_with_comment() -> None: content = 'a = "1, 2, 3"' doc = parse(content) - a = tomlkit.array() # type: ignore[assignment] + a = tomlkit.array() a.comment("`a` should be an array") - for x in doc["a"].split(","): # type: ignore[attr-defined] - a.append(int(x.strip())) # type: ignore[attr-defined] + 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() # type: ignore[assignment] + a = tomlkit.inline_table() a.comment("`a` should be an inline-table") - for x in doc["a"].split(","): # type: ignore[attr-defined] + for x in doc["a"].split(","): i = int(x.strip()) - a.append(chr(ord("a") + i - 1), i) # type: ignore[attr-defined] + 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 @@ -995,8 +995,8 @@ def test_pop_add_whitespace_and_insert_table_work_togheter() -> None: """ text = doc.as_string() out = parse(text) - assert out["d"] == 4 # type: ignore[comparison-overlap] - assert "d" not in out["e"] # type: ignore[operator] + assert out["d"] == 4 + assert "d" not in out["e"] assert text == dedent(expected) @@ -1049,7 +1049,7 @@ def test_nested_table_update_display_name() -> None: [bar] z = 3 """ - doc["parent"].update(parse(dedent(sub))) # type: ignore[attr-defined] + doc["parent"].update(parse(dedent(sub))) expected = """\ [parent] @@ -1073,9 +1073,9 @@ def test_build_table_with_dotted_key() -> None: for key, value in data.items(): if "." not in key: - doc.append(key, value) # type: ignore[arg-type] + doc.append(key, value) else: - doc.append(tomlkit.key(key.split(".")), value) # type: ignore[arg-type] + doc.append(tomlkit.key(key.split(".")), value) expected = """\ a.b.c = 1 @@ -1305,7 +1305,7 @@ def test_appending_to_super_table() -> None: doc = parse(content) table_a = doc["a"] - table_a.append(tomlkit.key(["c", "d"]), "foo") # type: ignore[attr-defined] + table_a.append(tomlkit.key(["c", "d"]), "foo") expected = """\ [a] diff --git a/tests/test_toml_file.py b/tests/test_toml_file.py index 28a0d790..4f3b3d13 100644 --- a/tests/test_toml_file.py +++ b/tests/test_toml_file.py @@ -15,7 +15,7 @@ def test_toml_file(example: Callable[[str], str]) -> None: content = toml.read() assert isinstance(content, TOMLDocument) - assert content["owner"]["organization"] == "GitHub" # type: ignore[comparison-overlap] + assert content["owner"]["organization"] == "GitHub" toml.write(content) @@ -100,9 +100,9 @@ def test_default_eol_is_os_linesep(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" toml_f = TOMLFile(toml_path) content = TOMLDocument() - content.append("a", 1) # type: ignore[arg-type] + content.append("a", 1) content["a"].trivia.trail = "\n" - content.append("b", 2) # type: ignore[arg-type] + content.append("b", 2) content["b"].trivia.trail = "\r\n" toml_f.write(content) linesep = os.linesep.encode() @@ -113,7 +113,7 @@ def test_default_eol_is_os_linesep(tmp_path: Path) -> None: def test_readwrite_eol_windows(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" doc = TOMLDocument() - doc.add("a", 1) # type: ignore[arg-type] + doc.add("a", 1) toml_f = TOMLFile(toml_path) toml_f.write(doc) readback = toml_f.read() diff --git a/tomlkit/api.py b/tomlkit/api.py index da2ab504..7455a1cd 100644 --- a/tomlkit/api.py +++ b/tomlkit/api.py @@ -57,18 +57,11 @@ def dumps(data: Mapping[str, Any], sort_keys: bool = False) -> str: """ Dumps a TOMLDocument into a string. """ - if not isinstance(data, (Table, InlineTable, Container)) and isinstance( - data, Mapping - ): - data = item(dict(data), _sort_keys=sort_keys) - - try: - # data should be a `Container` (and therefore implement `as_string`) - # for all type safe invocations of this function + if isinstance(data, (Table, InlineTable, Container)): return data.as_string() - except AttributeError as ex: - msg = f"Expecting Mapping or TOML Table or Container, {type(data)} given" - raise TypeError(msg) from ex + + table: Table = item(dict(data), _sort_keys=sort_keys) + return table.as_string() def load(fp: IO[str] | IO[bytes]) -> TOMLDocument: diff --git a/tomlkit/container.py b/tomlkit/container.py index 6e58b727..51164433 100644 --- a/tomlkit/container.py +++ b/tomlkit/container.py @@ -5,6 +5,7 @@ from collections.abc import Iterator from typing import Any +from typing import Self from tomlkit._compat import decode from tomlkit._types import _CustomDict @@ -92,7 +93,7 @@ def parsing(self, parsing: bool) -> None: for t in v.body: t.value.parsing(parsing) - def add(self, key: Key | Item | str, item: Item | None = None) -> Container: + def add(self, key: Key | Item | str, item: Any = None) -> Container: """ Adds an item to the current Container. @@ -160,7 +161,7 @@ def _validate_out_of_order_table(self, key: Key | None = None) -> None: OutOfOrderTableProxy.validate(self, current_idx) def append( - self, key: Key | str | None, item: Item, validate: bool = True + self, key: Key | str | None, item: Any, validate: bool = True ) -> Container: """Similar to :meth:`add` but both key and value must be given.""" if not isinstance(key, Key) and key is not None: @@ -702,10 +703,10 @@ def __iter__(self) -> Iterator[str]: return iter(dict.keys(self)) # Dictionary methods - def __getitem__(self, key: Key | str) -> Item: + def __getitem__(self, key: Key | str) -> Any: item = self.item(key) if isinstance(item, Item) and item.is_boolean(): - return item.value # type: ignore[no-any-return] + return item.value return item @@ -719,7 +720,7 @@ def __setitem__(self, key: Key | str, value: Any) -> None: def __delitem__(self, key: Key | str) -> None: self.remove(key) - def setdefault(self, key: Key | str, default: Any = None) -> Item: + def setdefault(self, key: Key | str, default: Any = None) -> Any: if key not in self: self[key] = default return self[key] @@ -841,10 +842,10 @@ def __setstate__(self, state: tuple[Any, ...]) -> None: if key is not None: dict.__setitem__(self, key.key, item.value) - def copy(self) -> Container: + def copy(self) -> Self: return copy.copy(self) - def __copy__(self) -> Container: + def __copy__(self) -> Self: c = self.__class__(self._parsed) for k, v in dict.items(self): dict.__setitem__(c, k, v) diff --git a/tomlkit/items.py b/tomlkit/items.py index b1c9b383..ecb041bc 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -41,9 +41,7 @@ from tomlkit import container class Encoder(Protocol): - def __call__( - self, __value: Any, _parent: Item | None = None, _sort_keys: bool = False - ) -> Item: ... + def __call__(self, __value: Any, /) -> Item: ... ItemT = TypeVar("ItemT", bound="Item") @@ -103,6 +101,10 @@ def item(value: dict[str, Any], _parent: Item | None = ..., _sort_keys: bool = . def item(value: ItemT, _parent: Item | None = ..., _sort_keys: bool = ...) -> ItemT: ... +@overload +def item(value: object, _parent: Item | None = ..., _sort_keys: bool = ...) -> Item: ... + + def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> Item: """Create a TOML item from a Python object. @@ -208,7 +210,7 @@ def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> I p.kind == p.VAR_KEYWORD for p in sig.parameters.values() ): # New style encoder that can accept additional parameters - rv = encoder(value, _parent=_parent, _sort_keys=_sort_keys) + rv = encoder(value, _parent=_parent, _sort_keys=_sort_keys) # type: ignore[call-arg] else: # Old style encoder that only accepts value rv = encoder(value) @@ -530,7 +532,7 @@ def __reduce__(self) -> tuple[type, tuple[object, ...]]: def __reduce_ex__(self, protocol: int) -> tuple[type, tuple[object, ...]]: # type: ignore[override] return self.__class__, self._getstate(protocol) - def __getitem__(self, key: Key | str | int) -> Item: + def __getitem__(self, key: Key | str | int) -> Any: raise TypeError(f"{type(self).__name__} does not support item access") def __setitem__(self, key: Key | str | int, value: Any) -> None: @@ -783,6 +785,18 @@ def __rxor__(self, other: object) -> Integer: return result # type: ignore[return-value] return self._new(result) + def __sub__(self, other: object) -> Integer: + result = int.__sub__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rsub__(self, other: object) -> Integer: + result = int.__rsub__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + def __trunc__(self) -> Integer: return self._new(int.__trunc__(self)) @@ -943,6 +957,18 @@ def __truediv__(self, other: object) -> Float: return result # type: ignore[return-value] return self._new(result) + def __sub__(self, other: object) -> Float: + result = float.__sub__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rsub__(self, other: object) -> Float: + result = float.__rsub__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + __trunc__ = float.__trunc__ __ceil__ = float.__ceil__ __floor__ = float.__floor__ @@ -1086,7 +1112,13 @@ def __add__(self, other: timedelta) -> DateTime: return self._new(result) - def __sub__(self, other: timedelta | datetime) -> DateTime | timedelta: # type: ignore[override] + @overload # type: ignore[override] + def __sub__(self, other: timedelta) -> DateTime: ... + + @overload + def __sub__(self, other: datetime) -> timedelta: ... + + def __sub__(self, other: timedelta | datetime) -> DateTime | timedelta: if PY38: result = datetime( self.year, @@ -1106,10 +1138,10 @@ def __sub__(self, other: timedelta | datetime) -> DateTime | timedelta: # type: return result - def replace(self, *args: object, **kwargs: object) -> datetime: # type: ignore[override] + def replace(self, *args: object, **kwargs: object) -> DateTime: return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type] - def astimezone(self, tz: tzinfo) -> datetime: # type: ignore[override] + def astimezone(self, tz: tzinfo) -> DateTime: # type: ignore[override] result = super().astimezone(tz) if PY38: return result @@ -1189,7 +1221,13 @@ def __add__(self, other: timedelta) -> Date: return self._new(result) - def __sub__(self, other: timedelta | date) -> Date | timedelta: # type: ignore[override] + @overload # type: ignore[override] + def __sub__(self, other: timedelta) -> Date: ... + + @overload + def __sub__(self, other: date) -> timedelta: ... + + def __sub__(self, other: timedelta | date) -> Date | timedelta: if PY38: result = date(self.year, self.month, self.day).__sub__(other) else: @@ -1200,7 +1238,7 @@ def __sub__(self, other: timedelta | date) -> Date | timedelta: # type: ignore[ return result - def replace(self, *args: object, **kwargs: object) -> date: # type: ignore[override] + def replace(self, *args: object, **kwargs: object) -> Date: return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type] def _new(self, result: date) -> Date: @@ -1258,7 +1296,7 @@ def value(self) -> time: def as_string(self) -> str: return self._raw - def replace(self, *args: object, **kwargs: object) -> time: # type: ignore[override] + def replace(self, *args: object, **kwargs: object) -> Time: return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type] def _new(self, result: time) -> Time: @@ -1522,8 +1560,8 @@ def __len__(self) -> int: def item(self, index: int) -> Item: return list.__getitem__(self, index) # type: ignore[no-any-return] - def __getitem__(self, key: int | slice) -> Item: # type: ignore[override] - return list.__getitem__(self, key) # type: ignore[return-value] + def __getitem__(self, key: int | slice) -> Any: # type: ignore[override] + return list.__getitem__(self, key) def __setitem__(self, key: int | slice, value: Any) -> None: # type: ignore[override] it = item(value, _parent=self) @@ -1731,7 +1769,7 @@ def remove(self: AT, key: Key | str) -> AT: def item(self, key: Key | str) -> Item: return self._value.item(key) - def setdefault(self, key: Key | str, default: Any) -> Item: # type: ignore[override] + def setdefault(self, key: Key | str, default: Any) -> Any: # type: ignore[override] super().setdefault(key, default) return self[key] @@ -1752,7 +1790,7 @@ def __len__(self) -> int: def __delitem__(self, key: Key | str) -> None: # type: ignore[override] self.remove(key) - def __getitem__(self, key: Key | str) -> Item: # type: ignore[override] + def __getitem__(self, key: Key | str) -> Any: # type: ignore[override] return self._value[key] def __setitem__(self, key: Key | str, value: Any) -> None: # type: ignore[override] From c54a641539a432ba0437c5903b19b298c8b12379 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 22 Mar 2026 18:23:39 +0000 Subject: [PATCH 3/6] ruff check --- tests/conftest.py | 1 + tests/test_build.py | 1 + tests/test_items.py | 1 + tests/test_toml_file.py | 2 +- tests/test_toml_tests.py | 1 + tomlkit/_compat.py | 1 + tomlkit/items.py | 3 +-- 7 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index e665d68f..8a7d3013 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import os + from collections.abc import Callable import pytest diff --git a/tests/test_build.py b/tests/test_build.py index 6b64fcc7..18772105 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -1,4 +1,5 @@ import datetime + from collections.abc import Callable from tomlkit import aot diff --git a/tests/test_items.py b/tests/test_items.py index a70b1cae..2fb9ef29 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -1,6 +1,7 @@ import copy import math import pickle + from collections.abc import Callable from datetime import date from datetime import datetime diff --git a/tests/test_toml_file.py b/tests/test_toml_file.py index 4f3b3d13..586cf8ac 100644 --- a/tests/test_toml_file.py +++ b/tests/test_toml_file.py @@ -1,7 +1,7 @@ import os + from collections.abc import Callable from pathlib import Path -from typing import Any from tomlkit.toml_document import TOMLDocument from tomlkit.toml_file import TOMLFile diff --git a/tests/test_toml_tests.py b/tests/test_toml_tests.py index 3d02d5b1..e91f50f0 100644 --- a/tests/test_toml_tests.py +++ b/tests/test_toml_tests.py @@ -1,5 +1,6 @@ import json import os + from typing import Any from typing import Callable diff --git a/tomlkit/_compat.py b/tomlkit/_compat.py index 44dca7a8..2da7099d 100644 --- a/tomlkit/_compat.py +++ b/tomlkit/_compat.py @@ -3,6 +3,7 @@ import contextlib import sys + PY38 = sys.version_info >= (3, 8) diff --git a/tomlkit/items.py b/tomlkit/items.py index ecb041bc..2621339d 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -19,7 +19,6 @@ from enum import Enum from typing import TYPE_CHECKING from typing import Any -from typing import Any from typing import TypeVar from typing import overload @@ -1009,7 +1008,7 @@ def __bool__(self) -> bool: def __eq__(self, other: object) -> bool: if not isinstance(other, bool): - return NotImplemented + return NotImplemented return other == self._value From 8ea7f6c84895dbefa2cbe0407657720a4b4516ba Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 22 Mar 2026 18:23:56 +0000 Subject: [PATCH 4/6] ruff format --- tests/test_api.py | 12 +++++-- tests/test_build.py | 4 +-- tests/test_items.py | 12 +++++-- tests/test_parser.py | 4 ++- tests/test_toml_document.py | 12 +++++-- tests/test_toml_tests.py | 9 ++++- tomlkit/items.py | 67 +++++++++++++++++++++++++++---------- 7 files changed, 88 insertions(+), 32 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 8038f5af..2bcc71aa 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -65,7 +65,9 @@ def json_serial(obj: Any) -> str: "table_names", ], ) -def test_parse_can_parse_valid_toml_files(example: Callable[[str], str], example_name: str) -> None: +def test_parse_can_parse_valid_toml_files( + example: Callable[[str], str], example_name: str +) -> None: assert isinstance(parse(example(example_name)), TOMLDocument) assert isinstance(loads(example(example_name)), TOMLDocument) @@ -145,7 +147,9 @@ def test_parse_raises_errors_for_invalid_toml_files( "table_names", ], ) -def test_original_string_and_dumped_string_are_equal(example: Callable[[str], str], example_name: str) -> None: +def test_original_string_and_dumped_string_are_equal( + example: Callable[[str], str], example_name: str +) -> None: content = example(example_name) parsed = parse(content) @@ -463,7 +467,9 @@ def test_create_string(kwargs: dict[str, Any], example: str, expected: str) -> N ({"multiline": True, "literal": True}, "My'''String"), ], ) -def test_create_string_with_invalid_characters(kwargs: dict[str, Any], example: str) -> None: +def test_create_string_with_invalid_characters( + kwargs: dict[str, Any], example: str +) -> None: with pytest.raises(InvalidStringError): tomlkit.string(example, **kwargs) diff --git a/tests/test_build.py b/tests/test_build.py index 18772105..66fa2243 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -40,9 +40,7 @@ def test_build_example(example: Callable[[str], str]) -> None: servers = table() servers.add(nl()) - c = comment( - "You can indent as you please. Tabs or spaces. TOML don't care." - ) + c = comment("You can indent as you please. Tabs or spaces. TOML don't care.") c.indent(2) c.trivia.trail = "" servers.add(c) diff --git a/tests/test_items.py b/tests/test_items.py index 2fb9ef29..f766a329 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -269,7 +269,9 @@ def test_inf_and_nan_are_supported(example: Callable[[str], str]) -> None: assert math.isnan(doc["sf6"]) -def test_hex_octal_and_bin_integers_are_supported(example: Callable[[str], str]) -> None: +def test_hex_octal_and_bin_integers_are_supported( + example: Callable[[str], str], +) -> None: content = example("0.5.0") doc = parse(content) @@ -999,7 +1001,9 @@ def test_custom_encoders_with_parent_and_sort_keys() -> None: sort_keys_captured = None @api.register_encoder - def encode_decimal_with_context(obj: Any, _parent: Item | None = None, _sort_keys: bool = False) -> Item: + def encode_decimal_with_context( + obj: Any, _parent: Item | None = None, _sort_keys: bool = False + ) -> Item: nonlocal parent_captured, sort_keys_captured if isinstance(obj, decimal.Decimal): parent_captured = _parent @@ -1079,7 +1083,9 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @api.register_encoder - def encode_custom_dict(obj: Any, _parent: Item | None = None, _sort_keys: bool = False) -> Item: + def encode_custom_dict( + obj: Any, _parent: Item | None = None, _sort_keys: bool = False + ) -> Item: if isinstance(obj, CustomDict): # Create a table and use item() to convert nested values table = api.table() diff --git a/tests/test_parser.py b/tests/test_parser.py index daa529e0..eeb376e3 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -7,7 +7,9 @@ from tomlkit.parser import Parser -def test_parser_should_raise_an_internal_error_if_parsing_wrong_type_of_string() -> None: +def test_parser_should_raise_an_internal_error_if_parsing_wrong_type_of_string() -> ( + None +): parser = Parser('"foo"') with pytest.raises(InternalParserError) as e: diff --git a/tests/test_toml_document.py b/tests/test_toml_document.py index a8066a82..80c806c7 100644 --- a/tests/test_toml_document.py +++ b/tests/test_toml_document.py @@ -191,7 +191,9 @@ def test_toml_document_with_dotted_keys(example: Callable[[str], str]) -> None: assert doc["a"]["b"]["d"] == 2 -def test_toml_document_super_table_with_different_sub_sections(example: Callable[[str], str]) -> None: +def test_toml_document_super_table_with_different_sub_sections( + example: Callable[[str], str], +) -> None: content = example("pyproject") doc = parse(content) @@ -338,7 +340,9 @@ def test_inserting_after_deletion() -> None: assert expected == doc.as_string() -def test_toml_document_with_dotted_keys_inside_table(example: Callable[[str], str]) -> None: +def test_toml_document_with_dotted_keys_inside_table( + example: Callable[[str], str], +) -> None: content = example("0.5.0") doc = parse(content) @@ -351,7 +355,9 @@ def test_toml_document_with_dotted_keys_inside_table(example: Callable[[str], st assert t["a"]["c"] == 3 -def test_toml_document_with_super_aot_after_super_table(example: Callable[[str], str]) -> None: +def test_toml_document_with_super_aot_after_super_table( + example: Callable[[str], str], +) -> None: content = example("pyproject") doc = parse(content) diff --git a/tests/test_toml_tests.py b/tests/test_toml_tests.py index e91f50f0..667353ee 100644 --- a/tests/test_toml_tests.py +++ b/tests/test_toml_tests.py @@ -56,7 +56,14 @@ def _load_case_list() -> list[str]: return [line.strip() for line in f if line.strip()] -def _build_cases() -> tuple[list[dict[str, str]], list[str], list[dict[str, str]], list[str], list[str], list[str]]: +def _build_cases() -> tuple[ + list[dict[str, str]], + list[str], + list[dict[str, str]], + list[str], + list[str], + list[str], +]: valid_cases = [] valid_ids = [] invalid_decode_cases = [] diff --git a/tomlkit/items.py b/tomlkit/items.py index 2621339d..cfaa8f95 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -65,7 +65,9 @@ def item(value: str, _parent: Item | None = ..., _sort_keys: bool = ...) -> Stri @overload -def item(value: datetime, _parent: Item | None = ..., _sort_keys: bool = ...) -> DateTime: ... # type: ignore[overload-overlap] +def item( + value: datetime, _parent: Item | None = ..., _sort_keys: bool = ... +) -> DateTime: ... # type: ignore[overload-overlap] @overload @@ -89,11 +91,15 @@ def item( @overload -def item(value: dict[str, Any], _parent: Array = ..., _sort_keys: bool = ...) -> InlineTable: ... +def item( + value: dict[str, Any], _parent: Array = ..., _sort_keys: bool = ... +) -> InlineTable: ... @overload -def item(value: dict[str, Any], _parent: Item | None = ..., _sort_keys: bool = ...) -> Table: ... +def item( + value: dict[str, Any], _parent: Item | None = ..., _sort_keys: bool = ... +) -> Table: ... @overload @@ -714,7 +720,9 @@ def __pos__(self) -> Integer: return self._new(int.__pos__(self)) def __pow__(self, other: int, mod: int | None = None) -> Integer: # type: ignore[override] - result = int.__pow__(self, other) if mod is None else int.__pow__(self, other, mod) + result = ( + int.__pow__(self, other) if mod is None else int.__pow__(self, other, mod) + ) return self._new(result) def __radd__(self, other: object) -> Integer: @@ -763,7 +771,9 @@ def __round__(self, ndigits: int = 0) -> Integer: # type: ignore[override] return self._new(int.__round__(self, ndigits)) def __rpow__(self, other: int, mod: int | None = None) -> Integer: # type: ignore[misc] - result = int.__rpow__(self, other) if mod is None else int.__rpow__(self, other, mod) + result = ( + int.__rpow__(self, other) if mod is None else int.__rpow__(self, other, mod) + ) return self._new(result) def __rrshift__(self, other: object) -> Integer: @@ -1162,7 +1172,9 @@ def _new(self, result: datetime) -> DateTime: raw, ) - def _getstate(self, protocol: int = 3) -> tuple[int, int, int, int, int, int, int, tzinfo | None, Trivia, str]: + def _getstate( + self, protocol: int = 3 + ) -> tuple[int, int, int, int, int, int, int, tzinfo | None, Trivia, str]: return ( self.year, self.month, @@ -1182,7 +1194,14 @@ class Date(Item, date): A date literal. """ - def __new__(cls, year: int, month: int, day: int, trivia: Trivia | None = None, raw: str = "") -> Date: + def __new__( + cls, + year: int, + month: int, + day: int, + trivia: Trivia | None = None, + raw: str = "", + ) -> Date: return date.__new__(cls, year, month, day) def __init__( @@ -1311,7 +1330,9 @@ def _new(self, result: time) -> Time: raw, ) - def _getstate(self, protocol: int = 3) -> tuple[int, int, int, int, tzinfo | None, Trivia, str]: + def _getstate( + self, protocol: int = 3 + ) -> tuple[int, int, int, int, tzinfo | None, Trivia, str]: return ( self.hour, self.minute, @@ -1339,7 +1360,11 @@ def __init__( self.comment = comment def __iter__(self) -> Iterator[Item]: - return (x for x in (self.indent, self.value, self.comma, self.comment) if x is not None) + return ( + x + for x in (self.indent, self.value, self.comma, self.comment) + if x is not None + ) def __repr__(self) -> str: return repr(tuple(self)) @@ -1594,10 +1619,7 @@ def insert(self, pos: int, value: Any) -> None: # type: ignore[override] # The last item is a pure whitespace(\n ), insert before it idx -= 1 _indent = self._value[idx].indent - if ( - _indent is not None - and "\n" in _indent.s - ): + if _indent is not None and "\n" in _indent.s: default_indent = "\n " indent: Whitespace | None = None comma: Whitespace | None = Whitespace(",") if pos < length else None @@ -1741,7 +1763,9 @@ def add(self: AT, key: Comment | Whitespace) -> AT: ... @overload def add(self: AT, key: Key | str, value: Any = ...) -> AT: ... - def add(self: AT, key: Key | str | Comment | Whitespace, value: Any | None = None) -> AT: + def add( + self: AT, key: Key | str | Comment | Whitespace, value: Any | None = None + ) -> AT: if value is None: if not isinstance(key, (Comment, Whitespace)): msg = "Non comment/whitespace items must have an associated key" @@ -1772,7 +1796,8 @@ def setdefault(self, key: Key | str, default: Any) -> Any: # type: ignore[overr super().setdefault(key, default) return self[key] - def __str__(self) -> str: return str(self.value) + def __str__(self) -> str: + return str(self.value) def copy(self: AT) -> AT: return copy.copy(self) @@ -1956,7 +1981,9 @@ def invalidate_display_name(self) -> None: if hasattr(child, "invalidate_display_name"): child.invalidate_display_name() - def _getstate(self, protocol: int = 3) -> tuple[container.Container, Trivia, bool, bool | None, str | None, str | None]: + def _getstate( + self, protocol: int = 3 + ) -> tuple[container.Container, Trivia, bool, bool | None, str | None, str | None]: return ( self._value, self._trivia, @@ -2090,7 +2117,9 @@ class String(str, Item): # type: ignore[misc] A string literal. """ - def __new__(cls, t: StringType, value: str, original: str, trivia: Trivia) -> String: + def __new__( + cls, t: StringType, value: str, original: str, trivia: Trivia + ) -> String: return super().__new__(cls, value) def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None: @@ -2130,7 +2159,9 @@ def _getstate(self, protocol: int = 3) -> tuple[StringType, str, str, Trivia]: return self._t, str(self), self._original, self._trivia @classmethod - def from_raw(cls, value: str, type_: StringType = StringType.SLB, escape: bool = True) -> String: + def from_raw( + cls, value: str, type_: StringType = StringType.SLB, escape: bool = True + ) -> String: value = decode(value) invalid = type_.invalid_sequences From dae1bbf1eea09d137a99b44eeae3f2694a1772f1 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 22 Mar 2026 18:31:35 +0000 Subject: [PATCH 5/6] update and tighten mypy configuration --- poetry.lock | 204 +++++++++++++++++++++++++++++++++++-------- pyproject.toml | 10 ++- tomlkit/container.py | 6 +- tomlkit/items.py | 7 +- 4 files changed, 181 insertions(+), 46 deletions(-) diff --git a/poetry.lock b/poetry.lock index 602bc61c..b00f9c33 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "alabaster" @@ -436,6 +436,107 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "librt" +version = "0.8.1" +description = "Mypyc runtime library" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc"}, + {file = "librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7"}, + {file = "librt-0.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d56bc4011975f7460bea7b33e1ff425d2f1adf419935ff6707273c77f8a4ada6"}, + {file = "librt-0.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdc0f588ff4b663ea96c26d2a230c525c6fc62b28314edaaaca8ed5af931ad0"}, + {file = "librt-0.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c2b54ff6717a7a563b72627990bec60d8029df17df423f0ed37d56a17a176b"}, + {file = "librt-0.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f1125e6bbf2f1657d9a2f3ccc4a2c9b0c8b176965bb565dd4d86be67eddb4b6"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f4bb453f408137d7581be309b2fbc6868a80e7ef60c88e689078ee3a296ae71"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c336d61d2fe74a3195edc1646d53ff1cddd3a9600b09fa6ab75e5514ba4862a7"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eb5656019db7c4deacf0c1a55a898c5bb8f989be904597fcb5232a2f4828fa05"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c25d9e338d5bed46c1632f851babf3d13c78f49a225462017cf5e11e845c5891"}, + {file = "librt-0.8.1-cp310-cp310-win32.whl", hash = "sha256:aaab0e307e344cb28d800957ef3ec16605146ef0e59e059a60a176d19543d1b7"}, + {file = "librt-0.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:56e04c14b696300d47b3bc5f1d10a00e86ae978886d0cee14e5714fafb5df5d2"}, + {file = "librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd"}, + {file = "librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965"}, + {file = "librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da"}, + {file = "librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0"}, + {file = "librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e"}, + {file = "librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe"}, + {file = "librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb"}, + {file = "librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b"}, + {file = "librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9"}, + {file = "librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a"}, + {file = "librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9"}, + {file = "librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb"}, + {file = "librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d"}, + {file = "librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7"}, + {file = "librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0"}, + {file = "librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a"}, + {file = "librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444"}, + {file = "librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d"}, + {file = "librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35"}, + {file = "librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583"}, + {file = "librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c"}, + {file = "librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04"}, + {file = "librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363"}, + {file = "librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d"}, + {file = "librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a"}, + {file = "librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79"}, + {file = "librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0"}, + {file = "librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f"}, + {file = "librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c"}, + {file = "librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc"}, + {file = "librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c"}, + {file = "librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3"}, + {file = "librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78"}, + {file = "librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023"}, + {file = "librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730"}, + {file = "librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3"}, + {file = "librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1"}, + {file = "librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994"}, + {file = "librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a"}, + {file = "librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4"}, + {file = "librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61"}, + {file = "librt-0.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3dff3d3ca8db20e783b1bc7de49c0a2ab0b8387f31236d6a026597d07fcd68ac"}, + {file = "librt-0.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08eec3a1fc435f0d09c87b6bf1ec798986a3544f446b864e4099633a56fcd9ed"}, + {file = "librt-0.8.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e3f0a41487fd5fad7e760b9e8a90e251e27c2816fbc2cff36a22a0e6bcbbd9dd"}, + {file = "librt-0.8.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bacdb58d9939d95cc557b4dbaa86527c9db2ac1ed76a18bc8d26f6dc8647d851"}, + {file = "librt-0.8.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d7ab1f01aa753188605b09a51faa44a3327400b00b8cce424c71910fc0a128"}, + {file = "librt-0.8.1-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4998009e7cb9e896569f4be7004f09d0ed70d386fa99d42b6d363f6d200501ac"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2cc68eeeef5e906839c7bb0815748b5b0a974ec27125beefc0f942715785b551"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0bf69d79a23f4f40b8673a947a234baeeb133b5078b483b7297c5916539cf5d5"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:22b46eabd76c1986ee7d231b0765ad387d7673bbd996aa0d0d054b38ac65d8f6"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:237796479f4d0637d6b9cbcb926ff424a97735e68ade6facf402df4ec93375ed"}, + {file = "librt-0.8.1-cp39-cp39-win32.whl", hash = "sha256:4beb04b8c66c6ae62f8c1e0b2f097c1ebad9295c929a8d5286c05eae7c2fc7dc"}, + {file = "librt-0.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:64548cde61b692dc0dc379f4b5f59a2f582c2ebe7890d09c1ae3b9e66fa015b7"}, + {file = "librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73"}, +] + [[package]] name = "markupsafe" version = "2.1.5" @@ -508,53 +609,64 @@ files = [ [[package]] name = "mypy" -version = "0.990" +version = "1.19.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mypy-0.990-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aaf1be63e0207d7d17be942dcf9a6b641745581fe6c64df9a38deb562a7dbafa"}, - {file = "mypy-0.990-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d555aa7f44cecb7ea3c0ac69d58b1a5afb92caa017285a8e9c4efbf0518b61b4"}, - {file = "mypy-0.990-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f694d6d09a460b117dccb6857dda269188e3437c880d7b60fa0014fa872d1e9"}, - {file = "mypy-0.990-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:269f0dfb6463b8780333310ff4b5134425157ef0d2b1d614015adaf6d6a7eabd"}, - {file = "mypy-0.990-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8798c8ed83aa809f053abff08664bdca056038f5a02af3660de00b7290b64c47"}, - {file = "mypy-0.990-cp310-cp310-win_amd64.whl", hash = "sha256:47a9955214615108c3480a500cfda8513a0b1cd3c09a1ed42764ca0dd7b931dd"}, - {file = "mypy-0.990-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4a8a6c10f4c63fbf6ad6c03eba22c9331b3946a4cec97f008e9ffb4d3b31e8e2"}, - {file = "mypy-0.990-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd2dd3730ba894ec2a2082cc703fbf3e95a08479f7be84912e3131fc68809d46"}, - {file = "mypy-0.990-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7da0005e47975287a92b43276e460ac1831af3d23032c34e67d003388a0ce8d0"}, - {file = "mypy-0.990-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262c543ef24deb10470a3c1c254bb986714e2b6b1a67d66daf836a548a9f316c"}, - {file = "mypy-0.990-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3ff201a0c6d3ea029d73b1648943387d75aa052491365b101f6edd5570d018ea"}, - {file = "mypy-0.990-cp311-cp311-win_amd64.whl", hash = "sha256:1767830da2d1afa4e62b684647af0ff79b401f004d7fa08bc5b0ce2d45bcd5ec"}, - {file = "mypy-0.990-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6826d9c4d85bbf6d68cb279b561de6a4d8d778ca8e9ab2d00ee768ab501a9852"}, - {file = "mypy-0.990-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46897755f944176fbc504178422a5a2875bbf3f7436727374724842c0987b5af"}, - {file = "mypy-0.990-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0680389c34284287fe00e82fc8bccdea9aff318f7e7d55b90d967a13a9606013"}, - {file = "mypy-0.990-cp37-cp37m-win_amd64.whl", hash = "sha256:b08541a06eed35b543ae1a6b301590eb61826a1eb099417676ddc5a42aa151c5"}, - {file = "mypy-0.990-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:be88d665e76b452c26fb2bdc3d54555c01226fba062b004ede780b190a50f9db"}, - {file = "mypy-0.990-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b8f4a8213b1fd4b751e26b59ae0e0c12896568d7e805861035c7a15ed6dc9eb"}, - {file = "mypy-0.990-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2b6f85c2ad378e3224e017904a051b26660087b3b76490d533b7344f1546d3ff"}, - {file = "mypy-0.990-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee5f99817ee70254e7eb5cf97c1b11dda29c6893d846c8b07bce449184e9466"}, - {file = "mypy-0.990-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49082382f571c3186ce9ea0bd627cb1345d4da8d44a8377870f4442401f0a706"}, - {file = "mypy-0.990-cp38-cp38-win_amd64.whl", hash = "sha256:aba38e3dd66bdbafbbfe9c6e79637841928ea4c79b32e334099463c17b0d90ef"}, - {file = "mypy-0.990-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9d851c09b981a65d9d283a8ccb5b1d0b698e580493416a10942ef1a04b19fd37"}, - {file = "mypy-0.990-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d847dd23540e2912d9667602271e5ebf25e5788e7da46da5ffd98e7872616e8e"}, - {file = "mypy-0.990-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc6019808580565040cd2a561b593d7c3c646badd7e580e07d875eb1bf35c695"}, - {file = "mypy-0.990-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3150d409609a775c8cb65dbe305c4edd7fe576c22ea79d77d1454acd9aeda8"}, - {file = "mypy-0.990-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3227f14fe943524f5794679156488f18bf8d34bfecd4623cf76bc55958d229c5"}, - {file = "mypy-0.990-cp39-cp39-win_amd64.whl", hash = "sha256:c76c769c46a1e6062a84837badcb2a7b0cdb153d68601a61f60739c37d41cc74"}, - {file = "mypy-0.990-py3-none-any.whl", hash = "sha256:8f1940325a8ed460ba03d19ab83742260fa9534804c317224e5d4e5aa588e2d6"}, - {file = "mypy-0.990.tar.gz", hash = "sha256:72382cb609142dba3f04140d016c94b4092bc7b4d98ca718740dc989e5271b8d"}, + {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, + {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"}, + {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"}, + {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"}, + {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"}, + {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"}, + {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"}, + {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"}, + {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"}, + {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"}, + {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"}, + {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"}, + {file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"}, + {file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"}, + {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"}, + {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"}, ] [package.dependencies] -mypy-extensions = ">=0.4.3" +librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""} +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=3.10" +typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] +mypyc = ["setuptools (>=50)"] reports = ["lxml"] [[package]] @@ -596,6 +708,24 @@ files = [ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] +[[package]] +name = "pathspec" +version = "1.0.4" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, + {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, +] + +[package.extras] +hyperscan = ["hyperscan (>=0.7)"] +optional = ["typing-extensions (>=4)"] +re2 = ["google-re2 (>=1.1)"] +tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] + [[package]] name = "platformdirs" version = "4.0.0" @@ -1070,4 +1200,4 @@ test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-it [metadata] lock-version = "2.1" python-versions = ">=3.9" -content-hash = "2528f55c9a960b658ee758585ba3b1c76546a8bf1c53fe61bc724e6eb045ade6" +content-hash = "0eec9d00c51b390c077534841b531d85c22a7aa138e622b20525166f6a628967" diff --git a/pyproject.toml b/pyproject.toml index 0fc50bc2..b424f57f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ pytest = "^7.2.0" pytest-cov = "^4.0.0" PyYAML = "^6.0" pre-commit = "^2.20.0" -mypy = "^0.990" +mypy = "1.19.1" Sphinx = "^4.3.2" furo = "^2022.9.29" @@ -55,9 +55,13 @@ lines-after-imports = 2 lines-between-types = 1 [tool.mypy] +files = "tomlkit, tests" strict = true -warn_return_any = true -warn_unused_configs = true +enable_error_code = [ + "ignore-without-code", + "redundant-expr", + "truthy-bool", +] [build-system] requires = ["poetry-core>=1.0.0a9"] diff --git a/tomlkit/container.py b/tomlkit/container.py index 51164433..05f9e875 100644 --- a/tomlkit/container.py +++ b/tomlkit/container.py @@ -497,7 +497,7 @@ def _insert_at(self, idx: int, key: Key | str, item: Any) -> Container: return self - def item(self, key: Key | str) -> Item: + def item(self, key: Key | str) -> Item | OutOfOrderTableProxy: """Get an item for the given key.""" if not isinstance(key, Key): key = SingleKey(key) @@ -510,7 +510,7 @@ def item(self, key: Key | str) -> Item: # The item we are getting is an out of order table # so we need a proxy to retrieve the proper objects # from the parent container - return OutOfOrderTableProxy(self, idx) # type: ignore[return-value] + return OutOfOrderTableProxy(self, idx) return self._body[idx][1] @@ -711,7 +711,7 @@ def __getitem__(self, key: Key | str) -> Any: return item def __setitem__(self, key: Key | str, value: Any) -> None: - if key is not None and key in self: + if key in self: old_key = next(filter(lambda k: k == key, self._map)) self._replace(old_key, key, value) else: diff --git a/tomlkit/items.py b/tomlkit/items.py index cfaa8f95..84b3554a 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -37,6 +37,7 @@ if TYPE_CHECKING: from typing import Protocol + from tomlkit.container import OutOfOrderTableProxy from tomlkit import container class Encoder(Protocol): @@ -65,9 +66,9 @@ def item(value: str, _parent: Item | None = ..., _sort_keys: bool = ...) -> Stri @overload -def item( +def item( # type: ignore[overload-overlap] value: datetime, _parent: Item | None = ..., _sort_keys: bool = ... -) -> DateTime: ... # type: ignore[overload-overlap] +) -> DateTime: ... @overload @@ -1789,7 +1790,7 @@ def remove(self: AT, key: Key | str) -> AT: return self - def item(self, key: Key | str) -> Item: + def item(self, key: Key | str) -> Item | OutOfOrderTableProxy: return self._value.item(key) def setdefault(self, key: Key | str, default: Any) -> Any: # type: ignore[override] From e87dbf86c9b60bcc9b8181544d7e2bbca07bcfb5 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 22 Mar 2026 19:09:36 +0000 Subject: [PATCH 6/6] pipeline fixes --- tests/test_api.py | 3 +-- tests/test_items.py | 2 ++ tomlkit/container.py | 6 +++++- tomlkit/items.py | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 2bcc71aa..eb272296 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -8,7 +8,6 @@ from types import MappingProxyType from typing import Any from typing import Callable -from typing import Type import pytest @@ -128,7 +127,7 @@ def test_parsed_document_are_properly_json_representable( ], ) def test_parse_raises_errors_for_invalid_toml_files( - invalid_example: Callable[[str], str], error: Type[Exception], example_name: str + invalid_example: Callable[[str], str], error: type[Exception], example_name: str ) -> None: with pytest.raises(error): parse(invalid_example(example_name)) diff --git a/tests/test_items.py b/tests/test_items.py index f766a329..f5cbbde8 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import copy import math import pickle diff --git a/tomlkit/container.py b/tomlkit/container.py index 05f9e875..43874716 100644 --- a/tomlkit/container.py +++ b/tomlkit/container.py @@ -4,8 +4,12 @@ import math from collections.abc import Iterator +from typing import TYPE_CHECKING from typing import Any -from typing import Self + + +if TYPE_CHECKING: + from typing import Self from tomlkit._compat import decode from tomlkit._types import _CustomDict diff --git a/tomlkit/items.py b/tomlkit/items.py index 84b3554a..8670f03a 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -37,8 +37,8 @@ if TYPE_CHECKING: from typing import Protocol - from tomlkit.container import OutOfOrderTableProxy from tomlkit import container + from tomlkit.container import OutOfOrderTableProxy class Encoder(Protocol): def __call__(self, __value: Any, /) -> Item: ...