反映数据库对象

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

A Table 对象可以被指示从数据库中已经存在的相应数据库架构对象加载关于其自身的信息。此过程称为 反射 。在最简单的情况下,您只需指定表名,即 MetaData 对象,而 autoload_with 参数::

>>> messages = Table('messages', meta, autoload_with=engine)
>>> [c.name for c in messages.columns]
['message_id', 'message_name', 'date']

上面的操作将使用给定的引擎查询数据库以获取有关 messages 表,然后将生成 ColumnForeignKey 以及与此信息相对应的其他对象, Table 对象是用python手工构建的。

当表被反射时,如果一个给定的表通过外键引用另一个表,则为一秒。 Table 对象是在 MetaData 表示连接的对象。下面,假设表格 shopping_cart_items 引用名为的表 shopping_carts . 反映 shopping_cart_items 表的作用是 shopping_carts 还将加载表:

>>> shopping_cart_items = Table('shopping_cart_items', meta, autoload_with=engine)
>>> 'shopping_carts' in meta.tables:
True

这个 MetaData 有一个有趣的“单例”行为,如果您单独请求两个表, MetaData 确保只有一个 Table 对象是为每个不同的表名创建的。这个 Table 构造函数实际上返回给您已经存在的 Table 对象(如果已存在具有给定名称的对象)。如下图所示,我们可以访问已经生成的 shopping_carts 表的名称:

shopping_carts = Table('shopping_carts', meta)

当然,这是一个好主意,可以使用 autoload_with=engine 不管上面的表是什么。这样,如果表的属性尚未加载,则它们将被加载。仅当表尚未加载时,才会对其执行自动加载操作;一旦加载,就会对 Table 不会重新发出任何反射查询。

覆盖反射列

在反射表时,可以用显式值覆盖各个列;这对于指定自定义数据类型、约束(如数据库中可能未配置的主键等)非常方便:

>>> mytable = Table('mytable', meta,
... Column('id', Integer, primary_key=True),   # override reflected 'id' to have primary key
... Column('mydata', Unicode(50)),    # override reflected 'mydata' to be Unicode
... # additional Column objects which require no change are reflected normally
... autoload_with=some_engine)

参见

使用自定义类型和反射 -说明了上述列重写技术如何应用于具有表反射的自定义数据类型的使用。

反射视图

反射系统也可以反射视图。基本用法与表的用法相同:

my_view = Table("some_view", metadata, autoload_with=engine)

上面, my_view 是一个 Table 对象与 Column 对象,表示视图“某些视图”中每个列的名称和类型。

通常,在反射视图时希望至少有主键约束(如果不是外键的话)。视图反射不会推断这些约束。

为此,请使用“重写”技术,显式指定属于主键的一部分或具有外键约束的列:

my_view = Table("some_view", metadata,
                Column("view_id", Integer, primary_key=True),
                Column("related_thing", Integer, ForeignKey("othertable.thing_id")),
                autoload_with=engine
)

同时反射所有表

这个 MetaData 对象还可以获取表列表并反映完整集。这是通过使用 reflect() 方法。调用后,所有定位的表都出现在 MetaData 对象的表字典:

metadata_obj = MetaData()
metadata_obj.reflect(bind=someengine)
users_table = metadata_obj.tables['users']
addresses_table = metadata_obj.tables['addresses']

metadata.reflect() 还提供了清除或删除数据库中所有行的简便方法:

metadata_obj = MetaData()
metadata_obj.reflect(bind=someengine)
for table in reversed(metadata_obj.sorted_tables):
    someengine.execute(table.delete())

带检查员的细颗粒反射

还提供了一个低级接口,它提供了从给定数据库加载模式、表、列和约束描述列表的后端不可知系统。这就是所谓的“检查员”::

from sqlalchemy import create_engine
from sqlalchemy import inspect
engine = create_engine('...')
insp = inspect(engine)
print(insp.get_table_names())
Object NameDescription

Inspector

执行数据库架构检查。

class sqlalchemy.engine.reflection.Inspector(bind)

当映射表的列时,使用 Table.autoload_with 的参数 Table 或者 Inspector.get_columns() 一种方法 Inspector ,则数据类型将尽可能特定于目标数据库。这意味着,如果MySQL数据库反映了“整数”数据类型,则该类型将由 sqlalchemy.dialects.mysql.INTEGER 类,它包括特定于MySQL的属性,如“display_width”。或在PostgreSQL上,是特定于PostgreSQL的数据类型,例如 sqlalchemy.dialects.postgresql.INTERVALsqlalchemy.dialects.postgresql.ENUM 可能会被退回。

反射有一个用例,即给定的 Table 将被转移到不同的供应商数据库。为了适应这个用例,有一种技术可以在飞翔上将这些特定于供应商的数据类型转换为SQLAlChemy后端无关的数据类型的实例,例如上面的类型 IntegerIntervalEnum 。这可以通过使用 DDLEvents.column_reflect() 事件与 TypeEngine.as_generic() 方法。

在MySQL中给定表(之所以选择,是因为MySQL有许多特定于供应商的数据类型和选项):

CREATE TABLE IF NOT EXISTS my_table (
    id INTEGER PRIMARY KEY AUTO_INCREMENT,
    data1 VARCHAR(50) CHARACTER SET latin1,
    data2 MEDIUMINT(4),
    data3 TINYINT(2)
)

上表包括仅限MySQL的整数类型 MEDIUMINTTINYINT 以及一个 VARCHAR 这包括仅限MySQL的 CHARACTER SET 选项。如果我们正常地反映这个表,它会产生一个 Table 对象,该对象将包含那些特定于MySQL的数据类型和选项:

>>> from sqlalchemy import MetaData, Table, create_engine
>>> mysql_engine = create_engine("mysql://scott:tiger@localhost/test")
>>> metadata_obj = MetaData()
>>> my_mysql_table = Table("my_table", metadata_obj, autoload_with=mysql_engine)

上面的示例将上面的表架构反映到一个新的 Table 对象。出于演示目的,我们可以使用以下命令打印特定于MySQL的“CREATE TABLE”语句 CreateTable 构造:

>>> from sqlalchemy.schema import CreateTable
>>> print(CreateTable(my_mysql_table).compile(mysql_engine))
CREATE TABLE my_table (
id INTEGER(11) NOT NULL AUTO_INCREMENT,
data1 VARCHAR(50) CHARACTER SET latin1,
data2 MEDIUMINT(4),
data3 TINYINT(2),
PRIMARY KEY (id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

上面,保留了特定于MySQL的数据类型和选项。如果我们想要一个 Table 我们可以干净利落地将其转移到另一家数据库供应商,替换特殊的数据类型 sqlalchemy.dialects.mysql.MEDIUMINTsqlalchemy.dialects.mysql.TINYINT 使用 Integer ,我们可以选择“泛化”该表上的数据类型,或者以任何我们想要的方式更改它们,方法是使用 DDLEvents.column_reflect() 事件。自定义处理程序将利用 TypeEngine.as_generic() 方法将上述特定于MySQL的类型对象转换为泛型对象,方法是将 "type" 传递给事件处理程序的列字典条目中的条目。本词典的格式在 Inspector.get_columns()

>>> from sqlalchemy import event
>>> metadata_obj = MetaData()

>>> @event.listens_for(metadata_obj, "column_reflect")
>>> def genericize_datatypes(inspector, tablename, column_dict):
...     column_dict["type"] = column_dict["type"].as_generic()

>>> my_generic_table = Table("my_table", metadata_obj, autoload_with=mysql_engine)

我们现在有了一个新的 Table 它是通用的,并且使用 Integer 用于这些数据类型。例如,我们现在可以在PostgreSQL数据库上发出“CREATE TABLE”语句:

>>> pg_engine = create_engine("postgresql://scott:tiger@localhost/test", echo=True)
>>> my_generic_table.create(pg_engine)
CREATE TABLE my_table (
    id SERIAL NOT NULL,
    data1 VARCHAR(50),
    data2 INTEGER,
    data3 INTEGER,
    PRIMARY KEY (id)
)

还请注意,SQLAlChemy通常会对其他行为做出不错的猜测,比如MySQL AUTO_INCREMENT 指令在PostgreSQL中使用 SERIAL 自动递增数据类型。

1.4 新版功能: 添加了 TypeEngine.as_generic() 方法,并额外改进了 DDLEvents.column_reflect() 事件,以便它可以应用于 MetaData 为方便起见反对。

反射的局限性

需要注意的是,反射过程会重新创建 Table 仅使用关系数据库中表示的信息的元数据。根据定义,此进程无法还原数据库中实际未存储的架构的各个方面。反射不可用的状态包括但不限于:

  • 客户端默认值,可以是python函数,也可以是使用 default 关键字 Column (注意,这与 server_default 这是通过反射可以得到的)。

  • 列信息,例如可能放入 Column.info 词典

  • 的值 .quote 设置为 ColumnTable

  • 特定的联系 Sequence 用给定的 Column

在许多情况下,关系数据库还以与SQLAlchemy中指定的格式不同的格式报告表元数据。这个 Table 从反射返回的对象不能总是依赖于生成与原始python定义的相同的ddl Table 物体。出现这种情况的区域包括服务器默认值、与列关联的序列以及有关约束和数据类型的各种特性。服务器端默认值可以用CAST指令返回(通常PostgreSQL将包括 ::<type> 或不同于最初指定的引用模式。

另一类限制包括模式结构,反射仅部分定义或尚未定义。最近对反射的改进允许反射视图、索引和外键选项等内容。在撰写本文时,检查约束、表注释和触发器等结构不会反映出来。