Data Model
Using Data Models
Data models are a core concept of pylite-orm, defined by inheriting from the DbModel class and used during CRUD operations.
💡 Almost all modern ORMs use data models. Beyond being tagged as "object-oriented," it indeed greatly simplifies data access operations.
Let's look at a simple data model definition first:
from pylite_orm import DbModel, DbField, DbType
class User(DbModel):
id = DbField(db_type = DbType.INT, pk = True)
name = DbField(db_type = DbType.TEXT)
age = DbField(db_type = DbType.INT)
created_at = DbField(db_type = DbType.TEXT, default_factory = datetime.now)
Does it have a SQLAlchemy/Tortoise-ORM/Peewee style? (So there's no learning curve when using pylite-orm)
DbField is used to set parameters for model fields (these parameters will be introduced in detail later). If you use the data migration tool, corresponding tables will be created in the database. If you modify the model definition, the tables in the database will be updated synchronously when you re-run the migration command.
A more complex data model looks like this:
from pylite_orm import DbModel, DbField, RelationField, OnDelete, DbType
class Article(DbModel):
id = DbField(db_type = DbType.INT, pk = True)
title = DbField(db_type = DbType.TEXT)
content = DbField(db_type = DbType.TEXT)
user_id = DbField(db_type = DbType.INT, relation = 'user.id', on_delete = OnDelete.CASCADE)
author:User = RelationField(fk = 'user_id')
class Meta:
table = 'art'
indexes = ['user_id']
This time an article table is defined, not only with a foreign key user_id but also with a relation field user , representing the article's author information.
Another new element is the Meta configuration, where you can specify the actual table name in the database and define indexes. The current example has only one regular index. If needed, you can define more indexes, including composite indexes.
DbModel is not just a mapping of database tables (used in CRUD), you can treat it as a special data type, especially in desktop software projects, where it can conveniently store and pass data, and persist to the database at any time.
Fields in Data Models
DbModel has two types of fields: one is DbField , which can be called a regular field; the other is RelationField, which can be called a relation field.
DbField
Contains 8 optional configuration parameters:
| Parameter | Type | Description |
|---|---|---|
| db_column | str | Custom database field name If not specified, the model field name becomes the database field name. For example, if the model field name is uname, the database field name will also be uname |
| db_type | DbType | Defines the field type Through DbType enumeration, defaults to DbType.TEXT |
| pk | bool | Whether it is a primary key Each data model allows only 1 primary key, and it can only be set on integer type (DbType.INT) fields, which automatically becomes an auto-increment field |
| default | any | Specifies a default value for the current field Note: This is the default value for the data model and will not be set on the corresponding database field, but the effect is actually the same |
| default_factory | callable | Default value generator If the default value is a function, it must be set here. If it exists together with the default setting, the default shall prevail |
| nullable | bool | Whether the current field allows NULL Defaults to True, meaning NULL is allowed |
| relation | str | Specifies the foreign key relationship Format is "main_table.field_name". Once specified, the current field becomes a foreign key field |
| on_delete | OnDelete | Association deletion strategy Defined through OnDelete enumeration, defaults to OnDelete.CASCADE |
Model fields defined through DbField will create corresponding table fields in the database. If the model definition is modified, the table in the database will be updated synchronously when the migration command is re-run.
RelationField
Has only 1 configuration parameter (and must be configured):
| Parameter | Type | Description |
|---|---|---|
| fk | str | Specifies the foreign key field name Note this is the actual field name in the data table, not the data model field name (be careful about the difference). Example: fk='user_id' (this is the database field name) |
Model fields defined through RelationField will not create corresponding table fields in the database. It is just a virtual field, or rather, it is just another data model object embedded in the current data model, used to carry associated data.
Meta Conf in Data Models
Currently there are only 2 optional configuration items:
| Configuration | Type | Description |
|---|---|---|
| table | str | Custom data table name If not customized, the snake_case form of the data model name is used as the actual data table name by default. For example: User --> user, UserRole --> user_role |
| indexes | list | Index list A single-field index is just a field name string, format is ["field_name", "field_name", ...]. If it's a composite index, it needs to be placed in a tuple. A setting with both single-field indexes and composite indexes looks like this: indexes = ["user_id", ("org_id", "created_at")] |
pylite-orm does not support unique indexes for a simple reason: the purpose of indexes is to accelerate queries, not to limit data uniqueness. Data validation and constraints should be done at the application layer.
Enum in Data Models
DbType
Data type enumeration, used for the db_type parameter of DbField.
| Enum Value | Description |
|---|---|
| DbType.INT | Integer type |
| DbType.TEXT | Text type |
| DbType.REAL | Floating-point type |
| DbType.BLOB | Binary type |
OnDelete
Association deletion strategy enumeration, used for the on_delete parameter of DbField .
| Enum Value | Description |
|---|---|
| OnDelete.CASCADE | Cascade delete. When the main table record is deleted, the related table record will also be deleted |
| SET_NULL | Set to NULL. When the main table record is deleted, the association field of the related table record will be set to NULL |
| OnDelete.SET_DEFAULT | Set to default value. When the main table record is deleted, the association field of the related table record will be set to the default value |
| OnDelete.RESTRICT | Restrict deletion. When the related table record exists, the main table record is not allowed to be deleted |
| OnDelete.NO_ACTION | Restrict deletion. When the related table record exists, nothing happens when deleting the main table record |
About One-to-One
Let's look at the example first:
class User(DbModel):
id = DbField(db_type = DbType.INT, pk = True)
name = DbField(db_type = DbType.TEXT)
profile:'Profile' = RelationField(fk='user_id')
class Profile(DbModel):
id = DbField(db_type = DbType.INT, pk = True)
user_id = DbField(db_type = DbType.INT, relation = 'user.id')
age = DbField(db_type = DbType.INT)
sex = DbField(db_type = DbType.INT, default = 1)
weight = DbField(db_type = DbType.REAL)
author:User = RelationField(fk = 'user_id')
class Meta:
indexes = ['user_id']
💡 No need to explain why the Profile type annotation in the User class is quoted, right?
User and Profile have a typical one-to-one relationship. By creating a user_id field in Profile and specifying relation = "user.id" (main_table_name.field_name), the relationship between the two is established. And user_id thus becomes a foreign key.
In the current example, user_id does not specify an on_delete strategy, so it defaults to OnDelete.CASCADE , meaning when the main table (user) record is deleted, the related table (profile) record will also be deleted.
The profile object in User and the author object in Profile are optional. The benefit of creating such RelationField virtual fields is that you don't need to consider join operations during queries, and you can directly access related objects when accessing the current object. For example:
# Assume a user object has been queried and returned earlier
print(user.profile.age)
In relationship settings, the most easily confused part is fk = user_id and relation = user.id . fk refers to the foreign key field name, while relation refers to the main table's primary key field name.
They are both database field names, not model field names.
💡 Usually there's no need to specifically distinguish between database field names and model field names because they are consistent. But when you customize database field names to be different from model field names, you need to be careful to distinguish them.
About One-to-Many
Let's look at the example first:
class User(DbModel):
id = DbField(db_type = DbType.INT, pk = True)
name = DbField(db_type = DbType.TEXT)
arts:list['Article'] = RelationField(fk='user_id')
class Article(DbModel):
id = DbField(db_type = DbType.INT, pk = True)
title = DbField(db_type = DbType.TEXT)
content = DbField(db_type = DbType.TEXT)
user_id = DbField(db_type = DbType.INT, relation = 'user.id', on_delete = OnDelete.SET_NULL)
author:User = RelationField(fk = 'user_id')
User and Article have a typical one-to-many relationship. By creating a user_id field in Article and specifying relation = "user.id" , the relationship between the two is established. And user_id thus becomes a foreign key.
To preserve articles, user_id specifies the on_delete strategy as OnDelete.SET_NULL , meaning when the main table (user) record is deleted, the user_id field of the related table (article) record will be set to NULL, rather than synchronously deleting the article.
Unlike before, a user may have multiple articles, so arts in User is an object list.
About Many-to-Many
pylite-orm doesn't have a dedicated many-to-many setting syntax, but implements many-to-many relationships through 2 one-to-many relationships. Project practice has proven that dedicated many-to-many syntax in ORMs is used in less than 10% of scenarios.
Implementing many-to-many with 2 one-to-many:
class User(DbModel):
id = DbField(db_type = DbType.INT, pk = True)
name = DbField(db_type = DbType.TEXT)
class Role(DbModel):
id = DbField(db_type = DbType.INT, pk = True)
code = DbField(db_type = DbType.TEXT)
class UserRole(DbModel):
id = DbField(db_type = DbType.INT, pk = True)
user_id = DbField(db_type = DbType.INT, relation = 'user.id')
role_id = DbField(db_type = DbType.INT, relation = 'role.id')
user:User = RelationField(fk = 'user_id')
role:Role = RelationField(fk = 'role_id')
The actual database table is essentially many-to-many (of course, it is indeed 2 one-to-many relationships, hope you're not dizzy).
Methods in Data Models
DbModel has only 3 methods:
| Method | Type | Description |
|---|---|---|
| table_name() | Class Method | Returns the mapped database table name |
| pk_name() | Class Method | Returns the primary key field name |
| asdict() | Instance Method | Converts the current object to a dictionary. The exc_unset parameter specifies whether to exclude unset fields |