簡體   English   中英

如何不在金字塔中自動開始交易?

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

我創建了一個簡單的Pyramid應用程序,該應用程序使用SQLAlchemy,pyramid_tm,pyramid_beaker和alembic。 數據庫是PostgreSQL,適配器是pg8000。 現在,我正在嘗試實現登錄,但是對數據庫的第一個數據庫查詢將創建BEGIN事務並永久掛起。 我只想在需要時設置事務(UPDATE,DELETE,INSERT和更復雜的多查詢)。

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

@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'))

日志:

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 ...

如果我從add_user()刪除add_user() subtransactions=Trueadd_user()得到:

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

另外,當我發布到/login我在DebugToolbar的Request Vars選項卡中看到了會話變量,其中包含_accessed_time_creation_time但與用戶ID無關,並且在重定向到/根本沒有會話變量。

執行插入和處理錯誤(回滾)的適當方法是使用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

但是,您甚至沒有對示例中的錯誤做任何事情,因此您可以簡單地使用.add而無需任何額外的樣板。

在請求結束時,pyramid_tm將發出最終提交。 刷新在數據庫上的打開事務中執行掛起的SQL命令,從而使您能夠捕獲潛在的錯誤。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM