Open xalien10 opened 1 year ago
I've achieved something similar using FastAPI. I basically registered a middleware that is responsive for dynamically generating the db_config the first time a request for a particular db is processed. I then cache the instance for subsequent requests.
@intigratech can you please share some snippet or links related to that?
@xalien10 @grigi @long2ice
So, in my case I needed to support multiple databases because each client would have a separate database. The databases may have some shared apps/models but they could be different in some cases. The database name is always the subdomain so we extracted it from the host header to identify the db. Im then generating the db_config dynamically for the db with the relevant apps. The apps are also dynamic. Then Tortoise is inited and the db name is passed in the transaction. In theory this should work?
@app.middleware("http")
async def set_database_for_request(request: Request, call_next):
if request.url.path.startswith('/static') or 'create-database' in request.url.path or 'delete-database' in request.url.path or 'backup-database' in request.url.path or 'restore-database' in request.url.path or 'database/manager' in request.url.path:
return await call_next(request)
# Extracting environment variables and host header
environment = ENVIRONMENT
db_filter = DB_FILTER
host_header = request.headers.get("Host")
# Determine the database name based on the request
db_name = DEFAULT_DB
if environment == "development" or db_filter == "False":
db_name = DEFAULT_DB
elif db_filter == "domain":
db_name = host_header.split(".")[1] # Domain as DB name
elif db_filter == "subdomain":
db_name = host_header.split(".")[0] # Subdomain as DB name
#Set DB name in request for use in depedencies injection
request.state.db = db_name
# Dynamic initialization for each unique database
if db_name not in initialized_orms:
# Load installed modules for the determined database name
apps_config = await load_installed_modules(db_name)
# Database connection config, replace with actual dynamic config based on db_name
db_config = {
"connections": {
db_name: {
"engine": "tortoise.backends.asyncpg", # Engine for asyncpg (PostgreSQL)
"credentials": {
"database": db_name, # Database name dynamically determined
"host": DB_HOST, # Host for your PostgreSQL database
"port": DB_PORT, # Port for your PostgreSQL database, typically 5432
"user": DB_USERNAME, # User for your PostgreSQL database
"password": DB_PASSWORD, # Password for your PostgreSQL database
"max_size": MAX_CONNECTIONS, # Maximum number of connections in the pool
"min_size": MIN_CONNECTIONS, # Minimum number of connections in the pool
"max_inactive_connection_lifetime": MAX_IDLE_TIME, # Maximum idle time for connections
}
}
},
"apps": apps_config
}
# Initialize Tortoise ORM for this db_name if not already done
await Tortoise.init(config=db_config)
await build_model_registry(db_name)
# Store the config to avoid re-initialization
initialized_orms[db_name] = db_config
# Store db_name as a request attribute
db_ctx.set(db_name)
response = None
try:
async with in_transaction(db_ctx.get()) as connection:
response = await call_next(request)
except Exception as e:
logger.error(f"An error occurred, rolling back the transaction: {e}")
raise e
return response
Problem Definition I'm trying to use
tortoise-orm
to build a multi-tenant application using postgres schema. I've two models:Tenant: Tenant model will be kept in the default/public schema of the postgres database. And it'll have the following structure:
class User(Model): email = fields.CharField( maxlength=50, unique=True, validators=[RegexValidator("([A-Za-z0-9]+[.-])*[A-Za-z0-9]+@[A-Za-z0-9-]+(.[A-Z|a-z]{2,})+", re.I)] ) first_name = fields.CharField(max_length=100, null=True) last_name = fields.CharField(max_length=100, null=True) password = fields.CharField(max_length=128, null=False) date_joined = fields.DatetimeField(auto_now_add=True, use_tz=False, null=True) last_login_at = fields.DatetimeField(auto_now=True, use_tz=False, null=True) status = fields.CharEnumField(StatusChoices, default=StatusChoices.UNVERIFIED)
TORTOISE_ORM = { "connections": { "public": "postgres://root:admin@127.0.0.1:5432/root", "tenants": "postgres://root:admin@127.0.0.1:5432/root" }, "apps": { "public": { "models": ["apps.tenants.models", "aerich.models"], "default_connection": "public", }, "tenants": { "models": ["apps.users.models"], "default_connection": "tenants", } }, }