如何“索引”作为主键和关系的SQLAlchemy模型属性

时间:2012-04-08 17:41:10

标签: python sqlalchemy

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

要求:

  1. 在班级,(X|Y|Z).primary_keys会返回
    的集合 各个班级的“主键”(InstrumentedAttribute 对象)我也希望(X|Y|Z).relations引用该类' 关系的方式相同

  2. 在实例级别,我希望引用相同的属性 那些属性的实例化值,无论它们是否已经存在 使用我自己的构造函数填充,个别属性
    setters,或SQLAlchemy从中检索行时所做的任何事情 db。

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

    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'模块据称在{{1}中无法识别并且逃避到Nonetype。

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

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

1 个答案:

答案 0 :(得分: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