Skip to content

Unified API for working with multiple dataclass-like libraries

License

Notifications You must be signed in to change notification settings

pyapp-kit/fieldz

Repository files navigation

fieldz

License PyPI Python Version CI codecov

Unified API for working with multiple dataclass-like libraries

Dataclass patterns

There are many libraries that implement a similar dataclass-like pattern!

import dataclasses

@dataclasses.dataclass
class SomeDataclass:
    a: int = 0
    b: str = "b"
    c: list[int] = dataclasses.field(default_factory=list)
import pydantic

class SomePydanticModel(pydantic.BaseModel):
    a: int = 0
    b: str = "b"
    c: list[int] = pydantic.Field(default_factory=list)
import attr

@attr.define
class SomeAttrsModel:
    a: int = 0
    b: str = "b"
    c: list[int] = attr.field(default=attr.Factory(list))
import msgspec

class SomeMsgspecStruct(msgspec.Struct):
    a: int = 0
    b: str = "b"
    c: list[int] = msgspec.field(default_factory=list)

etc...

Unified API

These are all awesome libraries, and each has its own strengths and weaknesses. Sometimes, however, you just want to be able to query basic information about a dataclass-like object, such as getting field names or types, or converting it to a dictionary.

fieldz provides a unified API for these operations (following or extending the API from dataclasses when possible).

def fields(obj: Any) -> tuple[Field, ...]:
    """Return a tuple of fieldz.Field objects for the object."""

def replace(obj: Any, /, **changes: Any) -> Any:
    """Return a copy of obj with the specified changes."""

def asdict(obj: Any) -> dict[str, Any]:
    """Return a dict representation of obj."""

def astuple(obj: Any) -> tuple[Any, ...]:
    """Return a tuple representation of obj."""

def params(obj: Any) -> DataclassParams:
    """Return parameters used to define the dataclass."""

The fieldz.Field and fieldz.DataclassParam objects are simple dataclasses that match the protocols of dataclasses.Field and the (private) dataclasses._DataclassParams objects, respectively. The field object also adds a native_field attribute that is the original field object from the underlying library.

Example

from fieldz import Field, fields

standardized_fields = (
    Field(name="a", type=int, default=0),
    Field(name="b", type=str, default="b"),
    Field(name="c", type=list[int], default_factory=list),
)

assert (
    fields(SomeDataclass)
    == fields(SomePydanticModel)
    == fields(SomeAttrsModel)
    == fields(SomeMsgspecStruct)
    == standardized_fields
)

Supported libraries

... maybe someday?