SqlAlchemy 2.0 中文文档(三十)(2)https://developer.aliyun.com/article/1562436
明确指定类
提示
如果明确的类在应用程序中占主导地位,请考虑改用DeferredReflection
。
automap
扩展允许以与DeferredReflection
类相似的方式明确定义类。 从AutomapBase
继承的类表现得像常规的声明性类一样,但在构造后不会立即映射,而是在调用AutomapBase.prepare()
时映射。 AutomapBase.prepare()
方法将利用我们基于所使用的表名建立的类。 如果我们的模式包含表user
和address
,我们可以定义要使用的一个或两个类:
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, )
从上面的映射中,我们现在会有 User
和 Address
两个类,其中从 User
到 Address
的集合被称为 User.addresses
:
User, Address = Base.classes.User, Base.classes.Address u1 = User(addresses=[Address(email="foo@bar.com")])
关系检测
automap 的绝大部分工作是根据外键生成relationship()
结构。它对于多对一和一对多关系的工作机制如下:
- 已知映射到特定类的给定
Table
,会被检查其是否存在ForeignKeyConstraint
对象。 - 对于每一个
ForeignKeyConstraint
,远程的Table
对象被匹配到其要映射的类,如果有的话,否则将被跳过。 - 由于我们正在检查的
ForeignKeyConstraint
对应于从直接映射类的引用,该关系将被设置为指向被引用类的多对一关系;在被引用的类上将创建一个相应的一对多反向引用,指向该类。 - 如果
ForeignKeyConstraint
的任何一列不可为空(例如,nullable=False
),将会将all, delete-orphan
的relationship.cascade
关键字参数添加到要传递给关联或反向引用的关键字参数中。如果ForeignKeyConstraint
报告对于一组非空列设置了CASCADE
或对于可为空列设置了SET NULL
的ForeignKeyConstraint.ondelete
,则将在关系关键字参数集合中将选项relationship.passive_deletes
标志设置为True
。请注意,并非所有后端都支持删除操作的反射。 - 关联的名称是使用
AutomapBase.prepare.name_for_scalar_relationship
和AutomapBase.prepare.name_for_collection_relationship
可调用函数确定的。重要的是要注意,默认的关联命名从实际类名派生名称。如果您通过声明为特定类指定了显式名称,或指定了替代类命名方案,则关系名称将从该名称派生。 - 检查类以查找与这些名称匹配的现有映射属性。如果在一侧检测到一个属性,但在另一侧没有,则
AutomapBase
尝试在缺失的一侧创建一个关联,然后使用relationship.back_populates
参数指向新关联到另一侧。 - 在通常情况下,如果任何一侧都没有关联,则
AutomapBase.prepare()
会在“多对一”一侧产生一个relationship()
,并使用relationship.backref
参数将其与另一侧匹配。 relationship()
的生成以及可选的backref()
的生成被交由AutomapBase.prepare.generate_relationship
函数处理,该函数可以由最终用户提供,以增强传递给relationship()
或backref()
的参数,或者利用这些函数的自定义实现。
自定义关系参数
AutomapBase.prepare.generate_relationship
钩子可用于向关系添加参数。对于大多数情况,我们可以利用现有的generate_relationship()
函数,在用自己的参数扩充给定关键字字典后返回对象。
下面是如何向所有一对多关系发送relationship.cascade
和relationship.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
参数的关系。生成这些关系的过程如下:
- 给定的
Table
在分配任何映射类之前将被检查其ForeignKeyConstraint
对象。 - 如果表包含两个且仅两个
ForeignKeyConstraint
对象,并且此表中的所有列都是这两个ForeignKeyConstraint
对象的成员,则假定该表是“次要”表,并且不会直接映射。 Table
引用的两个(对于自引用的情况则为一个)外部表会与它们将要映射到的类匹配,如果有的话。- 如果两边的映射类被定位,那么在两个类之间将创建一个多对多的双向
relationship()
/backref()
对。 - 对于多对多的覆盖逻辑与一对多/多对一的逻辑相同;调用
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", }
从Engineer
到Employee
的外键不是用于关系,而是用于在两个类之间建立联合继承。
请注意,这意味着 automap 将不会为从子类到超类的外键生成 任何 关系。 如果映射还具有从子类到超类的实际关系,那么这些关系需要显式说明。 如下,由于从Engineer
到Employee
有两个单独的外键,我们需要设置我们想要的关系以及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( 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)
自定义关系参数
AutomapBase.prepare.generate_relationship
钩子可用于向关系添加参数。对于大多数情况,我们可以利用现有的 generate_relationship()
函数,在使用我们自己的参数扩充给定的关键字字典后返回对象。
下面是如何将 relationship.cascade
和 relationship.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
参数的关系。生成这些关系的过程如下:
- 在为其分配任何映射类之前,将检查给定的
Table
是否包含ForeignKeyConstraint
对象。 - 如果表包含两个并且仅有两个
ForeignKeyConstraint
对象,并且此表中的所有列都是这两个ForeignKeyConstraint
对象的成员,则假定该表是一个“次要”表,并且不会直接映射。 Table
所引用的两个(或一个,用于自引用)外部表将与它们将被映射到的类匹配,如果有的话。- 如果两侧的映射类位于同一处,则在两个类之间创建一个双向的多对多
relationship()
/backref()
对。 - 对于多对多的覆盖逻辑与一对多/多对一的逻辑相同;调用
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", }
从 Engineer
到 Employee
的外键不是用于关系,而是用于在两个类之间建立联合继承。
请注意,这意味着 automap 不会为从子类到超类的外键生成任何关系。如果映射实际上还有从子类到超类的关系,那么这些关系需要是显式的。在下面的例子中,由于从 Engineer
到 Employee
有两个单独的外键,我们需要设置我们想要的关系以及 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( autoload_with=engine, 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(autoload_with=engine)
SqlAlchemy 2.0 中文文档(三十)(4)https://developer.aliyun.com/article/1562438