简体   繁体   English

Flask-Restful端点抛出werkzeug.routing.BuildError

[英]Flask-Restful Endpoint Throwing werkzeug.routing.BuildError

I am using Flask-Restful and SQLAlchemy to develop an API and am coming across a behaviour which I have a workaround for, but I am not sure the sticking with the workaround is a good long term strategy. 我正在使用Flask-Restful和SQLAlchemy开发API,并且遇到了我有解决方法的行为,但是我不确定坚持该解决方法是否是一个好的长期策略。

Pretty standard API with a List item resource with POST function and a item single resource with GET function. 相当标准的API,具有带有POST功能的列表项资源和带有GET功能的项单一资源。 I tested the POST function via curl and also via a web form, and am getting the following error message: 我通过curl和Web表单测试了POST函数,并收到以下错误消息:

BuildError: Could not build url for endpoint 'brand' with values ['_sa_instance_state']. Did you forget to specify values ['brand_symbol']?

The odd thing is that adding a print statement (see commented out print statement near the end of the Resource declaration below) makes the error go away. 奇怪的是,添加一条打印语句(请参见下面的资源声明末尾附近的注释注释)可以使错误消失。

And I know that even when the Error message is encountered 而且我知道,即使遇到错误消息

  • the item was successfully committed in the database, and 该项目已成功提交到数据库中,并且
  • performing a GET on the item displays the URI perfectly fine 在项目上执行GET可以完美显示URI

So based on this, I think the problem is perhaps with the POST return statement. 因此,基于此,我认为问题可能出在POST return语句上。 Any help would be gratefully received - I don't want to live with a flaky workaround! 我们将不胜感激地获得任何帮助-我不想忍受不稳定的解决方法!

Endpoint 终点

api.add_resource(BrandListResource, '/brands', endpoint = 'brands')
api.add_resource(BrandResource, '/brands/<string:brand_symbol>', endpoint = 'brand')

Resource 资源资源

brand_fields = {
    'id': fields.Integer,
    'brand_symbol': fields.String,
    'brand_name': fields.String,
    'uri': fields.Url('brand', absolute=True)
}

class BrandResource(Resource):
    @marshal_with(brand_fields)
    def get(self, brand_symbol):
        brand = db.session.query(Brand).filter(Brand.brand_symbol == brand_symbol).first()
        if not brand:
            abort(404, message="Brand {} doesn't exist".format(brand_symbol))
        return brand

class BrandListResource(Resource):
    @marshal_with(brand_fields)
    def get(self):
        brands = db.session.query(Brand).all()
        return brands

    @marshal_with(brand_fields)
    def post(self):
        parsed_args = parser.parse_args()
        brand_symbol = parsed_args['brand_symbol']
        brand_name = parsed_args['brand_name']

        brand = db.session.query(Brand).filter(Brand.brand_symbol == brand_symbol).first()
        if brand:
            abort(404, message="Brand {} already exists".format(brand_symbol))

        brand = Brand(brand_symbol=brand_symbol, brand_name=brand_name)
        db.session.add(brand)
        db.session.commit()

        #print brand
        return brand, 201

SQLAlchemy Model SQLAlchemy模型

class Brand(db.Model):
    __tablename__ = 'brand'

    id = db.Column(db.Integer, primary_key=True)
    brand_symbol = db.Column(db.String(5), unique=True)
    brand_name = db.Column(db.String(200), nullable=False)

    def __init__(self, brand_symbol, brand_name):
        self.brand_symbol = brand_symbol
        self.brand_name = brand_name

    def __repr__(self):
        return '<brand_symbol {}>'.format(self.brand_symbol)

Full Error Traceback 完整错误回溯

Traceback (most recent call last):
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask/app.py", line 2000, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask/app.py", line 1991, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask_restful/__init__.py", line 271, in error_router
    return original_handler(e)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask/app.py", line 1567, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask_restful/__init__.py", line 268, in error_router
    return self.handle_error(e)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask/app.py", line 1988, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask/app.py", line 1641, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask_restful/__init__.py", line 271, in error_router
    return original_handler(e)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask/app.py", line 1544, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask_restful/__init__.py", line 268, in error_router
    return self.handle_error(e)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask/app.py", line 1639, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask/app.py", line 1625, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask_restful/__init__.py", line 477, in wrapper
    resp = resource(*args, **kwargs)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask/views.py", line 84, in view
    return self.dispatch_request(*args, **kwargs)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask_restful/__init__.py", line 587, in dispatch_request
    resp = meth(*args, **kwargs)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask_restful/__init__.py", line 682, in wrapper
    return marshal(data, self.fields, self.envelope), code, headers
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask_restful/__init__.py", line 640, in marshal
    return OrderedDict([(envelope, OrderedDict(items))]) if envelope else OrderedDict(items)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/collections.py", line 57, in __init__
    self.__update(*args, **kwds)
  File "/Users/skavie/testproject/lib/python2.7/_abcoll.py", line 571, in update
    for key, value in other:
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask_restful/__init__.py", line 639, in <genexpr>
    for k, v in fields.items())
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask_restful/fields.py", line 307, in output
    o = urlparse(url_for(endpoint, _external=self.absolute, **data))
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask/helpers.py", line 332, in url_for
    return appctx.app.handle_url_build_error(error, endpoint, values)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask/app.py", line 1811, in handle_url_build_error
    reraise(exc_type, exc_value, tb)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/flask/helpers.py", line 322, in url_for
    force_external=external)
  File "/Users/skavie/testproject/lib/python2.7/site-packages/werkzeug/routing.py", line 1758, in build
    raise BuildError(endpoint, values, method, self)
BuildError: Could not build url for endpoint 'brand' with values ['_sa_instance_state']. Did you forget to specify values ['brand_symbol']?

The brand object is expired when committing the SQLAlchemy session. 提交SQLAlchemy会话时, brand对象已过期。 The stacktrace gives a hint by pointing out that the url could not be build because there is some information missing in the object you are returning. stacktrace通过指出由于您要返回的对象中缺少某些信息而无法构建url来提供提示。

In this case the brand object needs to refreshed so it will contain all data again. 在这种情况下, brand对象需要刷新,因此它将再次包含所有数据。 You can do that by printing it, but it seems a bit better to use the refresh() method of the session like this: 您可以通过打印它来做到这一点,但是像这样使用会话的refresh()方法似乎要好一些:

...
db.session.commit()
db.session.refresh(brand)
return brand, 201

There is a small caveat however, the documentation of the refresh method states the following at the time of writing: 需要注意的是,在编写本文时,refresh方法的文档指出以下内容:

Note that a highly isolated transaction will return the same values as were previously read in that same transaction, regardless of changes in database state outside of that transaction - usage of refresh() usually only makes sense if non-ORM SQL statement were emitted in the ongoing transaction, or if autocommit mode is turned on. 请注意,高度隔离的事务将返回与先前在同一事务中读取的值相同的值,而不管该事务之外的数据库状态如何变化-使用refresh()通常仅在非ORM SQL语句在数据库中发出时才有意义。正在进行的事务,或者如果打开了自动提交模式。

This means that the information you are getting back on a refresh could be outdated, but I guess that is almost theoretical in this case. 这意味着您要重新获取的信息可能已过时,但是我想在这种情况下这几乎是理论上的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM