Models¶
Usage¶
To get working with models, first you should import them
from tortoise.models import Model
With that you can start describing your own models like that
class Tournament(Model):
id = fields.IntField(primary_key=True)
name = fields.TextField()
created = fields.DatetimeField(auto_now_add=True)
def __str__(self):
return self.name
class Event(Model):
id = fields.IntField(primary_key=True)
name = fields.TextField()
tournament = fields.ForeignKeyField('models.Tournament', related_name='events')
participants = fields.ManyToManyField('models.Team', related_name='events', through='event_team')
modified = fields.DatetimeField(auto_now=True)
prize = fields.DecimalField(max_digits=10, decimal_places=2, null=True)
def __str__(self):
return self.name
class Team(Model):
id = fields.IntField(primary_key=True)
name = fields.TextField()
def __str__(self):
return self.name
Let see in details what we accomplished here:
class Tournament(Model):
Every model should be derived from base model. You also can derive from your own model subclasses and you can make abstract models like this
class AbstractTournament(Model):
id = fields.IntField(primary_key=True)
name = fields.TextField()
created = fields.DatetimeField(auto_now_add=True)
class Meta:
abstract = True
def __str__(self):
return self.name
This models won’t be created in schema generation and won’t create relations to other models.
Further we have field fields.DatetimeField(auto_now=True)
. Options auto_now
and auto_now_add
work like Django’s options.
Use of __models__
¶
If you define the variable __models__
in the module which you load your models from, generate_schema
will use that list, rather than automatically finding models for you.
Primary Keys¶
In Tortoise ORM we require that a model has a primary key.
That primary key will be accessible through a reserved field pk
which will be an alias of whichever field has been nominated as a primary key.
That alias field can be used as a field name when doing filtering e.g. .filter(pk=...)
etc…
Note
We currently support single (non-composite) primary keys of any indexable field type, but only these field types are recommended:
IntField
BigIntField
CharField
UUIDField
One must define a primary key by setting a primary_key
parameter to True
.
If you don’t define a primary key, we will create a primary key of type IntField
with name of id
for you.
Note
If this is used on an Integer Field, generated
will be set to True
unless you explicitly pass generated=False
as well.
Any of these are valid primary key definitions in a Model:
id = fields.IntField(primary_key=True)
checksum = fields.CharField(primary_key=True)
guid = fields.UUIDField(primary_key=True)
Inheritance¶
When defining models in Tortoise ORM, you can save a lot of repetitive work by leveraging from inheritance.
You can define fields in more generic classes and they are automatically available in derived classes. Base classes are not limited to Model classes. Any class will work. This way you are able to define your models in a natural and easy to maintain way.
Let’s have a look at some examples.
from tortoise import fields
from tortoise.models import Model
class TimestampMixin():
created_at = fields.DatetimeField(null=True, auto_now_add=True)
modified_at = fields.DatetimeField(null=True, auto_now=True)
class NameMixin():
name = fields.CharField(40, unique=True)
class MyAbstractBaseModel(Model):
id = fields.IntField(primary_key=True)
class Meta:
abstract = True
class UserModel(TimestampMixin, MyAbstractBaseModel):
# Overriding the id definition
# from MyAbstractBaseModel
id = fields.UUIDField(primary_key=True)
# Adding additional fields
first_name = fields.CharField(20, null=True)
class Meta:
table = "user"
class RoleModel(TimestampMixin, NameMixin, MyAbstractBaseModel):
class Meta:
table = "role"
Using the Meta
class is not necessary. But it is a good habit, to
give your table an explicit name. This way you can change the model name
without breaking the schema. So the following definition is valid.
class RoleModel(TimestampMixin, NameMixin, MyAbstractBaseModel):
pass
The Meta
class¶
- class tortoise.models.Model.Meta¶
The
Meta
class is used to configure metadata for the Model.Usage:
class Foo(Model): ... class Meta: table="custom_table" unique_together=(("field_a", "field_b"), )
- abstract = False¶
Set to
True
to indicate this is an abstract class
- schema = ""¶
Set this to configure a schema name, where table exists
- table = ""¶
Set this to configure a manual table name, instead of a generated one
- table_description = ""¶
Set this to generate a comment message for the table being created for the current model
- unique_together = None¶
Specify
unique_together
to set up compound unique indexes for sets of columns.It should be a tuple of tuples (lists are fine) in the format of:
unique_together=("field_a", "field_b") unique_together=(("field_a", "field_b"), ) unique_together=(("field_a", "field_b"), ("field_c", "field_d", "field_e"))
- indexes = None¶
Specify
indexes
to set up compound non-unique indexes for sets of columns.It should be a tuple of tuples (lists are fine) in the format of:
indexes=("field_a", "field_b") indexes=(("field_a", "field_b"), ) indexes=(("field_a", "field_b"), ("field_c", "field_d", "field_e"))
- ordering = None¶
Specify
ordering
to set up default ordering for given model. It should be iterable of strings formatted in same way as.order_by(...)
receives. If query is built withGROUP_BY
clause using.annotate(...)
default ordering is not applied.ordering = ["name", "-score"]
- manager = tortoise.manager.Manager¶
Specify
manager
to override the default manager. It should be instance oftortoise.manager.Manager
or subclass.manager = CustomManager()
ForeignKeyField
¶
tournament = fields.ForeignKeyField('models.Tournament', related_name='events')
participants = fields.ManyToManyField('models.Team', related_name='events')
modified = fields.DatetimeField(auto_now=True)
prize = fields.DecimalField(max_digits=10, decimal_places=2, null=True)
In event model we got some more fields, that could be interesting for us.
fields.ForeignKeyField('models.Tournament', related_name='events')
Here we create foreign key reference to tournament. We create it by referring to model by it’s literal, consisting of app name and model name.
models
is default app name, but you can change it inclass Meta
withapp = 'other'
.related_name
Is keyword argument, that defines field for related query on referenced models, so with that you could fetch all tournaments’s events with like this:
await Tournament.first().prefetch_related("events")
The DB-backing field¶
Note
A ForeignKeyField
is a virtual field, meaning it has no direct DB backing.
Instead it has a field (by default called FKNAME_id
(that is, just an _id
is appended)
that is the actual DB-backing field.
It will just contain the Key value of the related table.
This is an important detail as it would allow one to assign/read the actual value directly, which could be considered an optimization if the entire foreign object isn’t needed.
Specifying an FK can be done via either passing the object:
await SomeModel.create(tournament=the_tournament)
# or
somemodel.tournament=the_tournament
or by directly accessing the DB-backing field:
await SomeModel.create(tournament_id=the_tournament.pk)
# or
somemodel.tournament_id=the_tournament.pk
Querying a relationship is typically done by appending a double underscore, and then the foreign object’s field. Then a normal query attr can be appended. This can be chained if the next key is also a foreign object:
FKNAME__FOREIGNFIELD__gt=3
or
FKNAME__FOREIGNFK__VERYFOREIGNFIELD__gt=3
There is however one major limitation. We don’t want to restrict foreign column names, or have ambiguity (e.g. a foreign object may have a field called isnull
)
Then this would be entirely ambiguous:
FKNAME__isnull
To prevent that we require that direct filters be applied to the DB-backing field of the foreign key:
FKNAME_id__isnull
Fetching the foreign object¶
Fetching foreign keys can be done with both async and sync interfaces.
Async fetch:
events = await tournament.events.all()
You can async iterate over it like this:
async for event in tournament.events:
...
Sync usage requires that you call fetch_related before the time, and then you can use common functions such as:
await tournament.fetch_related('events')
events = list(tournament.events)
eventlen = len(tournament.events)
if SomeEvent in tournament.events:
...
if tournament.events:
...
firstevent = tournament.events[0]
To get the Reverse-FK, e.g. an event.tournament we currently only support the sync interface.
await event.fetch_related('tournament')
tournament = event.tournament
ManyToManyField
¶
Next field is fields.ManyToManyField('models.Team', related_name='events')
. It describes many to many relation to model Team.
To add to a ManyToManyField
both the models need to be saved, else you will get an OperationalError
raised.
Resolving many to many fields can be done with both async and sync interfaces.
Async fetch:
participants = await tournament.participants.all()
You can async iterate over it like this:
async for participant in tournament.participants:
...
Sync usage requires that you call fetch_related before the time, and then you can use common functions such as:
await tournament.fetch_related('participants')
participants = list(tournament.participants)
participantlen = len(tournament.participants)
if SomeParticipant in tournament.participants:
...
if tournament.participants:
...
firstparticipant = tournament.participants[0]
The reverse lookup of team.event_team
works exactly the same way.
Improving relational type hinting¶
Since Tortoise ORM is still a young project, it does not have such widespread support by various editors who help you writing code using good autocomplete for models and different relations between them. However, you can get such autocomplete by doing a little work yourself. All you need to do is add a few annotations to your models for fields that are responsible for the relations.
Here is an updated example from Getting started, that will add autocomplete for all models including fields for the relations between models.
from tortoise.models import Model
from tortoise import fields
class Tournament(Model):
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=255)
events: fields.ReverseRelation["Event"]
def __str__(self):
return self.name
class Event(Model):
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=255)
tournament: fields.ForeignKeyRelation[Tournament] = fields.ForeignKeyField(
"models.Tournament", related_name="events"
)
participants: fields.ManyToManyRelation["Team"] = fields.ManyToManyField(
"models.Team", related_name="events", through="event_team"
)
def __str__(self):
return self.name
class Team(Model):
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=255)
events: fields.ManyToManyRelation[Event]
def __str__(self):
return self.name
Reference¶
- class tortoise.models.Model(**kwargs)[source]¶
Base class for all Tortoise ORM Models.
- class Meta[source]¶
The
Meta
class is used to configure metadata for the Model.Usage:
class Foo(Model): ... class Meta: table="custom_table" unique_together=(("field_a", "field_b"), )
- classmethod annotate(**kwargs)[source]¶
Annotates the result set with extra Functions/Aggregations/Expressions.
-
classmethod bulk_create(objects, batch_size=
None
, ignore_conflicts=False
, update_fields=None
, on_conflict=None
, using_db=None
)[source]¶ Bulk insert operation:
Note
The bulk insert operation will do the minimum to ensure that the object created in the DB has all the defaults and generated fields set, but may be incomplete reference in Python.
e.g.
IntField
primary keys will not be populated.This is recommended only for throw away inserts where you want to ensure optimal insert performance.
User.bulk_create([ User(name="...", email="..."), User(name="...", email="...") ])
- Parameters:¶
- on_conflict=
None
¶ On conflict index name
- update_fields=
None
¶ Update fields when conflicts
- ignore_conflicts=
False
¶ Ignore conflicts when inserting
- objects¶
List of objects to bulk create
- batch_size=
None
¶ How many objects are created in a single query
- using_db=
None
¶ Specific DB connection to use instead of default bound
- on_conflict=
- Return type:¶
BulkCreateQuery
[Model]
-
classmethod bulk_update(objects, fields, batch_size=
None
, using_db=None
)[source]¶ Update the given fields in each of the given objects in the database. This method efficiently updates the given fields on the provided model instances, generally with one query.
users = [ await User.create(name="...", email="..."), await User.create(name="...", email="...") ] users[0].name = 'name1' users[1].name = 'name2' await User.bulk_update(users, fields=['name'])
- clone(pk=<object object>)[source]¶
Create a new clone of the object that when you do a
.save()
will create a new record.
-
async classmethod create(using_db=
None
, **kwargs)[source]¶ Create a record in the DB and returns the object.
user = await User.create(name="...", email="...")
Equivalent to:
user = User(name="...", email="...") await user.save()
-
async delete(using_db=
None
)[source]¶ Deletes the current model object.
- Parameters:¶
- using_db=
None
¶ Specific DB connection to use instead of default bound
- using_db=
- Raises:¶
OperationalError – If object has never been persisted.
- Return type:¶
None
-
classmethod describe(serializable=
True
)[source]¶ Describes the given list of models or ALL registered models.
- Parameters:¶
- serializable=
True
¶ False
if you want raw python objects,True
for JSON-serializable data. (Defaults toTrue
)
- serializable=
- Return type:¶
dict
- Returns:¶
A dictionary containing the model description.
The base dict has a fixed set of keys that reference a list of fields (or a single field in the case of the primary key):
{ "name": str # Qualified model name "app": str # 'App' namespace "table": str # DB table name "abstract": bool # Is the model Abstract? "description": str # Description of table (nullable) "docstring": str # Model docstring (nullable) "unique_together": [...] # List of List containing field names that # are unique together "pk_field": {...} # Primary key field "data_fields": [...] # Data fields "fk_fields": [...] # Foreign Key fields FROM this model "backward_fk_fields": [...] # Foreign Key fields TO this model "o2o_fields": [...] # OneToOne fields FROM this model "backward_o2o_fields": [...] # OneToOne fields TO this model "m2m_fields": [...] # Many-to-Many fields }
Each field is specified as defined in
tortoise.fields.base.Field.describe()
- classmethod earliest(*orderings)[source]¶
Generates a QuerySet with the filter applied that returns the first record.
- Params orderings:¶
Fields to order by.
- Return type:¶
QuerySetSingle
[Optional
[typing_extensions.Self]]
-
classmethod exists(*args, using_db=
None
, **kwargs)[source]¶ Return True/False whether record exists with the provided filter parameters.
result = await User.exists(username="foo")
-
async classmethod fetch_for_list(instance_list, *args, using_db=
None
)[source]¶ Fetches related models for provided list of Model objects.
Fetch related fields.
User.fetch_related("emails", "manager")
The related fields that should be fetched.
Specific DB connection to use instead of default bound
None
-
classmethod first(using_db=
None
)[source]¶ Generates a QuerySet that returns the first record.
- Return type:¶
QuerySetSingle
[Optional
[typing_extensions.Self]]
-
classmethod get(*args, using_db=
None
, **kwargs)[source]¶ Fetches a single record for a Model type using the provided filter parameters.
user = await User.get(username="foo")
- Parameters:¶
- Raises:¶
MultipleObjectsReturned – If provided search returned more than one object.
DoesNotExist – If object can not be found.
- Return type:¶
QuerySetSingle
[typing_extensions.Self]
-
async classmethod get_or_create(defaults=
None
, using_db=None
, **kwargs)[source]¶ Fetches the object if exists (filtering on the provided parameters), else creates an instance with any unspecified parameters as default values.
- Parameters:¶
- Raises:¶
IntegrityError – If create failed
TransactionManagementError – If transaction error
ParamsError – If defaults conflict with kwargs
- Return type:¶
Tuple
[typing_extensions.Self,bool
]
-
classmethod get_or_none(*args, using_db=
None
, **kwargs)[source]¶ Fetches a single record for a Model type using the provided filter parameters or None.
user = await User.get_or_none(username="foo")
-
async classmethod in_bulk(id_list, field_name=
'pk'
, using_db=None
)[source]¶ Return a dictionary mapping each of the given IDs to the object with that ID. If id_list isn’t provided, evaluate the entire QuerySet.
-
classmethod last(using_db=
None
)[source]¶ Generates a QuerySet that returns the last record.
- Return type:¶
QuerySetSingle
[Optional
[typing_extensions.Self]]
- classmethod latest(*orderings)[source]¶
Generates a QuerySet with the filter applied that returns the last record.
- Params orderings:¶
Fields to order by.
- Return type:¶
QuerySetSingle
[Optional
[typing_extensions.Self]]
- property pk : Any¶
Alias to the models Primary Key. Can be used as a field name when doing filtering e.g.
.filter(pk=...)
etc…- Return type:¶
Any
-
classmethod raw(sql, using_db=
None
)[source]¶ Executes a RAW SQL and returns the result
result = await User.raw("select * from users where name like '%test%'")
-
async refresh_from_db(fields=
None
, using_db=None
)[source]¶ Refresh latest data from db. When this method is called without arguments all db fields of the model are updated to the values currently present in the database.
user.refresh_from_db(fields=['name'])
- classmethod register_listener(signal, listener)[source]¶
Register listener to current model class for special Signal.
-
async save(using_db=
None
, update_fields=None
, force_create=False
, force_update=False
)[source]¶ Creates/Updates the current model object.
- Parameters:¶
- update_fields=
None
¶ If provided, it should be a tuple/list of fields by name.
This is the subset of fields that should be updated. If the object needs to be created
update_fields
will be ignored.- using_db=
None
¶ Specific DB connection to use instead of default bound
- force_create=
False
¶ Forces creation of the record
- force_update=
False
¶ Forces updating of the record
- update_fields=
- Raises:¶
IncompleteInstanceError – If the model is partial and the fields are not available for persistence.
IntegrityError – If the model can’t be created or updated (specifically if force_create or force_update has been set)
- Return type:¶
None
-
classmethod select_for_update(nowait=
False
, skip_locked=False
, of=()
, using_db=None
)[source]¶ Make QuerySet select for update.
Returns a queryset that will lock rows until the end of the transaction, generating a SELECT … FOR UPDATE SQL statement on supported databases.
- update_from_dict(data)[source]¶
Updates the current model with the provided dict. This can allow mass-updating a model from a dict, also ensuring that datatype conversions happen.
This will ignore any extra fields, and NOT update the model with them, but will raise errors on bad types or updating Many-instance relations.
- Parameters:¶
- data¶
The parameters you want to update in a dict format
- Return type:¶
- Returns:¶
The current model instance
- Raises:¶
ConfigurationError – When attempting to update a remote instance (e.g. a reverse ForeignKey or ManyToMany relation)
ValueError – When a passed parameter is not type compatible