Python: 3.7+
I have a dataclass and a subclass of it as following:
from abc import ABC
from dataclasses import dataclass
from typing import Dict, List, Optional
from dbconn import DBConnector
@dataclass
class User:
uid: int
name: str
@dataclass
class Model(ABC):
database: DBConnector
user: User
def func(self, *args, **kwargs):
pass
@dataclass
class Command(Model):
message: Optional[str] = "Hello"
def __post_init__(self):
self.user_id: str = str(self.user.uid)
self.message = f"{self.user.name}: {self.message}"
I could get the type hint for database
, user
and message
using typing.get_type_hints(Command)
.
How can I get the type hints for user_id
?
One workaround would to be pass in the user.uid and user.name as separate params to Command but that's not pragmatic when User object has many useful attributes.
I believe the reason why it doesn't work in the first place is because init gets called at runtime and that's why type checking doesn't take those attrs into account. One possible solution would be to parse the ast of the class but I'm not sure if that's recommended and generic enough. If yes, would appreciate a working example.
Figured out a hacky solution by using inspect.get_source and regex matching Type Hinted attributes. Also had to convert dataclass into a normal class for the end Model.
from abc import ABC
from dataclasses import dataclass
import inspect
import re
from typing import Dict, List, Optional
from dbconn import DBConnector
@dataclass
class User:
uid: int
name: str
@dataclass
class Model(ABC):
database: DBConnector
user: User
def func(self, *args, **kwargs):
pass
def get_type_hints(self):
source = inspect.getsource(self.__class__)
# Only need type hinted attributes
patt = r"self\.(?P<name>.+):\s(?P<type>.+)\s="
attrs = re.findall(patt, source)
for attr in attrs:
yield attr + (getattr(self, attr[0]), )
class Command(Model):
message: Optional[str] = "Hello"
def __init__(
self, database: DBConnector,
user: User,
message: Optional[str] = "Hello"
):
super().__init__(database, user)
self.user_id: str = str(self.user.uid)
self.message: Optional[str] = f"{self.user.name}: {self.message}"
cmd = Command(DBConnector(), User(123, 'Stack Overflow'))
for attr in cmd.get_type_hints():
print(attr)
# Output
('user_id', 'str', '123')
('message', 'str', 'Stack Overflow: Hello')
If someone can come up with a more robust solution, I'm definitely interested in it. For now, I'll mark this as my answer, in case someone stumbles upon this and is ok with a hacky solution.