Python 我怎么能';指数';作为主键和关系的SQLAlchemy模型属性

Python 我怎么能';指数';作为主键和关系的SQLAlchemy模型属性,python,sqlalchemy,Python,Sqlalchemy,假设我有一些类X、Y和Z,它们使用SQLAlchemy声明性语法来定义一些简单的列和关系 要求: 在类级别,(X | Y | Z)。主键返回一组 相应的类“主键”(InstrumentedAttribute 对象)我还希望(X | Y | Z).relations引用类的 同理 在实例级别,我希望引用相同的属性 这些属性的实例化值,无论它们是否 使用我自己的构造函数、单个属性填充 setters,或者SQLAlchemy在从 db 到目前为止,我有以下几点 import collections

假设我有一些类X、Y和Z,它们使用SQLAlchemy声明性语法来定义一些简单的列和关系

要求:

  • 在类级别,
    (X | Y | Z)。主键
    返回一组
    相应的类“主键”(
    InstrumentedAttribute
    对象)我还希望
    (X | Y | Z).relations
    引用类的 同理

  • 在实例级别,我希望引用相同的属性 这些属性的实例化值,无论它们是否 使用我自己的构造函数、单个属性填充
    setters,或者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\u class\u lens
    中未被识别,并评估为Nonetype

    我不太确定应该如何在实例级别继续。 我已经研究了事件界面。我看到ORM事件
    init
    ,但它似乎在我的模型上定义的
    \uuuu init\uuuu
    函数之前运行,这意味着当时还没有填充实例属性,因此我无法在它们上构建“镜头”。 我还想知道属性event
    set
    是否有帮助。这是我的下一次尝试,尽管我仍然怀疑这是否是最合适的方式


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

    我认为元类与声明性的关系是按照老的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