tortoise / tortoise-orm

Familiar asyncio ORM for python, built with relations in mind
https://tortoise.github.io
Apache License 2.0
4.55k stars 374 forks source link

The "apps" configuration directive is extremely confusing #851

Open Fingel opened 3 years ago

Fingel commented 3 years ago

Hi, I am having a lot of trouble figuring out how the "apps" section of tortoise config is supposed to be set up. I have a project layout that looks like this:

├── app
│   ├── config.py
│   ├── db.py
│   ├── main.py
│   └── targets
│       ├── __init__.py
│       ├── models.py
├── README.md
├── requirements.txt
├── setup.py
└── tests

There are Tortoise models defined in app.targets.models

My Tortoise config object looks like this:

{
    'connections': {
        'default': settings.DATABASE_URL
    },
    'apps': {
        'targets:': {
            'models': ['app.targets.models'],
        }
    }

And then I register tortoise like so:

fastapi.register_tortoise(
    app=app,
    config=settings.TORTOISE,
    generate_schemas=True,
    add_exception_handlers=True
)

But tortoise does not find the models:

INFO:     Waiting for application startup.
/home/austin/.virtualenvs/sdexchange-server/lib/python3.9/site-packages/tortoise/__init__.py:601: RuntimeWarning: Module "app.targets.models" has no models
  cls._init_apps(apps_config)
INFO:     Tortoise-ORM started, {'default': <tortoise.backends.asyncpg.client.AsyncpgDBClient object at 0x7fa7a5f275b0>}, {'targets:': {}}

I've tried the following things:

Adding app = 'Targes' to the Meta class of each model. Adding __models__=[Model1, Model2] to both models.py and app.targets.__init__.py Changing the path in the models config array to app.targets.

None of these attempts work.

However, if I use different keyword arguments for register_tortoise, Tortoise finds the models:

fastapi.register_tortoise(
    app=app,
    db_url=settings.DATABASE_URL,
    modules={'targets': ['app.targets.models']},
    generate_schemas=True,
    add_exception_handlers=True
)

Are we only supposed to use the modules keyword argument for register_tortoise? In what circumstance should we be using the tortoise config object? For example in the documenation for Aerich it says to add aerich.models to the tortoise config, which seems to be at odds with how you set up tortoise for FastAPI.

Any thoughts?

panla commented 3 years ago

@Fingel

try

class User(Model):
    ...

    class Meta:
        app = 'targets'
        # it is your config `modules={'targets': ['app.targets.models']}` targets

        ....
andcan86 commented 3 years ago

I agree it is extremely confusing. Also the doc does not clearly say what one should use for the various arguments of the functions of the Tortoise, such as: models_paths, app_label, ..

I got several error messages which did not explain what was the problem, but all of them were related to tring to define a foreign key reference among two objects. If you are stuck, try removing all the ForeignKeyField() from your Tortoise models and see if you make it work at least like that, and then move on adding the relations, otherwise it's a "model not registered/not found error .. " very hard to tackle.

I had to read the source code to find out what was supposed to happen, and it was still very unclear. In my case anything else than the "model" keyword for the app_name would not work, regardless of the fact that I have a module called like that in my code.

For clarity, that's how I registered the tortoise orm in the main.py file of a fastapi app:

imports ..

app = FastAPI(title="fastapi app")

Tortoise.init_models(["models.sql_models"], app_label="models")

register_tortoise(
    app,
    db_url="postgres://postgres:postgres@:5432/db",
    modules={
        "models": [
            "models.sql_models"]
    },
    generate_schemas=False,
    add_exception_handlers=True,
)
killgron commented 1 year ago

Hi, I have been puzzled by this problem for several days before, and now I have solved this problem. Here is my solution: If you have a project layout that looks like this:

├── app
│   ├── config.py
│   ├── db.py
│   ├── main.py
│   └── targets
│       ├── __init__.py
│       ├── models.py
├── README.md
├── requirements.txt
├── setup.py
└── tests

and you configuration like this:

{
    'connections': {
        'default': settings.DATABASE_URL
    },
    'apps': {
        'app1:': {
            'models': ['app.targets.models'],
        }
    }

Now you have registered an app called 'app1' and if your file app.targets.models has Models like this:

class Account(models.Model):
    """Account Model
    """
    pass

class Role(models.Model):
    """Role Model
    """

    # foreignkey
    accounts = fields.ForeignKeyField(
       " app1.Account", related_name="role_id",on_delete=fields.RESTRICT)  

you must use your registered app app1 in fields.ForeignKeyField() . This approach helped me solve my problem.