Skip to main content

Additional functionality for Python dataclasses

Project description

dataclass-extensions

Additional functionality for Python dataclasses

Installation

Python 3.10 or newer is required. You can install the package from PyPI:

pip install dataclass-extensions

Features

Encode/decode to/from JSON-safe dictionaries

from dataclasses import dataclass
from dataclass_extensions import decode, encode


@dataclass
class Fruit:
    calories: int
    price: float

@dataclass
class FruitBasket:
    fruit: Fruit
    count: int

basket = FruitBasket(fruit=Fruit(calories=200, price=1.0), count=2)
assert encode(basket) == {"fruit": {"calories": 200, "price": 1.0}, "count": 2}
assert decode(FruitBasket, encode(basket)) == basket

You can also define how to encode/decode non-dataclass types:

from dataclasses import dataclass
from dataclass_extensions import decode, encode


class Foo:
    def __init__(self, x: int):
        self.x = x

@dataclass
class Bar:
    foo: Foo

encode.register_encoder(lambda foo: {"x": foo.x}, Foo)
decode.register_decoder(lambda d: Foo(d["x"]), Foo)

bar = Bar(foo=Foo(10))
assert encode(bar) == {"foo": {"x": 10}}
assert decode(Bar, encode(bar)) == bar

Merge dictionaries into a dataclass

from dataclasses import dataclass
from dataclass_extensions import merge


@dataclass
class Optimizer:
    lr: float
    steps: int

@dataclass
class Config:
    optimizer: Optimizer
    name: str = "default"

config = Config(optimizer=Optimizer(lr=0.1, steps=100), name="run1")

# Override top-level fields
updated = merge(config, {"name": "run2"})
assert updated.name == "run2"
assert updated.optimizer.lr == 0.1  # unchanged

# Merge recursively into nested dataclasses
updated = merge(config, {"optimizer": {"lr": 0.001}})
assert updated.optimizer.lr == 0.001
assert updated.optimizer.steps == 100  # unchanged
assert updated.name == "run1"          # unchanged

# The original is never modified
assert config.optimizer.lr == 0.1

Override dataclass fields from the command line

merge_from_dotlist() works like merge() but accepts strings of the form "field=value", where the value is parsed as YAML. Nested fields are targeted with dot notation. This gives you a cheap way to expose a dataclass config to a CLI:

import sys
from dataclasses import dataclass
from dataclass_extensions import merge_from_dotlist


@dataclass
class Optimizer:
    lr: float = 1e-3
    steps: int = 1000

@dataclass
class Config:
    optimizer: Optimizer = None  # type: ignore
    name: str = "default"
    seed: int = 42

    def __post_init__(self):
        if self.optimizer is None:
            self.optimizer = Optimizer()

# Both "field=value" and "--field=value" forms are accepted, so this works
# whether argv looks like ["optimizer.lr=1e-4", "name=run1"] or
# ["--optimizer.lr=1e-4", "--name=run1"].
config = merge_from_dotlist(Config(), *sys.argv[1:])

# Values are parsed as YAML, so types are handled automatically:
assert config.optimizer.lr  == 0.0001  (float)
assert config.optimizer.steps == 500   (int)
assert config.name == "run1"           (str)

Supported value syntax includes plain scalars (0.001, 100, true, null), quoted strings ("hello world"), lists ([1, 2, 3]), and inline mappings ({a: 1}). Values containing = work correctly because the split happens on the first = only.

Polymorphism through registrable subclasses

from dataclasses import dataclass
from dataclass_extensions import Registrable, decode, encode


@dataclass
class Fruit(Registrable):
    calories: int
    price: float

@Fruit.register("banana")
@dataclass
class Banana(Fruit):
    calories: int = 200
    price: float = 1.25

@Fruit.register("apple")
@dataclass
class Apple(Fruit):
    calories: int = 150
    price: float = 1.50
    variety: str = "Granny Smith"

@dataclass
class FruitBasket:
    fruit: Fruit
    count: int

basket = FruitBasket(fruit=Apple(), count=2)
assert encode(basket) == {
    "fruit": {
        "type": "apple",  # corresponds to the registered name
        "calories": 150,
        "price": 1.5,
        "variety": "Granny Smith",
    },
    "count": 2,
}
assert decode(FruitBasket, encode(basket)) == basket

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

dataclass_extensions-0.4.0.tar.gz (20.3 kB view details)

Uploaded Source

Built Distribution

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

dataclass_extensions-0.4.0-py3-none-any.whl (19.7 kB view details)

Uploaded Python 3

File details

Details for the file dataclass_extensions-0.4.0.tar.gz.

File metadata

  • Download URL: dataclass_extensions-0.4.0.tar.gz
  • Upload date:
  • Size: 20.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for dataclass_extensions-0.4.0.tar.gz
Algorithm Hash digest
SHA256 a159d90f391cbd9d963044653a02240c8cf8b6751e4a00b9e2c6a6b197045ea1
MD5 6c31383f83232188e0a301552ff91986
BLAKE2b-256 2d04c15fa11c4057d59bbf3b61453f50eb5d7480fcdc15574498789b5d7e791c

See more details on using hashes here.

File details

Details for the file dataclass_extensions-0.4.0-py3-none-any.whl.

File metadata

File hashes

Hashes for dataclass_extensions-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2411670f759948e341413be854dc952cbfe48ce0250a9741a732570873bbf163
MD5 357a703b62ee3fc9beeb66f89b06b171
BLAKE2b-256 ab48c360fc3d50e635cc8d84fa829a206b64c2fb47244d9fd91a0cbac492a62b

See more details on using hashes here.

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