Pydantic error, not defined with circular import

72 Views Asked by At

I'm encountering an issue with circular imports in my FastAPI application, and I'd appreciate some guidance on how to resolve it. Here's an overview of my setup:

Problem Statement: I have a FastAPI application with the following structure:

A router that handles requests to retrieve data about IDNow clients. A service layer that interacts with a DAO (data access object) to fetch client data. A DAO that uses SQLAlchemy to query a database and return IdnowClient objects. clients.py and client_states.py modules that define the models used in the application.

My router:

@idnow_router.get(
    "/client/{id_entity}",
    summary="Get idnow client",
    response_model=IdnowClientRead,
    dependencies=[SecurityAcls(acl_info_idnow)],
)
async def get_client(
    id_entity: int,
    service: IDNowService = Depends(),
):
    """Return client from id_entity"""
    return await service.get_client(id_entity=id_entity)

The service, just call the dao:

    async def get_client(self, id_entity: int) -> IdnowClientRead:
        return await self.idnow_dao.get_client(id_entity)

The dao, this use sqlmodel and return an IdnowClient:

    async def get_client(self, id_entity: int) -> IdnowClient:
        """Return IdnowClientRead object by id_entity"""
        statement = select(IdnowClient).where(IdnowClient.id_entity == id_entity)
        results = await self.customer_session.exec(statement)
        return results.one()

my models: clients.py:

import sys  # noqa

from datetime import datetime
from os import path
from sqlalchemy import text
from sqlmodel import Field, Relationship
from typing import TYPE_CHECKING, List, Optional
from uuid import UUID, uuid4

sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))

from common import IdGenericModel, TimestampGenericModel  # noqa

prefix = "idnow"


if TYPE_CHECKING:
    from .client_states import IdnowClientState
    from .documents import IdnowDocument

class IdnowClientBase(TimestampGenericModel):
    id_entity: int = Field(nullable=False, index=True)
    date_first_documents_submission: datetime = Field(
        default_factory=datetime.utcnow,
        nullable=True,
        sa_column_kwargs={"server_default": text("current_timestamp(0)")},
    )


class IdnowClient(IdGenericModel, IdnowClientBase, table=True):
    __tablename__ = f"{prefix}_clients"

    states: List["IdnowClientState"] | None = Relationship(
        back_populates="client", sa_relationship_kwargs={"lazy": "raise"}
    )

    documents: List["IdnowDocument"] | None = Relationship(
        back_populates="client", sa_relationship_kwargs={"lazy": "raise"}
    )


class IdnowClientRead(IdnowClientBase):
    id: int | None = None
    states: Optional[List["IdnowClientState"]] = None
    documents: List["IdnowDocument"] | None = None

And client_states.py:

import sys  # noqa
from datetime import datetime
from os import path
from typing import List, Optional

from sqlalchemy import Column, event, text
from sqlalchemy.dialects.postgresql import ENUM
from sqlmodel import Field, Relationship, SQLModel

sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))

from .clients import IdnowClient  # noqa
from common import IdGenericModel, TimestampGenericModel  # noqa

prefix = "idnow"

type_client_enum = ENUM("STANDARD", "ATYPICAL", name=f"{prefix}_type_client_enum")


class IdnowClientStateReason(IdGenericModel, table=True):
    __tablename__ = f"{prefix}_client_states_reasons"

    reason: str = Field(nullable=False, default="", max_length=64)

    client_states: Optional[List["IdnowClientState"]] = Relationship(
        back_populates="reason", sa_relationship_kwargs={"lazy": "raise"}
    )


class IdnowClientState(IdGenericModel, TimestampGenericModel, table=True):
    __tablename__ = f"{prefix}_client_states"

    id_idnow_client: int = Field(
        foreign_key="idnow_clients.id",
        index=True,
        nullable=False,
    )
    id_reason_state: int = Field(
        foreign_key="idnow_client_states_reasons.id",
        index=True,
        nullable=False,
    )

    reason: IdnowClientStateReason = Relationship(
        back_populates="client_states", sa_relationship_kwargs={"lazy": "raise"}
    )
    client: IdnowClient = Relationship(
        back_populates="states", sa_relationship_kwargs={"lazy": "raise"}
    )

I got error: pydantic.errors.PydanticUndefinedAnnotation: name 'IdnowClientState' is not defined

What I've Tried: I've attempted to refactor the imports in my clients.py and client_states.py modules to break the circular dependency, but I haven't been successful so far.

Request for Help: I'm seeking guidance on how to properly resolve the circular import issue and ensure that my application functions as intended.

Any assistance or insights into resolving this problem would be greatly appreciated!

Thank you.

0

There are 0 best solutions below