DataError: (DataError) invalid input syntax for integer: "None"

DataError: (DataError) invalid input syntax for integer: "None"

背景

我们正在使用 Flask 开发一个相当简单的应用程序,"scratch an itch" 我们在处理之前的项目时意识到我们有。会话管理是通过我们用于用户身份验证的 Flask-Login extension, coupled with the Github-Flask 扩展来处理的。该应用程序使用 Flask-SQLAlchemy 和 Psycopg2 (2.6.3) 连接到存储用户数据的 PostgreSQL 9.1 数据库。

问题

当应用程序 运行 在本地(或部署到远程服务器)进行测试时,我们在第一次登录尝试时看到了一个奇怪的情况。第一次登录时,我成功通过了 Github 身份验证集,但立即看到此错误。

DataError: (DataError) invalid input syntax for integer: "None"
LINE 3: WHERE "user".id = 'None'
                      ^
'SELECT "user".id AS user_id, "user".date_created AS user_date_created,
"user".date_modified AS user_date_modified, "user".nickname AS user_nickname,
"user".email AS user_email, "user".about_me AS user_about_me,
"user".github_access_token AS user_github_access_token, "user".github_id AS
user_github_id \nFROM "user" \nWHERE "user".id = %(param_1)s' {'param_1': u'None'}

我可以关闭浏览器选项卡,然后重新访问应用程序 URL 并看到相同的消息。但是,如果我关闭浏览器 window,完全清除浏览器的缓存,然后尝试重新登录,它会按预期工作并且我能够使用该应用程序。从那时起,我可以注销、切换浏览器、再次清除浏览器缓存等,而且似乎没有任何问题——只是第一次登录,直到清除浏览器缓存才会发生这种情况。

我的测试表明,这发生在任何用户身上,并且两个不同的用户可以同时处于两种不同的状态(一个已经清除了缓存,现在没有问题,另一个没有采取这些步骤还是卡在错误状态。

最初我们使用本地 SQLite 数据库而不是 Postgres。那里不存在这个问题。它仅在切换到 Postgres 后才发生。

我不知道可以采取什么措施来补救这种情况。到目前为止,我知道要问的问题在网上搜索时还没有找到正确的答案。

作为参考,我还将包括完整的堆栈跟踪。

完整堆栈跟踪

Traceback (most recent call last):
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/flask/app.py", line 1836, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/flask/app.py", line 1820, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/flask/app.py", line 1403, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/flask/app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/flask_debugtoolbar/__init__.py", line 124, in dispatch_request
    return view_func(**req.view_args)
  File "/Users/dev/PRODUCT/app/modules/mod_profile/controllers.py", line 26, in profile
    title='PRODUCT')
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/flask/templating.py", line 126, in render_template
    ctx.app.update_template_context(context)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/flask/app.py", line 716, in update_template_context
    context.update(func())
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/flask_login.py", line 825, in _user_context_processor
    return dict(current_user=_get_user())
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/flask_login.py", line 794, in _get_user
    current_app.login_manager._load_user()
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/flask_login.py", line 363, in _load_user
    return self.reload_user()
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/flask_login.py", line 325, in reload_user
    user = self.user_callback(user_id)
  File "/Users/dev/PRODUCT/app/modules/mod_auth/controllers.py", line 31, in load_user
    return User.query.get(id)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 840, in get
    return loading.load_on_ident(self, key)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/sqlalchemy/orm/loading.py", line 231, in load_on_ident
    return q.one()
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 2395, in one
    ret = list(self)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 2438, in __iter__
    return self._execute_and_instances(context)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 2453, in _execute_and_instances
    result = conn.execute(querycontext.statement, self._params)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 729, in execute
    return meth(self, multiparams, params)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/sqlalchemy/sql/elements.py", line 322, in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 826, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 958, in _execute_context
    context)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1159, in _handle_dbapi_exception
    exc_info
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/sqlalchemy/util/compat.py", line 199, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 951, in _execute_context
    context)
  File "/Users/dev/PRODUCT/venv/lib/python2.7/site-packages/sqlalchemy/engine/default.py", line 436, in do_execute
    cursor.execute(statement, parameters)
DataError: (DataError) invalid input syntax for integer: "None"
LINE 3: WHERE "user".id = 'None'
                          ^
 'SELECT "user".id AS user_id, "user".date_created AS user_date_created, "user".date_modified AS user_date_modified, "user".nickname AS user_nickname, "user".email AS user_email, "user".about_me AS user_about_me, "user".github_access_token AS user_github_access_token, "user".github_id AS user_github_id \nFROM "user" \nWHERE "user".id = %(param_1)s' {'param_1': u'None'}

已解决!

这是一个竞争条件。在登录过程中,我们的应用程序将经过身份验证的用户重定向到他们的个人资料页面。这是正确触发的,但对于新用户,它是在用户实际提交到数据库之前被调用的。

视觉上它同时发生,所以你可以确认回调提供了 "right" 信息,但直到我解构然后重建登录步骤我才意识到它就在记录实际提交到数据库之前正在访问路由。由于添加记录的函数没有被重定向中断,所以它做了它应该做的,但只是迟到了。

也许其他人会发现我的这段代码摘录在解决类似问题时很有用:

## from mod_auth/controllers.py
@mod_auth.before_app_request
def before_request():
    g.user = current_user

@mod_auth.route('/login', methods=['GET'])
def login():
    if g.user.is_authenticated():
        redirect(url_for('mod_home.index'))
    return github.authorize()

@login_manager.user_loader
def load_user(id):
    return User.query.get(id)

@github.access_token_getter
def token_getter():
    user = g.user
    if user is not None:
        return user.github_access_token

@mod_auth.route('/github')
@github.authorized_handler
def authorized(oauth_token):

    next_url = request.args.get('next') or url_for('mod_home.index')
    if oauth_token is None:
        flash("Authorization failed.")
        return redirect(next_url)

    user = User.query.filter_by(github_access_token=oauth_token).first()
    if user is None:
        user = User(github_access_token=oauth_token)
        db.session.add(user)
        db.session.commit() ## <-- adding a commit here fixed the issue

    user.github_access_token = oauth_token

    login_user(user) 
    ## login_user called, but because the oauth_token exist and had been
    ## added to the session, the user was flagged as "is_authenticated"
    ## and the redirect was triggered

    ghinfo = github.get('user')

    if 'login' in ghinfo:
        user.nickname = ghinfo['login'].lower()
    else:
        user.nickname = 'nameless'

    db.session.commit() ## this was the only commit, originally

    return redirect(url_for('mod_profile.profile', username=user.nickname))