#πŸ”’ Circular import hell...

24 messages Β· Page 1 of 1 (latest)

robust dustBOT
#

@pastel tiger

Python help channel opened

Remember to:

  • Ask your Python question, not if you can ask or if there's an expert who can help.
  • Show a code sample as text (rather than a screenshot) and the error message, if you've got one.
  • Explain what you expect to happen and what actually happens.

:warning: Do not pip install anything that isn't related to your question, especially if asked to over DMs.

pastel tiger
#

my message disappeard...

remote python
#

Just break the circular import. One of the 2 files is importing the other. Re-arrange it

pastel tiger
#

@remote python is there no way to fix it?

remote python
pastel tiger
# remote python I just told you. Don't have both files import each other

@remote python yea, I get it. But that seems like avoiding the problem. I come from rust land where you can do that all the time... it seems silly that you can't do it here.

I have

class ItemBase(BaseModel):

    # Core Fields
    id: uuid.UUID
    name: str
    description: Optional[str] = None
    item_types: list[ItemTypeEnum]

    # Manufacturer and Supplier information
    manufacturer: Optional[str] = None
    item_model_number: Optional[str] = None
    serial_number: Optional[str] = None
    suppliers: Optional[list[SupplierBaseSimple]] = None

    # Location information
    in_plant_location: Optional[str] = None
    image_url: Optional[str] = None

    # Parts
    parts: Optional[list[ItemBaseSimple]]

    # Item Requests
    item_requests: Optional[list["ItemRequestBase"]] = None

    # Metadata
    status: ItemStatusEnum
    created_at: datetime
    updated_at: datetime

AND

class ItemRequestBase(BaseModel):
    # Core Fields
    id: uuid.UUID
    description: Optional[str] = None
    image_url: Optional[str] = None
    requestor: Optional[str] = None

    # Associated Equipment
    item_id: Optional[uuid.UUID] = None
    item: Optional["ItemBase"] = None

    # Requested Parts
    parts: Optional[List[PartRequest]] = None

    # Metadata
    status: ItemRequestStatusEnum = ItemRequestStatusEnum.ACTIVE
    created_at: datetime
    updated_at: datetime

They are two Pydantic models that model SQL tables that have a many to many relationship. It seems idiomatic to be able to import the files into each other.

remote python
pastel tiger
#

they model different things. These are just small parts of their files.

#

putting them in the same file to avoid the python problem seems silly

remote python
pastel tiger
remote python
#

its like saying

# a.py
from b import c

print(c)
d = 10
# b.py
from a import d

print(d)
c = 5

Neither are going to get what they want because one as to wait for the other to finish importing

#

So to fix this is basically put one into the other

#
# a.py
c = 5
d = 10
pastel tiger
#

got it. Yea, seems silly... but the other tricks I looked at don't seem to be working.

sterile elbow
#

Are you only importing those for type hints?

#

because if so you can use TYPE_CHECKING from the builtin typing module

surreal rampart
# pastel tiger <@930453529192181770> yea, I get it. But that seems like avoiding the problem. ...

im not a frequent pydantic user and this does seem janky, but i believe you can solve this while keeping both files separate by deferring the runtime import to just after your model declaration, combined with .model_rebuild() for pydantic to evaluate your postponed annotations: ```py

item.py

from pydantic import BaseModel

class Item(BaseModel):
...
item_requests: "list[ItemRequest]" = []

from .item_request import ItemRequest
Item.model_rebuild() py

item_request.py

from pydantic import BaseModel
from .item import Item

class ItemRequest(BaseModel):
item: Item | None = None```

#

oh huh, the model_rebuild call isn't even needed, just the deferred import...

surreal rampart
#

pydantic also doesn't seem to mind omitting .model_rebuild() if i only use a type checking guard (i.e. what slamsandwich said) for one of the models, as long as both modules are imported before ItemRequest() is instantiated: ```py

pkg/item.py

from pydantic import BaseModel
from .item_request import ItemRequest

class Item(BaseModel):
item_requests: list[ItemRequest] = [] py

pkg/item_request.py

from typing import TYPE_CHECKING
from pydantic import BaseModel

if TYPE_CHECKING:
from .item import Item

class ItemRequest(BaseModel):
item: "Item | None" = None py

pkg/main.py

from .item import Item
from .item_request import ItemRequest

ItemRequest() # works unless item.py import is removed
``` this is what i'd conventionally do to break a circular import for type annotations anyway, but pydantic is also doing some magic behind the scenes to automatically figure out the schema for Item / ItemRequest while python is executing your modules sequentially (item_request.py -> item.py)

robust dustBOT
#
Python help channel closed

This help channel has been closed and it's no longer possible to send messages here. If your question wasn't answered, feel free to create a new post in #1035199133436354600. To maximize your chances of getting a response, check out this guide on asking good questions.