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.

Asked By: probinso

||

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:

  1. 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.
  2. 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.
Answered By: BlackBeans

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}
Answered By: AKX

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.

Answered By: Luis G.