SQLAlchemy:动态'multiple inheritance'与'association_proxy'创建函数
我现在正在尝试用SQLAlchemy(使用ext.declarative)创建以下数据库结构:
我有一个基础类 MyBaseClass
,它为我所有公开的类提供一些共同的功能,还有一个混合类 MetadataMixin
,它提供从imdb查询元数据并存储的功能。每个继承了 MetadataMixin
的类都有一个字段 persons
,这个字段提供了一个多对多的关系,连接到 Person
类的实例,还有一个字段 persons_roles
,它提供了一个一对多的关系,连接到一个对象(每个子类一个),这个对象存储了具体的 Person
在子类实例中扮演的 role
。
这是我目前代码的简化版本:
from sqlalchemy import Column, Integer, Enum, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class MyBaseClass(object):
"""Base class for all publicly accessible classes"""
id = Column(Integer, primary_key=True)
class Person(MyBaseClass):
"""A Person"""
name = Column(Unicode)
movies = association_proxy('movie_roles', 'movie',
creator=lambda m: _PersonMovieRole(movie=m))
shows = association_proxy('show_roles', 'show',
creator=lambda s: _PersonShowRole(show=s=))
class _PersonMovieRole(Base):
"""Role for a Person in a Movie"""
__tablename__ = 'persons_movies'
id = Column(Integer, primary_key=True)
role = Column(Enum('none', 'actor', 'writer', 'director', 'producer'),
default='none')
person_id = Column(Integer, ForeignKey('persons.id'))
person = relationship('Person', backref='movie_roles')
movie_id = Column(Integer, ForeignKey('movies.id'))
movie = relationship('Movie', backref='persons_roles')
class _PersonShowRole(Base):
"""Role for a Person in a Show"""
__tablename__ = 'persons_shows'
id = Column(Integer, primary_key=True)
role = Column(Enum('none', 'actor', 'writer', 'director', 'producer'),
default='none')
person_id = Column(Integer, ForeignKey('persons.id'))
person = relationship('Person', backref='show_roles')
show_id = Column(Integer, ForeignKey('shows.id'))
show = relationship('Episode', backref='persons_roles')
class MetadataMixin(object):
"""Mixin class that provides metadata-fields and methods"""
# ...
persons = association_proxy('persons_roles', 'person',
creator= #...???...#)
class Movie(Base, MyBaseClass, MetadataMixin):
#....
pass
我想做的是创建一个通用的 creator
函数,用于 association_proxy
,这个函数可以根据具体实例的类创建一个 PersonMovieRole
或者 PersonShowRole
对象。现在我遇到的问题是,我不知道怎么把调用类传递给这个创建函数。这样做可能吗?或者有没有更简单的方法来实现我想要的功能?
1 个回答
在你定义 persons
这个字段的时候,其实你并不知道它最终会属于哪个类。Python 会把类成员的信息放在一个准备好的字典里,然后通过 type.__new__
创建类,但在这个过程中,这些成员已经完全定义好了。
所以,你需要直接把所需的信息提供给 mixin,并且接受代码中会出现一些小的重复。我建议使用类似下面这样的接口:
class Movie(Base, MyBaseClass, MetadataMixin('Movie')):
pass
你也不能使用 MetadataMixin(Movie)
,原因是一样的:Movie
在创建的时候需要它的基类已经完全定义好。
要实现这种“参数化类”,你只需要用一个函数:
def MetadataMixin(cls_name):
"""Mixin class that provides metadata-fields and methods"""
person_role_cls_name = 'Person%sRole' % cls_name
person_role_cls = Base._decl_class_registry[person_role_cls_name]
class Mixin(object):
# ...
persons = association_proxy('persons_roles', 'person',
creator=person_role_cls)
return Mixin
这样做是可行的,因为我们在 Base._decl_class_registry
中查找的不是最终的类(比如 Movie
),而是关联对象(比如 PersonMovieRole
)。