2012-04-08 86 views
1

我「指數」 SQLAlchemy的模型屬性所以說我有一些類X,Y和Z使用的SQLAlchemy聲明的語法來定義一些簡單的列和關係怎麼可能是主鍵和關係

要求:

  1. 在類級別,(X|Y|Z).primary_keys返回
    各個類的主鍵(InstrumentedAttribute 對象)的集合我也想(X|Y|Z).relations引用類的在同一 關係方式

  2. 在實例級,我想同樣的屬性引用 這些屬性實例化的價值觀,他們是否已經 用自己的構造稀少,個別屬性
    制定者,或什麼的SQLAlchemy做時,它從db檢索 行。

到目前爲止,我有以下幾點。

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__函數之前運行,這意味着實例屬性當時尚未填充,所以我無法在其上創建「鏡頭」。 我也想知道屬性事件set可能有幫助。這是我的下一次嘗試,但我仍然懷疑它是否是最合適的方式。

總而言之,我真的不知道我是否錯過了一些非常優雅的方法來解決這個問題。

回答

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