tortoise / tortoise/tortoise-orm
Passing a schema into transaction context
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 5.6k
- Forks
- 516
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 9
Description
Is your feature request related to a problem? Please describe.
I've managed to get multi-tenant per schema working correctly by having 2 separate Tortoise apps, one for 'shared' and one for 'tenant'. The 'shared' app is straight-forward as I can just pass that connection into any transaction context e.g.
async with in_transaction(connection_name: 'shared') as conn:
shared_widgets = await SharedWidgets.all()
However, the 'tenant' app is a bit of a hack and represents a blueprint of all the tenant-specific tables and models to ensure all of the relations work correctly e.g. it is used to qualify relationships between the tenant-specific models. When a new tenant signs up, I generate a new db schema for that specific tenant (let's say 'tenant_schema_12345') and clone all the 'tenant' app tables into this specific db schema. The term 'schema' here refers literally to a postgres database schema, e.g. the 'tenant_schema' part of select from tenant_schema.widgets.
Then finally, when e.g. user requests data specific to their tenant I do:
async with in_transaction(connection_name: 'tenant') as conn:
# Set the tenant schema context
conn.execute_script(f"set search_path to tenant_schema_12345")
tenant_specific_widgets = await TenantSpecificWidgets.all()
This all works perfectly well, but I just want to add a small quality of life enhancement to pass e.g. 'tenant_schema_12345' into in_transaction(), or create a utility function wrapper for it but I can't figure out how best to do that. I don't want to remember to add the conn.execute_script(f"set search_path to tenant_schema_12345") line each time.
Ideally (from a syntactic point of view) it would be nice to add it as an extension to QuerySet, e.g.
def using_schema(schema_name: str) -> QuerySet:
// Do something that's used when awaiting the query
QuerySet.using_schema = using_schema
...
...
...
tenant_specific_widgets = await TenantSpecificWidgets.all().using_schema('tenant_schema_12345')
I've found one spot where it might work but can't figure out how to go about extending/monkey-patching Tortoise to get it to use the following:
from tortoise.backends.base.client import TransactionContext
class TenantTransactionContext(TransactionContext):
async def __aenter__(self):
await self.ensure_connection()
await self.lock.acquire() # type:ignore
self.token = connections.set(self.connection_name, self.connection)
await self.connection.start()
# Set the tenant schema context
self.connection.execute_script(f"set search_path to {schema_name}")
return self.connection
I'm happy to dig further but some pointers from someone who knows the Tortoise design well would be much appreciated.
Thanks!
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with tortoise.backends.base.client.TransactionContext and the in_transaction entry point, then trace how QuerySet execution obtains its connection. Compare the proposed tenant context with the existing connection lifecycle and determine where a schema value could be applied safely. Done means callers can provide a schema without repeating execute_script, while existing transaction behavior remains unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- postgresql, python
- Domain
- database
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100