SQLAlchemy: замена объекта на новый, по умолчанию

#sqlalchemy

#sqlalchemy

Вопрос:

Я хочу создать новый экземпляр объекта SQLAlchemy, чтобы поля были заполнены значениями по умолчанию, но я хочу передать это в базу данных, генерирующую UPDATE строку, которая уже существует с тем же первичным ключом, эффективно сбрасывая ее до значений по умолчанию. Есть ли какой-нибудь простой способ сделать это?

Ответ №1:

Я пытался это сделать и потерпел неудачу, потому что сеанс SQLAlchemy отслеживает состояние объектов. Таким образом, нет простого способа сделать сеанс для отслеживания нового объекта как постоянного.

Но вы хотите вернуть объекту значение по умолчанию, не так ли? Есть простой способ сделать это:

 from sqlalchemy.ext.declarative import declarative_base

class Base(object):

    def reset(self):
        for name, column in self.__class__.__table__.columns.items():
            if column.default is not None:
                setattr(self, name, column.default.execute())

Base = declarative_base(bind=engine, cls=Base)
  

Это добавляет reset метод ко всем вашим классам модели.

Вот полный рабочий пример, с которым можно поработать:

 import os
from datetime import datetime


from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql import functions


here = os.path.abspath(os.path.dirname(__file__))
engine = create_engine('sqlite:///%s/db.sqlite' % here, echo=True)
Session = sessionmaker(bind=engine)


class Base(object):

    def reset(self):
        for name, column in self.__class__.__table__.columns.items():
            if column.default is not None:
                setattr(self, name, column.default.execute())

Base = declarative_base(bind=engine, cls=Base)


class Thing(Base):
    __tablename__ = 'things'

    id = Column(Integer, primary_key=True)
    value = Column(String(255), default='default')
    ts1 = Column(DateTime, default=datetime.now)
    ts2 = Column(DateTime, default=functions.now())

    def __repr__(self):
        return '<Thing(id={0.id!r}, value={0.value!r}, ' 
               'ts1={0.ts1!r}, ts2={0.ts2!r})>'.format(self)


if __name__ == '__main__':
    Base.metadata.drop_all()
    Base.metadata.create_all()

    print("---------------------------------------")
    print("Create a new thing")
    print("---------------------------------------")
    session = Session()
    thing = Thing(
        value='some value',
        ts1=datetime(2014, 1, 1),
        ts2=datetime(2014, 2, 2),
    )
    session.add(thing)
    session.commit()
    session.close()

    print("---------------------------------------")
    print("Quering it from DB")
    print("---------------------------------------")
    session = Session()
    thing = session.query(Thing).filter(Thing.id == 1).one()
    print(thing)
    session.close()

    print("---------------------------------------")
    print("Reset it to default")
    print("---------------------------------------")
    session = Session()
    thing = session.query(Thing).filter(Thing.id == 1).one()
    thing.reset()
    session.commit()
    session.close()

    print("---------------------------------------")
    print("Quering it from DB")
    print("---------------------------------------")
    session = Session()
    thing = session.query(Thing).filter(Thing.id == 1).one()
    print(thing)
    session.close()
  

Комментарии:

1. К сожалению, подход сброса не решает проблему для меня. Да, я хочу вернуться к значениям по умолчанию, но очень важно, чтобы я сделал это, создав новый экземпляр со значениями по умолчанию, и выбор между вставкой нового или обновлением существующего решается.

2. @PedroWerneck В этом случае попробуйте взломать состояние объекта . Открытый интерфейс доступен только для чтения, но его можно изменить. ИМХО, это грязное нестабильное решение (никто не дает гарантии, что оно будет работать в будущих версиях SQLAlchemy). Однако это может быть полезно для вас.

3. Я был бы рад, если бы вы могли отредактировать свой ответ и подробнее рассказать о том, как это сделать.

Ответ №2:

Есть ли какой-нибудь простой способ сделать это?

При дальнейшем рассмотрении, не совсем. Самым простым способом будет определить ваши значения по умолчанию в __init__ . Конструктор никогда не вызывается при извлечении объектов из базы данных, поэтому он совершенно безопасен. Вы также можете использовать внутренние функции, такие как current_timestamp() .

 class MyObject(Base):
    id = Column(sa.Integer, primary_key=True)
    column1 = Column(sa.String)
    column2 = Column(sa.Integer)
    columnN = Column(sa.String)
    updated = Column(sa.DateTime)

    def __init__(self, **kwargs):
        kwargs.setdefault('column1', 'default value')
        kwargs.setdefault('column2', 123)
        kwargs.setdefault('columnN', None)
        kwargs.setdefault('updated', sa.func.current_timestamp())
        super(MyObject, self).__init__(**kwargs)

default_obj = MyObject()
default_obj.id = old_id
session.merge(default_obj)
session.commit()
  

Комментарии:

1. Вы уверены? Для меня это вообще не работает. На самом деле, после фиксации у меня исходный объект остается неизменным, как будто ничего не произошло.

2. На самом деле я только что заметил, что мой тестовый код использовал init для установки некоторых значений, что, по-видимому, имеет большое значение. Слияние отлично работает для отредактированных значений, но не запускает функцию default(). Вы всегда можете просто создать новый экземпляр, скопировать ключ, удалить старый и добавить новый.