使用SQLAlchemy行为异常更新表中的值

时间:2019-01-31 16:23:17

标签: python postgresql sqlalchemy

我使用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()

第二条记录保持不变,但第一条记录已更新。当我重新运行此代码时,它开始交替更新第一条和第二条记录。

有人可以解释我,我想念什么吗?

谢谢

0 个答案:

没有答案