From f01f472893b6dc8b4a5252f982a88c82eefe6154 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Mon, 24 Nov 2025 23:34:50 +0100 Subject: [PATCH 1/7] Sort `__slots__` --- src/dataclass_binder/_impl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dataclass_binder/_impl.py b/src/dataclass_binder/_impl.py index 87dc1ae..5e8b767 100644 --- a/src/dataclass_binder/_impl.py +++ b/src/dataclass_binder/_impl.py @@ -260,7 +260,8 @@ class Binder(Generic[T]): Binds TOML data to a specific dataclass. """ - __slots__ = ("_dataclass", "_instance", "_class_info") + __slots__ = "_class_info", "_dataclass", "_instance" + _dataclass: type[T] _instance: T | None _class_info: _ClassInfo[T] From b50d8f0740d1afe3812df9930ce8cc6f8b7e7d09 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Mon, 24 Nov 2025 23:36:26 +0100 Subject: [PATCH 2/7] Prefix names of unused variables --- src/dataclass_binder/_impl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dataclass_binder/_impl.py b/src/dataclass_binder/_impl.py index 5e8b767..8420e39 100644 --- a/src/dataclass_binder/_impl.py +++ b/src/dataclass_binder/_impl.py @@ -324,7 +324,7 @@ def _bind_to_single_type(self, value: object, field_type: type, context: str) -> elif issubclass(origin, Mapping): if not isinstance(value, dict): raise TypeError(f"Value for '{context}' has type '{type(value).__name__}', expected table") - key_type, elem_type = get_args(field_type) + _key_type, elem_type = get_args(field_type) mapping = { key: self._bind_to_field(elem, elem_type, None, f'{context}["{key}"]') for key, elem in value.items() } @@ -507,7 +507,7 @@ def _format_toml_table( origin = get_origin(field_type) if origin is not None: if issubclass(origin, Mapping): - key_type, value_type = get_args(field_type) + _key_type, value_type = get_args(field_type) if isinstance(value_type, Binder): if value is None: nested_map = {f"{key_fmt}.": None} From c6f28718cf972f022ba96b2a9887fb1e235c13d3 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Mon, 24 Nov 2025 23:37:38 +0100 Subject: [PATCH 3/7] Add missing raw string prefixes to regex patterns Most `match` arguments were already raw strings, but not all. --- tests/test_formatting.py | 4 ++-- tests/test_parsing.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_formatting.py b/tests/test_formatting.py index 48d39c5..7a4e517 100644 --- a/tests/test_formatting.py +++ b/tests/test_formatting.py @@ -240,9 +240,9 @@ def test_format_value_nested_dataclass(*, optional: bool, string: bool) -> None: def test_format_value_unsupported_type() -> None: - with pytest.raises(TypeError, match="^NoneType$"): + with pytest.raises(TypeError, match=r"^NoneType$"): format_toml_pair("unsupported", None) - with pytest.raises(TypeError, match="^NoneType$"): + with pytest.raises(TypeError, match=r"^NoneType$"): list(_iter_format_value(None)) diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 79a4a93..74f8eaa 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -659,7 +659,7 @@ def test_bind_timedelta_direct() -> None: duration = false """ ) as stream, - pytest.raises(TypeError, match="^Value for 'TimeDeltaConfig.duration' has type 'bool', expected time$"), + pytest.raises(TypeError, match=r"^Value for 'TimeDeltaConfig.duration' has type 'bool', expected time$"), ): Binder(TimeDeltaConfig).parse_toml(stream) @@ -693,7 +693,7 @@ def test_bind_timedelta_suffix() -> None: ) as stream, pytest.raises( TypeError, - match="^Value for 'TimeDeltaConfig.duration' with suffix 'weeks' has type 'bool', expected number$", + match=r"^Value for 'TimeDeltaConfig.duration' with suffix 'weeks' has type 'bool', expected number$", ), ): Binder(TimeDeltaConfig).parse_toml(stream) From 693e5773a73fedfc2f291646992877292b787be3 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Mon, 24 Nov 2025 23:47:50 +0100 Subject: [PATCH 4/7] Update Ruff to 0.14.6 --- .pre-commit-config.yaml | 2 +- pyproject.toml | 2 +- src/dataclass_binder/_impl.py | 2 +- tests/test_formatting.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3241c6f..2e776d6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,6 +13,6 @@ repos: hooks: - id: isort - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.274 + rev: v0.14.6 hooks: - id: ruff diff --git a/pyproject.toml b/pyproject.toml index a7b668b..1dc6b4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ pre-commit = "^4.5.0" mypy = "^1.5" pytest = "^7.2.0" pytest-cov = "^4.0.0" -ruff = "0.0.274" +ruff = "0.14.6" [tool.poetry.group.coverage.dependencies] coverage = {version = "^7.0.0", extras = ["toml"]} diff --git a/src/dataclass_binder/_impl.py b/src/dataclass_binder/_impl.py index 8420e39..d825fd9 100644 --- a/src/dataclass_binder/_impl.py +++ b/src/dataclass_binder/_impl.py @@ -187,7 +187,7 @@ def _get_fields(cls: type) -> Iterator[tuple[Field, type]]: continue if isinstance(annotation, str): try: - annotation = eval(annotation, cls_globals, cls_locals) # noqa: PGH001 + annotation = eval(annotation, cls_globals, cls_locals) except NameError as ex: raise TypeError(f"Failed to parse annotation of field '{cls.__name__}.{name}': {ex}") from None yield field, annotation diff --git a/tests/test_formatting.py b/tests/test_formatting.py index 7a4e517..646923b 100644 --- a/tests/test_formatting.py +++ b/tests/test_formatting.py @@ -821,7 +821,7 @@ def test_format_template_depth_first() -> None: ) -@pytest.fixture() +@pytest.fixture def sourceless_class() -> type[Any]: """A class for which no source code is available.""" From dcd05c38907e5ccdcf32be732f180fb01ffaac88 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Mon, 24 Nov 2025 23:52:15 +0100 Subject: [PATCH 5/7] Put Ruff linter config in new preferred location --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1dc6b4c..ceca098 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,8 @@ line_length = 120 line-length = 120 target-version = "py310" src = ["src"] + +[tool.ruff.lint] select = [ "F", "E", "W", "I", "N", "UP", "ANN0", "ANN2", "FBT", "B", "A", "C4", "FA", "ISC", "ICN", "G", "INP", "PIE", "T20", "PT", "Q", "RSE", "RET", "SIM", From 6b37207fd0d3ea31df6db4928fb7f6ec8eeb7086 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Mon, 24 Nov 2025 23:55:48 +0100 Subject: [PATCH 6/7] Use Ruff for formatting --- .pre-commit-config.yaml | 13 ++++--------- pyproject.toml | 9 --------- src/dataclass_binder/_impl.py | 13 ++++--------- tests/test_formatting.py | 6 +++--- 4 files changed, 11 insertions(+), 30 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2e776d6..3fb4966 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,15 +4,10 @@ repos: hooks: - id: end-of-file-fixer - id: trailing-whitespace -- repo: https://github.com/psf/black - rev: 22.12.0 - hooks: - - id: black -- repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.14.6 hooks: - - id: ruff + - id: ruff-check + - id: ruff-check + args: [--select, I, --fix] + - id: ruff-format diff --git a/pyproject.toml b/pyproject.toml index ceca098..6b979e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,15 +31,6 @@ ruff = "0.14.6" [tool.poetry.group.coverage.dependencies] coverage = {version = "^7.0.0", extras = ["toml"]} -[tool.black] -line-length = 120 -target-version = ["py310"] - -[tool.isort] -profile = "black" -multi_line_output = 3 -line_length = 120 - [tool.ruff] line-length = 120 target-version = "py310" diff --git a/src/dataclass_binder/_impl.py b/src/dataclass_binder/_impl.py index d825fd9..9d9e4a3 100644 --- a/src/dataclass_binder/_impl.py +++ b/src/dataclass_binder/_impl.py @@ -211,7 +211,6 @@ def _check_field(field: Field, field_type: type, context: str) -> None: @dataclass(slots=True) class _ClassInfo(Generic[T]): - _cache: ClassVar[MutableMapping[type[Any], _ClassInfo[Any]]] = WeakKeyDictionary() dataclass: type[T] @@ -271,12 +270,10 @@ def __class_getitem__(cls: type[Binder[T]], dataclass: type[T]) -> Binder[T]: return cls(dataclass) @overload - def __init__(self, class_or_instance: type[T]) -> None: - ... + def __init__(self, class_or_instance: type[T]) -> None: ... @overload - def __init__(self, class_or_instance: T) -> None: - ... + def __init__(self, class_or_instance: T) -> None: ... def __init__(self, class_or_instance: type[T] | T) -> None: if isinstance(class_or_instance, type): @@ -569,12 +566,10 @@ def _format_toml_table( # These definitions exist to support the deprecated `Binder[DC]` syntax in mypy. @classmethod - def bind(cls, data: Mapping[str, Any]) -> T: - ... + def bind(cls, data: Mapping[str, Any]) -> T: ... @classmethod - def parse_toml(cls, file: BinaryIO | str | Path) -> T: - ... + def parse_toml(cls, file: BinaryIO | str | Path) -> T: ... else: diff --git a/tests/test_formatting.py b/tests/test_formatting.py index 646923b..ffe5ed2 100644 --- a/tests/test_formatting.py +++ b/tests/test_formatting.py @@ -100,9 +100,9 @@ def round_trip_value(value: T, dc: type[Any]) -> T: "\"both\" 'quotes'", "embedded\nnewline", r"back\slash", - 'I\'m a string. "You can quote me". Name\tJos\u00E9\nLocation\tSF.', - "complex string with back\\slash, \"both\" 'quotes' and \u0000control\u007Fchars\u0007", - "\U0001F44D", + 'I\'m a string. "You can quote me". Name\tJos\u00e9\nLocation\tSF.', + "complex string with back\\slash, \"both\" 'quotes' and \u0000control\u007fchars\u0007", + "\U0001f44d", date(2022, 10, 5), datetime(2022, 10, 5, 19, 16, 29), time(19, 16, 29), From 2e8ad8147267f01506cdb6bdf56c064a98bc7a58 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Mon, 24 Nov 2025 23:59:54 +0100 Subject: [PATCH 7/7] Have Ruff collapse imports that fit on a single line As Ruff will auto-add the trailing commas when splitting imports over multiple lines, the trailing commas in imports are generally not intentional, unlike in other places in the code. --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 6b979e7..63c0fff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,9 @@ ignore = [ "TRY301", # alternatives might be worse ] +[tool.ruff.lint.isort] +split-on-trailing-comma = false + [tool.mypy] disallow_incomplete_defs = true disallow_untyped_defs = true