当继承db.Model
基类的子类被声明创建时,根据db.Model
基类继承的元类中设置的行为,类声明后会将表信息注册到db.Model.metadata.tables
属性中。
create_all()
方法被调用时正是通过这个属性来获取表信息。因此,当我们调用create_all()
前,需要确保模型类被声明创建。如果模型类存储在单独的模块中,不导入该模块就不会执行其中的代码,模型类便不会被创建,进而便无法注册表信息到db.Model.metadata.tables
中,所以这时需要导入相应的模块。
因为我们的目的是让模型类被创建,所以不论是导入整个模块还是导入其中某个模型类都可以,并不需要导入全部模型类。
一般情况下,我们不用关心这个问题。在单脚本的Flask程序中自不必说,在使用包组织的Flask程序中,创建程序实例时必然需要导入视图函数所在的模块,或是蓝本所在的模块,而这些模块会导入模型类。
下面我们会通过解析源码简单了解模型类对应的表信息是如何注册到db.Model.metadata.tables中的。
不论是单独使用SQLAlchemy时创建的Base
基类,还是使用Flask-SQLAlchemy时创建db对象后的db.Model
基类,都是通过declarative_base()
函数创建,具体源码如下:
def declarative_base(bind=None, metadata=None, mapper=None, cls=object, name='Base', constructor=_declarative_constructor, class_registry=None, metaclass=DeclarativeMeta): lcl_metadata = metadata or MetaData() if bind: lcl_metadata.bind = bind if class_registry is None: class_registry = weakref.WeakValueDictionary() bases = not isinstance(cls, tuple) and (cls,) or cls class_dict = dict(_decl_class_registry=class_registry, metadata=lcl_metadata) if isinstance(cls, type): class_dict['__doc__'] = cls.__doc__ if constructor: class_dict['__init__'] = constructor if mapper: class_dict['__mapper_cls__'] = mapper return metaclass(name, bases, class_dict)
源码位置:https://github.com/zzzeek/sqlalchemy/blob/master/lib/sqlalchemy/ext/declarative/api.py#L286
这个函数返回一个元类实例,对应的元类为DeclarativeMeta
,这个元类定义了一些特殊行为:
class DeclarativeMeta(type): def __init__(cls, classname, bases, dict_): if '_decl_class_registry' not in cls.__dict__: _as_declarative(cls, classname, cls.__dict__) type.__init__(cls, classname, bases, dict_) def __setattr__(cls, key, value): _add_attribute(cls, key, value) def __delattr__(cls, key): _del_attribute(cls, key)
源码位置:https://github.com/zzzeek/sqlalchemy/blob/master/lib/sqlalchemy/ext/declarative/api.py#L62
在_as_declarative()
函数以及附加的其他多层调用为这个类进行了更多设置,比如添加__table__
属性为存储表信息的Table
对象,设置metadata
属性等等。
其中魔法方法__setattr__()
中调用了_add_attribute()
函数,这个函数执行了一系列模型类属性的注册操作,其中的一个操作便是向基类 __table__
属性指向的Table
对象调用append_column()
方法添加表字段信息:
def _add_attribute(cls, key, value): if '__mapper__' in cls.__dict__: if isinstance(value, Column): _undefer_column_name(key, value) cls.__table__.append_column(value) cls.__mapper__.add_property(key, value) ...
源码位置:https://github.com/zzzeek/sqlalchemy/blob/master/lib/sqlalchemy/ext/declarative/base.py#L646
经过这一系列注册操作,表信息就被添加到db.Model.metadata.tables
属性中,这个属性返回包含所有表信息的字典(表名称与Table
实例的映射),下面是一个示例:
immutabledict({'comment': Table('comment', MetaData(bind=None), Column('id' , Integer(), table=<comment>, primary_key=True, nullable=False), Column('bo dy', Text(), table=<comment>), Column('timestamp', DateTime(), table=<comme nt>, default=ColumnDefault(<function utcnow at 0x03D2E3F0>)), Column('flag' , Integer(), table=<comment>, default=ColumnDefault(0)), Column('author_id ', Integer(), ForeignKey('user.id'), table=<comment>), Column('photo_id', I nteger(), ForeignKey('photo.id'), table=<comment>), schema=None)})
db.Model.metadata
存储MetaData类实例,MetaData
类实例存储Table类实例的集合,而Table
类实例存储模型类对应的数据库表字段信息,可以通过模型类的__table__
属性获取。create_all()
方法实际调用的是db.Model.metadata.create_all
方法,这个方法会将Table
类实例存储的信息转换为数据库模式(通过sqlclchemy.sql.ddl.SchemaGenerator)。
顺便说一句,因为表信息存储在特定的基类中,所以为了正确创建数据库表,你需要对模型类继承的基类调用create_all()
方法,即db.create_all()
,或是Base.metadata.create_all(engine)
。如果你在测试时新创建一个db
对象或是Base
基类,那么它是不会包含表信息的。
这篇文章来自我在知乎写的这个回答,作为备份。