# Python : Database : Tortoise : Overview
\[ [tortoise-orm](https://pypi.org/project/tortoise-orm/) | [src](https://github.com/tortoise/tortoise-orm/) | [docs](https://tortoise.github.io/) \| [aerich](https://github.com/tortoise/aerich/) *(migrations)* ]
> [!NOTE] Optional Dependencies
> Performance improves with `uvloop` and `orjson` (if using JSON fields).
## Basic Usage
**Setup**
```python
await Tortoise.init(
db_url = "sqlite://db.sqlite3",
modules = {
"APP": [ "path.to.models", ... ], # dotted paths to modules containing models
},
)
await Tortoise.generate_schemas()
async def main():
...
run_async( main() ) # utility for running async code with automatic connection cleanup
-or-
await Tortoise.close_connections() # cleanup manually
```
**Schema**
```python
class Tournament( Model ):
id = IntField( primary_key = True ) # if not specified, 'id' field will be defined automatically
name = CharField( max_length = 255 )
class Event( Model ):
id = IntField( primary_key = True )
name = CharField( max_length = 255 )
tournament = ForeignKeyField( "models.Tournament", related_name="events" )
participants = ManyToManyField( "models.Team", related_name="events", through="event_team" )
# .add, .clear, .remove, async for (then just 'for')
class Team( Model ):
id = IntField( primary_key = True )
name = CharField( max_length = 255 )
__models__ = [ Tournament, Event, Team ] # optional; will be used instead of scanning all module attributes
```
**CRUD**
```python
t = Tournament( name="FooBar" )
await t.save()
-or-
await Tournament.create( name="FooBar" )
e = await Event.create( name="Game 1", tournament=t )
await e.participants.add( *[ await Team.create( name=name ) for name in ( "Foxes", "Badgers" ) ] )
```
**Querying**
```python
t = await Tournament.get( pk=42 )
t = await Tournament.filter( name__contains="oo" ).first()
t.name
# Prefetching:
for event in await Event.filter( participants = team1.id ).prefetch_related( "participants", "tournament" ):
print( event.tournament.name )
print( [ t.name for t in event.participants ] )
# Fetch all events for each team, and in those events, tournament will be prefetched:
await Team.all().prefetch_related( "events__tournament" )
# Filter and order by related models:
await Tournament.filter( events__name__in=[ "Game 3", "Game 4" ] ).order_by( "-events__participants__name" ).distinct()
```
**Transactions**
```python
@atomic( connection_name=None ) # wrapped function runs inside transaction
def myfunc( ... ):
...
async with in_transaction( connection_name=None ):
...
```
## Notes & Concepts
Implements singleton pattern.
- The `Tortoise` class has no constructor and is not supposed to be instantiated. All state is kept in class attributes, and the class is initialized by the `init` class method.
- The `ConnectionHandler` class is instantiated once (upon import), and the instance stored in the `connections` module attribute.
- A **connection** is an instance of a subclass of `BaseDBAsyncClient`. (If using the `asyncpg` driver/engine, that subclass is `AsyncpgDBClient`.)
- One connection is created (client instance) for each configured **alias** (usually one per database — so usually just one in total) and stored in a `contextvars`, and therefore.... TODO
- Clients doen't actually connect until your first model-related query. (At least, not `AsyncpgDBClient`.)
Poorly implements the concept of **apps**.
- "apps" are named buckets of configuration; each bucket specifies models and a connection to use with them
- each model is associated with an "app", either implicitly or explciitly, which is part of it's fully-qualified name ("APP.MODEL"), which is used when defining relations
The init process...
- pre-creates the connection for each alias
- discovers all model classes
- creates backwards relations between models
Models & Fields
- supports single (non-composite) PKs of any indexable type, but recommends: `IntField`, `BigIntField`, `CharField`, `UUIDField`
## Config
When calling `Tortoise.init()`, you must pass exactly one of: `config`, `config_file` or (`db_url` + `modules`)
The last option is the simplest one.
```python
config = None # a dict with config for: apps, connections, routers, and timezone usage
config_file = None # str w/path to JSON file (or YAML if PyYAML installed)
_create_db = False
db_url = None # str | dict; ex: "postgres://..."
modules = None # { str: [ str|Module ] }
use_tz = False # whether datetimes should be tz-aware
timezone = "UTC"
routers = None
table_name_generator = None # f( Model ) -> str (default: Model.__name__.lower())
```
**db_url**
- Can be a string, like: `postgres://USER:PASS@HOST:PORT/NAME?OPTION=VALUE` (defaults to asyncpg) (can't handle "postgresql://")
- Can be a dict, like:
```python
engine : "tortoise.backends.asyncpg"
credentials :
host : "localhost"
port : "5432"
user : "tortoise"
password : "qwerty123"
database : "test"
OPTION : VALUE
```
- Will be assigned alias "default".
*Note: If you want to define multiple connection aliases with different engines and credentials, use the `config` param.*
**modules**
Is a dict of app names and lists of modules containing `Model` subclasses:
```python
modules = dict(
app1 = [ "path.to.module", ... ]
)
```
While scanning the modules attached to an app, Tortoise will only collect model classes without `Meta.app` set, or with `Meta.app` matching the app who's models are being loaded. Also, if `Meta.app` is not yet set, it will be set to that app.
If a module contains a `__models__` attribute with a list of model classes, that will be used instead of scanning the whole module.
*Note: If you want to define different default connections for different groups of models, use the `config` param.*
#### Config Structure
```
connections :
# dict format
ALIAS :
engine : "tortoise.backends.asyncpg"
credentials :
host : "localhost"
port : "5432"
user : "tortoise"
password : "qwerty123"
database : "test"
OPTION : VALUE # ex: server_settings = dict( application_name="foo" )
# string format
ALIAS : "postgres://postgres:qwerty123@localhost:5432/test"
apps :
APP :
models : [ "__main__" ]
default_connection : "default" # if not specified, defaults to "default"
routers : [ "path.router1", "path.router2" ]
use_tz : False
timezone : "UTC"
```
If you use `db_url` and `modules`, Tortoise will convert those values:
```
db_url = "postgres://postgres:qwerty123@localhost:5432/test"
modules = { app1: [ "mod1" ] }
```
Into this:
```
connections :
default : expand_db_url( "postgres://postgres:qwerty123@localhost:5432/test" )
apps :
app1 :
models : [ "mod1" ]
default_connections : "default"
```