tortoise / tortoise/tortoise-orm

Passing a schema into transaction context

Open
#1,379 1 comment 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.