草庐IT

Tortoise ORM 简单使用

A-L-Kun 2024-02-24 原文

文章目录

Tortoise ORM

一、 简介

1、 ORM

当您构建使用关系数据库的应用程序或服务时,有时您不能仅仅使用参数化查询甚至查询构建器就可以逃脱,您只是不断重复自己,为每个实体编写略有不同的代码。代码不知道数据之间的关系,因此您最终几乎是手动连接数据。访问数据库的方式也很容易出错,从而很容易发生 SQL 注入攻击。您的数据规则也是分布式的,增加了管理数据的复杂性,更糟糕的是,应用不一致。

ORM(对象关系映射器)旨在解决这些问题,通过集中您的数据模型和数据规则,确保您的数据得到安全管理(提供对 SQL 注入的免疫力)并跟踪关系,因此您不必。

2、 介绍

Tortoise ORM 是受 Django 启发的易于使用的asyncioORM (对象关系映射器)

Tortoise ORM 的构建类似于 Django ORM。它的设计中不仅使用表格,还使用关系数据。

与其他 Python ORM 相比,它也表现良好,与 Pony ORM 进行交易:

Django ORM 语法

SQLAlchemy 语法

官方文档地址:https://tortoise-orm.readthedocs.io/en/latest/index.html

3、 简单使用

创建模型:

from tortoise.models import Model
from tortoise import fields

class Tournament(Model):
    id = fields.IntField(pk=True)
    name = fields.TextField()

创建表,初始化数据库:

from tortoise import Tortoise, run_async

async def init():
    # Here we create a SQLite DB using file "db.sqlite3"
    #  also specify the app name of "models"
    #  which contain models from "app.models"
    await Tortoise.init(
        db_url='sqlite://db.sqlite3',
        modules={'models': ['app.models']}
    )
    # Generate the schema
    await Tortoise.generate_schemas()

# run_async is a helper function to run simple async Tortoise scripts.
run_async(init())

数据查询:

# Create instance by save
tournament = Tournament(name='New Tournament')
await tournament.save()

# Or by .create()
await Tournament.create(name='Another Tournament')

# Now search for a record
tour = await Tournament.filter(name__contains='Another').first()
print(tour.name)

4、 环境配置

pip install tortoise-orm

# 安装数据库驱动
pip install tortoise-orm[asyncpg]
pip install tortoise-orm[aiomysql]
pip install tortoise-orm[asyncmy]
# 除此之外,还支持:aiosqlite

二、 基础配置

1、 数据库链接

Tortoise 目前支持以下数据库:

  • SQLite
  • PostgreSQL >= 9.4(使用asyncpg
  • MySQL/MariaDB(使用aiomysql

要使用,请确保已安装asyncpg和/或aiomysql

Tortoise 支持以 URL 形式指定数据库配置:

语法:

  • DB_TYPE://USERNAME:PASSWORD@HOST:PORT/DB_NAME?PARAM1=value&PARAM2=value

支持的DB_TYPE

  • sqlite

    通常以 So if the is “/data/db.sqlite3” 那么字符串将是(注意三个 /)sqlite://{DB_FILE}

    如:sqlite:///data/db.sqlite

  • postgres

    通常采用以下形式:postgres://postgres:pass@db.host:5432/somedb

  • mysql

    通常采用以下形式:mysql://myuser:mypass:pass@db.host:3306/somedb

更详细的可以进入官方文档查看:https://tortoise-orm.readthedocs.io/en/latest/databases.html

2、 创建数据库

from tortoise import Tortoise, run_async

async def init():
    # Here we create a SQLite DB using file "db.sqlite3"
    #  also specify the app name of "models"
    #  which contain models from "app.models"
    await Tortoise.init(
        db_url='sqlite://db.sqlite3',
        modules={'models': ['app.models']}
    )
    # Generate the schema
    await Tortoise.generate_schemas()  # safe:仅在表不存在时创建表

run_async(init())  # 会自动进入上下文处理,在运行完成时,自动关闭数据库连接

三、 模型

1、 创建

from tortoise.models import Model

class Tournament(Model):
    id = fields.IntField(pk=True)
    name = fields.TextField()
    created = fields.DatetimeField(auto_now_add=True)

    def __str__(self):
        return self.name


class Event(Model):
    id = fields.IntField(pk=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(pk=True)
    name = fields.TextField()

     class Meta:
        abstract = True  # 设置为True表明这是一个抽象类
        table = "team"  # 设置表名
        table_description = ""  # 设置此项可为为当前模型创建的表生成注释消息
		unique_together = ()  # 指定unique_together为列集设置复合唯一索引,其为元组的元组(列表很好)
        indexes = ()  # 指定indexes为列集设置复合非唯一索引,它应该是元组的元组(列表很好)
		ordering = []  # 指定ordering为给定模型设置默认排序。.order_by(...)它应该可以迭代以与接收相同的方式格式化的字符串。如果查询是GROUP_BY使用默认排序的子句构建的,.annotate(...)则不应用。

		manager = tortoise.manager.Manager  # 指定manager覆盖默认管理器。它应该是实例tortoise.manager.Manager或子类。

2、 多表关联

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"]

    def __str__(self):
        return self.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"
    )

    def __str__(self):
        return self.name


class Team(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=255)

    events: fields.ManyToManyRelation[Event]

    def __str__(self):
        return self.name

3、 字段

更详细的信息可以查看官方文档:https://tortoise-orm.readthedocs.io/en/latest/fields.html

3.1 数据字段

Field(source_field = None , generated = False , pk = False , null = False , default = None , unique = False , index = False , description = None , model = None , validators = None , ** kwargs

参数:

  • source_field (Optional[str]) : 如果 DB 列名称需要是特定的而不是从字段名称中生成,则提供 source_field 名称。
  • generated (bool) : 该字段是否由数据库生成
  • pk (bool) :该字段是否为主键
  • null (bool) :主键是否可以为空
  • default (Optional[Any]):该字段的默认值
  • unique (bool) :该字段的值是否唯一
  • index (bool):设置该字段是否为索引
  • description (Optional[str]) :字段描述,也将出现在Tortoise.describe_model() 生成的 DDL 中并作为 DB 注释出现。
  • validators (Optional[List[Union[Validator, Callable]]]) :此字段的验证器

3.2 关系字段

ForeignKeyField( model_name , related_name = None , on_delete = 'CASCADE' , db_constraint = True , ** kwargs )

参数:

  • model_name:关联模型的名称{app}.{models}
  • related_name:相关模型上的属性名称,用于反向解析外键
  • on_delete
    • field.CASCADE:表示如果相关模型被删除,该模型应该被级联删除
    • field.RESTRICT:表示只要有外键指向,相关模型删除就会受到限制
    • field.SET_NULL:将字段重置为 NULL,以防相关模型被删除。仅当字段已设置时才能null=True设置
    • field.SET_DEFAULT:将字段重置为default值,以防相关模型被删除。只能设置是字段有一个default集合
  • to_field:建立外键关系的相关模型上的属性名。如果未设置,则使用 pk
  • db_constraint: 控制是否应在数据库中为此外键创建约束。默认值为 True,这几乎可以肯定是您想要的;将此设置为 False 可能对数据完整性非常不利

ManyToManyField(model_name, through=None, forward_key=None, backward_key='', related_name='', on_delete='CASCADE', db_constraint=True, **kwargs)

参数:

  • through:通过中间表进行连接
  • forward_key: 直通表上的正向查找键。默认值通常是安全的
  • backward_key: 通表上的向后查找键。默认值通常是安全的

OneToOneField( model_name , related_name = None , on_delete = 'CASCADE' , db_constraint = True , ** kwargs )

这些参数在上面两个字段中全部都解释了,就不再进行解释

4、 查询

4.1 基础

模型本身有几种方法可以启动查询:

  • filter(*args, **kwargs):使用给定的过滤器创建 QuerySet

  • exclude(*args, **kwargs):使用给定的排除过滤器创建 QuerySet

  • all():创建不带过滤器的查询集

  • first():创建仅限于一个对象的查询集并返回实例而不是列表

  • annotate(): 使用额外的函数/聚合对结果进行再过滤

    此方法返回QuerySet对象,允许进一步过滤和一些更复杂的操作

模型类也有这个方法来创建对象:

  • create(**kwargs):使用给定的 kwargs 创建对象
  • get_or_create(defaults, **kwargs):获取给定 kwargs 的对象,如果未找到,则使用默认字典中的其他 kwargs 创建它

模型本身的实例也有这些方法:

  • save():更新实例,或者插入它,如果它以前从未保存过
  • delete():从数据库中删除实例
  • fetch_related(*args):获取与实例相关的对象。它可以获取 key关系、backward-key 关系。
values()  # 返回queryset里面字段的值
values_list()  # 返回queryset里面字段的值,同时,内部可以指定需要返回的字段的值

events = await Event.filter(id__in=[1,2,3]).values('id', 'name', tournament_name='tournament__name')  # 比如

使用values()values_list()生成更有效的查询

filter可以指定的对象:

  • not
  • in:检查字段的值是否在传递列表中
  • not_in
  • gte:大于或等于传递的值
  • gt:大于传递值
  • lte:低于或等于传递的值
  • lt:低于通过值
  • range:介于和给定两个值之间
  • isnull:字段为空
  • not_isnull:字段不为空
  • contains:字段包含指定的子字符串
  • icontains:不区分大小写contains
  • startswith:如果字段以值开头
  • istartswith:不区分大小写startswith
  • endswith:如果字段以值结尾
  • iendswith:不区分大小写endswith
  • iexact:不区分大小写等于
  • search:全文搜索

使用示例:

from tortoise.functions import Count, Trim, Lower, Upper, Coalesce

# This query will fetch all tournaments with 10 or more events, and will
# populate filed `.events_count` on instances with corresponding value
await Tournament.annotate(events_count=Count('events')).filter(events_count__gte=10)
await Tournament.annotate(clean_name=Trim('name')).filter(clean_name='tournament')
await Tournament.annotate(name_upper=Upper('name')).filter(name_upper='TOURNAMENT')
await Tournament.annotate(name_lower=Lower('name')).filter(name_lower='tournament')
await Tournament.annotate(desc_clean=Coalesce('desc', '')).filter(desc_clean='')

同时,对于一对多和多对多,可以直接使用那个字段名进行数据的操作

4.2 Q 对象

Q 对象非常通用,一些示例用例:

  • 创建 OR 过滤器
  • 嵌套过滤器
  • 倒置过滤器
  • 结合以上任何一种来简单地编写复杂的多层过滤器

比如:

Q( * args , join_type = 'AND' , ** kwargs )

参数:

  • join_type:连接类型,OR\AND
  • args ( Q) :Q要包装的内部表达式
  • kwargs ( Any) :此 Q 对象应封装的过滤语句
found_events = await Event.filter(
    Q(Q(name='Event 1'), Q(name='Event 2'), join_type="OR")  # Q(name='Event 1') | Q(name='Event 2')
)

4.3 F 表达式

F对象表示模型字段的值。它可以引用模型字段值并使用它们执行数据库操作,而无需将它们从数据库中拉出到 Python 内存中

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'])

五、 迁移

我们使用Aerich进行迁移 :https://github.com/tortoise/aerich

安装:pip install aerich

aerich -h

Usage: aerich [OPTIONS] COMMAND [ARGS]...

Options:
  -c, --config TEXT  Config file.  [default: aerich.ini]
  --app TEXT         Tortoise-ORM app name.  [default: models]
  -n, --name TEXT    Name of section in .ini file to use for aerich config.
                     [default: aerich]
  -h, --help         Show this message and exit.

Commands:
  downgrade  Downgrade to specified version.
  heads      Show current available heads in migrate location.
  history    List all migrate items.
  init       Init config file and generate root migrate location.
  init-db    Generate schema and generate app migrate location.
  migrate    Generate migrate changes file.
  upgrade    Upgrade to latest version.

用法:

您需要先将 aerich.models添加到您的Tortoise-ORM配置中

TORTOISE_ORM = {
    "connections": {"default": "mysql://root:123456@127.0.0.1:3306/test"},
    "apps": {
        "models": {
            "models": ["tests.models", "aerich.models"],  
            "default_connection": "default",
        },
    },
}

初始化配置文件和设置:

aerich init -t tests.backends.mysql.TORTOISE_ORM

对数据库进行操作:

aerich init-db  # 初始化数据库
aerich migrate --name drop_column  # 更新并进行迁移
aerich upgrade  # 升级数据库
aerich downgrade  # 降级数据库到某个版本
aerich history  # 显示迁移历史
aerich heads  # 显示迁移的磁头

这里大部分是理论知识,例子可以自行到官方文档查看:https://tortoise-orm.readthedocs.io/en/latest/examples/

有关Tortoise ORM 简单使用的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

随机推荐