fastapi是一个很优秀的框架,但是缺少一个合适的orm,官方代码里面使用的是sqlalchemy,异步也是使用的这个。但是我这边看到有tortoise-orm这个异步orm框架,不知道效率如何,这里先学习,之后做一个性能测试比较一下。
整个框架非常接近django,如果我没写的地方,要么是和django差不多,要么是没这功能。
fastapi引入
在main.py文件里面引入如下代码:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from models import User_Pydantic, UserIn_Pydantic, Users
from tortoise.contrib.fastapi import HTTPNotFoundError, register_tortoise
app = FastAPI(title="Tortoise ORM FastAPI example")
...
register_tortoise(#这里是启动app的,之后会考虑和使用uvicorn启动的性能差别
app,
db_url="sqlite://:memory:",#数据库信息
modules={
"models": ["models"]},#models列表
generate_schemas=True,#如果数据库为空,则自动生成对应表单,生产环境不要开
add_exception_handlers=True,#生产环境不要开,会泄露调试信息
)
引入tortoise-orm的方法:register_tortoise
def register_tortoise(
app: FastAPI,
config: Optional[dict] = None,
config_file: Optional[str] = None,
db_url: Optional[str] = None,
modules: Optional[Dict[str, List[str]]] = None,
generate_schemas: bool = False,
add_exception_handlers: bool = False,
) -> None:
"""
在fastapi注册startup和shutdown
使用 ``config``, ``config_file``或 ``(db_url, modules)``三者之一来配置
示例
----------
config:
Dict containing config:
Example
-------
{
'connections': {
# Dict format for connection
'default': {
'engine': 'tortoise.backends.asyncpg',
'credentials': {
'host': 'localhost',
'port': '5432',
'user': 'tortoise',
'password': 'qwerty123',
'database': 'test',
}
},
# Using a DB_URL string
'default': 'postgres://postgres:qwerty123@localhost:5432/events'
},
'apps': {
'models': {
'models': ['__main__'],
# If no default_connection specified, defaults to 'default'
'default_connection': 'default',
}
}
}
config_file:
Path to .json or .yml (if PyYAML installed) file containing config with
same format as above.
db_url:
Use a DB_URL string. See :ref:`db_url`
modules:
Dictionary of ``key``: [``list_of_modules``] that defined "apps" and modules that
should be discovered for models.
generate_schemas:
True立即生成模式。仅适用于开发环境或SQLite ' ':memory: ' '数据库,生产环境数据库一定手动生成。
add_exception_handlers:
为' ' DoesNotExist ' ' & ' ' IntegrityError ' '添加一些自动异常处理程序。
不建议用于生产系统,因为它可能会泄漏数据。
"""
pass
创建对应数据模型
tortoise-orm能使用的数据类型还挺丰富,满足了日常使用的需求。
创建Model
from tortoise import fields
from tortoise.models import Model
class User(Model):
id=fields.IntField(pk=True)#主键必不可少
通过继承的方式创建Model
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(pk=True)
class Meta:
abstract = True
#注意数据库里面对应表顺序可能会比较乱。。
class UserModel(TimestampMixin, MyAbstractBaseModel):
# 覆盖继承的字段
id = fields.UUIDField(pk=True)
# 新增一些字段
first_name = fields.CharField(20, null=True)
class Meta:
table = "user"
class RoleModel(TimestampMixin, NameMixin, MyAbstractBaseModel):
class Meta:
table = "role"
设置数据库字段field
主要有如下字段(这些字段都没啥好说的,):
#常规字段
BigIntField,
BinaryField,
BooleanField,
CharEnumField,
CharField,
DateField,
DatetimeField,
DecimalField,
FloatField,
IntEnumField,#继承于SmallIntField
IntField,
JSONField,
SmallIntField,
TextField,
TimeDeltaField,
UUIDField,
#关系字段(relation不知道啥用,)
BackwardFKRelation,
BackwardOneToOneRelation,
ForeignKeyField,#外键
ForeignKeyNullableRelation,
ForeignKeyRelation,
ManyToManyField,#多对多
ManyToManyRelation,#反向代码提示的工具
OneToOneField,#一对一
OneToOneNullableRelation,
OneToOneRelation,
ReverseRelation,
关系键讲解:
ForeignKeyField:
tournament = fields.ForeignKeyField('models.Tournament', related_name='events')#related_name关键字参数,用于为引用的模型定义查询自己的字段,默认为名字+set
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)
反向代码提示:
在使用外键的时候,我们需要获取到反向代码提示(即被绑定的model查询绑定model),这个时候需要使用relation字段来提示,(其实你不加也没啥关系,只是个提示作用,在django里面是编辑器和插件提前内置了相关字段所以不用手写)
示例代码如下:
from tortoise.models import Model
from tortoise import fields
class Tournament(Model):
id = fields.IntField(pk=True)
name = fields.CharField(max_length=255)
events: fields.ReverseRelation["Event"]#仅用于代码提示,注意events必须和Event里面的外键指定的related_name同名
class Event(Model):
id = fields.IntField(pk=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"
)#注意多对多,两个model里面都要写,虽然复杂了点,但是有代码提示还是很合算的。。through在django里面是指定多对多表的名字和功能,需要手动创建,这里可能是示例代码不全吧。。得测试
class Team(Model):
id = fields.IntField(pk=True)
name = fields.CharField(max_length=255)
events: fields.ManyToManyRelation[Event]#反向关系,
字段介绍
基本字段:
source_field :自定义数据库对应字段名称
generated :是否映射到数据库,
pk 是否为主键
null
default 可以为值或可调用对象
unique
index
description 描述功能,数据库注释用
自定义字段
tortoise-orm支持自定义字段,这个功能挺好用的,这里先不讲了,一般用不到。。。请自行查看:link.
可以通过继承,然后重构相关字段控制字段在数据库的存储方式
设置Meta
from tortoise import fields
from tortoise.models import Model
class User(Model):
id=fields.IntField(pk=True)#主键必不可少
class Meta:
abstract=True#抽象模型,用于继承
table="xxx"#该模型对应的表名称
unique_together=(("field_a", "field_b"), )#设置唯一索引,参考django
table_description = ""#数据库对该表的注释
indexes=(("field_a", "field_b"), )#指定列集为非唯一索引,类似django在字段上的index
ordering = ["name", "-score"]#设置默认查询结果的顺序
Model模型方法
#常用查询方法我就不提了,讲一些不常用的
annotate()#使用额外的函数/聚合对结果进行再过滤,,
bulk_create()#批量插入:
User.bulk_create([
User(name="...", email="..."),
User(name="...", email="...")
])
check()#检查model数据是否正确
describe()#序列化model,返回json
exists()#True/False 记录是否存在筛选器参数模式的model
register_listener()#侦听器,参数为信号,注意,更新到最新版才有。。数据的保存和删除前后可以被监听,挺有用的一个东西,使用说明查看链接:https://tortoise-orm.readthedocs.io/en/latest/examples/basic.html#model-signals
update_from_dict()#通过dict更新数据,配合schema很有用,主要用于字段更新,schema.dict有一个只获取填写结果字段的方法,配合这个可以实现局部更新
查询
参考django,略。
Q对象查询
这一块主要针对复杂查询。
有时,您需要执行比简单 AND 提供的更复杂的查询。幸运的是,我们有Q对象来调味的东西,并帮助您找到你需要的。然后,这些 Q 对象可用作参数。.filter().filter()
Q 对象用途极多,例如用例:
创建 OR 筛选器
嵌套筛选器
倒置过滤器
例如,查找名称或 的事件:Event 1Event 2
found_events = await Event.filter(
Q(name='Event 1') | Q(name='Event 2')
)
#等效于:
found_events = await Event.filter(
Q(Q(name='Event 1'), Q(name='Event 2'), join_type="OR")#如果省略join_type,则为AND
)
字段过滤
在搜索的时候增加后缀可以实现字段的过滤效果:
比如:
teams = await Team.filter(name__icontains='CON')
相关参数如下:
- not
- in- 检查字段的值是否位于传递列表中
- not_in
- gte- 大于或等于传递的值
- gt- 大于传递的值
- lte- 低于或等于传递的值
- lt- 低于传递值
- range- 介于和给定两个值之间
- isnull- 字段为空
- not_isnull- 字段不为空
- contains- 字段包含指定的子字符串
- icontains- 不区分大小写contains
- startswith- 如果字段以值开头
- istartswith- 不区分大小写startswith
- endswith- 如果字段以值结尾
- iendswith- 不区分大小写endswith
- iequals- 区分大小写等于
预取
通过预取,可以减少数据库读取次数,然后提高响应速度
有时只需要获取某些相关记录。您可以使用对象实现:Prefetch
示例:
tournament_with_filtered = await Tournament.all().prefetch_related(
Prefetch('events', queryset=Event.filter(name='First'))
).first()
更多的参考:预取详情
F表达式
某些时候,我们只是需要将数据进行一次计算或处理然后保存,我们并不在意值是多少,只是想把值进行我们指定的修改,就可以使用F表达式,这样就可以减少一次数据库读取(我感觉好像没卵用啊。。。)
参考如下:
from tortoise.expressions import F
await User.filter(id=1).update(balance = F('balance') - 10)
await User.filter(id=1).update(balance = F('balance') + F('award'), award = 0)
# or use .save()
user = await User.get(id=1)
user.balance = F('balance') - 10
await user.save(update_fields=['balance'])
功能和聚合
略
请参考功能和聚合
事务
略 请参考事务
根据Model生成Schema
讲道理schema这个东西名字挺奇葩的。。。不过既然官网这么弄就这么弄吧。这个可以很方便的生成相关字段
注意,schema不要有相同的类名,会报错的
User_Pydantic = pydantic_model_creator(Users, name="User")
UserIn_Pydantic = pydantic_model_creator(Users, name="UserIn", exclude_readonly=True)
下面这个文档里面还没讲解;
可以通过在model里面创建一个class PydanticMeta来实现创建schema的控制:
class PydanticMeta:
"""
The ``PydanticMeta`` class is used to configure metadata for generating the pydantic Model.
Usage:
.. code-block:: python3
class Foo(Model):
...
class PydanticMeta:
exclude = ("foo", "baa")
computed = ("count_peanuts", )
"""
#: If not empty, only fields this property contains will be in the pydantic model
include: Tuple[str, ...] = ()
#: Fields listed in this property will be excluded from pydantic model
exclude: Tuple[str, ...] = ()
#: Computed fields can be listed here to use in pydantic model
computed: Tuple[str, ...] = ()
#: Use backward relations without annotations - not recommended, it can be huge data
#: without control
backward_relations: bool = True
#: Maximum recursion level allowed
max_recursion: int = 3
#: Allow cycles in recursion - This can result in HUGE data - Be careful!
#: Please use this with ``exclude``/``include`` and sane ``max_recursion``
allow_cycles: bool = False
#: If we should exclude raw fields (the ones have _id suffixes) of relations
exclude_raw_fields: bool = True
#: Sort fields alphabetically.
#: If not set (or ``False``) then leave fields in declaration order
sort_alphabetically: bool = False
如果你想跨表搜索或join搜索,在computed里面定义。