我有以下基于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"
}
但是,如果我减少渲染输出时使用的字段数量,那么删除updated
或kind
字段就足够了,然后当我发布另一个新对象时(并且只有当我发布时),我得到了以下错误:
BuildError: ('v1.foo', {'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x1054a3d10>}, None)
这只是因为在编组的字段中仍然有fields.Url('.foo')
,如果我删除它,那么一切都会再次起作用。无论如何,GET请求始终有效。
我还可以通过访问对象帖子中的一个字段db.session.commit()
来完成工作,即在返回之前只显示print foo.id
。
有人可以解释为什么这里的代码看起来有点脆弱吗?我可以看到,通过访问提交后的字段,它可能会触发从数据库中读取。
答案 0 :(得分:1)
我认为我发现了这个问题,我认为它基本上取决于Python词典中键的自然排序顺序。
如果我禁用返回足够的键,则在迭代字典时,包含fields.Url('.foo')
值的字段将成为第一个键,因为SQL Alchemy的默认行为是在会话提交后使任何实例失效我的新对象POST请求暂时变为None
,这解释了尝试计算URL值时的错误。
访问任何其他字段,无论是通过在迭代字典的键时首先出现,还是由我自己使用print foo.id
hack,都足以触发从数据库读取以刷新对象状态,然后计算URL值按预期工作。
我想我可以通过以某种方式在SQL Alchemy会话对象中设置expire_on_commit=False
或使用OrderedDict
并确保URL字段不是第一个来解决此问题。我不确定前一种解决方案在副作用方面引入了什么。