Я использую unique_constructor для взаимодействия с базой данных: https://github.com/sqlalchemy/sqlalchemy/wiki/UniqueObject, но похоже, что я использую его неправильно, или я использую postgresql неправильно.
У меня есть объект с 2 внешними ключами и счетчиком событий.Внешние ключи: один ведет к таблице с фактическими данными, другой ведет к некоторой идентификационной таблице.Когда объект в базе данных уже существует, я хочу увеличить счетчик, в противном случае я создаю его и устанавливаю текущий счетчик.
Вот код:
from sqlalchemy import Column
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy import ForeignKey
from sqlalchemy import create_engine
from sqlalchemy.orm import relationship
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.scoping import scoped_session
from sqlalchemy.ext.declarative import declarative_base
BaseTable = declarative_base()
class Database:
engine = create_engine("postgresql://postgres:postgres@127.0.0.1:5432/test", echo=True, pool_recycle=3600)
session = scoped_session(sessionmaker(bind=engine, expire_on_commit=False))
db = Database()
def unique_constructor(db, hashfunc, queryfunc):
"""
https://github.com/sqlalchemy/sqlalchemy/wiki/UniqueObject
"""
def decorate(cls):
def _unique(session, cls, hashfunc, queryfunc, constructor, arg, kw):
cache = getattr(session, '_unique_cache', None)
if cache is None:
session._unique_cache = cache = {}
key = (cls, hashfunc(*arg, **kw))
if key in cache:
return cache[key]
else:
with session.no_autoflush:
q = session.query(cls)
q = queryfunc(q, *arg, **kw)
obj = q.first()
if not obj:
obj = constructor(*arg, **kw)
session.add(obj)
cache[key] = obj
return obj
def _null_init(self, *arg, **kw):
pass
def __new__(cls, bases, *arg, **kw):
# no-op __new__(), called
# by the loading procedure
if not arg and not kw:
return object.__new__(cls)
session = db.session()
def constructor(*arg, **kw):
obj = object.__new__(cls)
obj._init(*arg, **kw)
return obj
return _unique(
session,
cls,
hashfunc,
queryfunc,
constructor,
arg,
kw
)
# note: cls must be already mapped for this part to work
cls._init = cls.__init__
cls.__init__ = _null_init
cls.__new__ = classmethod(__new__)
return cls
return decorate
@unique_constructor(db, lambda value: value, lambda query, value: query.filter(Ident.value == value))
class Ident(BaseTable):
__tablename__ = "ident"
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
value = Column(Integer, unique=True)
@unique_constructor(db, lambda data: data, lambda query, data: query.filter(Foo.data == data))
class Foo(BaseTable):
__tablename__ = "foo"
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
data = Column(String, unique=True)
@unique_constructor(db, lambda foo, ident: (foo, ident), lambda query, foo, ident: query.filter(
Foo.data == foo.data, Ident.value == ident.value
))
class Bar(BaseTable):
__tablename__ = "bar"
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
foo_id = Column(Integer, ForeignKey("foo.id"), nullable=False)
ident_id = Column(Integer, ForeignKey("ident.id"), nullable=False)
cnt = Column(Integer, default=0)
foo = relationship(Foo)
ident = relationship(Ident)
BaseTable.metadata.create_all(db.engine)
if __name__ == "__main__":
b = Bar(foo=Foo(data="abcdef"), ident=Ident(value=38))
b.cnt = 6
db.session.add(b)
db.session.commit()
сейчас, когда я создаю новую запись
b = Bar(foo=Foo(data="abcdefgh"), ident=Ident(value=180))
b.cnt = 6
db.session.add(b)
db.session.commit()
все хорошо.Проблема начинается сейчас, когда я хочу обновить вторую запись:
b = Bar(foo=Foo(data="abcdefgh"), ident=Ident(value=180))
b.cnt += 6
db.session.add(b)
db.session.commit()
вторая запись остается неизменной, но первая обновляется.Когда я повторно запускаю этот код, он начинает чередоваться при обновлении первой и второй записи.
Может кто-нибудь объяснить мне, чего мне не хватает?
Спасибо