2012-04-08 2 views
1

So sind sagen, dass ich einige Klassen X, Y und Z SQLAlchemy deklarative Syntax mit einigen einfachen Spalten und Beziehungen definierenWie kann ich ‚index‘ SQLAlchemy Modell Attribute, die Primärschlüssel und Beziehungen

Anforderungen:

  1. auf Klassenebene, (X|Y|Z).primary_keys gibt eine Sammlung von
    der jeweiligen Klasse Primärschlüssel (InstrumentedAttribute Objekte) ich die Klasse Beziehungen in derselben verweisen auch (X|Y|Z).relations wollen Weg

  2. auf Instanzebene, würde ich die gleichen Attribute wie die Attribute instanzierte Werte verweisen, ob sie waren bevölkerten meine eigenen Konstrukteure verwenden, einzelne Attribute
    Einrichter, oder was auch immer SQLAlchemy tut, wenn es ruft Zeilen von der DB ab.

Bisher habe ich folgendes.

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") 

... 
... 

Ok, so dass wird mir auf der Ebene der Klassen durch, obwohl ich fühle mich wie ich dumme Sachen mit metaclasses tue, und ich einige seltsame intermittierende Fehler, bei denen das ‚sqlalchemy‘ -Modul angeblich nicht in erkannt wird build_class_lens und evals zu Nonetype.

Ich bin mir nicht sicher, wie ich auf der Instanzenebene vorgehen soll. Ich habe in die Ereignisschnittstelle geschaut. Ich sehe das ORM-Ereignis init, aber es scheint vor der __init__-Funktion zu laufen, die auf meinen Modellen definiert ist, was bedeutet, dass die Instanzattribute zu dieser Zeit noch nicht gefüllt waren, also kann ich meine "Linse" nicht auf ihnen aufbauen. Ich frage mich auch, ob das Attribut Ereignis set hilfreich sein könnte. Das ist mein nächster Versuch, obwohl ich mich immer noch frage, ob es der passendste Weg ist.

Alles in allem frage ich mich wirklich, ob mir eine wirklich elegante Möglichkeit fehlt, dieses Problem anzugehen.

Antwort

3

Ich denke, die metaclass Sache mit deklarativen geht nach dem alten XML-Sprichwort, "Wenn Sie ein Problem haben, und XML verwenden, haben Sie jetzt zwei Probleme". Die Metaklasse in Python ist genauso nützlich wie ein Hook, um die Konstruktion neuer Klassen zu erkennen, und das ist es auch schon. Wir haben jetzt genug Ereignisse, dass es keine Notwendigkeit mehr geben sollte, eine Metaklasse zu verwenden, die über das deklarative bereits hinausgeht.

In diesem Fall habe ich ein wenig weiter gehen würde und sagen, dass der Ansatz, diese Sammlungen aktiv aufzubauen, zu versuchen, nicht wirklich wert ist - es ihnen viel einfacher ist, faul zu erzeugen, wie unten:

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