1

所以说我有一些类 X、Y 和 Z 使用 SQLAlchemy 声明性语法来定义一些简单的列和关系

要求:

  1. 在类级别,(X|Y|Z).primary_keys返回
    各个类的'主键'(InstrumentedAttribute 对象)的集合我也想(X|Y|Z).relations以相同的方式引用类的关系

  2. 在实例级别,我希望相同的属性引用这些属性的实例化值,无论它们是使用我自己的构造函数、单独的属性
    设置器还是 SQLAlchemy 在从数据库中检索行时所做的任何填充。

到目前为止,我有以下内容。

import collections 
import sqlalchemy
import sqlalchemy.ext.declarative
from sqlalchemy import MetaData, Column, Table, ForeignKey, Integer, String, Date, Text
from sqlalchemy.orm import relationship, backref

class IndexedMeta(sqlalchemy.ext.declarative.DeclarativeMeta):
        """Metaclass to initialize some class-level collections on models"""
    def __new__(cls, name, bases, defaultdict):
        cls.pk_columns = set()
        cls.relations = collections.namedtuple('RelationshipItem', 'one many')( set(), set())
        return super().__new__(cls, name, bases, defaultdict)

Base = sqlalchemy.ext.declarative.declarative_base(metaclass=IndexedMeta)


def build_class_lens(cls, key, inst):
    """Populates the 'indexes' of primary key and relationship attributes with the attributes' names. Additionally, separates "x to many" relationships from "x to one" relationships and associates "x to one" relathionships with the local-side foreign key column"""
    if isinstance(inst.property, sqlalchemy.orm.properties.ColumnProperty):
        if inst.property.columns[0].primary_key:
            cls.pk_columns.add(inst.key)

    elif isinstance(inst.property, sqlalchemy.orm.properties.RelationshipProperty):
        if inst.property.direction.name == ('MANYTOONE' or 'ONETOONE'):
            local_column = cls.__mapper__.get_property_by_column(inst.property.local_side[0]).key
            cls.relations.one.add( (local_column, inst.key) )
        else:
            cls.relations.many.add(inst.key)


sqlalchemy.event.listen(Base, 'attribute_instrument', build_class_lens)

class Meeting(Base):
    __tablename__ = 'meetings'
    def __init__(self, memo):
        self.memo = memo
    id = Column(Integer, primary_key=True)
    date = Column(Date)
    memo = Column('note', String(60), nullable=True)
    category_name = Column('category', String(60), ForeignKey('categories.name'))
    category = relationship("Category", backref=backref('meetings'))
    topics = relationship("Topic",
        secondary=meetings_topics,
        backref="meetings")

...
...

好的,所以这让我在课堂上有所收获,尽管我觉得我在用元类做一些愚蠢的事情,并且我遇到了一些奇怪的间歇性错误,其中据称“sqlalchemy”模块未被识别build_class_lens并且评估为 Nonetype。

我不太确定我应该如何在实例级别进行。我查看了事件界面。我看到了 ORM 事件init,但它似乎__init__在我的模型上定义的函数之前运行,这意味着当时尚未填充实例属性,所以我无法在它们上构建我的“镜头”。我还想知道 Attribute 事件set是否有帮助。那是我的下一次尝试,尽管我仍然想知道这是否是最合适的方式。

总而言之,我真的想知道我是否错过了一些非常优雅的方法来解决这个问题。

4

1 回答 1

3

我认为带有声明性的元类事物遵循旧的 XML 说法,“如果你有问题,使用 XML,现在你有两个问题”。Python 中的元类非常有用,可以作为检测新类构造的钩子,仅此而已。我们现在有足够的事件,除了声明性已经做的事情之外,不需要使用元类。

在这种情况下,我会进一步说,尝试积极构建这些集合的方法并不值得 - 懒惰地生成它们要容易得多,如下所示:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
import collections
from sqlalchemy.orm.properties import RelationshipProperty

class memoized_classproperty(object):
    """A decorator that evaluates once at the class level, 
       assigns the new value to the class.
    """

    def __init__(self, fget, doc=None):
        self.fget = fget
        self.__doc__ = doc or fget.__doc__
        self.__name__ = fget.__name__

    def __get__(desc, self, cls):
        result = desc.fget(cls)
        setattr(cls, desc.__name__, result)
        return result

class Lens(object):
    @memoized_classproperty
    def pk_columns(cls):
        return class_mapper(cls).primary_key

    @memoized_classproperty
    def relations(cls):
        props = collections.namedtuple('RelationshipItem', 'one many')(set(), set())
        # 0.8 will have "inspect(cls).relationships" here
        mapper = class_mapper(cls)
        for item in mapper.iterate_properties:
            if isinstance(item, RelationshipProperty):
                if item.direction.name == ('MANYTOONE' or 'ONETOONE'):
                    local_column = mapper.get_property_by_column(item.local_side[0]).key
                    props.one.add((local_column, item.key))
                else:
                    props.many.add(item.key)
        return props

Base= declarative_base(cls=Lens)

meetings_topics = Table("meetings_topics", Base.metadata,
    Column('topic_id', Integer, ForeignKey('topic.id')),
    Column('meetings_id', Integer, ForeignKey('meetings.id')),
)
class Meeting(Base):
    __tablename__ = 'meetings'
    def __init__(self, memo):
        self.memo = memo
    id = Column(Integer, primary_key=True)
    date = Column(Date)
    memo = Column('note', String(60), nullable=True)
    category_name = Column('category', String(60), ForeignKey('categories.name'))
    category = relationship("Category", backref=backref('meetings'))
    topics = relationship("Topic",
        secondary=meetings_topics,
        backref="meetings")

class Category(Base):
    __tablename__ = 'categories'
    name = Column(String(50), primary_key=True)

class Topic(Base):
    __tablename__ = 'topic'
    id = Column(Integer, primary_key=True)

print Meeting.pk_columns
print Meeting.relations.one

# assignment is OK, since prop is memoized
Meeting.relations.one.add("FOO")

print Meeting.relations.one
于 2012-04-10T01:06:39.577 回答