Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.0.274
rev: v0.14.6
hooks:
- id: ruff
- id: ruff-check
- id: ruff-check
args: [--select, I, --fix]
- id: ruff-format
16 changes: 6 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,17 @@ 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"]}

[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"
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",
Expand All @@ -57,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
Expand Down
22 changes: 9 additions & 13 deletions src/dataclass_binder/_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]
Expand Down Expand Up @@ -260,7 +259,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]
Expand All @@ -270,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):
Expand Down Expand Up @@ -323,7 +321,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()
}
Expand Down Expand Up @@ -506,7 +504,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}.<name>": None}
Expand Down Expand Up @@ -568,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:

Expand Down
12 changes: 6 additions & 6 deletions tests/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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))


Expand Down Expand Up @@ -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."""

Expand Down
4 changes: 2 additions & 2 deletions tests/test_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down