【问题标题】:How can I 'index' SQLAlchemy model attributes that are primary keys and relationships如何“索引”作为主键和关系的 SQLAlchemy 模型属性
【发布时间】:2012-04-21 08:02:57
【问题描述】:

假设我有一些类 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")

...
...

好的,这让我在课堂上顺利通过,虽然我觉得我在用元类做一些愚蠢的事情,而且我遇到了一些奇怪的间歇性错误,其中据称在 build_class_lens 中无法识别“sqlalchemy”模块,并且评估为 Nonetype。

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

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

【问题讨论】:

    标签: python sqlalchemy


    【解决方案1】:

    我认为带有声明性的元类是按照旧的 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
    

    【讨论】:

      猜你喜欢
      • 2013-10-01
      • 2013-09-19
      • 2014-08-19
      • 1970-01-01
      • 2013-05-27
      • 2018-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多