Confusing way to use TypeVar

in file1.py

from typing import TypeVar
T = TypeVar('T', ???)
class A:
   def __init__(self, model: T):
       self.model = model
   def func(self, param: T):
       pass

in file2.py

@dataclass
class BModel:
   x: str
   y: int

class B(A):
   def __init__(self):
       super().__init__(BModel)

in file3.py

@dataclass
class CModel:
   a: list
   b: dict

class C(A)
   def __init__(self):
        super().__init__(CModel)

what should the ??? be replaced with, I attempted using,

from typing import Union
T = TypeVar('T', Union[BModel, CModel])

but it does not work as expected.
Or is there another way to alter T based on what argument is passed by child class.

What you are looking for is Self type, available in 3.11 and from typing_extensions for older versions. See PEP 673 – Self Type | peps.python.org

Unfortunately mypy doesn’t support it yet, so you’ll have to mimick it using bounded TypeVars like the examples in the PEP.

In this case you can do something like this:

from typing import TypeVar

T = TypeVar('T', bound="A")

class A:
   def __init__(self: T, model: T):
       self.model = model

   def func(self: T, param: T):
       pass
2 Likes

When I use pydantic BaseModel, this trick gives me error, ’TypeError: issubclass() arg 1 must be a class’, should I change the pydantic model to dataclass?

Hard to say what’s going wrong without actually seeing the code.

Also after reading the OP again I realize what you wanted there also seems a bit different and more dynamic/impossible than I imagined.

Can you show the exact code and commands that give the errors you mention?

let me describe the problem a bit more,
I have a setup like this,
in child_one.py

from pydantic import BaseModel
class BModel(BaseModel):
   x: str
   y: int

class B(A):
   def __init__(self):
       super().__init__(BModel)

in child_two.py

from pydantic import BaseModel
class CModel(BaseModel):
   a: list
   b: dict

class C(A)
   def __init__(self):
        super().__init__(CModel)

in parent.py

from .child_one import BModel
from .child_two import CModel
from typing import TypeVar, Union
T = TypeVar('T', Union[BModel, CModel])
class A:
   def __init__(self, model: T):
       self.model = model
   def func(self, param: T):
       pass

I want the param: T to consider the T as per what is passed in __init__ of the child classes.
So, if the
T in model: T, considers BModel, then param also considers BModel, and if the
T in model: T considers CModel, then param also considers CModel.

But the Union does not work, it makes T to consider BModel for both the cases.

I use it with fastapi, to get the request body, based on what T is, if we use BModel then the request body would ask for a string and an int, if we use CModel then the request body would ask for a list and a dict.

The other way is I write func in both child_one.py and child_two.py, that is,
in child_two.py

class C(A)
   def __init__(self):
        super().__init__()
   def func(self, param: CModel):
       ...

in child_one.py

class B(A):
   def __init__(self):
       super().__init__()
    def func(self, param: BModel):
        ...

but I do not want to specify func in both child files

A is not defined, so if that is really your code, you will get a NameError when you try to run it.

both child_one.py and child_two.py have

from .parent import A

Okay sounds like you want A to be Generic then.

from typing import Generic, TypeVar

from pydantic import BaseModel

T = TypeVar("T")


class A(Generic[T]):
    def __init__(self, model: T):
        self.model = model

    def func(self, param: T):
        pass


class BModel(BaseModel):
    x: str
    y: int


class CModel(BaseModel):
    a: list
    b: dict


class B(A[BModel]):
    pass


class C(A[CModel]):
    pass

If furthermore you want to restrict T to only BModel and CModel, I think you can write

T = TypeVar("T", BModel, CModel)

https://mypy.readthedocs.io/en/stable/generics.html#type-variables-with-value-restriction

I was able to make this work, but I am currently not using TypeVar or Generic.
Using Generic makes param a string, so it also did not work as expected.
I had to make the request body a dependency, and return the values from the dependency to use them in a function.