Making classfactory that derive from dataclass objects informed by types in python
Question:
When using dataclasses.dataclass
the type information informs how parameters are parsed. I would like to take a defined dataclass
and produce a class that changes all the attribute type declarations from X
to Optional[List[X]]
.
from dataclasses import dataclass
from dataclasses_json import DataClassJsonMixin
from datetime import datetime
@dataclass
class SrcClass(DataClassJsonMixin):
number: int
name: str
at: datetime
SrcClassLister = make_lister(SrcClass)
I want the function make_lister
to produce a class similar to the class defined below.
@dataclass
class SrcClassLister(DataClassJsonMixin):
numbers: Optional[List[int]]
names: Optional[List[str]]
ats: Optional[List[datetime]]
I am unsure how type information is captured for parsing by the dataclass
.
My reason for doing this is I have a high variety of dataclass definitions and I would like to automatically make a spec for filtering. This spec would take a list of values that are acceptable for a pass-filter.
Answers:
It’s relatively straightforward:
from dataclasses import make_dataclass
def make_lister(cls)
return make_dataclass(
cls.__name__ + "Filter", # Assuming you want to name
# the new class like that
[(key, Optional[List[value]]) for key, value in cls.__annotations__.items()],
)
Note that there are a few quirks:
- If you want to use this function as a decorator instead, maybe you shouldn’t change the name of the dataclass. Dataclasses must know their name, it’s like that.
- This will ditch any additional information about fields (like their default constructor or stuff like that). If you want that, you should probably go through the
Field
interface instead; it’s just that, since you didn’t mention how you wanted to handle that, I didn’t do anything about it.
I’d imagine something like
import dataclasses
import typing
from dataclasses import dataclass
from dataclasses_json import DataClassJsonMixin
from datetime import datetime
@dataclass
class SrcClass(DataClassJsonMixin):
number: int
name: str
at: datetime
purpose: int = 42
def pluralize(name):
# TODO: improve this if you will
return name + "s"
def make_lister(src_cls):
fields = [
(pluralize(field.name), typing.Optional[typing.List[field.type]], dataclasses.field(default=None))
for field in dataclasses.fields(src_cls)
]
name = f"{src_cls.__name__}Lister"
return dataclasses.make_dataclass(name, fields, bases=(DataClassJsonMixin,))
SrcClassLister = make_lister(SrcClass)
scl = SrcClassLister(numbers=[1, 2])
print(scl)
print(scl.to_json())
works for you – this prints out
SrcClassLister(numbers=[1, 2], names=None, ats=None, purposes=None)
{"numbers": [1, 2], "names": null, "ats": null, "purposes": null}
You can do it by creating a decorator, or by doing this:
from typing import List, Optional
from dataclasses_json import DataClassJsonMixin
from dataclasses import dataclass
from datetime import datetime
@dataclass
class SrcClass(DataClassJsonMixin):
number: int
name: str
at: datetime
def make_lister(some_class):
modified_class = type('Lister', (some_class,), {})
new_annotations = {}
for attr, type_ in modified_class.__annotations__.items():
new_annotations[attr + 's'] = Optional[List[type_]]
modified_class.__annotations__ = new_annotations
return modified_class
SrcClassLister = make_lister(SrcClass)
print(SrcClassLister.__annotations__)
Printing the expected output.
When using dataclasses.dataclass
the type information informs how parameters are parsed. I would like to take a defined dataclass
and produce a class that changes all the attribute type declarations from X
to Optional[List[X]]
.
from dataclasses import dataclass
from dataclasses_json import DataClassJsonMixin
from datetime import datetime
@dataclass
class SrcClass(DataClassJsonMixin):
number: int
name: str
at: datetime
SrcClassLister = make_lister(SrcClass)
I want the function make_lister
to produce a class similar to the class defined below.
@dataclass
class SrcClassLister(DataClassJsonMixin):
numbers: Optional[List[int]]
names: Optional[List[str]]
ats: Optional[List[datetime]]
I am unsure how type information is captured for parsing by the dataclass
.
My reason for doing this is I have a high variety of dataclass definitions and I would like to automatically make a spec for filtering. This spec would take a list of values that are acceptable for a pass-filter.
It’s relatively straightforward:
from dataclasses import make_dataclass
def make_lister(cls)
return make_dataclass(
cls.__name__ + "Filter", # Assuming you want to name
# the new class like that
[(key, Optional[List[value]]) for key, value in cls.__annotations__.items()],
)
Note that there are a few quirks:
- If you want to use this function as a decorator instead, maybe you shouldn’t change the name of the dataclass. Dataclasses must know their name, it’s like that.
- This will ditch any additional information about fields (like their default constructor or stuff like that). If you want that, you should probably go through the
Field
interface instead; it’s just that, since you didn’t mention how you wanted to handle that, I didn’t do anything about it.
I’d imagine something like
import dataclasses
import typing
from dataclasses import dataclass
from dataclasses_json import DataClassJsonMixin
from datetime import datetime
@dataclass
class SrcClass(DataClassJsonMixin):
number: int
name: str
at: datetime
purpose: int = 42
def pluralize(name):
# TODO: improve this if you will
return name + "s"
def make_lister(src_cls):
fields = [
(pluralize(field.name), typing.Optional[typing.List[field.type]], dataclasses.field(default=None))
for field in dataclasses.fields(src_cls)
]
name = f"{src_cls.__name__}Lister"
return dataclasses.make_dataclass(name, fields, bases=(DataClassJsonMixin,))
SrcClassLister = make_lister(SrcClass)
scl = SrcClassLister(numbers=[1, 2])
print(scl)
print(scl.to_json())
works for you – this prints out
SrcClassLister(numbers=[1, 2], names=None, ats=None, purposes=None)
{"numbers": [1, 2], "names": null, "ats": null, "purposes": null}
You can do it by creating a decorator, or by doing this:
from typing import List, Optional
from dataclasses_json import DataClassJsonMixin
from dataclasses import dataclass
from datetime import datetime
@dataclass
class SrcClass(DataClassJsonMixin):
number: int
name: str
at: datetime
def make_lister(some_class):
modified_class = type('Lister', (some_class,), {})
new_annotations = {}
for attr, type_ in modified_class.__annotations__.items():
new_annotations[attr + 's'] = Optional[List[type_]]
modified_class.__annotations__ = new_annotations
return modified_class
SrcClassLister = make_lister(SrcClass)
print(SrcClassLister.__annotations__)
Printing the expected output.