0

SQLAlchemyをデータストアとして使用するようにライブラリを変換しています。PickleType 列の柔軟性は気に入っていますが、SA オブジェクト (テーブル行) をピクルするときはうまく機能しないようです。unpickle 時に setstate と getstate をオーバーロードしてクエリ + セッション マージを実行しても、その pickle 境界を越えた参照整合性はありません。つまり、オブジェクトのコレクションを照会することはできません。

class Bar(Base):
    id = Column(Integer, primary_key=True)
    __tablename__ = 'bars'
    foo_id = Column(Integer, ForeignKey('foos.id'), primary_key=True)

class Foo(Base):
    __tablename__ = 'foos'
    values = Column(PickleType)
    #values = relationship(Bar)  # list interface (one->many), but can't assign a scalar or use a dictionary
    def __init__(self):
        self.values = [Bar(), Bar()]

        # only allowed with PickleType column
        #self.values = Bar()
        #self.values = {'one' : Bar()}
        #self.values = [ [Bar(), Bar()], [Bar(), Bar()]]

# get all Foo's with a Bar whose id=1
session.query(Foo).filter(Foo.values.any(Bar.id == 1)).all()

1 つの回避策は、ここで行われているように、独自の変更可能なオブジェクト タイプを実装することです。私は、コレクションを横断し、それらをより単純な1->多の関係に追加する、ある種のフラット化スキームがあると想像しています。おそらく、平坦化されたリストは、ピクルされたコレクションのオブジェクトへの弱参照でなければならないのでしょうか?

変更や参照を追跡するのは面白くないように思えますし、他の場所で SA 行を pickling する例を見つけることができません (おそらく、私の設計が悪いことを示しているのでしょうか?)。何かアドバイス?

EDIT 1:いくつかの議論の後、リクエストを簡素化しました。スカラーまたはコレクションとして動作できる単一のプロパティを探しています。ここに私の(失敗した)試みがあります:

from sqlalchemy import MetaData, Column, Integer, PickleType, String, ForeignKey, create_engine
from sqlalchemy.orm import relationship, Session
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm.collections import attribute_mapped_collection


# from http://www.sqlalchemy.org/trac/browser/examples/vertical
from sqlalchemy_examples.vertical import dictlike_polymorphic as dictlike

metadata = MetaData()
Base = declarative_base()
engine = create_engine('sqlite://', echo=True)
Base.metadata.bind = engine
session = Session(engine)


class AnimalFact(dictlike.PolymorphicVerticalProperty, Base):
    """key/value attribute whose value can be one of several types"""
    __tablename__ = 'animalfacts'
    type_map = {#str: ('string', 'str_value'),
                list: ('list', 'list_value'),
                tuple: ('tuple', 'tuple_value')}
    id = Column(Integer, primary_key=True)
    animal_id = Column(Integer, ForeignKey('animal.id'), primary_key=True)
    key = Column(String, primary_key=True)
    type = Column(String)
    #str_value = Column(String)
    list_value = relationship('StringEntry')
    tuple_value = relationship('StringEntry2')


class Animal(Base, dictlike.VerticalPropertyDictMixin):
    __tablename__ = 'animal'
    _property_type = AnimalFact
    _property_mapping = 'facts'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    facts = relationship(AnimalFact, backref='animal',
                          collection_class=attribute_mapped_collection('key'))

    def __init__(self, name):
        self.name = name


class StringEntry(Base):
    __tablename__ = 'stringentry'
    id = Column(Integer, primary_key=True)
    animalfacts_id = Column(Integer, ForeignKey('animalfacts.id'))
    value = Column(String)

    def __init__(self, value):
        self.value = value


class StringEntry2(Base):
    __tablename__ = 'stringentry2'
    id = Column(Integer, primary_key=True)
    animalfacts_id = Column(Integer, ForeignKey('animalfacts.id'))
    value = Column(String)

    def __init__(self, value):
        self.value = value

Base.metadata.create_all()


a = Animal('aardvark')
a['eyes'] = [StringEntry('left side'), StringEntry('right side')]  # works great
a['eyes'] = (StringEntry2('left side'), StringEntry2('right side'))  # works great
#a['cute'] = 'sort of'  # failure
4

1 に答える 1

3

PickleTypeは、実際には、押しのけたい任意のオブジェクトがあるエッジケースを回避するためのハックな方法です。PickleTypeを使用すると、それらをフィルタリング/クエリできるなど、関係上の利点を放棄することになります。

したがって、ORMマップされたオブジェクトをPickleに配置することは、基本的にひどい考えです。

スカラー値のコレクションが必要な場合は、従来のマッピングとrelationship()をassociation_proxyと組み合わせて使用​​します。http://docs.sqlalchemy.org/en/rel_0_7/orm/extensions/associationproxy.html#simplifying-scalar-collectionsを参照してください。

「または辞書」。attribute_mapped_collectionを使用します:http: //docs.sqlalchemy.org/en/rel_0_7/orm/collections.html#dictionary-collections

「辞書とスカラー」:attribute_mapped_collectionとassociation_proxyの両方を組み合わせる:http://docs.sqlalchemy.org/en/rel_0_7/orm/extensions/associationproxy.html#proxying-to-dictionary-based-collections

編集1:ええと、あなたはそこで本当に難解で複雑な例を掘り下げました。association_proxyは、オブジェクトをスカラーのように動作させたいこれらのケースを回避するためのはるかに簡単な方法です。したがって、「垂直」の例のクレイジーな定型文がなくても、これは非常に複雑なので避けたいと思います。あなたの例は主キーのスタイルについて未定のようだったので、私は複合バージョンを使用しました。サロゲートとコンポジットを1つのテーブルに混在させることはできません(混合することはできますが、関係的に正しくありません。キーは、行を識別する最小の単位である必要があります-http://en.wikipedia.org/wiki/Unique_keyが適していますこれに関するさまざまな主題を読んだトップレベル)。

from sqlalchemy import Integer, String, Column, create_engine, ForeignKey, ForeignKeyConstraint
from sqlalchemy.orm import relationship, Session
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy

Base = declarative_base()

class AnimalFact(Base):
    """key/value attribute whose value can be either a string or a list of strings"""
    __tablename__ = 'animalfacts'

    # use either surrogate PK id, or the composite animal_id/key - but
    # not both.   id/animal_id/key all together is not a proper key.
    # Personally I'd go for "id" here, but here's the composite version.

    animal_id = Column(Integer, ForeignKey('animal.id'), primary_key=True)
    key = Column(String, primary_key=True)

    # data
    str_value = Column(String)
    _list_value = relationship('StringEntry')

    # proxy list strings
    list_proxy = association_proxy('_list_value', 'value')

    def __init__(self, key, value):
        self.key = key
        self.value = value

    @property
    def value(self):
        if self.str_value is not None:
            return self.str_value
        else:
            return self.list_proxy

    @value.setter
    def value(self, value):
        if isinstance(value, basestring):
            self.str_value = value
        elif isinstance(value, list):
            self.list_proxy = value
        else:
            assert False

class Animal(Base):
    __tablename__ = 'animal'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    _facts = relationship(AnimalFact, backref='animal',
                          collection_class=attribute_mapped_collection('key'))
    facts = association_proxy('_facts', 'value')

    def __init__(self, name):
        self.name = name

    # dictionary interface around "facts".
    # I'd just use "animal.facts" here, but here's how to skip that.
    def __getitem__(self, key):
        return self.facts.__getitem__(key)

    def __setitem__(self, key, value):
        self.facts.__setitem__(key, value)

    def __delitem__(self, key):
        self.facts.__delitem__(key)

    def __contains__(self, key):
        return self.facts.__contains__(key)

    def keys(self):
        return self.facts.keys()


class StringEntry(Base):
    __tablename__ = 'myvalue'
    id = Column(Integer, primary_key=True)
    animal_id = Column(Integer)
    key = Column(Integer)
    value = Column(String)

    # because AnimalFact has a composite PK, we need
    # a composite FK.
    __table_args__ = (ForeignKeyConstraint(
                        ['key', 'animal_id'],
                        ['animalfacts.key', 'animalfacts.animal_id']),
                    )
    def __init__(self, value):
        self.value = value

engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)

session = Session(engine)


# create a new animal
a = Animal('aardvark')

a['eyes'] = ['left side', 'right side']

a['cute'] = 'sort of'

session.add(a)
session.commit()
session.close()

for animal in session.query(Animal):
    print animal.name, ",".join(["%s" % animal[key] for key in animal.keys()])
于 2012-08-27T19:59:59.490 に答える