Стратегия SQLAlchemy: ORM + Core для классов с большими объемами данных - PullRequest
1 голос
/ 26 апреля 2019

Очевидно, использование ORM и Core в тандеме возможно , но я не смог найти какого-либо твердого объяснения стратегии для этого.

Вот класс использования:

class DataHolder(Base):
    __tablename__ = 'data_holder'

    id = Column(Integer, primary_key=True)
    dataset_id = Column(Integer, ForeignKey('data_set.id'))
    name = Column(String)

    _dataset_table = Table('data_set', Base.metadata,
        Column('id', Integer, primary_key=True),
    )

    _datarows_table = Table('data_rows', Base.metadata,
        Column('id', Integer, primary_key=True),
        Column('dataset_id', None, ForeignKey('data_set.id')),
        Column('row', Integer),
        Column('col_0', Integer),
        Column('col_1', Integer),
        Column('col_2', Integer),
    )

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

    def _pack_info(self):
        # Return __class__ and other info needed for packing.

    def _unpack_info(self):
        # Return info needed for unpacking.

name следует сохранить с помощью ORM . data, который будет большим массивом NumPy (или аналогичным типом), должен быть сохранен через Core .

Существует промежуточная таблица 'data_set', которая существует с целью отношения «многие к одному» между DataHolder и данными. Это позволяет наборам данных существовать независимо в некоторой библиотеке. (Единственная цель этой таблицы - генерировать идентификаторы для новых наборов данных.)

Фактическое постоянство было бы достигнуто через класс, который реализует некоторые слушатели, такие как следующие.

class PersistenceManager:
    def __init__(self):
        self.init_db()
        self.init_listeners()

    def init_db(self):
        engine = create_engine('sqlite:///path/to/database.db')
        self.sa_engine = engine
        self.sa_sessionmaker = sessionmaker(bind=engine)
        Base.metadata.create_all(engine)

    def init_listeners(self):
        @event.listens_for(Session, 'transient_to_pending')
        def pack_data(session, instance):
            try:
                pack_info = instance._pack_info()
                # Use Core to execute INSERT for bulky data.
            except AttributeError:
                pass
        @event.listens_for(Session, 'loaded_as_persistent')
        def unpack_data(session, instance):
            try:
                unpack_info = instance._unpack_info()
                # Use Core to execute SELECT for bulky data.
            except AttributeError:
                pass

    def persist(self, obj):
        session.add(obj)

    def load(self, class_, spec):
        obj = session.query(class_).filter_by(**spec).all()[-1]
        return obj

    def session_scope(self):
        session = self.sa_sessionmaker()
        try:
            yield session
            session.commit()
        except:
            session.rollback()
            raise
        finally:
            session.close()

Идея состоит в том, что всякий раз, когда DataHolder сохраняется, его данные также сохраняются в то же (или почти одинаковое) время.

Прослушивание событий 'transient_to_pending' (для «упаковки») и 'loaded_as_persistent' (для «распаковки») будет работать для простого сохранения и загрузки. Однако, похоже, следует позаботиться и о прослушивании события 'pending_to_transient'. В случае отката данные, добавленные через Core, не будут извлекаться из базы данных так же, как данные, связанные с ORM.

Есть ли другой, лучший способ манипулировать этим поведением, кроме прослушивания 'pending_to_transient'? Это может вызвать проблемы в случае, когда два разных DataHolder ссылаются на один и тот же набор данных: один DataHolder может выполнить откат, удалив набор данных из базы данных, так что другой DataHolder больше не сможет его использовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...