自动程序

优质
小牛编辑
168浏览
2023-12-01

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

0.9.1 新版功能: 补充 sqlalchemy.ext.automap .

希望 AutomapBase 系统为解决著名的 SQLSoup 还试图解决从现有数据库中快速生成基本对象模型的问题。通过严格地在映射器配置级别解决这个问题,并与现有的声明性类技术完全集成, AutomapBase 寻求提供一种很好的集成方法来方便地自动生成即席映射。

基本用途

最简单的用法是将现有数据库反映到新模型中。我们创造了一个新的 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(engine, reflect=True)

# 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"
print (u1.address_collection)

以上呼叫 AutomapBase.prepare() 当经过 AutomapBase.prepare.reflect 参数表示 MetaData.reflect() 将对此声明性基类调用方法' MetaData 收集;然后,每个 可行的 TableMetaData 将自动生成新的映射类。这个 ForeignKeyConstraint 将各种表链接在一起的对象将用于生成新的双向 relationship() 类之间的对象。类和关系遵循我们可以自定义的默认命名方案。在这一点上,我们的基本映射包括 UserAddress 类可以以传统的方式使用。

注解

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

从现有元数据生成映射

我们可以通过预申报 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, Address, Order = Base.classes.user, Base.classes.address,\
    Base.classes.user_order

显式指定类

这个 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(engine, reflect=True)

# 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(engine, reflect=True,
            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 我们正在检查与即时映射类的引用相对应的关系,该关系将设置为引用引用该类的多对一关系;将在引用该类的引用类上创建一个对应的一对多backref。

  4. 如果任何列是 ForeignKeyConstraint 不能为空(例如 nullable=False a) relationship.cascade 的关键字参数 all, delete-orphan 将添加到要传递给关系或backref的关键字参数中。如果 ForeignKeyConstraint 报道说 ForeignKeyConstraint.ondelete 设置为 CASCADE 对于非空值或 SET NULL 对于可以为空的列集,选项 relationship.passive_deletes 标志设置为 True 在relationship关键字参数集中。请注意,并非所有后端都支持删除时的反射。

    1.0.0 新版功能: -automap将在生成一对多关系时检测不可为空的外键约束,并建立默认的级联 all, delete-orphan 如果是;另外,如果约束指定 ForeignKeyConstraint.ondelete 属于 CASCADE 对于不可为空的或 SET NULL 对于可以为空的列, 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 hook可用于向关系添加参数。在大多数情况下,我们可以利用现有的 generate_relationship() 函数返回对象,在用我们自己的参数扩充给定的关键字字典之后。

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

from sqlalchemy.ext.automap import generate_relationship

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(engine, reflect=True,
            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不会生成 any 从子类链接到超类的外键的关系。如果映射具有从子类到超类的实际关系,那么这些关系也需要是显式的。下面,因为我们有两个独立的 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(engine, reflect=True,
    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(engine, reflect=True)

在显式声明中使用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 映射 ForeignKey 我们定义的 Address.user_id 允许双向关系对 Address.userUser.address_collection 在映射类上生成。

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

截取列定义

这个 MetaDataTable 对象支持事件钩子 DDLEvents.column_reflect() 它可以用来截获在 Column 对象被构造。例如,如果我们想使用命名约定映射列,例如 "attr_<columnname>" ,事件可以应用为:

@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(engine, reflect=True)

1.4.0b2 新版功能: 这个 DDLEvents.column_reflect() 事件可以应用于 MetaData 对象。

参见

DDLEvents.column_reflect()

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

API引用

Object NameDescription

automap_base([declarative_base], **kw)

生成声明性automap基。

AutomapBase

“automap”架构的基类。

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=None, **kw)

生成声明性automap基。

此函数生成一个新的基类,它是 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 类总是与实际的声明性基一起用作mixin。

一个新的子类 AutomapBase 通常使用 automap_base() 功能。

参见

自动程序

attribute sqlalchemy.ext.automap.AutomapBase.classes = None

的实例 Properties 包含类。

此对象的行为与 .c 表上的集合。类以它们的名称出现,例如:

Base = automap_base()
Base.prepare(engine=some_engine, reflect=True)

User, Address = Base.classes.User, Base.classes.Address
method sqlalchemy.ext.automap.AutomapBase.classmethod prepare(autoload_with=None, engine=None, reflect=False, schema=None, classname_for_table=None, collection_class=None, name_for_scalar_relationship=None, name_for_collection_relationship=None, generate_relationship=None, reflection_options={})

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

参数
function sqlalchemy.ext.automap.classname_for_table(base, tablename, table)

返回应使用的类名(给定表名)。

默认实现为:

return str(tablename)

可以使用 AutomapBase.prepare.classname_for_table 参数。

参数
返回

字符串类名。…注意::在python 2中,用于类名的字符串 must 非Unicode对象,例如 str() 对象。这个 .name 属性 Table 通常是Python unicode子类,因此 str() 在考虑了任何非ASCII字符之后,应该对该名称应用函数。

function sqlalchemy.ext.automap.name_for_scalar_relationship(base, local_cls, referred_cls, constraint)

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

默认实现为:

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, local_cls, referred_cls, constraint)

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

默认实现为:

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, direction, return_fn, attrname, local_cls, referred_cls, **kw)

生成一个 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_fnbackref() 函数,则此名称是分配给backref的名称。

  • local_cls -- 此关系或backref将在本地存在的“本地”类。

  • referred_cls -- 关系或backref所指的“引用的”类。

  • **kw -- 所有其他关键字参数都将传递给函数。

返回

relationship()backref() 按照 generate_relationship.return_fn 参数。