SQLAlchemy:动态'multiple inheritance'与'association_proxy'创建函数

5 投票
1 回答
1570 浏览
提问于 2025-04-16 16:17

我现在正在尝试用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 个回答

3

在你定义 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)。

撰写回答