つまり、SQLAlchemy宣言型構文を使用して、いくつかの単純な列と関係を定義するクラスX、Y、およびZがあるとします。
要件:
クラスレベルでは 、それぞれのクラスの「主キー」(オブジェクト)
(X|Y|Z).primary_keys
のコレクションを返します。同じ方法でクラスの関係 も参照したいと思います。InstrumentedAttribute
(X|Y|Z).relations
インスタンスレベルでは、同じ属性がそれらの属性のインスタンス化された値を参照するようにします。これらの値は、独自のコンストラクター、個々の属性
セッター、またはデータベースから行を取得するときに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_lens
Nonetypeで認識されず、評価されないという奇妙な断続的なエラーが発生します。
インスタンスレベルでどのように進めるべきかよくわかりません。イベントインターフェイスを調べました。ORMイベントinit
が表示されますが、モデルで定義された関数の前に実行さ__init__
れているようです。つまり、その時点ではインスタンス属性がまだ入力されていないため、「レンズ」を作成できません。また、Attributeイベントset
が役立つのではないかと思います。それが私の次の試みですが、それが最も適切な方法であるかどうかはまだ疑問です。
全体として、この問題に取り組むための本当にエレガントな方法が欠けているのではないかと思います。