You could use a generic type-conversion descriptor, declared in descriptors.py
:
import sys
class TypeConv:
__slots__ = (
'_name',
'_default_factory',
)
def __init__(self, default_factory=None):
self._default_factory = default_factory
def __set_name__(self, owner, name):
self._name = "_" + name
if self._default_factory is None:
# determine default factory from the type annotation
tp = owner.__annotations__[name]
if isinstance(tp, str):
# evaluate the forward reference
base_globals = getattr(sys.modules.get(owner.__module__, None), '__dict__', {})
idx_pipe = tp.find('|')
if idx_pipe != -1:
tp = tp[:idx_pipe].rstrip()
tp = eval(tp, base_globals)
# use `__args__` to handle `Union` types
self._default_factory = getattr(tp, '__args__', [tp])[0]
def __get__(self, instance, owner):
return getattr(instance, self._name)
def __set__(self, instance, value):
setattr(instance, self._name, self._default_factory(value))
Usage in main.py
would be like:
from __future__ import annotations
from dataclasses import dataclass
from descriptors import TypeConv
@dataclass
class Test:
value: int | str = TypeConv()
test = Test(value=1)
print(test)
test = Test(value='12')
print(test)
# watch out: the following assignment raises a `ValueError`
try:
test.value = '3.21'
except ValueError as e:
print(e)
Output:
Test(value=1)
Test(value=12)
invalid literal for int() with base 10: '3.21'
Note that while this does work for other simple types, it does not handle conversions for certain types - such as bool
or datetime
- as normally expected.
If you are OK with using third-party libraries for this, I have come up with a (de)serialization library called the dataclass-wizard that can perform type conversion as needed, but only when fromdict()
is called:
from __future__ import annotations
from dataclasses import dataclass
from dataclass_wizard import JSONWizard
@dataclass
class Test(JSONWizard):
value: int
is_active: bool
test = Test.from_dict({'value': '123', 'is_active': 'no'})
print(repr(test))
assert test.value == 123
assert not test.is_active
test = Test.from_dict({'is_active': 'tRuE', 'value': '3.21'})
print(repr(test))
assert test.value == 3
assert test.is_active