Hi all! I’m very green with the advanced use of the typing module, so please bear with me.
I’ve seen a lot of discussion around generics, ParamSpec, and how to copy signatures of functions to other functions, like those discussed here and here. However, it seems these solutions involve explicitly passing in the function to a decorator, or to create a TypeDict, which I don’t think will work for my use case. Maybe my use case is not a good one and should be handled in another way (I’m all ears), but let me explain what I’m trying to accomplish.
I have a class which inherits from pydantic’s BaseModel
from pydantic import BaseModel
class MyClass(BaseModel):
field1: int
field2: str
foo = MyClass(field1=..., field2=...)
When I go to type the last line, the type hinter (I’m using VS Code with the built-in pyright type checker), it correctly provides field1 and field2 when I go to create the class. I know this is some magic done in pydantic by replacing signatures (maybe only for runtime?) and with the use of the @typing.dataclass_transform decorator on BaseModel or its parent metaclass. I haven’t looked into the details of how the decorator works.
I am looking to build a mixin class which adds a “alternate constructor” for these classes. Example:
from pydantic import BaseModel
class MyMixin:
@classmethod
def alt_construct(cls, *args, **kwargs):
...
return cls(*args, **kwargs)
class MyClass(MyMixin, BaseModel):
field1: int
field2: str
foo = MyClass.alt_constructor(...)
Right now, if I type the last line, the signature for alt_constructor is just *args: Any, **kwargs: Any. I am seeking a way that I can type hint these mixin class methods with the signature of the __init__ or __call__ functions of the class new class inheriting the mixin.
I came across this comment which sounds like they’re trying to doing what I want to achieve but within the same class.
I have tried to use a decorator approach, but that requires a syntax like:
def source_func(a: int, b: str) -> float: ...
@copy_params_from(source_func)
def target_func(*args, **kwargs): ...
The source_func where the parameters are being copied from has to be in scope. In the mixin, they are not in scope where the decorated function is written.
I have tried to write a Generic class with a ParamSpec, but so far none of them have resulted in the correct type hinting - all of them have been empty type hints (e.g. (...)) or just (*args: Any, **kwargs: Any) straight from the signature of target_func. The closest I have gotten is a solution using typing.cast, but this required the alternate constructor to be in the same class and have an explicitly defined __init__ function. Even then, it copied the self parameter in addition to the others.
Is there a way that I can type hint alt_constructor from my example MyMixin class in a generic way that the static type hint readers can understand, and which changes depending on which class it’s mixed into?