diff --git a/README.md b/README.md index 9027fa1..92a6ddf 100644 --- a/README.md +++ b/README.md @@ -392,6 +392,27 @@ None ``` +This library also provide a lightweight dataclass-like decorator and field +function that supports these converters and converters in general. + +```pycon +>>> from dataclassish.converters import dataclass, field + +>>> @dataclass +... class MyClass: +... a: int | None = field(converter=Optional(int)) +... b: str = field(converter=str.upper) + +>>> obj = MyClass(a="1", b="hello") +>>> obj +MyClass(a=1, b='HELLO') + +>>> obj = MyClass(a=None, b="there") +>>> obj +MyClass(a=None, b='THERE') + +``` + ### Flags `dataclassish` provides flags for customizing the behavior of functions. For diff --git a/src/dataclassish/_src/converters.py b/src/dataclassish/_src/converters.py index 11026cd..3b42244 100644 --- a/src/dataclassish/_src/converters.py +++ b/src/dataclassish/_src/converters.py @@ -164,7 +164,7 @@ def __call__(self, value: ArgT | PassThroughTs, /) -> RetT | PassThroughTs: ##################################################################### # Minimal implementation of a dataclass supporting converters. -_CT = TypeVar("_CT") +_CT = TypeVar("_CT") # class type # TODO: how to express default_factory is mutually exclusive with default? @@ -228,7 +228,25 @@ class DataclassInstance(Protocol): __dataclass_fields__: ClassVar[dict[str, dataclasses.Field[Any]]] -def _process_dataclass(cls: type[_CT], **kwargs: Any) -> type[_CT]: +def process_dataclass(cls: type[_CT], **kwargs: Any) -> type[_CT]: + """Process a class into a dataclass with converters. + + Parameters + ---------- + cls : type + The class to transform into a dataclass. + **kwargs : Any + Additional keyword arguments to pass to `dataclasses.dataclass`. + + Returns + ------- + type[DataclassInstance] + The dataclass, it's a transformed version of the input class `cls`. This + also adds the argument ``_skip_convert`` to the `__init__` method, which + allows for skipping the conversion of fields. This provides a fast path + for when the input values are already converted. + + """ # Make the dataclass from the class. # This does all the usual dataclass stuff. dcls: type[_CT] = dataclasses.dataclass(cls, **kwargs) @@ -292,8 +310,8 @@ def dataclass( Parameters ---------- cls : type | None, optional - The class to transform into a dataclass. If `None`, returns a partial - function that can be used as a decorator. + The class to transform into a dataclass. If `None`, `dataclass` returns + a partial function that can be used as a decorator. **kwargs : Any Additional keyword arguments to pass to `dataclasses.dataclass`. @@ -327,5 +345,5 @@ def dataclass( """ if cls is None: - return functools.partial(_process_dataclass, **kwargs) - return _process_dataclass(cls, **kwargs) + return functools.partial(process_dataclass, **kwargs) + return process_dataclass(cls, **kwargs) diff --git a/src/dataclassish/converters.py b/src/dataclassish/converters.py index bd0ede9..bcfd175 100644 --- a/src/dataclassish/converters.py +++ b/src/dataclassish/converters.py @@ -5,9 +5,26 @@ includes: ``attrs`` and ``equinox``. This module provides a few useful converter functions. If you need more, check out ``attrs``! +This library also provide a lightweight dataclass-like decorator and field +function that supports these converters and converters in general. + +>>> from dataclassish.converters import dataclass, field, Optional + +>>> @dataclass +... class MyClass: +... a: int | None = field(converter=Optional(int)) +... b: str = field(converter=str.upper) + +>>> obj = MyClass(a="1", b="hello") +>>> obj +MyClass(a=1, b='HELLO') + +>>> obj = MyClass(a=None, b="there") +>>> obj +MyClass(a=None, b='THERE') + """ -__all__ = ["AbstractConverter", "Optional", "Unless"] +__all__ = ["AbstractConverter", "Optional", "Unless", "field", "dataclass"] -from ._src.converters import AbstractConverter, Optional, Unless -# TODO: make dataclass & field public +from ._src.converters import AbstractConverter, Optional, Unless, dataclass, field diff --git a/tests/test_converters.py b/tests/test_converters.py index 26bdcea..030a9ab 100644 --- a/tests/test_converters.py +++ b/tests/test_converters.py @@ -37,11 +37,6 @@ def test_unless_object(): assert converter("1.0") == 1.0 -def test_field_not_public(): - """Test `field` is not public.""" - assert not hasattr(dataclassish.converters, "field") - - def test_field(): """Test `field`.""" converter = dataclassish.converters.Optional(int) diff --git a/uv.lock b/uv.lock index dfac8be..fc1eb5c 100644 --- a/uv.lock +++ b/uv.lock @@ -219,7 +219,7 @@ wheels = [ [[package]] name = "dataclassish" -version = "0.4.1.dev3+g33b6075.d20241203" +version = "0.7.2.dev1+g7fb9f2f.d20250115" source = { editable = "." } dependencies = [ { name = "plum-dispatch" },