为什么fields.Url(' ...')根据渲染字段的数量在Flask-RESTful中断

时间:2015-10-21 13:16:43

标签: python rest flask flask-sqlalchemy flask-restful

我有以下基于Flask-RESTful的小型API:

from app import api, db, models
from flask import request
from flask.ext.restful import Resource, fields, marshal_with

foo_fields = {
    'id': fields.Integer,
    'name': fields.String,
    'self': fields.Url('.foo'),
    'created': fields.DateTime(dt_format='iso8601'),
    'updated': fields.DateTime(dt_format='iso8601'),
    'kind': fields.Raw('foo'),
}

class Foo(Resource):
    @marshal_with(foo_fields)
    def get(self, id):
        account = models.Foo.query.filter_by(id=id).first_or_404()
        return account

class FooList(Resource):
    @marshal_with(foo_fields)
    def get(self):
        accounts = models.Foo.query.order_by(models.Foo.id).all()
        return accounts

    @marshal_with(foo_fields)
    def post(self):
        json = request.get_json()
        foo = models.Foo()
        foo.name = json['name']
        db.session.add(foo)
        db.session.commit()
        return foo, 201

api.add_resource(Foo, '/foo/<int:id>', endpoint='foo')
api.add_resource(FooList, '/foo', endpoint='foo_list')

有一个小型模型,使用SQL Alchemy目前使用SQLite进行测试支持:

from app import app, db
from sqlalchemy.sql import func

class Foo(db.Model):
    __tablename__ = 'foo'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), index=True, unique=True)
    created = db.Column(db.DateTime, default=func.now())
    updated = db.Column(db.DateTime, default=func.now())

现在,我可以使用它来发布一个像这样的新对象:

$ curl -v -X POST localhost:5000/api/v1/foo -H 'Content-Type: application/json' -d '{ "name": "foo" }'
{
    "created": "2015-10-21T12:39:41", 
    "id": 1, 
    "kind": "foo", 
    "name": "foo", 
    "self": "/api/v1/foo/1", 
    "updated": "2015-10-21T12:39:41"
}

但是,如果我减少渲染输出时使用的字段数量,那么删除updatedkind字段就足够了,然后当我发布另一个新对象时(并且只有当我发布时),我得到了以下错误:

BuildError: ('v1.foo', {'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x1054a3d10>}, None)

这只是因为在编组的字段中仍然有fields.Url('.foo'),如果我删除它,那么一切都会再次起作用。无论如何,GET请求始终有效。

我还可以通过访问对象帖子中的一个字段db.session.commit()来完成工作,即在返回之前只显示print foo.id

有人可以解释为什么这里的代码看起来有点脆弱吗?我可以看到,通过访问提交后的字段,它可能会触发从数据库中读取。

1 个答案:

答案 0 :(得分:1)

我认为我发现了这个问题,我认为它基本上取决于Python词典中键的自然排序顺序。

如果我禁用返回足够的键,则在迭代字典时,包含fields.Url('.foo')值的字段将成为第一个键,因为SQL Alchemy的默认行为是在会话提交后使任何实例失效我的新对象POST请求暂时变为None,这解释了尝试计算URL值时的错误。

访问任何其他字段,无论是通过在迭代字典的键时首先出现,还是由我自己使用print foo.id hack,都足以触发从数据库读取以刷新对象状态,然后计算URL值按预期工作。

我想我可以通过以某种方式在SQL Alchemy会话对象中设置expire_on_commit=False或使用OrderedDict并确保URL字段不是第一个来解决此问题。我不确定前一种解决方案在副作用方面引入了什么。