Declare a Mapping
当使用ORM时,配置过程首先是描述我们要处理的数据库表,然后是定义我们自己的类,这些类将被映射到这些表。在现代SQLAlchemy中,这两个任务通常是一起执行的,使用一个称为声明式扩展Declarative Extensions的系统,它允许我们创建包含指令的类,以描述它们将被映射到的实际数据库表。
使用声明式系统映射的类是用一个基类来定义的,这个基类维护着一个相对于这个基类的类和表的目录–这被称为声明式基类declarative base class。我们的应用程序通常在一个常用的导入模块中只有一个这个基类的实例。我们使用declarative_base()函数来创建基类,如下所示。
from sqlalchemy.orm import declarative_base
Base = declarative_base()
现在我们有了一个 “base”,我们可以在它的基础上定义任何数量的映射类。我们将从一个名为user的表开始,它将存储使用我们的应用程序的终端用户的记录。一个名为User的新类将是我们映射到这个表的类。在这个类中,我们定义了关于我们将映射到的表的细节,主要是表名,以及列的名称和数据类型。
from sqlalchemy import Column, Integer, String
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
fullname = Column(String)
nickname = Column(String)
def __repr__(self):
return "<User(name='%s', fullname='%s', nickname='%s')>" % (
self.name, self.fullname, self.nickname)
一个使用声明式的类至少需要一个__tablename__属性,以及至少一个作为主键一部分的Column。SQLAlchemy从来不会自己对一个类所引用的表做任何假设,包括它没有内置的名称、数据类型或约束的约定。但这并不意味着需要模板,相反,我们鼓励你使用助记函数和mixin类创建自己的自动约定,这在Mixin和自定义基类中有详细介绍。
当我们的类被构造出来时,Declarative会用称为描述符的特殊Python访问器替换所有的Column对象;这是一个被称为 "工具化 "的过程。被 "工具化 "的映射类将为我们提供在 SQL 上下文中引用我们的表,以及从数据库中持久化和加载列的值的方法。
除了映射过程对我们的类所做的事情之外,这个类在其他方面主要还是一个普通的Python类,我们可以对它定义任何数量的普通属性和我们的应用程序所需要的方法。
Create a Schema
通过Declarative系统构造的User
类,我们已经定义了关于我们的表的信息,称为表元数据table metadata。SQLAlchemy用来表示特定表的这些信息的对象叫做Table对象,这里Declarative为我们做了一个对象。我们可以通过检查__table__
属性来查看这个对象。
>>> User.__table__
Table('users', MetaData(),
Column('id', Integer(), table=<users>, primary_key=True, nullable=False),
Column('name', String(), table=<users>),
Column('fullname', String(), table=<users>),
Column('nickname', String(), table=<users>), schema=None)
当我们声明我们的类时,Declarative使用了一个Python元类,以便在类声明完成后执行额外的活动;在这个阶段内,它就根据我们的规范创建了一个Table
对象,并通过构造一个Mapper
对象将其与类关联起来。这个对象是一个我们通常不需要直接处理的幕后对象(虽然它可以在我们需要的时候提供大量关于我们映射的信息)。
Table
对象是一个更大的集合MetaData
的成员。当使用声明式时,这个对象可以通过我们声明式基类的.metadata
属性获得。
MetaData
是一个注册表(registry),它包括向数据库发出一组有限的模式生成命令的能力。由于我们的SQLite数据库实际上并没有users
表存在,所以我们可以使用MetaData
向数据库发出CREATE TABLE语句,用于所有还不存在的表。下面,我们调用MetaData.create_all()
方法,传入我们的Engine
作为数据库连接的来源。我们会看到,首先会发出特殊的命令来检查users
表是否存在,之后会发出实际的CREATE TABLE
语句。
>>> Base.metadata.create_all(engine)
BEGIN...
CREATE TABLE users (
id INTEGER NOT NULL,
name VARCHAR,
fullname VARCHAR,
nickname VARCHAR,
PRIMARY KEY (id)
)
[...] ()
COMMIT