Home > Software design >  Python, model <> model, how to avoid circular import
Python, model <> model, how to avoid circular import

Time:10-19

Let's say that I have an pydantic (it could be any kind of) model that shold know how to convert itself to orm (it could be any kind of) model. To do so I have constructed mod_a.py:

from pydantic import BaseModel

from mod2mod.mod_b import DBUser

class User(BaseModel):

    name: str
    surname: str

    def to_orm(self) -> DBUser:
        return DBUser(
            full_name = f"{self.surname},{self.name}"
        )

I would also like my orm model to be able to convert itself to pydantic model, so my mod_b.py is:

from sqlalchemy.orm import declarative_base
from sqlalchemy import Column, Integer, String
from mod_a import User

Base = declarative_base()

class DBUser(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    full_name = Column(String)

    def to_pydantic(self) -> User:
        surname, name = self.full_name.split(',')
        User(
            name=name,
            surname=surname
        )

Idea is to be able to go from model to model at will like in mod2mod.py:

import pydantic
from mod_a import User
from mod_b import DBUser


user = User(name='John', surname='Doe')
orm_user = user.to_orm()

pydantic_user = orm_user.to_pydantic()

print(user)
print(pydantic_user)

Obviously this will not work because:

ImportError: cannot import name 'User' from partially initialized module 'mod_a' (most likely due to a circular import) (/mod2mod/mod_a.py)

How to solve this type of problem? Do I need another entity that will now how to do conversions between models and put current model methods there or there is some other design pattern?

CodePudding user response:

You have to to do a local import and us TYPE_CHECKING to be able to do a type hint.

from typing import TYPE_CHECKING
from pydantic import BaseModel

if TYPE_CHECKING:
    from mod2mod.mod_b import DBUser
    

class User(BaseModel):

    name: str
    surname: str

    def to_orm(self) -> "DBUser":
        from mod2mod.mod_b import DBUser

        return DBUser(
            full_name = f"{self.surname},{self.name}"
        )

Do this for DBUser as well.

CodePudding user response:

You can use import ... style instead of from ... import .... And wrap return type hints in quotes (I did such below) or use postponed annotation evaluation PEP-563 (by the way, it is default for Python 3.10)

# mod_a.py
from pydantic import BaseModel
import mod_b


class User(BaseModel):
    name: str
    surname: str

    def to_orm(self) -> 'mod_b.DBUser':
        return mod_b.DBUser()
# mod_b.py
import mod_a

class DBUser:
    def to_pydantic(self) -> 'mod_a.User':
        return mod_a.User(
            name="",
            surname=""
        )
  • Related