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

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


原文:docs.sqlalchemy.org/en/20/contents.html

Automap

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

定义一个扩展到sqlalchemy.ext.declarative系统的系统,自动生成从数据库模式到映射类和关系,通常而不一定是一个反射的数据库模式。

希望AutomapBase系统提供了一个快速和现代化的解决方案,解决了非常著名的SQLSoup也试图解决的问题,即从现有数据库动态生成快速和基本的对象模型。通过严格在映射器配置级别解决该问题,并与现有的声明类技术完全集成,AutomapBase试图提供一个与问题紧密集成的方法,以迅速自动生成临时映射。

提示

Automap 扩展针对“零声明”方法,其中可以从数据库模式动态生成包括类和预命名关系在内的完整 ORM 模型。对于仍希望使用显式类声明以及与表反射结合使用的显式关系定义的应用程序,描述在使用 DeferredReflection 中的DeferredReflection类是更好的选择。

基本用法

最简单的用法是将现有数据库反映到一个新模型中。我们创建一个新的AutomapBase类,方式类似于我们创建声明性基类,使用automap_base()。然后,我们调用AutomapBase.prepare()在生成的基类上,要求它反映模式并生成映射:

from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
from sqlalchemy import create_engine
Base = automap_base()
# engine, suppose it has two tables 'user' and 'address' set up
engine = create_engine("sqlite:///mydatabase.db")
# reflect the tables
Base.prepare(autoload_with=engine)
# mapped classes are now created with names by default
# matching that of the table name.
User = Base.classes.user
Address = Base.classes.address
session = Session(engine)
# rudimentary relationships are produced
session.add(Address(email_address="foo@bar.com", user=User(name="foo")))
session.commit()
# collection-based relationships are by default named
# "<classname>_collection"
u1 = session.query(User).first()
print(u1.address_collection)

在上面,调用AutomapBase.prepare()并传递AutomapBase.prepare.reflect参数,表示将在此声明基类的MetaData集合上调用MetaData.reflect()方法; 然后,MetaData中的每个** viable **Table都将自动生成一个新的映射类。将连接各个表的ForeignKeyConstraint对象将用于在类之间生成新的双向relationship()对象。类和关系遵循一个默认命名方案,我们可以自定义。在这一点上,我们基本的映射包含了相关的UserAddress类,可以以传统方式使用。

注意

通过** viable **,我们指的是表必须指定主键才能进行映射。此外,如果检测到表是两个其他表之间的纯关联表,则不会直接映射该表,而是将其配置为两个引用表的映射之间的多对多表。

从现有元数据生成映射

我们可以将预先声明的MetaData对象传递给automap_base()。该对象可以以任何方式构造,包括以编程方式、从序列化文件或从使用MetaData.reflect()反映的自身构造。下面我们演示了反射和显式表声明的组合:

from sqlalchemy import create_engine, MetaData, Table, Column, ForeignKey
from sqlalchemy.ext.automap import automap_base
engine = create_engine("sqlite:///mydatabase.db")
# produce our own MetaData object
metadata = MetaData()
# we can reflect it ourselves from a database, using options
# such as 'only' to limit what tables we look at...
metadata.reflect(engine, only=["user", "address"])
# ... or just define our own Table objects with it (or combine both)
Table(
    "user_order",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("user_id", ForeignKey("user.id")),
)
# we can then produce a set of mappings from this MetaData.
Base = automap_base(metadata=metadata)
# calling prepare() just sets up mapped classes and relationships.
Base.prepare()
# mapped classes are ready
User = Base.classes.user
Address = Base.classes.address
Order = Base.classes.user_order

从多个模式生成映射

当使用反射时,AutomapBase.prepare() 方法一次最多只能从一个模式中反射表,使用 AutomapBase.prepare.schema 参数来指示要从中反射的模式的名称。为了从多个模式中填充 AutomapBase 中的表,可以多次调用 AutomapBase.prepare(),每次将不同的名称传递给 AutomapBase.prepare.schema 参数。AutomapBase.prepare() 方法会保持一个内部列表,其中包含已经映射过的 Table 对象,并且只会为自上次运行 AutomapBase.prepare() 以来新增的那些 Table 对象添加新的映射:

e = create_engine("postgresql://scott:tiger@localhost/test")
Base.metadata.create_all(e)
Base = automap_base()
Base.prepare(e)
Base.prepare(e, schema="test_schema")
Base.prepare(e, schema="test_schema_2")

新版本 2.0 中新增了 AutomapBase.prepare() 方法,可以任意调用;每次运行时只会映射新增的表。在 1.4 版本及之前的版本中,多次调用会导致错误,因为它会尝试重新映射已经映射过的类。之前的解决方法是直接调用 MetaData.reflect(),该方法仍然可用。

跨多个模式自动映射同名表

对于多个模式可能有同名表的常见情况,因此可能生成同名类,可以通过使用 AutomapBase.prepare.classname_for_table 钩子在每个模式基础上应用不同的类名来解决冲突,或者通过使用 AutomapBase.prepare.modulename_for_table 钩子来解决同名类的歧义,该钩子允许通过更改它们的有效 __module__ 属性来区分同名类。在下面的示例中,此钩子用于为所有类创建一个 __module__ 属性,其形式为 mymodule.,如果没有模式,则使用模式名称 default

e = create_engine("postgresql://scott:tiger@localhost/test")
Base.metadata.create_all(e)
def module_name_for_table(cls, tablename, table):
    if table.schema is not None:
        return f"mymodule.{table.schema}"
    else:
        return f"mymodule.default"
Base = automap_base()
Base.prepare(e, modulename_for_table=module_name_for_table)
Base.prepare(e, schema="test_schema", modulename_for_table=module_name_for_table)
Base.prepare(e, schema="test_schema_2", modulename_for_table=module_name_for_table)

同名类被组织成可在 AutomapBase.by_module 中使用的分层集合。使用特定包/模块的点分隔名称向下遍历到所需的类名。

注意

当使用 AutomapBase.prepare.modulename_for_table 钩子来返回一个不是 None 的新 __module__ 时,类不会被放入 AutomapBase.classes 集合中;只有那些没有给定显式模块名的类才会放在这里,因为该集合不能单独表示同名类。

在上面的示例中,如果数据库中包含了三个默认模式、test_schema 模式和 test_schema_2 模式中都命名为 accounts 的表,将会有三个单独的类可用,分别是:

Base.by_module.mymodule.default.accounts
Base.by_module.mymodule.test_schema.accounts
Base.by_module.mymodule.test_schema_2.accounts

对于所有 AutomapBase 类生成的默认模块命名空间是 sqlalchemy.ext.automap。如果没有使用 AutomapBase.prepare.modulename_for_table 钩子,AutomapBase.by_module 的内容将完全在 sqlalchemy.ext.automap 命名空间内(例如 MyBase.by_module.sqlalchemy.ext.automap.),其中将包含与 AutomapBase.classes 中看到的相同系列的类。因此,只有在存在显式 __module__ 约定时才通常需要使用 AutomapBase.by_module

显式指定类

提示

如果在应用程序中期望显式类占据主要地位,请考虑改用 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")])

关系检测

自动映射所实现的绝大部分是基于外键生成 relationship() 结构。其工作原理如下:

  1. 检查已知映射到特定类的给定 Table 是否存在ForeignKeyConstraint 对象。
  2. 对于每个 ForeignKeyConstraint,将匹配到的远程Table对象与其应映射到的类相匹配,如果有的话,否则将跳过。
  3. 由于我们正在检查的 ForeignKeyConstraint 对应于来自直接映射类的引用,因此关系将被设置为指向引用类的多对一关系;在引用类上将创建相应的一个对多反向引用,引用此类。
  4. 如果属于ForeignKeyConstraint 的任何列不可为空(例如 nullable=False),则将在要传递给关系或反向引用的关键字参数中添加一个 relationship.cascade 关键字参数,其值为 all, delete-orphan。如果ForeignKeyConstraint 报告对于一组非空列设置了 ForeignKeyConstraint.ondeleteCASCADE,或者对于可为空列设置了 SET NULL,则在关系关键字参数集合中将选项relationship.passive_deletes标志设置为 True。请注意,并非所有后端都支持对 ON DELETE 的反射。
  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 对象的成员,则假定该表是“secondary”表,并且不会直接映射
  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 的外键不是用于建立关系,而是用于在两个类之间建立连接的继承关系。

请注意,这意味着自动映射将不会为从子类到父类的外键生成 任何 关系。如果一个映射还具有从子类到父类的实际关系,那么这些关系需要是显式的。在下面的例子中,由于 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() 中的任何一个。例如,如果自动映射尝试将一个多对一关系命名为一个现有列相同的名称,可以有条件地选择替代约定。给定一个模式:

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)


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



相关文章
|
5月前
|
Python
SqlAlchemy 2.0 中文文档(三十)(3)
SqlAlchemy 2.0 中文文档(三十)
51 1
|
5月前
|
SQL 存储 缓存
SqlAlchemy 2.0 中文文档(三十)(5)
SqlAlchemy 2.0 中文文档(三十)
46 1
|
5月前
|
数据库连接 API 数据库
SqlAlchemy 2.0 中文文档(三十)(2)
SqlAlchemy 2.0 中文文档(三十)
73 0
|
5月前
|
SQL 存储 缓存
SqlAlchemy 2.0 中文文档(三十)(4)
SqlAlchemy 2.0 中文文档(三十)
101 0
|
5月前
|
SQL 存储 关系型数据库
SqlAlchemy 2.0 中文文档(二十九)(1)
SqlAlchemy 2.0 中文文档(二十九)
54 4
|
5月前
|
SQL 缓存 API
SqlAlchemy 2.0 中文文档(二十)(5)
SqlAlchemy 2.0 中文文档(二十)
33 1
|
5月前
|
SQL 测试技术 API
SqlAlchemy 2.0 中文文档(二十)(4)
SqlAlchemy 2.0 中文文档(二十)
41 1
|
5月前
|
SQL 存储 测试技术
SqlAlchemy 2.0 中文文档(二十)(3)
SqlAlchemy 2.0 中文文档(二十)
42 1
|
5月前
|
SQL Oracle 关系型数据库
SqlAlchemy 2.0 中文文档(七十)(3)
SqlAlchemy 2.0 中文文档(七十)
33 1
|
5月前
|
SQL Oracle 关系型数据库
SqlAlchemy 2.0 中文文档(七十)(1)
SqlAlchemy 2.0 中文文档(七十)
31 1