Skip to content

Commit

Permalink
✨ feat(converters): make dataclass and field public
Browse files Browse the repository at this point in the history
Signed-off-by: Nathaniel Starkman <nstarman@users.noreply.github.com>
  • Loading branch information
nstarman committed Jan 16, 2025
1 parent 7fb9f2f commit 31b6a21
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 15 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 24 additions & 6 deletions src/dataclassish/_src/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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)
23 changes: 20 additions & 3 deletions src/dataclassish/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 0 additions & 5 deletions tests/test_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 31b6a21

Please sign in to comment.