Search code examples
pythonoopsqlalchemyenumsfastapi

FastAPI app: dynamically create an Enum and use it in SQLAlchemy models without circular dependencies?


My app is structured as shown below.

app/
├── backend/
│   └── main.py
├── models/
│   └── my_model.py
└── utils/
    └── enums.py

main.py instantiates a config dict, containing key prefix, from a yaml file which is needed in enums.py to generate schema names at runtime.

utils/enums.py:

def create_schema_names(prefix: str) -> Type[Enum]:

    class SchemaNames(str, Enum):
        RAW = prefix + "_RAW"
        STAGING = prefix + "_STAGING"
        TRANSFORMED = prefix + "_TRANSFORMED"

    return SchemaNames

I want to use SchemaNames in my SQLAlchemy models in my_model.py to specify the schema for each model: __table_args__ = {"schema": SchemaNames.RAW.value}.

Problem:

Importing SchemaNames in my_model.py leads to circular dependencies because the config is defined in main.py. I want to avoid using global variables by exporting SchemaNames globally.

How can I dynamically create the SchemaNames enum based on the runtime config and use it in SQLAlchemy models without causing circular dependencies or relying on global exports? What best practice approach is there for this?

Edit: main.py is structured as follows:

from fastapi import FastAPI
from app.api import register_api_1

# in register_api_2 function my_model.py is imported so that data in it from the DB can be read.
from app.api import register_api_2

def make_config() -> ConfigClass:
    # parses config from yaml file
    return my_config

def create_app(config: ConfigClass) -> FastAPI:
    app = FastAPI()

    register_api_1(app, config)
    register_api_2(app)

    return app

def main():
    config = make_config()
    app = create_app(config)
    uvicorn.run(
        app,
        host=config.webserver.host,
        port=config.webserver.port,
        log_config=config.log_config,
    )

def __name__ == "__main__":
    main()

Solution

  • as suggested by M.O., I ended up placing make_config() in a settings.py file and importing the config from there wherever needed. This resolved the circular dependency issue and also made the app structure less convoluted.

    Any suggestions are, of course, welcomed.