'how to solve the recursion problem when specifying type hints for classes from different files
how to solve the recursion problem when specifying type hints for classes from different files
models1.py
from models2 import Second
@dataclass
class First:
attribute: Second
models2.py
from models1 import First
@dataclass
class Second:
attribute: First
In real code, I wanted to split SentInvoice
and User
models into different files.
class User(models.Model):
user_id = fields.BigIntField(index=True, unique=True)
username = fields.CharField(32, unique=True, index=True, null=True)
first_name = fields.CharField(255, null=True)
last_name = fields.CharField(255, null=True)
language = fields.CharField(32, default="ru")
balance: Balance = fields.OneToOneField("models.Balance", on_delete=fields.CASCADE)
sent_invoice: fields.OneToOneNullableRelation["SentInvoice"] # here
registered_user: RegisteredUser
@classmethod
async def create(cls: Type[MODEL], **kwargs: Any):
return await super().create(**kwargs, balance=await Balance.create())
class SentInvoice(models.Model):
amount = fields.DecimalField(17, 7)
shop_id = fields.CharField(50)
order_id = fields.CharField(10, null=True)
email = fields.CharField(20, null=True)
currency = fields.CharField(5, default="RUB", description="USD, RUB, EUR, GBP")
user: fields.ForeignKeyRelation[User] = fields.ForeignKeyField("models.User", on_delete=fields.CASCADE) # here
created_invoice: fields.OneToOneNullableRelation[CreatedInvoice]
async def send(self) -> CreatedInvoice:
cryptocloud = config.payment.cryptocloud
async with aiohttp.ClientSession(headers={"Authorization": f"Token {cryptocloud.api_key}"}) as session:
async with session.post(cryptocloud.create_url, data=dict(self)) as res:
created_invoice = await CreatedInvoice.create(**await res.json(), sent_invoice=self)
return created_invoice
Solution 1:[1]
You need to use two techniques that are specific to type hinting in python, 1) forward references, and 2) importing types within a TYPE_CHECKING
guard (check e.g. this post for a longer explanation of its implications). The first one allows you to reference a type that is not know to the interpreter at runtime, and the latter resolves types in a "type checking context".
Long story short:
models1.py
from dataclasses import dataclass
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from models2 import Second
@dataclass
class First:
attribute: "Second"
models2.py
from dataclasses import dataclass
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from models1 import First
@dataclass
class Second:
attribute: "First"
Executing the files with python3.8
or higher should work without any issues[1], and can work in python3.7
as well with a __futures__
import. Running mypy
on the files should work without any issues, too:
$ mypy models1.py models2.py
Success: no issues found in 2 source files
[1] As comments have pointed out, creating actual instances of your First
/Second
classes that would also pass a type check is impossible, but I assume that this is a toy example and your real code has, for example, one of the attribues as Optional
.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |