# 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" ```