SqlAlchemy 2.0 中文文档(十一)(4)https://developer.aliyun.com/article/1562990
关系参数的延迟评估
大多数前面部分的示例展示了映射,其中各种relationship()
构造使用字符串名称而不是类本身引用其目标类,例如在使用Mapped
时,会生成一个仅作为字符串存在的前向引用:
class Parent(Base): # ... children: Mapped[List["Child"]] = relationship(back_populates="parent") class Child(Base): # ... parent: Mapped["Parent"] = relationship(back_populates="children")
同样,在使用非注释形式,如非注释性的声明式或命令式映射时,relationship()
构造也直接支持字符串名称:
registry.map_imperatively( Parent, parent_table, properties={"children": relationship("Child", back_populates="parent")}, ) registry.map_imperatively( Child, child_table, properties={"parent": relationship("Parent", back_populates="children")}, )
这些字符串名称在映射器解析阶段被解析为类,这是一个内部过程,通常在定义所有映射之后发生,并且通常由映射本身的第一次使用触发。registry
对象是这些名称存储和解析为它们引用的映射类的容器。
除了relationship()
的主要类参数之外,还可以指定依赖于尚未定义类上存在的列的其他参数,这些参数可以是 Python 函数,或更常见的是字符串。对于这些参数中的大多数,除了主要参数之外,字符串输入都会使用 Python 内置的 eval()函数评估为 Python 表达式,因为它们旨在接收完整的 SQL 表达式。
警告
由于 Python 的eval()
函数用于解释传递给relationship()
映射配置构造的延迟评估的字符串参数,这些参数不应该被重新用于接收不受信任的用户输入;eval()
对不受信任的用户输入不安全。
在这个评估中可用的完整命名空间包括为这个声明基类映射的所有类,以及sqlalchemy
包的内容,包括表达式函数如desc()
和sqlalchemy.sql.functions.func
:
class Parent(Base): # ... children: Mapped[List["Child"]] = relationship( order_by="desc(Child.email_address)", primaryjoin="Parent.id == Child.parent_id", )
对于一个模块包含多个同名类的情况,字符串类名也可以在这些字符串表达式中作为模块限定路径指定:
class Parent(Base): # ... children: Mapped[List["myapp.mymodel.Child"]] = relationship( order_by="desc(myapp.mymodel.Child.email_address)", primaryjoin="myapp.mymodel.Parent.id == myapp.mymodel.Child.parent_id", )
在上述示例中,传递给Mapped
的字符串也可以通过直接将类位置字符串传递给relationship.argument
来消除特定类参数。下面说明了仅类型导入Child
的示例,结合了将运行时说明符与将在registry
中搜索正确名称的目标类相结合:
import typing if typing.TYPE_CHECKING: from myapp.mymodel import Child class Parent(Base): # ... children: Mapped[List["Child"]] = relationship( "myapp.mymodel.Child", order_by="desc(myapp.mymodel.Child.email_address)", primaryjoin="myapp.mymodel.Parent.id == myapp.mymodel.Child.parent_id", )
合格路径可以是任何消除名称之间歧义的部分路径。例如,要消除myapp.model1.Child
和myapp.model2.Child
之间的歧义,我们可以指定model1.Child
或model2.Child
:
class Parent(Base): # ... children: Mapped[List["Child"]] = relationship( "model1.Child", order_by="desc(mymodel1.Child.email_address)", primaryjoin="Parent.id == model1.Child.parent_id", )
relationship()
构造还接受 Python 函数或 lambda 作为这些参数的输入。Python 函数式方法可能如下所示:
import typing from sqlalchemy import desc if typing.TYPE_CHECKING: from myapplication import Child def _resolve_child_model(): from myapplication import Child return Child class Parent(Base): # ... children: Mapped[List["Child"]] = relationship( _resolve_child_model, order_by=lambda: desc(_resolve_child_model().email_address), primaryjoin=lambda: Parent.id == _resolve_child_model().parent_id, )
完整的参数列表接受 Python 函数/lambda 或将传递给eval()
的字符串的参数包括:
relationship.order_by
relationship.primaryjoin
relationship.secondaryjoin
relationship.secondary
relationship.remote_side
relationship.foreign_keys
relationship._user_defined_foreign_keys
警告
如前所述,relationship()
中的上述参数会作为 Python 代码表达式使用 eval()进行评估。不要将不受信任的输入传递给这些参数。
在声明后将关系添加到映射类
还应注意,与向现有的声明映射类添加附加列中描述的类似方式,任何MapperProperty
构造都可以随时添加到声明基础映射中(注意在此上下文中不支持注释形式)。如果我们想要在Address
类可用之后实现这个relationship()
,我们也可以随后应用它:
# first, module A, where Child has not been created yet, # we create a Parent class which knows nothing about Child class Parent(Base): ... # ... later, in Module B, which is imported after module A: class Child(Base): ... from module_a import Parent # assign the User.addresses relationship as a class variable. The # declarative base class will intercept this and map the relationship. Parent.children = relationship(Child, primaryjoin=Child.parent_id == Parent.id)
与 ORM 映射列一样,Mapped
注解类型无法参与此操作;因此,相关类必须直接在relationship()
构造中指定,可以是类本身、类的字符串名称或返回目标类引用的可调用函数。
注意
与 ORM 映射列一样,对已映射类的映射属性的赋值仅在使用“声明基类”类时才能正确执行,这意味着用户定义的DeclarativeBase
子类或declarative_base()
返回的动态生成类或registry.generate_base()
。这个“基”类包括一个 Python 元类,实现了一个特殊的__setattr__()
方法来拦截这些操作。
如果类使用像registry.mapped()
这样的装饰器或像registry.map_imperatively()
这样的命令式函数进行映射,则无法在运行时将类映射属性分配给映射类。 ### 使用多对多关系的“secondary”参数的延迟评估形式
多对多关系使用relationship.secondary
参数,通常表示对通常不映射的Table
对象或其他 Core 可选择对象的引用。通常使用 lambda 可调用进行延迟评估。
对于 Many To Many 中给出的示例,如果我们假设association_table
Table
对象将在模块中稍后定义,则我们可以使用 lambda 编写relationship()
:
class Parent(Base): __tablename__ = "left_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[List["Child"]] = relationship( "Child", secondary=lambda: association_table )
作为也是有效 Python 标识符的表名的快捷方式,relationship.secondary
参数也可以作为字符串传递,其中解析通过将字符串作为 Python 表达式进行评估来完成,简单标识符名称链接到当前registry
引用的相同命名的Table
对象。
在下面的示例中,表达式"association_table"
将作为名为"association_table"
的变量进行评估,该变量将根据MetaData
集合中的表名进行解析:
class Parent(Base): __tablename__ = "left_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[List["Child"]] = relationship(secondary="association_table")
注意
当作为字符串传递时,传递给relationship.secondary
的名称必须是有效的 Python 标识符,以字母开头,只包含字母数字字符或下划线。其他字符,如破折号等,将被解释为 Python 运算符,而不会解析为给定的名称。请考虑使用 lambda 表达式而不是字符串以提高清晰度。
警告
当作为字符串传递时,relationship.secondary
参数使用 Python 的eval()
函数进行解释,即使它通常是一个表的名称。不要将不受信任的输入传递给此字符串。###在声明后向映射类添加关系
还应注意,在类似于 Appending additional columns to an existing Declarative mapped class 描述的方式中,任何MapperProperty
构造都可以随时添加到声明基本映射中(注意,此上下文中不支持注释形式)。如果我们希望在Address
类可用后实现此relationship()
,我们也可以随后应用它:
# first, module A, where Child has not been created yet, # we create a Parent class which knows nothing about Child class Parent(Base): ... # ... later, in Module B, which is imported after module A: class Child(Base): ... from module_a import Parent # assign the User.addresses relationship as a class variable. The # declarative base class will intercept this and map the relationship. Parent.children = relationship(Child, primaryjoin=Child.parent_id == Parent.id)
与 ORM 映射列一样,Mapped
注解类型没有参与此操作的能力;因此,相关类必须直接在relationship()
构造中指定,可以是类本身、类的字符串名称,或者返回目标类引用的可调用函数。
注意
与 ORM 映射列一样,将映射属性分配给已经映射的类只有在使用“声明式基类”时才能正确运行,这意味着必须使用用户定义的DeclarativeBase
子类或者declarative_base()
返回的动态生成的类或者registry.generate_base()
返回的动态生成的类。这个“基类”包含一个实现了特殊__setattr__()
方法的 Python 元类,它拦截这些操作。
如果使用类似于registry.mapped()
这样的装饰器或像registry.map_imperatively()
这样的命令式函数来映射类,则无法在运行时将映射属性分配给映射类。
使用“secondary”参数的延迟评估形式来处理多对多关系
多对多关系使用relationship.secondary
参数,通常表示对通常非映射的Table
对象或其他核心可选择对象的引用。典型的延迟评估使用 lambda 可调用。
对于 Many To Many 中给出的例子,如果我们假设association_table
Table
对象将在模块中的某个后续点被定义,那么我们可以使用 lambda 来编写relationship()
,如下所示:
class Parent(Base): __tablename__ = "left_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[List["Child"]] = relationship( "Child", secondary=lambda: association_table )
作为表名的快捷方式,也可以将relationship.secondary
参数传递为字符串,其中解析工作通过将字符串作为 Python 表达式进行评估,简单标识符名称链接到与当前registry
引用的相同MetaData
集合中存在的同名Table
对象。
在下面的示例中,表达式"association_table"
被解析为一个名为"association_table"
的变量,该变量根据MetaData
集合中的表名解析:
class Parent(Base): __tablename__ = "left_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[List["Child"]] = relationship(secondary="association_table")
注意
当作为字符串传递时,传递给relationship.secondary
的名称必须是有效的 Python 标识符,以字母开头,仅包含字母数字字符或下划线。其他字符,如破折号等,将被解释为 Python 操作符,而不会解析为给定的名称。请考虑使用 lambda 表达式而不是字符串,以提高清晰度。
警告
当作为字符串传递时,relationship.secondary
参数将使用 Python 的eval()
函数进行解释,即使它通常是一个表的名称。不要将不受信任的输入传递给该字符串。
符串**。###在声明后向映射类添加关系
还应注意,在类似于 Appending additional columns to an existing Declarative mapped class 描述的方式中,任何MapperProperty
构造都可以随时添加到声明基本映射中(注意,此上下文中不支持注释形式)。如果我们希望在Address
类可用后实现此relationship()
,我们也可以随后应用它:
# first, module A, where Child has not been created yet, # we create a Parent class which knows nothing about Child class Parent(Base): ... # ... later, in Module B, which is imported after module A: class Child(Base): ... from module_a import Parent # assign the User.addresses relationship as a class variable. The # declarative base class will intercept this and map the relationship. Parent.children = relationship(Child, primaryjoin=Child.parent_id == Parent.id)
与 ORM 映射列一样,Mapped
注解类型没有参与此操作的能力;因此,相关类必须直接在relationship()
构造中指定,可以是类本身、类的字符串名称,或者返回目标类引用的可调用函数。
注意
与 ORM 映射列一样,将映射属性分配给已经映射的类只有在使用“声明式基类”时才能正确运行,这意味着必须使用用户定义的DeclarativeBase
子类或者declarative_base()
返回的动态生成的类或者registry.generate_base()
返回的动态生成的类。这个“基类”包含一个实现了特殊__setattr__()
方法的 Python 元类,它拦截这些操作。
如果使用类似于registry.mapped()
这样的装饰器或像registry.map_imperatively()
这样的命令式函数来映射类,则无法在运行时将映射属性分配给映射类。
使用“secondary”参数的延迟评估形式来处理多对多关系
多对多关系使用relationship.secondary
参数,通常表示对通常非映射的Table
对象或其他核心可选择对象的引用。典型的延迟评估使用 lambda 可调用。
对于 Many To Many 中给出的例子,如果我们假设association_table
Table
对象将在模块中的某个后续点被定义,那么我们可以使用 lambda 来编写relationship()
,如下所示:
class Parent(Base): __tablename__ = "left_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[List["Child"]] = relationship( "Child", secondary=lambda: association_table )
作为表名的快捷方式,也可以将relationship.secondary
参数传递为字符串,其中解析工作通过将字符串作为 Python 表达式进行评估,简单标识符名称链接到与当前registry
引用的相同MetaData
集合中存在的同名Table
对象。
在下面的示例中,表达式"association_table"
被解析为一个名为"association_table"
的变量,该变量根据MetaData
集合中的表名解析:
class Parent(Base): __tablename__ = "left_table" id: Mapped[int] = mapped_column(primary_key=True) children: Mapped[List["Child"]] = relationship(secondary="association_table")
注意
当作为字符串传递时,传递给relationship.secondary
的名称必须是有效的 Python 标识符,以字母开头,仅包含字母数字字符或下划线。其他字符,如破折号等,将被解释为 Python 操作符,而不会解析为给定的名称。请考虑使用 lambda 表达式而不是字符串,以提高清晰度。
警告
当作为字符串传递时,relationship.secondary
参数将使用 Python 的eval()
函数进行解释,即使它通常是一个表的名称。不要将不受信任的输入传递给该字符串。