Create python class with type annotations programatically
Asked Answered
F

1

5

I want to be able to create a python class like the following programmatically:

class Foo(BaseModel):
    bar: str = "baz"

The following almost works:

Foo = type("Foo", (BaseModel,), {"bar":"baz"})

But doesn't include the annotation, Foo.__annotations__ is set in first example but not the second.

Is there any way to achieve this?

My motivation is to create a class decorator that creates a clone of the decorated class with modified type annotations. The annotations have to be set during class creation (not after the fact) to that the metaclass of BaseModel will see them.

Flit answered 18/2, 2023 at 13:41 Comment(2)
A brute-force solution would be: data = {"bar":"baz"} data["__annotations__"]={key:type(value) for key,value in data.items()} y = type("Foo", (BaseModel,), data)Outandout
I want to set the type annotations explicitly rather than inferring them, but you've set me on the right track with passing annotations as a key in the attributes dictionary!Flit
F
5

The comment from @Scarlet made me realise the following solution answers my question.

Foo = type(
    "Foo", 
    (BaseModel,), 
    {
        "bar": "baz", 
        "__annotations__": {"bar": str}
    }
)

Although it turns solving my motivating problem with dynamically tweaking field types on pydantic classes required overriding the behaviour of the metaclass like so:

from pydantic.main import ModelMetaclass

class OverrideModelMetaclass(ModelMetaclass):
    def __new__(cls, name, bases, attrs):
        attrs["__annotations__"] = {
            attribute_name: cls.__transform_annotation(annotation)
            for attribute_name, annotation in attrs["__annotations__"].items()
        }
        return super().__new__(cls, name, bases, attrs)

    @classmethod
    def __transform_annotation(cls, annotation):
        # this method maps over the attribute annotations
        return Override[annotation]


class Foo(BaseModel, metaclass=OverrideModelMetaclass):
    bar: str

The above is equivalent to:

class Foo(BaseModel):
    bar: Override[str]
Flit answered 18/2, 2023 at 19:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.