Melom
Melom

Reputation: 608

Python circular import, can't import a function between two files?

I'm facing an annoying problem with a circular import error and I don't understand what I am doing wrong. Here the code:

main.py

import uvicorn
import app.config.config as config

app = config.load_config()
CONFIG = config.read_config()

if __name__ == "__main__":
    uvicorn.run(app, host=CONFIG["APPLICATION"]["HOST"], port=CONFIG["APPLICATION"]["PORT"])

config.py

import os
import toml
import logging
import app.api.routing as routing
from fastapi import FastAPI

BASE_DIR = os.getcwd()


def read_config():
    try:
        CONFIG = toml.load(BASE_DIR + "/config.toml")
        return CONFIG
    except FileNotFoundError as err:
        logging.error(f"Unable to read configuration file: {err}")
        return None


def load_config():
    app = FastAPI()
    app.include_router(routing.router)
    return app

database.py

from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
import app.config.config as config

CONFIG = config.read_config()

USERNAME = CONFIG["POSTGRES"]["USERNAME"]
PASSWORD = CONFIG["POSTGRES"]["PASSWORD"]
HOSTNAME = CONFIG["POSTGRES"]["HOSTNAME"]
DATABASE = CONFIG["POSTGRES"]["DATABASE"]
PORT = CONFIG["POSTGRES"]["PORT"]

SQLALCHEMY_DATABASE_URI = f"postgresql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}"
engine = create_engine(SQLALCHEMY_DATABASE_URI)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()


def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

ERROR:

CONFIG = config.read_config()
AttributeError: partially initialized module 'app.config.config' has no attribute 'read_config' (most likely due to a circular import)

I'm using FastAPI framework with Python 3.9. In the code, I have imported "app.config.config" 2 times (main.py and database.py) and I need to import this config file to use the function read_config() contained in it.

Anyone can help please? Thanks

THE PROJECT STRUCTURE:

.
├── app
│   ├── api
│   │   ├── __init__.py
│   │   └── routing.py
│   ├── config
│   │   ├── config.py
│   │   ├── __init__.py
│   │   └── settings.py
│   ├── database
│   │   ├── database.py
│   │   └── __init__.py
│   ├── __init__.py
│   ├── main.py
│   ├── models
│   │   ├── __init__.py
│   │   └──  placesModel.py
│   └── service
│       ├── crud.py
│       ├── __init__.py

Upvotes: 2

Views: 961

Answers (1)

Balaïtous
Balaïtous

Reputation: 896

I suspect that app.api.routing try to import database.

  • First try to refactor your code and split routing so it doesn't require database

  • Otherwise, make call to read_config in database just-in-time. There are various solutions to differ call to read_config.

def _get_session_local():
    NFIG = config.read_config()
    ...
    return sessionmaker(autocommit=False, autoflush=False, bind=engine)


def get_db():
    if get_db._get_session_local is None:
        get_db._session_local = _get_session_local()
    try:
        yield get_db._session_local()
    finally:
        db.close()
get_db._session_local = None

More generally, the execution of code during import is to be avoided.

Upvotes: 2

Related Questions