我已经将我的问题最小化到一个独立的flask应用程序+单元测试。当它和
pytest app.py
它大约有一半的时间(50次运行中的29次)有这个错误:
E werkzeug.routing.BuildError: Could not build url for endpoint 'thing' with values ['_sa_instance_state']. Did you forget to specify values ['id']?
令人沮丧的是,在
post()
方法使其始终通过(请参阅下面的注释)。
这感觉像是框架中某个地方的种族状况。sqlalchemy是否正在生成一个线程来执行提交和更新
t.id
是吗?
我可以通过做一个
del t.id
在注释的位置(确认错误来自丢失的t.id)。我可以强迫它通过做一个
t.id = 999
在同一地点。
我在这里做了什么明显的错误吗?或者这是其中一个包中的一个bug?
我运行的是Python3.5.2,我的requirements.txt是:
Flask==1.0.2
Flask-RESTful==0.3.6
Flask-SQLAlchemy==2.3.2
Jinja2==2.10
pytest==3.2.2
pytest-repeat==0.4.1
SQLAlchemy==1.2.8
Werkzeug==0.14.1
值得注意的是,这在大多数软件包的早期版本(flask 0.12、sqlalchemy 1.1.14等)中也失败了。
值得注意的是,当与
pytest --count=20 app.py
它总是通过或不通过整个计数,即20次通过或20次失败。但大约有一半的整体运行仍会失败。
应用程序如下:
#!/usr/bin/env python3
import json
from flask import Flask
from flask_restful import Api, Resource, fields, marshal, reqparse
from flask_sqlalchemy import SQLAlchemy
import pytest
app = Flask(__name__)
api = Api(app)
db = SQLAlchemy(app)
class Thing(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
thing_fields = {
'name': fields.String,
'uri': fields.Url('thing'),
}
class ThingListAPI(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('name', type=str, location='json')
super().__init__()
def post(self):
args = self.reqparse.parse_args()
t = Thing(name=args['name'])
db.session.add(t)
db.session.commit()
### <<< at this point inserting pretty much any statement
### will make the test pass >>>
return {'thing': marshal(t, thing_fields)}, 201
class ThingAPI(Resource):
def get(self, id):
pass
api.add_resource(ThingListAPI, '/things', endpoint='things')
api.add_resource(ThingAPI, '/things/<int:id>', endpoint='thing')
@pytest.fixture
def stub_app():
app.config['TESTING'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
client = app.test_client()
db.create_all()
yield client
db.drop_all()
def test_thing_post(stub_app):
resp = stub_app.post('/things', data=json.dumps({'name': 'stuff'}),
content_type='application/json')
assert(resp.status_code == 201)