Я свел свою проблему к отдельному приложению с колбой + модульный тест. Когда это выполняется с pytest app.py
, оно примерно половину времени дает сбой (29 из 50 запусков) с этой ошибкой:
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
в том же месте.
Я делаю что-то явно не так или это ошибка в одном из пакетов?
Я использую Python 3.5.2, и мои требования.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
Возможно, стоит отметить, что это также не удалось с более ранними версиями большинства этих пакетов (колба 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)