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
CodePudding user response:
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 our, 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
.