Declarively apply converter functions to class attributes.
Project description
Exert
Declaratively apply converter functions to class attributes.
Installation
Install via pip:
pip install exert
Usage
Use this to declaratively apply arbitrary converter functions to the attributes of a class. For example:
from __future__ import annotations
from typing import Annotated
from exert import exert
@exert
class Foo:
a: Annotated[int, lambda x: x**2]
b: Annotated[float, lambda x: x / 2]
def __init__(self, a: int, b: float) -> None:
self.a = a
self.b = b
foo = Foo(2, 42.0)
print(foo.a) # prints 4
print(foo.b) # prints 21.0
Here, the lambda function inside the Annotated
tag is the converter.
Using with dataclasses
Dataclasses can also be used to avoid writing the initializer by hand. For example:
...
from dataclasses import dataclass
@exert
@datclasses
class Foo:
a: Annotated[int, lambda x: x**2]
b: Annotated[float, lambda x: x / 2]
foo = Foo(2, 42.0)
print(foo.a) # prints 4
print(foo.b) # prints 21.0
Apply multiple converters sequentially
Multiple converters are allowed. For example:
...
@exert
@dataclass
class Foo:
a: Annotated[int, lambda x: x**2, lambda x: x**3]
b: Annotated[float, lambda x: x / 2, lambda x: x / 3]
foo = Foo(2, 42.0)
print(foo.a) # prints 64 [2**2=4, 4**3=64]
print(foo.b) # prints 7.0 [42.0/2=21.0, 21.0/3=7.0]
Excluding tagged fields
If you want to exclude a field that's tagged with Annotated
, you can do so using the tagged_exclude
parameter:
...
@exert(tagged_exclude=("b",))
@dataclass
class Foo:
a: Annotated[int, lambda x: x**2, lambda x: x**3]
b: Annotated[float, lambda x: x / 2, lambda x: x / 3]
foo = Foo(2, 42.0)
print(foo.a) # prints 64 [2**2=4, 4**3=64]
print(foo.b) # prints 42.0 [This field was ignored]
Including untagged fields
By default, exert
will ignore fields that aren't tagged with Annotated
. But you can
still apply converters to those fields. You'll need to use converters
and untagged_include
together:
...
@exert(converters=(lambda x: x**2, lambda x: x**3), untagged_include=("b",))
@dataclass
class Foo:
a: int
b: float
foo = Foo(2, 42.0)
print(foo.a) # prints 2 [This field remains untouched]
print(foo.b) # prints 5489031744.0 [42.0**2=1764, 1764**3=5489031744.0]
Apply common converters without repetition
Common converters can be applied to multiple fields without introducing any repetition. The the previous section already shows how to do it:
...
@exert(converters=(lambda x: x**2, ), untagged_include=("a", "b"))
@dataclass
class Foo:
a: int
b: float
foo = Foo(2, 42.0)
print(foo.a) # prints 2 [This field remains untouched]
print(foo.b) # prints 1764.0 [42.0**2=1764.0]
Apply common and tagged converters together
You can apply a sequence of common converters and tagged converters together. By default, the common converters are applied first and then the tagged converters are applied sequentially:
...
@exert(converters=(lambda x: x**2, lambda x: x**3), untagged_include=("b",))
@dataclass
class Foo:
a: Annotated[int, lambda x: x / 100]
b: float
foo = Foo(2, 42.0)
print(foo.a) # prints 0.64 [2**2=4, 4**3=64, 64/100=0.64]
print(foo.b) # prints 5489031744.0 [42.0**2=1764, 1764**3=5489031744.0]
You can also, choose to apply the common converters after the tagged ones. For this,
you'll need to set the apply_last
parameter to True
:
...
@exert(
converters=(lambda x: x**2, lambda x: x**3),
untagged_include=("b",),
apply_last=True,
)
@dataclass
class Foo:
a: Annotated[int, lambda x: x / 100]
b: float
foo = Foo(2, 42.0)
print(foo.a) # prints 6.401e-11 [2/100=0.02, 0.02**2=0.004, 0.0004**3=6.401e-11]
print(foo.b) # prints 5489031744.0 [42.0**2=1764, 1764**3=5489031744.0]
Include all untagged fields
To apply converters to all the untagged fields, use untagged_include="__all__"
:
...
@exert(
converters=(lambda x: x**2, lambda x: x**3),
untagged_include="__all__",
)
@dataclass
class Foo:
a: int
b: float
foo = Foo(2, 42.0)
print(foo.a) # prints 64 [2**2=4, 4**4=64]
print(foo.b) # prints 5489031744.0 [42.0**2=1764, 1764**3=5489031744.0]
Exclude all tagged fields
To ignore all the fields tagged with Annotated
, use tagged_exclude="__all__"
:
...
@exert(
converters=(lambda x: x**2, lambda x: x**3),
tagged_exclude="__all__",
)
@dataclass
class Foo:
a: Annotated[int, lambda x: x**2]
b: Annotated[float, lambda x: x**3]
foo = Foo(2, 42.0)
print(foo.a) # prints 2 [Untouched]
print(foo.b) # prints 42.0 [Untouched]
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.