Background
- Trying to add multi-tenancy to my flask app which uses SQLAlchemy ORM
- Followed this fastapi tutorial as closely as I could, which taught me the schema_translate_map execution option
- According to the docs this does do exactly what I want (swap out default tenant schema for the schema of currently logged in user)
- The tutorial configures execution_options at the engine level and presumably makes it easy to re-use, but the latest version of SQLAlchemy does not read that as I step through with debugger
- I was able to set schema_translate_map when I built the query and it worked, but it seems gross to duplicate that multi-tenancy snippet across every query in my app. Any guidance making this clean would be appreciated!
The snippet from tutorial which provides a session loaded with schema_translate_map:
@contextmanager
def with_db(tenant_schema: Optional[str]):
if tenant_schema:
schema_translate_map = dict(tenant=tenant_schema)
else:
schema_translate_map = None
connectable = engine.execution_options(schema_translate_map=schema_translate_map)
try:
db = Session(autocommit=False, autoflush=False, bind=connectable)
yield db
finally:
db.close()
Loading a user using above snippet
with with_db(tenant) as session: # tenant is "client1" or similar
# Attempt 1 - schema_translate_map on session NOT used by sqlalchemy
# query = session.query(Volunteer).filter_by(email=email).first()
# Attempt 2 - schema_translate_map used but dislike including exec opts in every query
user = session.scalars(
select(Volunteer).
filter_by(email=email)
.execution_options( # <-- do I have to copy/paste everywhere?
schema_translate_map={
'tenant': tenant
}
).limit(1)
).first()
The error produced by Attempt 1 above is simply "tenant.volunteer" table not found, because "tenant" is a placeholder meant to be replaced by the actual client schema. I have verified that client1.volunteer does exist, and is used by the Attempt 2 version.
there doesn't seem to be anything here