SqlAlchemy 2.0 中文文档(四十三)(6)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: SqlAlchemy 2.0 中文文档(四十三)

SqlAlchemy 2.0 中文文档(四十三)(5)https://developer.aliyun.com/article/1563064


在连接后修改 DBAPI 连接,或在连接后运行命令

对于 SQLAlchemy 创建的 DBAPI 连接,虽然没有问题,但我们想要在实际使用之前修改完成的连接的情况,例如设置特殊标志或运行某些命令,PoolEvents.connect()事件钩子是最合适的钩子。这个钩子在每次创建新连接时调用,然后由 SQLAlchemy 使用:

from sqlalchemy import event
engine = create_engine("postgresql+psycopg2://user:pass@hostname/dbname")
@event.listens_for(engine, "connect")
def connect(dbapi_connection, connection_record):
    cursor_obj = dbapi_connection.cursor()
    cursor_obj.execute("SET some session variables")
    cursor_obj.close()

完全替换 DBAPI 的connect()函数

最后,DialectEvents.do_connect()事件钩子也可以允许我们完全接管连接过程,建立连接并返回它:

from sqlalchemy import event
engine = create_engine("postgresql+psycopg2://user:pass@hostname/dbname")
@event.listens_for(engine, "do_connect")
def receive_do_connect(dialect, conn_rec, cargs, cparams):
    # return the new DBAPI connection with whatever we'd like to
    # do
    return psycopg2.connect(*cargs, **cparams)

DialectEvents.do_connect()钩子取代了以前的create_engine.creator钩子,后者仍然可用。DialectEvents.do_connect()的明显优势是解析自 URL 的完整参数也传递给用户定义的函数,而对于create_engine.creator不是这样。

配置日志记录

Python 的标准logging模块用于实现 SQLAlchemy 的信息和调试日志输出。这使得 SQLAlchemy 的日志记录可以以标准方式与其他应用程序和库集成。create_engine()上还有两个参数create_engine.echocreate_engine.echo_pool,它们允许立即将日志记录到sys.stdout以进行本地开发;这些参数最终与下面描述的常规 Python 记录器进行交互。

这一节假设您熟悉上面链接的日志记录模块。SQLAlchemy 执行的所有日志记录都存在于sqlalchemy命名空间下,就像logging.getLogger('sqlalchemy')所使用的那样。当日志记录已经配置好(例如通过logging.basicConfig()),可以打开的 SA 日志记录器的一般命名空间如下所示:

  • sqlalchemy.engine - 控制 SQL 回显。设置为logging.INFO以输出 SQL 查询结果,设置为logging.DEBUG以输出查询结果集。这些设置相当于create_engine.echo 上的echo=Trueecho="debug"
  • sqlalchemy.pool - 控制连接池日志记录。设置为logging.INFO以记录连接失效和回收事件;设置为logging.DEBUG以额外记录所有连接池的签入和签出。这些设置相当于create_engine.echo_pool 上的pool_echo=Truepool_echo="debug"
  • sqlalchemy.dialects - 控制 SQL 方言的自定义日志记录,以特定方言内部使用日志的程度为准,通常很少。
  • sqlalchemy.orm - 控制各种 ORM 函数的日志记录,以 ORM 内部使用日志的程度为准,通常很少。设置为logging.INFO以记录一些有关映射器配置的顶级信息。

例如,要使用 Python 日志记录而不是echo=True标志记录 SQL 查询:

import logging
logging.basicConfig()
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)

默认情况下,整个 sqlalchemy 命名空间的日志级别设置为logging.WARN,因此即使在已启用日志记录的应用程序中也不会发生任何日志操作。

注意

SQLAlchemy Engine 通过仅在检测到当前日志级别为logging.INFOlogging.DEBUG时发出日志语句来节省 Python 函数调用开销。仅在从连接池获取新连接时才检查此级别。因此,在更改已运行应用程序的日志配置时,任何当前活动的 Connection(更常见的是处于事务中的活动 Session 对象)都不会根据新配置记录任何 SQL,直到获取新的 Connection(在 Session 的情况下,这是在当前事务结束并开始新事务之后)。

更多关于 Echo 标志

如前所述,create_engine.echocreate_engine.echo_pool 参数是直接将日志记录到 sys.stdout 的快捷方式:

>>> from sqlalchemy import create_engine, text
>>> e = create_engine("sqlite://", echo=True, echo_pool="debug")
>>> with e.connect() as conn:
...     print(conn.scalar(text("select 'hi'")))
2020-10-24 12:54:57,701 DEBUG sqlalchemy.pool.impl.SingletonThreadPool Created new connection <sqlite3.Connection object at 0x7f287819ac60>
2020-10-24 12:54:57,701 DEBUG sqlalchemy.pool.impl.SingletonThreadPool Connection <sqlite3.Connection object at 0x7f287819ac60> checked out from pool
2020-10-24 12:54:57,702 INFO sqlalchemy.engine.Engine select 'hi'
2020-10-24 12:54:57,702 INFO sqlalchemy.engine.Engine ()
hi
2020-10-24 12:54:57,703 DEBUG sqlalchemy.pool.impl.SingletonThreadPool Connection <sqlite3.Connection object at 0x7f287819ac60> being returned to pool
2020-10-24 12:54:57,704 DEBUG sqlalchemy.pool.impl.SingletonThreadPool Connection <sqlite3.Connection object at 0x7f287819ac60> rollback-on-return

使用这些标志大致相当于:

import logging
logging.basicConfig()
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
logging.getLogger("sqlalchemy.pool").setLevel(logging.DEBUG)

需要注意的是,这两个标志独立于任何现有的日志配置工作,并且将无条件地使用 logging.basicConfig()。这将在除了任何现有记录器配置之外额外配置。因此,在显式配置日志时,请确保始终将所有回显标志设置为 False,以避免获取重复的日志行。

设置日志名称

诸如 EnginePool 的实例的记录器名称默认为使用截断的十六进制标识符字符串。要将其设置为特定名称,请使用 create_engine.logging_namecreate_engine.pool_logging_namesqlalchemy.create_engine();名称将附加到日志名称 sqlalchemy.engine.Engine

>>> import logging
>>> from sqlalchemy import create_engine
>>> from sqlalchemy import text
>>> logging.basicConfig()
>>> logging.getLogger("sqlalchemy.engine.Engine.myengine").setLevel(logging.INFO)
>>> e = create_engine("sqlite://", logging_name="myengine")
>>> with e.connect() as conn:
...     conn.execute(text("select 'hi'"))
2020-10-24 12:47:04,291 INFO sqlalchemy.engine.Engine.myengine select 'hi'
2020-10-24 12:47:04,292 INFO sqlalchemy.engine.Engine.myengine ()

提示

create_engine.logging_namecreate_engine.pool_logging_name 参数也可与 create_engine.echocreate_engine.echo_pool 结合使用。然而,如果其他引擎创建时将回显标志设置为 True 而没有日志名称,则将会发生无法避免的双重记录条件。这是因为将自动为 sqlalchemy.engine.Engine 添加处理程序,它将同时记录无名称的引擎和具有日志名称的引擎的消息。例如:

from sqlalchemy import create_engine, text
e1 = create_engine("sqlite://", echo=True, logging_name="myname")
with e1.begin() as conn:
    conn.execute(text("SELECT 1"))
e2 = create_engine("sqlite://", echo=True)
with e2.begin() as conn:
    conn.execute(text("SELECT 2"))
with e1.begin() as conn:
    conn.execute(text("SELECT 3"))

上述情况将会双重记录 SELECT 3。为解决此问题,请确保所有引擎都设置了 logging_name,或者使用显式的记录器/处理器设置,而不使用create_engine.echocreate_engine.echo_pool

设置每个连接/子引擎令牌

自 1.4.0b2 版本新增。

虽然在长期存在的Engine对象上建立日志名称是适当的,但它不够灵活,无法容纳任意长的名称列表,以跟踪日志消息中的单个连接和/或事务的情况。

对于此用例,由 ConnectionResult 对象生成的日志消息本身可以通过附加诸如事务或请求标识符之类的其他标记来增强。 Connection.execution_options.logging_token 参数接受一个字符串参数,该参数可用于建立每个连接的跟踪标记:

>>> from sqlalchemy import create_engine
>>> e = create_engine("sqlite://", echo="debug")
>>> with e.connect().execution_options(logging_token="track1") as conn:
...     conn.execute(text("select 1")).all()
2021-02-03 11:48:45,754 INFO sqlalchemy.engine.Engine [track1] select 1
2021-02-03 11:48:45,754 INFO sqlalchemy.engine.Engine [track1] [raw sql] ()
2021-02-03 11:48:45,754 DEBUG sqlalchemy.engine.Engine [track1] Col ('1',)
2021-02-03 11:48:45,755 DEBUG sqlalchemy.engine.Engine [track1] Row (1,)

Connection.execution_options.logging_token 参数也可以通过 create_engine.execution_optionsEngine.execution_options() 在引擎或子引擎上建立。这可能对将不同的日志标记应用于应用程序的不同组件而不创建新引擎非常有用:

>>> from sqlalchemy import create_engine
>>> e = create_engine("sqlite://", echo="debug")
>>> e1 = e.execution_options(logging_token="track1")
>>> e2 = e.execution_options(logging_token="track2")
>>> with e1.connect() as conn:
...     conn.execute(text("select 1")).all()
2021-02-03 11:51:08,960 INFO sqlalchemy.engine.Engine [track1] select 1
2021-02-03 11:51:08,960 INFO sqlalchemy.engine.Engine [track1] [raw sql] ()
2021-02-03 11:51:08,960 DEBUG sqlalchemy.engine.Engine [track1] Col ('1',)
2021-02-03 11:51:08,961 DEBUG sqlalchemy.engine.Engine [track1] Row (1,)
>>> with e2.connect() as conn:
...     conn.execute(text("select 2")).all()
2021-02-03 11:52:05,518 INFO sqlalchemy.engine.Engine [track2] Select 1
2021-02-03 11:52:05,519 INFO sqlalchemy.engine.Engine [track2] [raw sql] ()
2021-02-03 11:52:05,520 DEBUG sqlalchemy.engine.Engine [track2] Col ('1',)
2021-02-03 11:52:05,520 DEBUG sqlalchemy.engine.Engine [track2] Row (1,)

隐藏参数

Engine 发出的日志还显示了特定语句中存在的 SQL 参数摘录。为了防止出于隐私目的记录这些参数,请启用 create_engine.hide_parameters 标志:

>>> e = create_engine("sqlite://", echo=True, hide_parameters=True)
>>> with e.connect() as conn:
...     conn.execute(text("select :some_private_name"), {"some_private_name": "pii"})
2020-10-24 12:48:32,808 INFO sqlalchemy.engine.Engine select ?
2020-10-24 12:48:32,808 INFO sqlalchemy.engine.Engine [SQL parameters hidden due to hide_parameters=True]

回声标记的更多信息

正如前面提到的,create_engine.echocreate_engine.echo_pool 参数是立即记录到 sys.stdout 的快捷方式:

>>> from sqlalchemy import create_engine, text
>>> e = create_engine("sqlite://", echo=True, echo_pool="debug")
>>> with e.connect() as conn:
...     print(conn.scalar(text("select 'hi'")))
2020-10-24 12:54:57,701 DEBUG sqlalchemy.pool.impl.SingletonThreadPool Created new connection <sqlite3.Connection object at 0x7f287819ac60>
2020-10-24 12:54:57,701 DEBUG sqlalchemy.pool.impl.SingletonThreadPool Connection <sqlite3.Connection object at 0x7f287819ac60> checked out from pool
2020-10-24 12:54:57,702 INFO sqlalchemy.engine.Engine select 'hi'
2020-10-24 12:54:57,702 INFO sqlalchemy.engine.Engine ()
hi
2020-10-24 12:54:57,703 DEBUG sqlalchemy.pool.impl.SingletonThreadPool Connection <sqlite3.Connection object at 0x7f287819ac60> being returned to pool
2020-10-24 12:54:57,704 DEBUG sqlalchemy.pool.impl.SingletonThreadPool Connection <sqlite3.Connection object at 0x7f287819ac60> rollback-on-return

使用这些标志大致相当于:

import logging
logging.basicConfig()
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
logging.getLogger("sqlalchemy.pool").setLevel(logging.DEBUG)

需要注意的是,这两个标志独立于任何现有的日志配置工作,并且无条件使用 logging.basicConfig()。这会使其效果叠加到任何现有的记录器配置中。因此,在明确配置日志时,请始终确保所有回声标志都设置为 False,以避免获得重复的日志行。

设置日志名称

实例的记录器名称(如EnginePool)默认为使用截断的十六进制标识符字符串。要将其设置为特定名称,请使用create_engine.logging_namecreate_engine.pool_logging_namesqlalchemy.create_engine();该名称将附加到日志名称sqlalchemy.engine.Engine

>>> import logging
>>> from sqlalchemy import create_engine
>>> from sqlalchemy import text
>>> logging.basicConfig()
>>> logging.getLogger("sqlalchemy.engine.Engine.myengine").setLevel(logging.INFO)
>>> e = create_engine("sqlite://", logging_name="myengine")
>>> with e.connect() as conn:
...     conn.execute(text("select 'hi'"))
2020-10-24 12:47:04,291 INFO sqlalchemy.engine.Engine.myengine select 'hi'
2020-10-24 12:47:04,292 INFO sqlalchemy.engine.Engine.myengine ()

小贴士

create_engine.logging_namecreate_engine.pool_logging_name参数也可以与create_engine.echocreate_engine.echo_pool一起使用。但是,如果其他引擎设置了 echo 标志为 True 而没有设置日志名称,则将会发生不可避免的双重记录条件。这是因为将自动为sqlalchemy.engine.Engine添加处理程序,该处理程序将同时为无名称的引擎和具有日志名称的引擎记录消息。例如:

from sqlalchemy import create_engine, text
e1 = create_engine("sqlite://", echo=True, logging_name="myname")
with e1.begin() as conn:
    conn.execute(text("SELECT 1"))
e2 = create_engine("sqlite://", echo=True)
with e2.begin() as conn:
    conn.execute(text("SELECT 2"))
with e1.begin() as conn:
    conn.execute(text("SELECT 3"))

上述情景将对SELECT 3进行双重记录。为了解决此问题,请确保所有引擎都设置了logging_name,或者在不使用create_engine.echocreate_engine.echo_pool的情况下,使用显式的记录器/处理程序设置。

设置每个连接/子引擎令牌

1.4.0b2 版本中的新功能。

虽然在长期存在的Engine对象上建立记录名称是合适的,但是对于跟踪日志消息中的单个连接和/或事务的情况,它的灵活性不够。

对于这种用例,由ConnectionResult对象生成的日志消息本身可能会使用附加令牌(例如事务或请求标识符)进行扩充。Connection.execution_options.logging_token参数接受一个字符串参数,该参数可用于建立每个连接的跟踪令牌:

>>> from sqlalchemy import create_engine
>>> e = create_engine("sqlite://", echo="debug")
>>> with e.connect().execution_options(logging_token="track1") as conn:
...     conn.execute(text("select 1")).all()
2021-02-03 11:48:45,754 INFO sqlalchemy.engine.Engine [track1] select 1
2021-02-03 11:48:45,754 INFO sqlalchemy.engine.Engine [track1] [raw sql] ()
2021-02-03 11:48:45,754 DEBUG sqlalchemy.engine.Engine [track1] Col ('1',)
2021-02-03 11:48:45,755 DEBUG sqlalchemy.engine.Engine [track1] Row (1,)

Connection.execution_options.logging_token 参数也可以通过 create_engine.execution_optionsEngine.execution_options() 在引擎或子引擎上建立。这可能对于在不创建新引擎的情况下将不同的日志令牌应用于应用程序的不同组件很有用:

>>> from sqlalchemy import create_engine
>>> e = create_engine("sqlite://", echo="debug")
>>> e1 = e.execution_options(logging_token="track1")
>>> e2 = e.execution_options(logging_token="track2")
>>> with e1.connect() as conn:
...     conn.execute(text("select 1")).all()
2021-02-03 11:51:08,960 INFO sqlalchemy.engine.Engine [track1] select 1
2021-02-03 11:51:08,960 INFO sqlalchemy.engine.Engine [track1] [raw sql] ()
2021-02-03 11:51:08,960 DEBUG sqlalchemy.engine.Engine [track1] Col ('1',)
2021-02-03 11:51:08,961 DEBUG sqlalchemy.engine.Engine [track1] Row (1,)
>>> with e2.connect() as conn:
...     conn.execute(text("select 2")).all()
2021-02-03 11:52:05,518 INFO sqlalchemy.engine.Engine [track2] Select 1
2021-02-03 11:52:05,519 INFO sqlalchemy.engine.Engine [track2] [raw sql] ()
2021-02-03 11:52:05,520 DEBUG sqlalchemy.engine.Engine [track2] Col ('1',)
2021-02-03 11:52:05,520 DEBUG sqlalchemy.engine.Engine [track2] Row (1,)

隐藏参数

Engine 发出的日志还指示了对于特定语句存在的 SQL 参数的摘录。为了防止出于隐私目的记录这些参数,请启用 create_engine.hide_parameters 标志:

>>> e = create_engine("sqlite://", echo=True, hide_parameters=True)
>>> with e.connect() as conn:
...     conn.execute(text("select :some_private_name"), {"some_private_name": "pii"})
2020-10-24 12:48:32,808 INFO sqlalchemy.engine.Engine select ?
2020-10-24 12:48:32,808 INFO sqlalchemy.engine.Engine [SQL parameters hidden due to hide_parameters=True]

件使用 logging.basicConfig()。这会使其效果叠加到任何现有的记录器配置中。因此,在明确配置日志时,请始终确保所有回声标志都设置为 False,以避免获得重复的日志行。

设置日志名称

实例的记录器名称(如EnginePool)默认为使用截断的十六进制标识符字符串。要将其设置为特定名称,请使用create_engine.logging_namecreate_engine.pool_logging_namesqlalchemy.create_engine();该名称将附加到日志名称sqlalchemy.engine.Engine

>>> import logging
>>> from sqlalchemy import create_engine
>>> from sqlalchemy import text
>>> logging.basicConfig()
>>> logging.getLogger("sqlalchemy.engine.Engine.myengine").setLevel(logging.INFO)
>>> e = create_engine("sqlite://", logging_name="myengine")
>>> with e.connect() as conn:
...     conn.execute(text("select 'hi'"))
2020-10-24 12:47:04,291 INFO sqlalchemy.engine.Engine.myengine select 'hi'
2020-10-24 12:47:04,292 INFO sqlalchemy.engine.Engine.myengine ()

小贴士

create_engine.logging_namecreate_engine.pool_logging_name参数也可以与create_engine.echocreate_engine.echo_pool一起使用。但是,如果其他引擎设置了 echo 标志为 True 而没有设置日志名称,则将会发生不可避免的双重记录条件。这是因为将自动为sqlalchemy.engine.Engine添加处理程序,该处理程序将同时为无名称的引擎和具有日志名称的引擎记录消息。例如:

from sqlalchemy import create_engine, text
e1 = create_engine("sqlite://", echo=True, logging_name="myname")
with e1.begin() as conn:
    conn.execute(text("SELECT 1"))
e2 = create_engine("sqlite://", echo=True)
with e2.begin() as conn:
    conn.execute(text("SELECT 2"))
with e1.begin() as conn:
    conn.execute(text("SELECT 3"))

上述情景将对SELECT 3进行双重记录。为了解决此问题,请确保所有引擎都设置了logging_name,或者在不使用create_engine.echocreate_engine.echo_pool的情况下,使用显式的记录器/处理程序设置。

设置每个连接/子引擎令牌

1.4.0b2 版本中的新功能。

虽然在长期存在的Engine对象上建立记录名称是合适的,但是对于跟踪日志消息中的单个连接和/或事务的情况,它的灵活性不够。

对于这种用例,由ConnectionResult对象生成的日志消息本身可能会使用附加令牌(例如事务或请求标识符)进行扩充。Connection.execution_options.logging_token参数接受一个字符串参数,该参数可用于建立每个连接的跟踪令牌:

>>> from sqlalchemy import create_engine
>>> e = create_engine("sqlite://", echo="debug")
>>> with e.connect().execution_options(logging_token="track1") as conn:
...     conn.execute(text("select 1")).all()
2021-02-03 11:48:45,754 INFO sqlalchemy.engine.Engine [track1] select 1
2021-02-03 11:48:45,754 INFO sqlalchemy.engine.Engine [track1] [raw sql] ()
2021-02-03 11:48:45,754 DEBUG sqlalchemy.engine.Engine [track1] Col ('1',)
2021-02-03 11:48:45,755 DEBUG sqlalchemy.engine.Engine [track1] Row (1,)

Connection.execution_options.logging_token 参数也可以通过 create_engine.execution_optionsEngine.execution_options() 在引擎或子引擎上建立。这可能对于在不创建新引擎的情况下将不同的日志令牌应用于应用程序的不同组件很有用:

>>> from sqlalchemy import create_engine
>>> e = create_engine("sqlite://", echo="debug")
>>> e1 = e.execution_options(logging_token="track1")
>>> e2 = e.execution_options(logging_token="track2")
>>> with e1.connect() as conn:
...     conn.execute(text("select 1")).all()
2021-02-03 11:51:08,960 INFO sqlalchemy.engine.Engine [track1] select 1
2021-02-03 11:51:08,960 INFO sqlalchemy.engine.Engine [track1] [raw sql] ()
2021-02-03 11:51:08,960 DEBUG sqlalchemy.engine.Engine [track1] Col ('1',)
2021-02-03 11:51:08,961 DEBUG sqlalchemy.engine.Engine [track1] Row (1,)
>>> with e2.connect() as conn:
...     conn.execute(text("select 2")).all()
2021-02-03 11:52:05,518 INFO sqlalchemy.engine.Engine [track2] Select 1
2021-02-03 11:52:05,519 INFO sqlalchemy.engine.Engine [track2] [raw sql] ()
2021-02-03 11:52:05,520 DEBUG sqlalchemy.engine.Engine [track2] Col ('1',)
2021-02-03 11:52:05,520 DEBUG sqlalchemy.engine.Engine [track2] Row (1,)

隐藏参数

Engine 发出的日志还指示了对于特定语句存在的 SQL 参数的摘录。为了防止出于隐私目的记录这些参数,请启用 create_engine.hide_parameters 标志:

>>> e = create_engine("sqlite://", echo=True, hide_parameters=True)
>>> with e.connect() as conn:
...     conn.execute(text("select :some_private_name"), {"some_private_name": "pii"})
2020-10-24 12:48:32,808 INFO sqlalchemy.engine.Engine select ?
2020-10-24 12:48:32,808 INFO sqlalchemy.engine.Engine [SQL parameters hidden due to hide_parameters=True]
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
3月前
|
SQL 测试技术 数据库
SqlAlchemy 2.0 中文文档(五十二)(1)
SqlAlchemy 2.0 中文文档(五十二)
22 0
|
3月前
|
SQL NoSQL 数据库
SqlAlchemy 2.0 中文文档(五十二)(2)
SqlAlchemy 2.0 中文文档(五十二)
30 0
|
3月前
|
SQL 数据库连接 Linux
SqlAlchemy 2.0 中文文档(五十二)(7)
SqlAlchemy 2.0 中文文档(五十二)
42 0
|
3月前
|
SQL 数据库连接 Linux
SqlAlchemy 2.0 中文文档(五十二)(4)
SqlAlchemy 2.0 中文文档(五十二)
40 0
|
3月前
|
SQL JSON 数据库
SqlAlchemy 2.0 中文文档(五十二)(6)
SqlAlchemy 2.0 中文文档(五十二)
17 0
|
3月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(五十二)(3)
SqlAlchemy 2.0 中文文档(五十二)
26 0
|
3月前
|
SQL 缓存 JSON
SqlAlchemy 2.0 中文文档(四十三)(1)
SqlAlchemy 2.0 中文文档(四十三)
16 0
|
3月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(四十三)(4)
SqlAlchemy 2.0 中文文档(四十三)
29 0
|
3月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(四十三)(2)
SqlAlchemy 2.0 中文文档(四十三)
44 0
|
3月前
|
SQL 缓存 数据库
SqlAlchemy 2.0 中文文档(四十三)(5)
SqlAlchemy 2.0 中文文档(四十三)
27 0