Skip to main content

Implement your own type of dataclass without the usual boilerplate code.

Project description

dataclassbase: Implement custom dataclasses

See Documentation for the latest documentation.

What it is and how to get it

dataclassbase is a small toolkit for providing your own dataclasses. For dataclasses, attributes are declared via annotations, and constructors are created automatically making development fast.

However, often data in dataclasses shares common properties (e.g., data shape, etc.) or we might want to have more control over the fields in dataclasses. This is where dataclassbase comes to help: You can define your own dataclass-like behavior using typing.dataclass_transform or metaclasses, and control the behavior of your dataclass in a fine-grained manner.

You can install it into your environment from PyPI via

pip install dataclassbase

General note

While the project is generally usable, it is not yet production-ready. It is, however, being actively developed since I plan to use it as a base for further work.

License

MIT

The Basics

The dataclasses module provides very basic dataclasses with little customization. The objective of this package is to overcome this limitation by providing customizable dataclasses.

A possible definition for the built-in dataclasses is

from dataclasses import dataclass

@dataclass
class SomeClass:
    some_field: str
    some_defaulting_field: int = 1

In this framework, a similar approach is possible:

from dataclassbase import dataclass

@dataclass
class SomeClass:
    some_field: str
    some_defaulting_field: int = 1

The most obvious difference above is that we import from dataclassbase rather than dataclasses. However, looking at the signature of dataclassbase.dataclass, we note that there are two notable parameters: field_provider and object_handler. field_provider allows customization of the field generation and processing (if the user provides a dataclassbase.Field as a default value), object_handler implements the logic for the initialization (__init__) and attribute assignment (__setattr__) of objects of the generated dataclass. We therefore can hook into or override the behavior of the dataclass.

If you omit the field_provider or object_handler, the respective default implementations provided in the FieldProvider and ObjectHandler classes, which reproduce the default dataclass behavior, are used instead. If you provide a callable matching the signature of dataclassbase.Field to field_provider, a FieldProvider is generated. Therefore, you can directly specify the type of field to use, as long as it matches the respective signature. You can also replace the default-value if it indicates a value you want to process.

Note that while it suffices to call dataclassbase.dataclass on a class to initialize dataclass-like behavior, it does not truly behave like a dataclass to static type checkers since it lacks the typing.dataclass_transform decorator. My reason for that choice is that I would have to fix the field_specifiers parameter. (Instances of the types provided in field_specifiers are allowed as default values for fields since they are assumed to be fields or to be converted into some. We do that conversion in the provided field_provider.) In doing so, I would constrain proper static type checking on the fields. Therefore, I would rather let you indicate the types yourself. To achieve true dataclass-like behavior on a type checker, you can call typing.dataclass_transform yourself or rely on a metaclass derived from DataclassMetaBase[TField]:

Rather than using dataclassbase.dataclass as above, you can define your own dataclass by deriving a metaclass from class DataclassMetaBase[TField] and attaching a typing.dataclass_transform to it. DataclassMetaBase invokes make_dataclass when creating the new type (dataclass is the decorator-variant of make_dataclass). It provides the methods _field_provider and _object_handler which act as factories for the field provider and object handler for the types using the metaclass.

from dataclassbase import DataclassMetaBase, Field, FieldProvider
from typing import dataclass_transform


class CustomField(Field):
    # you can override the methods provided by `Field` to control the behavior of the field in derived classes,
    # superclasses, to check the assignments, etc. You can also change the behavior of `__init__` as long as its 
    # signature matches the one from the base
    pass


@dataclass_transform
class CustomDataclassMeta(DataclassMetaBase[Field]):
    @classmethod
    def _field_provider(cls) -> FieldProvider[Field]:
        # we want it to return our custom field. The factory works since the signature of `CustomField.__init__` matches
        # the one of `Field.__init__`
        return FieldProvider(CustomField)
    
class VariantA(metaclass=CustomDataclassMeta):
    field: int
    defaulting: int = 1

dataclassbase.Fields themselves can check whether the value assigned to them is valid. Furthermore, they can check whether fields in derived classes are valid and, in the opposite direction, whether they are valid overrides of fields of superclasses. As an example, you can look at the provided dataclassbase.TypeConstrainedField which provides simple type checking via isinstance.

The default implementation is DataclassMeta which just uses the default values and allows any dataclasses.Field as a default value for its fields. While it behaves in the same way as the built-in dataclasses-dataclasses, it does however not give us any benefits over it.

from dataclassbase import DataclassMeta

class VariantB(metaclass=DataclassMeta):
    field: int
    defaulting: int = 1    

Fine-tuning the behavior of the dataclasses

dataclasses.dataclass comes with quite a lot of nice additions: We can freeze the dataclass, equality and unequality operators get generated, a hash function may be provided, and so forth. This behavior can be imitated using the classes derived from the BehaviorModifier type. For now, these are

  • Frozen: Dataclasses cannot be assigned new values and cannot be reinitialized
  • Representable: Adds a string representation via __repr__ and __str__
  • Equatable: Adds overloads for __eq__ and __ne__, and, optionally, for __hash__
  • PositionalOnly: The initializer only allows positional arguments
  • KeywordOnly: The initializer only allows keyword arguments

Add these as modifiers when calling dataclass or make_dataclass or return them as _modifiers in metaclasses derived from DataclassMetaBase.

Note that some of the behaviors are "unsafe", and it is up to you to decide whether your use-case is safe. For example, hash codes typically change once fields of the dataclass change. Using such a class as a key is unsafe. You thus will typically combine Frozen and Equatable. Note that Frozen does not affect nested dataclasses: If these are not frozen, the dataclass itself may still be mutable. Another unsafe example is the combination of PositionalOnly and KeywordOnly which does not make any sense, but is not caught for now.

I may add such safeguards later, but for now it is obvious that such combinations do not make any sense.

When to use dataclassbase and not ...?

Pydantic

Pydantic's objective is to provide fast data validation and serialization [1]. dataclassbase aims to be much more abstract since it is not constrained to a single use-case such as data validation, and thus does (hopefully) not give too strong restrictions on what you can/cannot do.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dataclassbase-0.1.2.tar.gz (27.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dataclassbase-0.1.2-py3-none-any.whl (15.9 kB view details)

Uploaded Python 3

File details

Details for the file dataclassbase-0.1.2.tar.gz.

File metadata

  • Download URL: dataclassbase-0.1.2.tar.gz
  • Upload date:
  • Size: 27.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for dataclassbase-0.1.2.tar.gz
Algorithm Hash digest
SHA256 0ef8575547133f35ea35ec5a15e8325122f5432dd0f2b5c57a15d7faa3b12690
MD5 ecb25d85c388880e428bac781bed96a4
BLAKE2b-256 1052dca32547229e73033c5923ed11acb45e72e3eecc316df6e5769ce09e50c4

See more details on using hashes here.

Provenance

The following attestation bundles were made for dataclassbase-0.1.2.tar.gz:

Publisher: check_and_deploy.yml on omicronZero/dataclassbase

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dataclassbase-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: dataclassbase-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 15.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for dataclassbase-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 36d4a1b2ff8b6253d8d84e8acd65be0a2c47884d2891083871572bf98fd52604
MD5 17243ead92763d83668bb2ebda29e402
BLAKE2b-256 e07a4b67babb39caa8da666ec24c2f8e87aa1f02ab891acbae6425798ed5f42a

See more details on using hashes here.

Provenance

The following attestation bundles were made for dataclassbase-0.1.2-py3-none-any.whl:

Publisher: check_and_deploy.yml on omicronZero/dataclassbase

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page