Flask-sqlalchemy:移动模型导致 InvalidRequestError - 已附加到会话

rje*_*rje 2 sqlalchemy flask flask-sqlalchemy

将我的模型移动到它们自己的模块会导致 InvalidRequestError .. 这似乎与数据库会话和我在模型模块中导入 db 的方式有关。

我试图将这个问题减少到必要的最少代码量。首先,一个可用的 Flask 应用程序:

import os

from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SECRET_KEY'] = 'somesecret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(app.instance_path, 'app.db')
db = SQLAlchemy(app)


class Thing(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    text = db.Column(db.String(300), nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

    def __init__(self, user, text):
        self.user = user
        self.text = text


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True)
    things = db.relationship('Thing', backref='user', lazy='dynamic')

    def __init__(self, username):
        self.username = username


@app.route('/', methods=['GET', 'POST'])
def add():
    if request.method == 'POST':
        username = request.form['username']
        text = request.form['txt']

        user = User.query.filter_by(username=username).first()
        thing = Thing(user, text)

        db.session.add(thing)
        db.session.commit()

    return render_template('index.html')

def initdb():
    db.create_all()
    db.session.add(User('test'))
    db.session.commit()

if __name__ == '__main__':
    app.run(debug=True)
Run Code Online (Sandbox Code Playgroud)

索引模板,同样是最小的:

<html>
  <head>
  </head>
  <body>
        <form method="post">
          <input type="text" name="username"></input>
          <input type="text" name="txt"></input>
          <button type="submit">Submit</button>
        <form>
  </body>
</html>
Run Code Online (Sandbox Code Playgroud)

我们可以用app.initdb()创建一个数据库,然后启动应用程序并在表单中输入一些数据(用户名应该是'test')。一个新项目被插入到 Thing 表中。好的 :)

但随后我将模型移动到 models.py 中:

from app import db

class Thing(db.Model):
    ...

class User(db.Model):
    ...
Run Code Online (Sandbox Code Playgroud)

新的 app.py 看起来像:

import os

from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SECRET_KEY'] = 'somesecret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(app.instance_path, 'app.db')
db = SQLAlchemy(app)

#This line changes
import models


@app.route('/', methods=['GET', 'POST'])
def add():
    if request.method == 'POST':
        username = request.form['username']
        text = request.form['txt']

        # these 2 lines change: using models now
        user = models.User.query.filter_by(username=username).first()
        thing = models.Thing(user, text)

        db.session.add(thing)
        db.session.commit()

    return render_template('index.html')

def initdb():
    db.create_all()
    db.session.add(User('test'))
    db.session.commit()

if __name__ == '__main__':
    app.run(debug=True)
Run Code Online (Sandbox Code Playgroud)

现在发布表单给了我这个错误:

sqlalchemy.exc.InvalidRequestError
InvalidRequestError: Object '<Thing at 0x11111b950>' is already attached to session '1' (this is '2')
Run Code Online (Sandbox Code Playgroud)

当我调试这个时,有趣的是用户附加到与我尝试添加的会话不同的会话。那么显然 db 导入会导致模型类的数据库会话出现问题?谁能向我解释这里发生了什么?

顺便说一下,我可以通过更改Thing 类的init并分配 user_id 而不是 user来解决这个问题。这样 Thing 实例不会从 User 实例复制会话。但这并不能解释为什么 User 实例开始时有一个不正确的会话。

Sin*_*ion 5

您无意中导入了您的app.py模块两次。这是发生的事情:

  • 你打字 $ python path/to/app.py
  • 解释器创建一个名为 的模块__main__,并app.py在其中执行。
  • 你进口models,然后进口app
  • 没有命名app的模块,解释器创建一个命名的模块appapp.py在其中第二次执行
  • models.py正在使用app.db,但实际运行的烧瓶实例实际上正在使用__main__.db.

修复它的最佳方法是添加第三个模块,以便它可以称为 main,例如main_app.py,仅包含:

import app
if __name__ == '__main__':
    app.app.run(debug=True)
Run Code Online (Sandbox Code Playgroud)

并去掉 app.py 中相应的 if main 子句。然后运行您的应用程序$ python path/to/main_app.py