1

つまり、SQLAlchemy宣言型構文を使用して、いくつかの単純な列と関係を定義するクラスX、Y、およびZがあるとします。

要件:

  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_lensNonetypeで認識されず、評価されないという奇妙な断続的なエラーが発生します。

インスタンスレベルでどのように進めるべきかよくわかりません。イベントインターフェイスを調べました。ORMイベントinitが表示されますが、モデルで定義された関数の前に実行さ__init__れているようです。つまり、その時点ではインスタンス属性がまだ入力されていないため、「レンズ」を作成できません。また、Attributeイベントsetが役立つのではないかと思います。それが私の次の試みですが、それが最も適切な方法であるかどうかはまだ疑問です。

全体として、この問題に取り組むための本当にエレガントな方法が欠けているのではないかと思います。

4

1 に答える 1

3

宣言型のメタクラスは、「問題があり、XMLを使用すると、2つの問題が発生する」という古い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 に答える