tortoise / tortoise/tortoise-orm

Initializer doesn't support multiple apps

Open
#1,521 0 comments 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

Describe the bug
Initalizer only supports one app label in the arguments to passed to it. In trying to run it multiple times it fails at the first app giving an error for the other apps.

    raise ConfigurationError(
tortoise.exceptions.ConfigurationError: default_connection for the model <class 'app2.models.Category'> cannot be None

This makes it difficult to test when there are multiple apps.

To Reproduce
app1/models.py:

from tortoise.models import Model
from tortoise import fields

class Users(Model):
    id = fields.IntField(pk=True)
    password = fields.CharField(max_length=128, blank=True, null=True)
    email = fields.CharField(max_length=120, unique=True, index=True)

app2/models.py:

from tortoise.models import Model
from tortoise import fields

class Category(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=128)
    user = fields.ForeignKeyField('app1.Users')

main.py:

import asyncio
import os
from tortoise import Tortoise
from tortoise.contrib.test import finalizer, initializer

db_url = "sqlite://:memory:?cache=shared"

def setup_db():
    Tortoise.init_models(["app1.models"], "app1")
    Tortoise.init_models(["app2.models"], "app2")
    print('Initializing app1')
    initializer(['app1.models'], db_url=db_url, app_label="app1")
    print('Initializing app2')
    initializer(['app2.models'], db_url=db_url, app_label="app2")
    print('DB initialized')
    finalizer()
    print('finalized')

try:
    setup_db()
    print('setup_db done')
finally:
    asyncio.run(Tortoise.close_connections())

At the line where app1 is run through initializer the following error is given:

    raise ConfigurationError(tortoise.exceptions.ConfigurationError: default_connection for the model <class 'app2.models.Category'> cannot be None

The following also fails with the same error. Here both apps models.py are provided as modules, but there is still the restriction of only one app label.

def setup_db():
    Tortoise.init_models(["app1.models"], "app1")
    Tortoise.init_models(["app2.models"], "app2")
    initializer(['app1.models', 'app2.models'], db_url=db_url, app_label="app1")
    print('DB initialized')
    finalizer()
    print('finalized')
tortoise.exceptions.ConfigurationError: default_connection for the model <class 'app2.models.Category'> cannot be Non

Expected behavior
There should be a a way to run multiple apps through the initializer

Additional context (Possible Fix)

Changing initalizer to accept Dict[str, Iterable[Union[str, ModuleType]]], a dictionary of app_labels and models (same as the FastAPI register_tortoise CONFIG argument or how Aerich accepts apps and models config) fixes the issue.

The way it's done here is just a demonstration and would not be backward compatibile (but it would be easy to make it so, by using a separate argument to pass through multiple apps, same as register_tortoise has both config and modules arguments).

Changes to tortoise/contrib/test/__init__.py:

from typing import Dict

def getDBConfig(app_label: str, modules: Dict[str, Iterable[Union[str, ModuleType]]]) -> dict:
    """
    DB Config factory, for use in testing.

    :param app_label: Label of the app (must be distinct for multiple apps).
    :param modules: List of modules to look for models in.
    """
    return _generate_config(
        _TORTOISE_TEST_DB,
        app_modules=modules,
        testing=True,
        connection_label=app_label,
    )


def initializer(
    modules: Dict[str, Iterable[Union[str, ModuleType]]],
    db_url: Optional[str] = None,
    app_label: str = "models",
    loop: Optional[AbstractEventLoop] = None,
) -> None:
   ....# Rest of function

The setup_db function in main.py above would then be:

def setup_db():
    Tortoise.init_models(["app1.models"], "app1")
    Tortoise.init_models(["app2.models"], "app2")
    initializer({'app1': ['app1.models'], 'app2': ["app2.models"]}, db_url=db_url, app_label="default")
    print('DB initialized')
    finalizer()
    print('finalized')

I can make PR if you think I'm on the right track with this, unless I'm missing something about the current behaviour. Multiple apps are needed to be initialized in this way when there are foreign keys from one app model to another.

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 in tortoise/contrib/test/init.py by reading initializer and getDBConfig, then run the provided main.py reproduction with models from app1 and app2. Confirm that initialization supports multiple app labels and cross-app foreign keys without the default_connection error, while checking the existing single-app behavior for compatibility.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, sqlite
Domain
database, testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.