tortoise / tortoise-orm

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

Tortoise ORM tutorial missing context? #1560

Open ZackPlauche opened 4 months ago

ZackPlauche commented 4 months ago

Describe the bug I'm going through the tortoise-orm tutorial and I'm getting bugs at every step, and some parts are confusing.

In this part, I didn't understand where run_async was coming from or where I was supposed to be putting it, so I just put it at the bottom of this script once I finally found out

from tortoise import Tortoise, run_async

async def init():
    # Here we create a SQLite DB using file "db.sqlite3"
    #  also specify the app name of "models"
    #  which contain models from "app.models"
    await Tortoise.init(
        db_url='sqlite://db.sqlite3',
        modules={'models': ['app.models']}
    )
    # Generate the schema
    await Tortoise.generate_schemas()

run_async(init())

Then it said that after you run this you should be able to run the following, so I added it below that part:

# Create instance by save
tournament = Tournament(name='New Tournament')
await tournament.save()

# Or by .create()
await Event.create(name='Without participants', tournament=tournament)
event = await Event.create(name='Test', tournament=tournament)
participants = []
for i in range(2):
    team = await Team.create(name='Team {}'.format(i + 1))
    participants.append(team)

# M2M Relationship management is quite straightforward
# (look for methods .remove(...) and .clear())
await event.participants.add(*participants)

# You can query related entity just with async for
async for team in event.participants:
    pass

# After making related query you can iterate with regular for,
# which can be extremely convenient for using with other packages,
# for example some kind of serializers with nested support
for team in event.participants:
    pass

# Or you can make preemptive call to fetch related objects,
# so you can work with related objects immediately
selected_events = await Event.filter(
    participants=participants[0].id
).prefetch_related('participants', 'tournament')
for event in selected_events:
    print(event.tournament.name)
    print([t.name for t in event.participants])

# Tortoise ORM supports variable depth of prefetching related entities
# This will fetch all events for team and in those team tournament will be prefetched
await Team.all().prefetch_related('events__tournament')

# You can filter and order by related models too
await Tournament.filter(
    events__name__in=['Test', 'Prod']
).order_by('-events__participants__name').distinct()

First thing it says is that "await can't be used outside of a function".

What are the docs missing for me to be able to use this properly? To Reproduce Steps to reproduce the behavior, preferably a small code snippet. Follow the getting tutorial from scratch as a beginner who knows nothing about using async in Python and type the code in a file.

Expected behavior A clear and concise description of what you expected to happen. That I could use the code in the tutorial anywhere.

vlakius commented 2 months ago

This is not related to tortoise orm.

from the python docs:

6.4. Await expression Suspend the execution of coroutine on an awaitable object. Can only be used inside a coroutine function.

ZackPlauche commented 2 months ago

This is not related to tortoise orm.

from the python docs:

6.4. Await expression Suspend the execution of coroutine on an awaitable object. Can only be used inside a coroutine function.

I get that the error might be from somewhere else... But I would expect the example code from the tutorial to work and be useful otherwise it's useless.

The code I wrote is directly from the docs at the time of me posting it.

How can I make it work? What did I do wrong?

vlakius commented 2 months ago

ok

  1. Create a folder structure like this:
    ├── app
    │   ├── models.py
    ├── db.sqlite3
    ├── main.py

    in models.py

    
    from tortoise import fields
    from tortoise.models import Model

class Tournament(Model):

Defining id field is optional, it will be defined automatically

# if you haven't done it yourself
id = fields.IntField(pk=True)
name = fields.CharField(max_length=255)

# Defining ``__str__`` is also optional, but gives you pretty
# represent of model in debugger and interpreter
def __str__(self):
    return self.name

class Event(Model): id = fields.IntField(pk=True) name = fields.CharField(max_length=255)

References to other models are defined in format

# "{app_name}.{model_name}" - where {app_name} is defined in tortoise config
tournament = fields.ForeignKeyField("models.Tournament", related_name="events")
participants = fields.ManyToManyField(
    "models.Team", related_name="events", through="event_team"
)

def __str__(self):
    return self.name

class Team(Model): id = fields.IntField(pk=True) name = fields.CharField(max_length=255)

def __str__(self):
    return self.name

in main.py
```python
from tortoise import Tortoise, run_async

from app.models import Event, Team, Tournament

async def init():
    # Here we create a SQLite DB using file "db.sqlite3"
    #  also specify the app name of "models"
    #  which contain models from "app.models"
    await Tortoise.init(
        db_url="sqlite://db.sqlite3", modules={"models": ["app.models"]}
    )
    # Generate the schema
    await Tortoise.generate_schemas()

async def test_task():
    # Create instance by save
    tournament = Tournament(name="New Tournament")
    await tournament.save()

    # Or by .create()
    await Event.create(name="Without participants", tournament=tournament)
    event = await Event.create(name="Test", tournament=tournament)
    participants = []
    for i in range(2):
        team = await Team.create(name="Team {}".format(i + 1))
        participants.append(team)

    # M2M Relationship management is quite straightforward
    # (look for methods .remove(...) and .clear())
    await event.participants.add(*participants)

    # You can query related entity just with async for
    async for team in event.participants:
        pass

    # After making related query you can iterate with regular for,
    # which can be extremely convenient for using with other packages,
    # for example some kind of serializers with nested support
    for team in event.participants:
        pass

    # Or you can make preemptive call to fetch related objects,
    # so you can work with related objects immediately
    selected_events = await Event.filter(
        participants=participants[0].id
    ).prefetch_related("participants", "tournament")
    for event in selected_events:
        print(event.tournament.name)
        print([t.name for t in event.participants])

    # Tortoise ORM supports variable depth of prefetching related entities
    # This will fetch all events for team and in those team tournament will be prefetched
    await Team.all().prefetch_related("events__tournament")

    # You can filter and order by related models too
    await Tournament.filter(events__name__in=["Test", "Prod"]).order_by(
        "-events__participants__name"
    ).distinct()

async def main():
    await init()
    await test_task()

if __name__ == "__main__":
    run_async(main())

I agree that the documentation examples can be improved

ZackPlauche commented 2 months ago

Thanks!