Skip to main content

dataclass tools, extended by multiple dispatch

Project description

dataclassish

Tools from dataclasses, extended to all of Python

Python's dataclasses provides tools for working with objects, but only compatible @dataclass objects. 😢
This repository is a superset of those tools and extends them to work on ANY Python object you want! 🎉
You can easily register in object-specific methods and use a unified interface for object manipulation. 🕶️

For example,

from dataclassish import replace  # New object, replacing select fields

d1 = {"a": 1, "b": 2.0, "c": "3"}
d2 = replace(d1, c=3 + 0j)
print(d2)
# {'a': 1, 'b': 2.0, 'c': (3+0j)}

Installation

PyPI platforms PyPI version

pip install dataclassish

Documentation

Documentation Status

WIP. But if you've worked with a dataclass then you basically already know everything you need to know.

Quick example

In this Example we'll show how dataclassish works exactly the same as dataclasses when working with a @dataclass object.

from dataclassish import replace
from dataclasses import dataclass


@dataclass
class Point:
    x: float
    y: float


p = Point(1.0, 2.0)
print(p)
# Point(x=1.0, y=2.0)

p2 = replace(p, x=3.0)
print(p2)
# Point(x=3.0, y=2.0)

Now we'll work with a dict object. Note that you cannot use tools from dataclasses with dict objects.

from dataclassish import replace

p = {"x": 1, "y": 2.0}
print(p)
# {'x': 1, 'y': 2.0}

p2 = replace(p, x=3.0)
print(p2)
# {'x': 3.0, 'y': 2.0}

# If we try to `replace` a value that isn't in the dict, we'll get an error
try:
    replace(p, z=None)
except ValueError as e:
    print(e)
# invalid keys {'z'}.

Registering in a custom type is very easy! Let's make a custom object and define how replace will operate on it.

from typing import Any
from plum import dispatch


class MyClass:
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

    def __repr__(self) -> str:
        return f"MyClass(a={self.a},b={self.b},c={self.c})"


@dispatch
def replace(obj: MyClass, **changes: Any) -> MyClass:
    current_args = {k: getattr(obj, k) for k in "abc"}
    updated_args = current_args | changes
    return MyClass(**updated_args)


obj = MyClass(1, 2, 3)
print(obj)
# MyClass(a=1,b=2,c=3)

obj2 = replace(obj, c=4.0)
print(obj2)
# MyClass(a=1,b=2,c=4.0)

replace can also accept a second positional argument which is a dictionary specifying a nested replacement. For example consider the following dict:

p = {"a": {"a1": 1, "a2": 2}, "b": {"b1": 3, "b2": 4}, "c": {"c1": 5, "c2": 6}}

With replace the sub-dicts can be updated via:

replace(p, {"a": {"a1": 1.5}, "b": {"b2": 4.5}, "c": {"c1": 5.5}})
# {'a': {'a1': 1.5, 'a2': 2}, 'b': {'b1': 3, 'b2': 4.5}, 'c': {'c1': 5.5, 'c2': 6}}

In contrast in pure Python this would be:

from copy import deepcopy

newp = deepcopy(p)
newp["a"]["a1"] = 1.5
newp["b"]["b2"] = 4.5
newp["c"]["c1"] = 5.5

And this is the simplest case, where the mutability of a dict allows us to copy the full object and update it after. Note that we had to use deepcopy to avoid mutating the sub-dicts. So what if the objects are immutable?

@dataclass(frozen=True)
class Object:
    x: float | dict
    y: float


@dataclass(frozen=True)
class Collection:
    a: Object
    b: Object


p = Collection(Object(1.0, 2.0), Object(3.0, 4.0))
print(p)
Collection(a=Object(x=1.0, y=2.0), b=Object(x=3.0, y=4.0))

replace(p, {"a": {"x": 5.0}, "b": {"y": 6.0}})
# Collection(a=Object(x=5.0, y=2.0), b=Object(x=3.0, y=6.0))

With replace this remains a one-liner. Replace pieces of any structure, regardless of nesting.

To disambiguate dictionary fields from nested structures, use the F marker.

from dataclassish import F

replace(p, {"a": {"x": F({"thing": 5.0})}})
# Collection(a=Object(x={'thing': 5.0}, y=2.0),
#            b=Object(x=3.0, y=4.0))

Citation

DOI

If you found this library to be useful in academic work, then please cite.

Development

Actions Status

We welcome contributions!

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

dataclassish-0.2.tar.gz (14.4 kB view details)

Uploaded Source

Built Distribution

dataclassish-0.2-py3-none-any.whl (9.4 kB view details)

Uploaded Python 3

File details

Details for the file dataclassish-0.2.tar.gz.

File metadata

  • Download URL: dataclassish-0.2.tar.gz
  • Upload date:
  • Size: 14.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.0 CPython/3.12.5

File hashes

Hashes for dataclassish-0.2.tar.gz
Algorithm Hash digest
SHA256 b8a82cc69f33610a063d8ffd52f9b2dbd1fd20054c50f3ff36f01384253eaad5
MD5 a3e82fe44a3c508963d65384d11b4c3b
BLAKE2b-256 7d45f308d6678c37d5cee557967639ad09f3ecc8d642634998cdfd0c03889d49

See more details on using hashes here.

File details

Details for the file dataclassish-0.2-py3-none-any.whl.

File metadata

  • Download URL: dataclassish-0.2-py3-none-any.whl
  • Upload date:
  • Size: 9.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.0 CPython/3.12.5

File hashes

Hashes for dataclassish-0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 97935b963db5d87ec9ee8e223ca5b084a27192a669bb87f331aa16504571ccf2
MD5 886c21bc24a3d1af2b5e65751f17dc9f
BLAKE2b-256 e9703d4dcf6f2ec9bb7899595b328ab3134b5def2bda70ce9b3800d1827cdbc5

See more details on using hashes here.

Supported by

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