SqlAlchemy 2.0 中文文档(三十)(4)

简介: SqlAlchemy 2.0 中文文档(三十)

SqlAlchemy 2.0 中文文档(三十)(3)https://developer.aliyun.com/article/1562437


使用具有显式声明的 Automap

正如之前所指出的,automap 不依赖于反射,并且可以利用 Table 对象集合中的任何对象在 MetaData 集合中。由此可见,automap 也可以用于生成缺失的关系,只要有一个完全定义了表元数据的完整模型:

from sqlalchemy.ext.automap import automap_base
from sqlalchemy import Column, Integer, String, ForeignKey
Base = automap_base()
class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary_key=True)
    name = Column(String)
class Address(Base):
    __tablename__ = "address"
    id = Column(Integer, primary_key=True)
    email = Column(String)
    user_id = Column(ForeignKey("user.id"))
# produce relationships
Base.prepare()
# mapping is complete, with "address_collection" and
# "user" relationships
a1 = Address(email="u1")
a2 = Address(email="u2")
u1 = User(address_collection=[a1, a2])
assert a1.user is u1

在上面的例子中,给定了大部分完整的 UserAddress 映射,我们在 Address.user_id 上定义的 ForeignKey 允许在映射类上生成一个双向关系对 Address.userUser.address_collection

请注意,当子类化AutomapBase时,需要调用AutomapBase.prepare()方法;如果未调用,则我们声明的类处于未映射状态。

拦截列定义

MetaDataTable对象支持一个事件钩子DDLEvents.column_reflect(),可用于在构建Column对象之前拦截有关数据库列的反射信息。例如,如果我们想要使用命名约定来映射列,例如"attr_",则可以应用该事件如下:

@event.listens_for(Base.metadata, "column_reflect")
def column_reflect(inspector, table, column_info):
    # set column.key = "attr_<lower_case_name>"
    column_info["key"] = "attr_%s" % column_info["name"].lower()
# run reflection
Base.prepare(autoload_with=engine)

版本 1.4.0b2 中的新内容:DDLEvents.column_reflect()事件可以应用于一个MetaData对象。

另请参阅

DDLEvents.column_reflect()

从反射表自动命名方案 - 在 ORM 映射文档中

API 参考

对象名称 描述
automap_base([declarative_base], **kw) 生成一个声明式自动映射基类。
AutomapBase 用于“自动映射”模式的基类。
classname_for_table(base, tablename, table) 给定表名,返回应该使用的类名。
generate_relationship(base, direction, return_fn, attrname, …, **kw) 代表两个映射类生成一个relationship()或者backref()
name_for_collection_relationship(base, local_cls, referred_cls, constraint) 返回用于从一个类引用另一个类的属性名称,用于集合引用。
name_for_scalar_relationship(base, local_cls, referred_cls, constraint) 返回用于从一个类引用另一个类的属性名称,用于标量对象引用。
function sqlalchemy.ext.automap.automap_base(declarative_base: Type[Any] | None = None, **kw: Any) → Any

生成一个声明式自动映射基类。

此函数生成一个新的基类,该基类是由 AutomapBase 类和由 declarative_base() 产生的声明性基类的产品。

除了 declarative_base 外的所有参数都是直接传递给 declarative_base() 函数的关键字参数。

参数:

  • declarative_base – 由 declarative_base() 产生的现有类。当传递此参数时,函数不再调用 declarative_base() 自身,并且所有其他关键字参数都将被忽略。
  • **kw – 关键字参数会传递给 declarative_base()
class sqlalchemy.ext.automap.AutomapBase

用于“automap”模式的基类。

AutomapBase 类可以与由 declarative_base() 函数产生的“声明性基类”类进行比较。在实践中,AutomapBase 类始终与实际的声明性基类一起使用作为混入。

一个新的可子类化的 AutomapBase 通常是使用 automap_base() 函数实例化的。

成员

by_module, classes, metadata, prepare()

另请参阅

Automap

attribute by_module: ClassVar[ByModuleProperties]

包含点分隔的模块名称的层次结构,链接到类的 Properties 实例。

这个集合是 AutomapBase.classes 集合的一种替代方法,当利用 AutomapBase.prepare.modulename_for_table 参数时,该参数将为生成的类应用不同的 __module__ 属性。

automap 生成类的默认 __module__sqlalchemy.ext.automap;要使用 AutomapBase.by_module 访问此命名空间,看起来像这样:

User = Base.by_module.sqlalchemy.ext.automap.User

如果一个类的 __module__mymodule.account,访问此命名空间看起来像这样:

MyClass = Base.by_module.mymodule.account.MyClass

2.0 版中的新功能。

另请参阅

从多个模式生成映射

attribute classes: ClassVar[Properties[Type[Any]]]

包含类的 Properties 实例。

此对象的行为类似于表上的 .c 集合。类以其给定的名称存在,例如:

Base = automap_base()
Base.prepare(autoload_with=some_engine)
User, Address = Base.classes.User, Base.classes.Address

对于与 Properties 方法名重叠的类名,例如 items(),也支持使用 getitem 形式:

Item = Base.classes["items"]
attribute metadata: ClassVar[MetaData]

指的是将用于新 Table 对象的 MetaData 集合。

另请参见

访问表和元数据

classmethod prepare(autoload_with: Engine | None = None, engine: Any | None = None, reflect: bool = False, schema: str | None = None, classname_for_table: PythonNameForTableType | None = None, modulename_for_table: PythonNameForTableType | None = None, collection_class: Any | None = None, name_for_scalar_relationship: NameForScalarRelationshipType | None = None, name_for_collection_relationship: NameForCollectionRelationshipType | None = None, generate_relationship: GenerateRelationshipType | None = None, reflection_options: Dict[_KT, _VT] | immutabledict[_KT, _VT] = {}) → None

MetaData 中提取映射类和关系,并执行映射。

有关完整文档和示例,请参见 基本使用。

参数:

  • autoload_with – 使用与其执行模式反射的 EngineConnection;当指定时,MetaData.reflect() 方法将在此方法的范围内调用。
  • engine
    已弃用;使用 AutomapBase.autoload_with。用于指示在反映表时使用的 EngineConnection,如果 AutomapBase.reflect 为 True。
    自版本 1.4 起已弃用:AutomapBase.prepare.engine 参数已弃用,并将在未来版本中删除。请使用 AutomapBase.prepare.autoload_with 参数。
  • reflect
    已弃用;使用 AutomapBase.autoload_with。指示是否应调用 MetaData.reflect()
    自版本 1.4 起已弃用:AutomapBase.prepare.reflect 参数已弃用,并将在未来版本中删除。当传递了 AutomapBase.prepare.autoload_with 时启用反射。
  • classname_for_table – 一个可调用的函数,将根据表名生成新类名。默认为 classname_for_table()
  • modulename_for_table
    可调用函数,用于为内部生成的类生成有效的__module__,以允许在单个自动映射基类中具有相同名称的多个类,这些类将位于不同的“模块”中。
    默认为None,表示__module__不会被显式设置;Python 运行时将为这些类使用值sqlalchemy.ext.automap
    在为生成的类分配__module__时,可以基于点分隔的模块名称使用AutomapBase.by_module集合访问它们。使用此钩子分配了显式__module_的类不会放入AutomapBase.classes集合中,而只会放入AutomapBase.by_module中。
    2.0 版中的新功能。
    另请参见
    从多个模式生成映射
  • name_for_scalar_relationship – 可调用函数,用于为标量关系生成关系名称。默认为name_for_scalar_relationship()
  • name_for_collection_relationship – 可调用函数,用于为面向集合的关系生成关系名称。默认为name_for_collection_relationship()
  • generate_relationship – 可调用函数,用于实际生成relationship()backref()构造。默认为generate_relationship()
  • collection_class – 当创建代表集合的新relationship()对象时将使用的 Python 集合类。默认为list
  • schema
    反映表时要反映的模式名称,使用AutomapBase.prepare.autoload_with参数。该名称传递给MetaData.reflect()MetaData.reflect.schema参数。当省略时,将使用数据库连接使用的默认模式。
    注意
    AutomapBase.prepare.schema 参数支持一次反射单个模式。要包含来自多个模式的表,请多次调用 AutomapBase.prepare()
    有关多模式自动映射的概述,包括使用附加命名约定解决表名冲突的方法,请参阅从多个模式生成映射 部分。
    新版本 2.0 中:AutomapBase.prepare() 可以直接调用任意次数,并跟踪已处理的表,以避免再次处理它们。
  • reflection_options
    当存在时,此选项字典将传递给 MetaData.reflect() 以提供通用的反射特定选项,如 only 和/或特定于方言的选项,如 oracle_resolve_synonyms
    新版本 1.4 中。
function sqlalchemy.ext.automap.classname_for_table(base: Type[Any], tablename: str, table: Table) → str

返回应使用的类名,给定表的名称。

默认实现为:

return str(tablename)

可以使用 AutomapBase.prepare.classname_for_table 参数指定替代实现。

参数:

  • base – 进行准备的 AutomapBase 类。
  • tablenameTable 的字符串名称。
  • tableTable 对象本身。

返回:

一个字符串类名。

注意

在 Python 2 中,用于类名的字符串 必须 是非 Unicode 对象,例如 str() 对象。Table.name 属性通常是 Python unicode 子类,因此应在考虑任何非 ASCII 字符后,应用 str() 函数到此名称。

function sqlalchemy.ext.automap.name_for_scalar_relationship(base: Type[Any], local_cls: Type[Any], referred_cls: Type[Any], constraint: ForeignKeyConstraint) → str

返回应用于从一个类到另一个类的引用的属性名称,用于标量对象引用。

默认实现为:

return referred_cls.__name__.lower()

可以使用 AutomapBase.prepare.name_for_scalar_relationship 参数指定替代实现。

参数:

  • base – 进行准备的 AutomapBase 类。
  • local_cls – 映射到本地方的类。
  • referred_cls – 映射到引用方的类。
  • constraint – 正在检查以生成此关系的ForeignKeyConstraint
function sqlalchemy.ext.automap.name_for_collection_relationship(base: Type[Any], local_cls: Type[Any], referred_cls: Type[Any], constraint: ForeignKeyConstraint) → str

返回应用于从一个类到另一个类的引用的属性名称,用于集合引用。

默认实现如下:

return referred_cls.__name__.lower() + "_collection"

可以使用AutomapBase.prepare.name_for_collection_relationship参数指定替代实现。

参数:

  • base – 执行准备工作的AutomapBase类。
  • local_cls – 要映射到本地方的类。
  • referred_cls – 要映射到引用方的类。
  • constraint – 正在检查以生成此关系的ForeignKeyConstraint
function sqlalchemy.ext.automap.generate_relationship(base: Type[Any], direction: RelationshipDirection, return_fn: Callable[..., Relationship[Any]] | Callable[..., ORMBackrefArgument], attrname: str, local_cls: Type[Any], referred_cls: Type[Any], **kw: Any) → Relationship[Any] | ORMBackrefArgument

代表两个映射类生成一个relationship()backref()

可以使用AutomapBase.prepare.generate_relationship参数指定此函数的替代实现。

此函数的默认实现如下:

if return_fn is backref:
    return return_fn(attrname, **kw)
elif return_fn is relationship:
    return return_fn(referred_cls, **kw)
else:
    raise TypeError("Unknown relationship function: %s" % return_fn)

参数:

  • base – 执行准备工作的AutomapBase类。
  • direction – 指示关系的“方向”; 这将是ONETOMANYMANYTOONEMANYTOMANY之一。
  • return_fn – 默认用于创建关系的函数。这将是relationship()backref()之一。backref()函数的结果将用于在第二步生成新的relationship(),因此如果使用自定义关系函数,则用户定义的实现必须正确区分这两个函数。
  • attrname – 正在分配此关系的属性名称。如果generate_relationship.return_fn的值是backref()函数,则此名称是分配给反向引用的名称。
  • local_cls – 此关系或反向引用将在本地存在的“本地”类。
  • referred_cls – 此关系或反向引用所指向的“引用”类。
  • **kw – 所有附加的关键字参数都将传递给该函数。

返回值:

relationship()backref() 构造,由 generate_relationship.return_fn 参数所指定。

烘焙查询

原文:docs.sqlalchemy.org/en/20/orm/extensions/baked.html

bakedQuery对象提供了一种替代的创建模式,允许缓存对象的构建和字符串编译步骤。这意味着对于一个特定的Query构建场景,如果该场景被多次使用,那么从初始构建查询到生成 SQL 字符串所涉及的所有 Python 函数调用将只会发生一次,而不是每次构建和执行查询时都会发生。

这个系统的理念是极大地减少 Python 解释器在发出 SQL 之前发生的一切的开销。 “baked”系统的缓存不会以任何方式减少 SQL 调用或缓存来自数据库的返回结果。一个展示 SQL 调用和结果集本身缓存的技术在 Dogpile Caching 中可用。

从版本 1.4 开始弃用:SQLAlchemy 1.4 和 2.0 具有全新的直接查询缓存系统,不再需要BakedQuery系统。现在,对于所有 Core 和 ORM 查询,缓存现在是透明激活的,用户不需要采取任何操作,使用在 SQL Compilation Caching 中描述的系统。

深度炼金术

sqlalchemy.ext.baked扩展不适合初学者。正确使用它需要对 SQLAlchemy、数据库驱动程序以及后端数据库之间的交互有很好的高级理解。这个扩展提供了一种非常特定的优化,通常是不需要的。如上所述,它不会缓存查询,只会缓存 SQL 本身的字符串形式。

概要

使用 baked 系统的开始是生成所谓的“面包店”,它代表了一系列特定查询对象的存储:

from sqlalchemy.ext import baked
bakery = baked.bakery()

上述的“面包店”将缓存数据存储在一个默认为 200 个元素的 LRU 缓存中,需要注意的是 ORM 查询通常会包含一个用于调用 ORM 查询的条目,以及每个数据库方言的 SQL 字符串的一个条目。

该面包店允许我们通过指定其构造方式为一系列 Python 可调用对象(通常为 lambda 函数)来构建一个Query对象。为了简洁使用,它重写了+=运算符,使得典型的查询构建看起来像下面这样:

from sqlalchemy import bindparam
def search_for_user(session, username, email=None):
    baked_query = bakery(lambda session: session.query(User))
    baked_query += lambda q: q.filter(User.name == bindparam("username"))
    baked_query += lambda q: q.order_by(User.id)
    if email:
        baked_query += lambda q: q.filter(User.email == bindparam("email"))
    result = baked_query(session).params(username=username, email=email).all()
    return result

以下是关于上述代码的一些观察:

  1. baked_query 对象是 BakedQuery 的一个实例。该对象本质上是一个真正的 orm Query 对象的“构建者”,但它本身并不是实际的 Query 对象。
  2. 实际的 Query 对象根本没有构建,直到在函数的最后调用 Result.all() 时。
  3. 添加到 baked_query 对象的步骤都表示为 Python 函数,通常是 lambda。传递给 bakery() 函数的第一个 lambda 接收一个 Session 作为其参数。其余的 lambda 每个接收一个 Query 作为其参数。
  4. 在上述代码中,即使我们的应用程序可能多次调用 search_for_user(),即使在每次调用中我们都建立一个全新的 BakedQuery 对象,所有的 lambda 只调用一次。只要此查询在烘培中被缓存,每个 lambda 在此期间都不会被第二次调用。
  5. 缓存是通过存储lambda 对象本身的引用来实现的,以便构建缓存键;也就是说,Python 解释器将这些函数分配为 Python 标识,这决定了如何在后续运行中识别查询。对于那些指定了 email 参数的 search_for_user() 调用,可调用的 lambda q: q.filter(User.email == bindparam('email')) 将成为被检索到的缓存键的一部分;当 emailNone 时,这个可调用函数不会成为缓存键的一部分。
  6. 由于 lambda 都只调用一次,因此在 lambda 内部不得引用可能跨调用改变的变量;相反,假设这些是要绑定到 SQL 字符串中的值,我们使用 bindparam() 构建命名参数,稍后使用 Result.params() 应用它们的实际值。

性能

烘焙查询可能看起来有些奇怪、有些笨拙、有些冗长。然而,对于在应用程序中多次调用的查询,Python 性能的节约非常显著。在 性能 中演示的示例套件 short_selects 说明了查询的比较,每个查询仅返回一行,如下所示的常规查询:

session = Session(bind=engine)
for id_ in random.sample(ids, n):
    session.query(Customer).filter(Customer.id == id_).one()

相比于等效的“烘焙”查询:

bakery = baked.bakery()
s = Session(bind=engine)
for id_ in random.sample(ids, n):
    q = bakery(lambda s: s.query(Customer))
    q += lambda q: q.filter(Customer.id == bindparam("id"))
    q(s).params(id=id_).one()

对于每个块的 10000 次调用的 Python 函数调用计数的差异为:

test_baked_query : test a baked query of the full entity.
                   (10000 iterations); total fn calls 1951294
test_orm_query :   test a straight ORM query of the full entity.
                   (10000 iterations); total fn calls 7900535

以强大的笔记本电脑上的秒数来看,这是这样的:

test_baked_query : test a baked query of the full entity.
                   (10000 iterations); total time 2.174126 sec
test_orm_query :   test a straight ORM query of the full entity.
                   (10000 iterations); total time 7.958516 sec

请注意,此测试非常有意地包含了仅返回一行的查询。对于返回许多行的查询,烘焙查询的性能优势将越来越小,与获取行所花费的时间成比例。必须牢记的是,烘焙查询功能仅适用于构建查询本身,而不适用于获取结果。使用烘焙特性绝不是对更快应用程序的担保;它只是一种潜在有用的功能,适用于那些已经被证明受到这种特定形式的开销影响的应用程序。

理由

上述“lambda”方法是更传统的“参数化”方法的一个超集。假设我们希望构建一个简单的系统,在该系统中我们仅构建一次Query,然后将其存储在字典中以供重复使用。现在就可以通过简单地构建查询并通过调用my_cached_query = query.with_session(None)来移除其Session来实现这一点:

my_simple_cache = {}
def lookup(session, id_argument):
    if "my_key" not in my_simple_cache:
        query = session.query(Model).filter(Model.id == bindparam("id"))
        my_simple_cache["my_key"] = query.with_session(None)
    else:
        query = my_simple_cache["my_key"].with_session(session)
    return query.params(id=id_argument).all()

上述方法为我们带来了非常小的性能优势。通过重用Query,我们节省了session.query(Model)构造函数内部的 Python 工作以及调用filter(Model.id == bindparam('id')),这将跳过为我们构建核心表达式以及将其发送到Query.filter()的过程。然而,该方法仍然每次调用Query.all()时重新生成完整的Select对象,并且每次都将此全新的Select发送到字符串编译步骤,对于像上面这样的简单情况,这可能约占开销的 70%。

为了减少额外的开销,我们需要一些更专门的逻辑,一些记忆构造选择对象和 SQL 构造的方法。在维基百科的 BakedQuery 部分有一个示例,这是这个特性的前身,但在那个系统中,我们没有缓存查询的构造。为了去除所有开销,我们需要缓存查询的构造以及 SQL 编译。假设我们按照这种方式调整了配方,并制作了一个 .bake() 方法,用于预编译查询的 SQL,生成一个可以以最小开销调用的新对象。我们的例子变成了:

my_simple_cache = {}
def lookup(session, id_argument):
    if "my_key" not in my_simple_cache:
        query = session.query(Model).filter(Model.id == bindparam("id"))
        my_simple_cache["my_key"] = query.with_session(None).bake()
    else:
        query = my_simple_cache["my_key"].with_session(session)
    return query.params(id=id_argument).all()

在上述例子中,我们已经解决了性能问题,但我们仍然需要处理这个字符串缓存键。

我们可以使用“面包店”方法来重新构建上面的内容,使其看起来不像“逐步建立 lambda”方法那样不寻常,而更像是对简单“重用查询”方法的简单改进:

bakery = baked.bakery()
def lookup(session, id_argument):
    def create_model_query(session):
        return session.query(Model).filter(Model.id == bindparam("id"))
    parameterized_query = bakery.bake(create_model_query)
    return parameterized_query(session).params(id=id_argument).all()

在上述示例中,我们使用“烘焙”系统的方式与简单的“缓存查询”系统非常相似。但是,它使用了两行代码少,不需要制造一个“my_key”的缓存键,还包括与我们自定义的“烘焙”函数相同的功能,该函数从查询的构造函数到过滤器调用再到Select对象的生成,再到字符串编译步骤,都缓存了 100% 的 Python 调用工作。

从上面的内容,如果我们问自己,“如果查找需要根据查询结构做条件决策怎么办?”,这就是为什么“烘焙”是这样的方式的地方。我们可以从任意数量的函数构建参数化查询,而不是从一个函数(这是我们最初认为烘焙可能的工作方式)开始。考虑我们的简单例子,如果我们需要在条件基础上在查询中添加一个附加子句:

my_simple_cache = {}
def lookup(session, id_argument, include_frobnizzle=False):
    if include_frobnizzle:
        cache_key = "my_key_with_frobnizzle"
    else:
        cache_key = "my_key_without_frobnizzle"
    if cache_key not in my_simple_cache:
        query = session.query(Model).filter(Model.id == bindparam("id"))
        if include_frobnizzle:
            query = query.filter(Model.frobnizzle == True)
        my_simple_cache[cache_key] = query.with_session(None).bake()
    else:
        query = my_simple_cache[cache_key].with_session(session)
    return query.params(id=id_argument).all()

我们的“简单”参数化系统现在必须负责生成考虑到“include_frobnizzle”标志是否已传递的缓存键,因为此标志的存在意味着生成的  SQL 将完全不同。很明显,随着查询构建复杂性的提高,缓存这些查询的任务会非常快地变得繁重。我们可以将上述示例转换为以下对“面包店”直接使用:

bakery = baked.bakery()
def lookup(session, id_argument, include_frobnizzle=False):
    def create_model_query(session):
        return session.query(Model).filter(Model.id == bindparam("id"))
    parameterized_query = bakery.bake(create_model_query)
    if include_frobnizzle:
        def include_frobnizzle_in_query(query):
            return query.filter(Model.frobnizzle == True)
        parameterized_query = parameterized_query.with_criteria(
            include_frobnizzle_in_query
        )
    return parameterized_query(session).params(id=id_argument).all()

在上述示例中,我们再次缓存的不仅是查询对象,还有它需要执行的所有工作以生成 SQL。我们也不再需要处理确保生成准确考虑到我们所做的所有结构修改的缓存键;这现在是自动处理的,而且没有错误的机会。

此代码示例比朴素示例少了几行代码,消除了处理缓存键的需求,并且具有完整的所谓“已烘焙”功能的巨大性能优势。但仍然有点啰嗦!因此,我们将像BakedQuery.add_criteria()BakedQuery.with_criteria()这样的方法简化为操作符,并鼓励(尽管绝对不是必需的!)使用简单的 lambda 函数,仅作为减少冗长性的手段:

bakery = baked.bakery()
def lookup(session, id_argument, include_frobnizzle=False):
    parameterized_query = bakery.bake(
        lambda s: s.query(Model).filter(Model.id == bindparam("id"))
    )
    if include_frobnizzle:
        parameterized_query += lambda q: q.filter(Model.frobnizzle == True)
    return parameterized_query(session).params(id=id_argument).all()

在上述情况中,该方法更容易实现,并且在代码流程上更类似于非缓存查询函数的情况,因此使代码更易于移植。

上述描述本质上是对到达当前“已烘焙”方法的设计过程的总结。从“正常”方法开始,还需要解决缓存键的构建和管理、移除所有冗余的 Python 执行以及需要使用条件构建的查询等附加问题,从而导致了最终的方法。


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

相关文章
|
1月前
|
Python
SqlAlchemy 2.0 中文文档(三十)(3)
SqlAlchemy 2.0 中文文档(三十)
15 1
|
1月前
|
SQL 存储 缓存
SqlAlchemy 2.0 中文文档(三十)(5)
SqlAlchemy 2.0 中文文档(三十)
22 1
|
1月前
|
数据库连接 API 数据库
SqlAlchemy 2.0 中文文档(三十)(2)
SqlAlchemy 2.0 中文文档(三十)
29 0
|
1月前
|
数据库 Python
SqlAlchemy 2.0 中文文档(三十)(1)
SqlAlchemy 2.0 中文文档(三十)
16 1
|
1月前
|
SQL 关系型数据库 测试技术
SqlAlchemy 2.0 中文文档(四十)(3)
SqlAlchemy 2.0 中文文档(四十)
20 1
|
1月前
|
SQL 关系型数据库 测试技术
SqlAlchemy 2.0 中文文档(四十)(5)
SqlAlchemy 2.0 中文文档(四十)
36 2
|
1月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(四十)(2)
SqlAlchemy 2.0 中文文档(四十)
28 1
|
1月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(四十)(1)
SqlAlchemy 2.0 中文文档(四十)
32 1
|
1月前
|
SQL 算法 数据库
SqlAlchemy 2.0 中文文档(五十)(1)
SqlAlchemy 2.0 中文文档(五十)
16 0
|
1月前
|
JSON 数据库 数据格式
SqlAlchemy 2.0 中文文档(五十)(5)
SqlAlchemy 2.0 中文文档(五十)
16 0