简体   繁体   English

如何不在金字塔中自动开始交易?

[英]How not to start transaction automatically in Pyramid?

I've created simple Pyramid app which uses SQLAlchemy, pyramid_tm, pyramid_beaker and alembic. 我创建了一个简单的Pyramid应用程序,该应用程序使用SQLAlchemy,pyramid_tm,pyramid_beaker和alembic。 Database is PostgreSQL and adapter is pg8000. 数据库是PostgreSQL,适配器是pg8000。 Now I'm trying to implement login but the first DB query to the database creates BEGIN transaction and hangs forever. 现在,我正在尝试实现登录,但是对数据库的第一个数据库查询将创建BEGIN事务并永久挂起。 I'd like to setup transactions only when needed (UPDATE, DELETE, INSERT and more complex multi-queries). 我只想在需要时设置事务(UPDATE,DELETE,INSERT和更复杂的多查询)。

models/user.py : models/user.py

from sqlalchemy import Column
from sqlalchemy import Unicode
from sqlalchemy import Sequence
from sqlalchemy import Integer
from sqlalchemy import Index
from sqlalchemy import CheckConstraint
from sqlalchemy import text
from sqlalchemy import func
from sqlalchemy.dialects.postgresql import TIMESTAMP

from pyramid.security import Allow

import sqlalchemy.orm.exc as a_exc

import logging

log = logging.getLogger(__name__)


from ..models import DBSession
from ..models import Base

class UserNotFoundException(ValueError):
    pass


class User(Base):
    __tablename__ = 'users'

    __table_args__ = (
        CheckConstraint("login ~* '^[a-z]{3,}$'", name = __tablename__ + "_chk_login"),
        CheckConstraint("login != ''", name = __tablename__ + "_chk_login_not_empty"),
        CheckConstraint("password != ''", name = __tablename__ + "_chk_pw_not_empty"),
        Index(__tablename__ + "_idx_lower_login", text("lower(login)"), unique = True),
    )

    id = Column(Integer, Sequence('users_id_seq'), primary_key = True)
    login = Column(Unicode(64), unique = True, nullable = False, server_default = text("''"))
    password = Column(Unicode(255), nullable = False, server_default = text("''"))
    added = Column(TIMESTAMP, nullable = False, server_default = text("NOW()"))


    @property
    def __acl__(self):
        return [(Allow, self.login, 'view'), ]

    def __init__(self, login, password):
        self.login = login
        self.password = password

    @classmethod
    def get_user(self, login):
        try:
            u = DBSession.query(User).filter(User.login == login).one()
            DBSession.flush()
            return u
        except a_exc.NoResultFound as exc:
            raise UserNotFoundException(exc)

    @classmethod
    def get_user_count(self):
        u = DBSession.query(func.count(User.id)).scalar()
        DBSession.flush()
        return u

    @classmethod
    def create_session(self, login: str, password: str) -> object:
        u = self.get_user(login)

        import bcrypt
        password = password.encode('utf-8')

        try:
            verified = bcrypt.checkpw(password = password, hashed_password = u.password.encode('utf-8'))
        except Exception as exc:
            raise

        if verified != True:
            raise Exception("Coulnd't verify password hash")

        return {'userid': u.id}

    @classmethod
    def add_user(self, login, password):
        import bcrypt
        password = password.encode('utf-8')

        encrypted_pw = bcrypt.hashpw(password, bcrypt.gensalt())
        verified = False

        log.debug("Encrypted PW: '%s'", encrypted_pw)

        try:
            verified = bcrypt.checkpw(password = password, hashed_password = encrypted_pw)
        except Exception:
            raise

        if verified != True:
            raise Exception("Coulnd't verify password hash")

        try:
            DBSession.begin(subtransactions=True)
            DBSession.add(User(login = login, password = encrypted_pw.decode()))
            DBSession.commit()
            log.debug("User added: '%s'", login)
        except Exception as exc:
            DBSession.rollback()
            log.debug("User add failed for user '%s'", login)
            raise

views/views.py : views/views.py

@view_config(route_name = 'login', renderer = 'templates/login.pt')
def app_login_view(request: Request):
    if request.authenticated_userid:
        # Already logged in -> redirect
        import pyramid.httpexceptions as exc
        return exc.HTTPFound(request.route_path('home'))

    user_not_found_error = {
        'page_background': 'warning',
        'page_title':      _(u"Login failed"),
        'page_text':       _(u"Check username and password."),
    }

    form_user = request.POST.get('user')
    form_password = request.POST.get('password')

    from ..models import User, UserNotFoundException

    if User.get_user_count() == 0:
        # No users in DB
        log.debug("Creating admin user")
        User.add_user(u"admin", u"admin")

    try:
        ses = User.create_session(form_user, form_password)
        request.session['userid'] = ses['userid']
        request.session.save()
        remember(request, ses['userid'])
    except UserNotFoundException as exc:
        log.debug("User '%s' not found in database", form_user)
        return user_not_found_error
    except:
        raise

    # Redirect to front page
    import pyramid.httpexceptions as exc
    return exc.HTTPFound(request.route_path('home'))

Log: 日志:

INFO sqlalchemy.engine.base.Engine.dbconn BEGIN (implicit)
INFO sqlalchemy.engine.base.Engine.dbconn SELECT count(users.id) AS count_1 
FROM users
INFO sqlalchemy.engine.base.Engine.dbconn ()
DEBUG [waitress] Creating admin user
DEBUG [user][waitress] Encrypted PW: 'b'$2b$12$n6mN973Gz0wwX7B0kWI.Ae099h7mvLo.mEI.D2NFjZKaLKbGebK16''
INFO sqlalchemy.engine.base.Engine.dbconn INSERT INTO users (id, login, password) VALUES (nextval('users_id_seq'), %s, %s) RETURNING users.id
INFO sqlalchemy.engine.base.Engine.dbconn ('admin', '$2b$12$n6mN973Gz0wwX7B0kWI.Ae099h7mvLo.mEI.D2NFjZKaLKbGebK16')
INFO  [sqlalchemy.engine.base.Engine.dbconn:109][waitress] INSERT INTO users (id, login, password) VALUES (nextval('users_id_seq'), %s, %s) RETURNING users.id
INFO  [sqlalchemy.engine.base.Engine.dbconn:109][waitress] ('admin', '$2b$12$n6mN973Gz0wwX7B0kWI.Ae099h7mvLo.mEI.D2NFjZKaLKbGebK16')
... Hangs here forever ...

If I remove subtransactions=True from add_user() I get: 如果我从add_user()删除add_user() subtransactions=Trueadd_user()得到:

sqlalchemy.exc.InvalidRequestError: A transaction is already begun.  Use subtransactions=True to allow subtransactions.

Also when I POST to /login I see Session Variables in the Request Vars tab in the DebugToolbar with _accessed_time and _creation_time but nothing about userid and after the redirect to / there's no Session Variables at all. 另外,当我发布到/login我在DebugToolbar的Request Vars选项卡中看到了会话变量,其中包含_accessed_time_creation_time但与用户ID无关,并且在重定向到/根本没有会话变量。

The appropriate way to perform an insert and handle the error (rollback) is by using a savepoint and flush() . 执行插入和处理错误(回滚)的适当方法是使用savepoint和flush()

sp = request.tm.savepoint()
try:
    DBSession.add(User(login = login, password = encrypted_pw.decode()))
    DBSession.flush()
    log.debug("User added: '%s'", login)
except Exception as exc:
    sp.rollback()
    log.debug("User add failed for user '%s'", login)
    raise

However, you aren't even doing anything with the error in your example so you could simply be using .add without any of the extra boilerplate. 但是,您甚至没有对示例中的错误做任何事情,因此您可以简单地使用.add而无需任何额外的样板。

At the end of the request pyramid_tm will issue the final commit. 在请求结束时,pyramid_tm将发出最终提交。 The flush executes the pending SQL commands in an open transaction on the database, allowing you to catch potential errors. 刷新在数据库上的打开事务中执行挂起的SQL命令,从而使您能够捕获潜在的错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM