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

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

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


明确指定类

提示

如果明确的类在应用程序中占主导地位,请考虑改用DeferredReflection

automap扩展允许以与DeferredReflection类相似的方式明确定义类。 从AutomapBase继承的类表现得像常规的声明性类一样,但在构造后不会立即映射,而是在调用AutomapBase.prepare()时映射。 AutomapBase.prepare()方法将利用我们基于所使用的表名建立的类。 如果我们的模式包含表useraddress,我们可以定义要使用的一个或两个类:

from sqlalchemy.ext.automap import automap_base
from sqlalchemy import create_engine
# automap base
Base = automap_base()
# pre-declare User for the 'user' table
class User(Base):
    __tablename__ = "user"
    # override schema elements like Columns
    user_name = Column("name", String)
    # override relationships too, if desired.
    # we must use the same name that automap would use for the
    # relationship, and also must refer to the class name that automap will
    # generate for "address"
    address_collection = relationship("address", collection_class=set)
# reflect
engine = create_engine("sqlite:///mydatabase.db")
Base.prepare(autoload_with=engine)
# we still have Address generated from the tablename "address",
# but User is the same as Base.classes.User now
Address = Base.classes.address
u1 = session.query(User).first()
print(u1.address_collection)
# the backref is still there:
a1 = session.query(Address).first()
print(a1.user)

上面,更复杂的细节之一是,我们说明了如何覆盖relationship()对象之一,该对象 automap 将会创建。 为此,我们需要确保名称与 automap 通常生成的名称相匹配,即关系名称将为User.address_collection,并且从 automap 的角度来看,所引用的类的名称称为address,即使我们在对此类的使用中将其称为Address

覆盖命名方案

automap 被要求根据模式生成映射类和关系名称,这意味着它在确定这些名称的方式上有决策点。这三个决策点通过可以传递给AutomapBase.prepare()方法的函数来提供,分别称为classname_for_table()name_for_scalar_relationship()name_for_collection_relationship()。以下示例中提供了任意或全部这些函数,我们使用了“驼峰命名法”作为类名,并使用了 Inflect 包来对集合名称进行“复数化”:

import re
import inflect
def camelize_classname(base, tablename, table):
    "Produce a 'camelized' class name, e.g."
    "'words_and_underscores' -> 'WordsAndUnderscores'"
    return str(
        tablename[0].upper()
        + re.sub(
            r"_([a-z])",
            lambda m: m.group(1).upper(),
            tablename[1:],
        )
    )
_pluralizer = inflect.engine()
def pluralize_collection(base, local_cls, referred_cls, constraint):
    "Produce an 'uncamelized', 'pluralized' class name, e.g."
    "'SomeTerm' -> 'some_terms'"
    referred_name = referred_cls.__name__
    uncamelized = re.sub(
        r"[A-Z]",
        lambda m: "_%s" % m.group(0).lower(),
        referred_name,
    )[1:]
    pluralized = _pluralizer.plural(uncamelized)
    return pluralized
from sqlalchemy.ext.automap import automap_base
Base = automap_base()
engine = create_engine("sqlite:///mydatabase.db")
Base.prepare(
    autoload_with=engine,
    classname_for_table=camelize_classname,
    name_for_collection_relationship=pluralize_collection,
)

从上面的映射中,我们现在会有 UserAddress 两个类,其中从 UserAddress 的集合被称为 User.addresses

User, Address = Base.classes.User, Base.classes.Address
u1 = User(addresses=[Address(email="foo@bar.com")])

关系检测

automap 的绝大部分工作是根据外键生成relationship()结构。它对于多对一和一对多关系的工作机制如下:

  1. 已知映射到特定类的给定Table,会被检查其是否存在ForeignKeyConstraint对象。
  2. 对于每一个ForeignKeyConstraint,远程的Table对象被匹配到其要映射的类,如果有的话,否则将被跳过。
  3. 由于我们正在检查的ForeignKeyConstraint对应于从直接映射类的引用,该关系将被设置为指向被引用类的多对一关系;在被引用的类上将创建一个相应的一对多反向引用,指向该类。
  4. 如果ForeignKeyConstraint的任何一列不可为空(例如,nullable=False),将会将all, delete-orphanrelationship.cascade关键字参数添加到要传递给关联或反向引用的关键字参数中。如果ForeignKeyConstraint报告对于一组非空列设置了CASCADE或对于可为空列设置了SET NULLForeignKeyConstraint.ondelete,则将在关系关键字参数集合中将选项relationship.passive_deletes标志设置为True。请注意,并非所有后端都支持删除操作的反射。
  5. 关联的名称是使用AutomapBase.prepare.name_for_scalar_relationshipAutomapBase.prepare.name_for_collection_relationship可调用函数确定的。重要的是要注意,默认的关联命名从实际类名派生名称。如果您通过声明为特定类指定了显式名称,或指定了替代类命名方案,则关系名称将从该名称派生。
  6. 检查类以查找与这些名称匹配的现有映射属性。如果在一侧检测到一个属性,但在另一侧没有,则AutomapBase尝试在缺失的一侧创建一个关联,然后使用relationship.back_populates参数指向新关联到另一侧。
  7. 在通常情况下,如果任何一侧都没有关联,则AutomapBase.prepare()会在“多对一”一侧产生一个relationship(),并使用relationship.backref参数将其与另一侧匹配。
  8. relationship()的生成以及可选的backref()的生成被交由AutomapBase.prepare.generate_relationship函数处理,该函数可以由最终用户提供,以增强传递给relationship()backref()的参数,或者利用这些函数的自定义实现。

自定义关系参数

AutomapBase.prepare.generate_relationship钩子可用于向关系添加参数。对于大多数情况,我们可以利用现有的generate_relationship()函数,在用自己的参数扩充给定关键字字典后返回对象。

下面是如何向所有一对多关系发送relationship.cascaderelationship.passive_deletes选项的示例:

from sqlalchemy.ext.automap import generate_relationship
from sqlalchemy.orm import interfaces
def _gen_relationship(
    base, direction, return_fn, attrname, local_cls, referred_cls, **kw
):
    if direction is interfaces.ONETOMANY:
        kw["cascade"] = "all, delete-orphan"
        kw["passive_deletes"] = True
    # make use of the built-in function to actually return
    # the result.
    return generate_relationship(
        base, direction, return_fn, attrname, local_cls, referred_cls, **kw
    )
from sqlalchemy.ext.automap import automap_base
from sqlalchemy import create_engine
# automap base
Base = automap_base()
engine = create_engine("sqlite:///mydatabase.db")
Base.prepare(autoload_with=engine, generate_relationship=_gen_relationship)

多对多关系

automap将生成多对多关系,例如包含secondary参数的关系。生成这些关系的过程如下:

  1. 给定的Table在分配任何映射类之前将被检查其ForeignKeyConstraint对象。
  2. 如果表包含两个且仅两个ForeignKeyConstraint对象,并且此表中的所有列都是这两个ForeignKeyConstraint对象的成员,则假定该表是“次要”表,并且不会直接映射
  3. Table引用的两个(对于自引用的情况则为一个)外部表会与它们将要映射到的类匹配,如果有的话。
  4. 如果两边的映射类被定位,那么在两个类之间将创建一个多对多的双向 relationship() / backref() 对。
  5. 对于多对多的覆盖逻辑与一对多/多对一的逻辑相同;调用generate_relationship() 函数来生成结构,已存在的属性将被保留。

具有继承关系的关系

automap 不会在处于继承关系的两个类之间生成任何关系。 也就是说,给定以下两个类:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    type = Column(String(50))
    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }
class Engineer(Employee):
    __tablename__ = "engineer"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

EngineerEmployee的外键不是用于关系,而是用于在两个类之间建立联合继承。

请注意,这意味着 automap 将不会为从子类到超类的外键生成 任何 关系。 如果映射还具有从子类到超类的实际关系,那么这些关系需要显式说明。 如下,由于从EngineerEmployee有两个单独的外键,我们需要设置我们想要的关系以及inherit_condition,因为这些不是 SQLAlchemy 可以猜测的事情:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    type = Column(String(50))
    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }
class Engineer(Employee):
    __tablename__ = "engineer"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    favorite_employee_id = Column(Integer, ForeignKey("employee.id"))
    favorite_employee = relationship(Employee, foreign_keys=favorite_employee_id)
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "inherit_condition": id == Employee.id,
    }

处理简单的命名冲突

在映射过程中出现命名冲突的情况下,根据需要覆盖 classname_for_table()name_for_scalar_relationship()name_for_collection_relationship() 中的任何一个。 例如,如果 automap 正试图将多对一关系命名为现有列相同的名称,可以条件地选择替代约定。 给定一个模式:

CREATE  TABLE  table_a  (
  id  INTEGER  PRIMARY  KEY
);
CREATE  TABLE  table_b  (
  id  INTEGER  PRIMARY  KEY,
  table_a  INTEGER,
  FOREIGN  KEY(table_a)  REFERENCES  table_a(id)
);

上述模式首先将table_a表自动映射为一个名为table_a的类;然后将关系自动映射到table_b的类上,该关系的名称与此相关类的名称相同,例如table_a。 此关系名称与映射列table_b.table_a冲突,并且在映射时会发出错误。

我们可以通过以下方式使用下划线来解决此冲突:

def name_for_scalar_relationship(base, local_cls, referred_cls, constraint):
    name = referred_cls.__name__.lower()
    local_table = local_cls.__table__
    if name in local_table.columns:
        newname = name + "_"
        warnings.warn("Already detected name %s present.  using %s" % (name, newname))
        return newname
    return name
Base.prepare(
    autoload_with=engine,
    name_for_scalar_relationship=name_for_scalar_relationship,
)

或者,我们可以在列方面更改名称。 可以使用在 Naming Declarative Mapped Columns Explicitly 中描述的技术来修改映射的列,通过将列显式地分配给新名称:

Base = automap_base()
class TableB(Base):
    __tablename__ = "table_b"
    _table_a = Column("table_a", ForeignKey("table_a.id"))
Base.prepare(autoload_with=engine)

自定义关系参数

AutomapBase.prepare.generate_relationship 钩子可用于向关系添加参数。对于大多数情况,我们可以利用现有的 generate_relationship() 函数,在使用我们自己的参数扩充给定的关键字字典后返回对象。

下面是如何将 relationship.cascaderelationship.passive_deletes 选项传递给所有一对多关系的示例:

from sqlalchemy.ext.automap import generate_relationship
from sqlalchemy.orm import interfaces
def _gen_relationship(
    base, direction, return_fn, attrname, local_cls, referred_cls, **kw
):
    if direction is interfaces.ONETOMANY:
        kw["cascade"] = "all, delete-orphan"
        kw["passive_deletes"] = True
    # make use of the built-in function to actually return
    # the result.
    return generate_relationship(
        base, direction, return_fn, attrname, local_cls, referred_cls, **kw
    )
from sqlalchemy.ext.automap import automap_base
from sqlalchemy import create_engine
# automap base
Base = automap_base()
engine = create_engine("sqlite:///mydatabase.db")
Base.prepare(autoload_with=engine, generate_relationship=_gen_relationship)

多对多关系

automap 将生成多对多关系,例如那些包含 secondary 参数的关系。生成这些关系的过程如下:

  1. 在为其分配任何映射类之前,将检查给定的 Table 是否包含 ForeignKeyConstraint 对象。
  2. 如果表包含两个并且仅有两个 ForeignKeyConstraint 对象,并且此表中的所有列都是这两个 ForeignKeyConstraint 对象的成员,则假定该表是一个“次要”表,并且不会直接映射
  3. Table 所引用的两个(或一个,用于自引用)外部表将与它们将被映射到的类匹配,如果有的话。
  4. 如果两侧的映射类位于同一处,则在两个类之间创建一个双向的多对多 relationship() / backref() 对。
  5. 对于多对多的覆盖逻辑与一对多/多对一的逻辑相同;调用 generate_relationship() 函数来生成结构,并将保留现有属性。

继承关系

automap 将不会在处于继承关系的两个类之间生成任何关系。也就是说,对于以下两个给定的类:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    type = Column(String(50))
    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }
class Engineer(Employee):
    __tablename__ = "engineer"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

EngineerEmployee 的外键不是用于关系,而是用于在两个类之间建立联合继承。

请注意,这意味着 automap 不会为从子类到超类的外键生成任何关系。如果映射实际上还有从子类到超类的关系,那么这些关系需要是显式的。在下面的例子中,由于从 EngineerEmployee 有两个单独的外键,我们需要设置我们想要的关系以及 inherit_condition,因为这些是 SQLAlchemy 无法猜测的事情:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    type = Column(String(50))
    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }
class Engineer(Employee):
    __tablename__ = "engineer"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    favorite_employee_id = Column(Integer, ForeignKey("employee.id"))
    favorite_employee = relationship(Employee, foreign_keys=favorite_employee_id)
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "inherit_condition": id == Employee.id,
    }

处理简单的命名冲突

在映射过程中出现命名冲突的情况下,根据需要覆盖任何 classname_for_table()name_for_scalar_relationship()name_for_collection_relationship()。例如,如果 automap 尝试将一个多对一关系命名为现有列的名称,可以有条件地选择替代约定。给定一个模式:

CREATE  TABLE  table_a  (
  id  INTEGER  PRIMARY  KEY
);
CREATE  TABLE  table_b  (
  id  INTEGER  PRIMARY  KEY,
  table_a  INTEGER,
  FOREIGN  KEY(table_a)  REFERENCES  table_a(id)
);

上述模式将首先将 table_a 表自动映射为名为 table_a 的类;然后将在 table_b 类上自动映射一个与此相关类相同名称的关系,例如 table_a。这个关系名称与映射列 table_b.table_a 冲突,并且在映射时会发出错误。

通过使用下划线,我们可以解决这个冲突:

def name_for_scalar_relationship(base, local_cls, referred_cls, constraint):
    name = referred_cls.__name__.lower()
    local_table = local_cls.__table__
    if name in local_table.columns:
        newname = name + "_"
        warnings.warn("Already detected name %s present.  using %s" % (name, newname))
        return newname
    return name
Base.prepare(
    autoload_with=engine,
    name_for_scalar_relationship=name_for_scalar_relationship,
)

或者,我们可以在列的一侧更改名称。可以使用在 显式命名声明性映射列 中描述的技术修改映射的列,通过将列显式分配给一个新名称:

Base = automap_base()
class TableB(Base):
    __tablename__ = "table_b"
    _table_a = Column("table_a", ForeignKey("table_a.id"))
Base.prepare(autoload_with=engine)


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

相关文章
|
1月前
|
SQL 存储 缓存
SqlAlchemy 2.0 中文文档(三十)(5)
SqlAlchemy 2.0 中文文档(三十)
22 1
|
1月前
|
数据库连接 API 数据库
SqlAlchemy 2.0 中文文档(三十)(2)
SqlAlchemy 2.0 中文文档(三十)
29 0
|
1月前
|
SQL 存储 缓存
SqlAlchemy 2.0 中文文档(三十)(4)
SqlAlchemy 2.0 中文文档(三十)
25 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月前
|
存储 JSON 数据格式
SqlAlchemy 2.0 中文文档(五十)(2)
SqlAlchemy 2.0 中文文档(五十)
11 0
|
1月前
|
JSON 数据库 数据格式
SqlAlchemy 2.0 中文文档(五十)(5)
SqlAlchemy 2.0 中文文档(五十)
16 0