2016-11-14 4 views
0

Ich habe eine einfache Pyramid-App erstellt, die SQLAlchemy, pyramid_tm, pyramid_beaker und alembic verwendet. Die Datenbank ist PostgreSQL und der Adapter ist pg8000. Jetzt versuche ich, die Anmeldung zu implementieren, aber die erste DB-Abfrage an die Datenbank erstellt BEGIN-Transaktion und bleibt für immer hängen. Ich möchte Transaktionen nur bei Bedarf einrichten (UPDATE, DELETE, INSERT und komplexere Multi-Abfragen).Wie wird die Transaktion in Pyramid nicht automatisch gestartet?

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

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

Wenn ich subtransactions=True von add_user() entferne ich bekommen:

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

Auch wenn ich /login POST sehe ich Session-Variablen in den Request Vars Registerkarte im DebugToolbar mit _accessed_time und _creation_time aber nichts über Benutzer-ID und nach der Umleitung auf / überhaupt keine Session-Variablen gibt.

+0

Sie sollten die SQLAlchemy-Sitzung nicht unter Anfragen teilen. Erstellen Sie (und löschen Sie anschließend) eine neue "Sitzung" für jede Anfrage. – univerio

Antwort

0

Die geeignete Methode zum Einfügen und Behandeln des Fehlers (Rollback) ist die Verwendung eines Sicherungspunkts und 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 

aber Sie tun nicht einmal etwas mit dem Fehler in Ihrem Beispiel, so dass Sie einfach .add ohne die zusätzlichen Verwendung von vorformulierten werden könnten.

Am Ende der Anfrage wird pyramid_tm das endgültige Commit ausgeben. Der Flush führt die ausstehenden SQL-Befehle in einer geöffneten Transaktion in der Datenbank aus, sodass Sie potenzielle Fehler erkennen können.

Verwandte Themen