How to PUT with marshmallow-sqlalchemy + webargs?
See original GitHub issueHi guys.
I’m discovering marshmallow-sqlalchemy, trying to build a simple REST API with marshmallow-sqlalchemy. I’m using flask-smorest (apispec + webargs inside).
Here’s what it looks like:
class Member(db.Model):
id = sa.Column(UUIDType, primary_key=True, default=uuid.uuid4)
first_name = sa.Column(sa.String(length=40))
last_name = sa.Column(sa.String(length=40))
birthdate = sa.Column(sa.DateTime)
class MemberSchema(ModelSchema):
class Meta(ModelSchema.Meta):
model = Member
class MemberQueryArgsSchema(ma.Schema):
first_name = ma.fields.Str()
last_name = ma.fields.Str()
@blp.route('/')
class Members(MethodView):
@blp.arguments(MemberQueryArgsSchema, location='query')
@blp.response(MemberSchema(many=True))
def get(self, args):
"""List members"""
return db.session.query(Member).filter_by(**args)
@blp.arguments(MemberSchema)
@blp.response(MemberSchema, code=201)
def post(self, item):
"""Add a new member"""
db.session.add(item)
db.session.commit()
return item
@blp.route('/<uuid:item_id>')
class MembersById(MethodView):
@blp.response(MemberSchema)
def get(self, item_id):
"""Get member by ID"""
return db.session.query(Member).get_or_404(item_id)
@blp.arguments(MemberSchema)
@blp.response(MemberSchema)
def put(self, item, item_id):
"""Update a member"""
db.session.add(item)
db.session.commit()
return item
@blp.response(code=204)
def delete(self, item_id):
"""Delete a member"""
item = db.session.query(Member).get_or_404(item_id)
db.session.delete(item)
db.session.commit()
Looks like I’m almost there and I find it pretty neat, except for two things related to the update (PUT).
id must be passed in body
When PUTing a resource, the id is in the resource path, injected as item_id
in the view function. It shouldn’t have to be in the request body. In fact, I’d like to make the id field read-only since id should not be modified.
With the code above
- the id in the path is unused
- the user must pass the id in the request body
If the id is not passed in the request body but added in the view function, the make_instance
method of MemberSchema
does not find the existing instance and creates another one. Adding the id in the view generates a unique constraint failure on the id when committing.
I don’t see how to get out of this. Since the instantiation takes place in a post_load
method, I don’t see how to pass the id from path parameter. (Perhaps webargs could fetch it from path. I gave it a quick try with no luck, not sure why, but the single-schema/multi-location case is about to be dropped from webargs anyway.)
Looks like I need to back-off from marshmallow-sqlalchemy’s niceness and no-op make_instance
to do the actual job in the view function.
I can’t believe I’m the only one here. Any advice ?
missing fields are not nulled
Not really specific to marshmallow-sqlalchemy.
AFAIU, the update in make_instance
is similar to a dict
update in that it does not null missing fields. Technically, this is not a real PUT.
In REST the old representation should be completely replaced with the new one. Updates should be done using PATCH (and the dict update method is not really satisfying, as it can’t remove fields, which is why some advocate for a patch language such as json-patch, but that’s another story).
When the client PUTs a resource with a removed field (not null, removed, therefore missing), I expect the field to be removed (nulled or rather set to default value) from the object.
In another app, I use a custom method that uses the new data (as dict) and the schema to update the fields in the object and null all non-dump-only missing fields.
I thought make_instance
would do that but it doesn’t.
In fact, I thought this was the idea behind of #40. Since it reads “Require fields for non-nullable properties”, I figured nullable properties would not be required, therefore allowed to be missing and this made me think that missing values would be nullified, but it does not appear to be true.
It could be patched to do it, though.
Again, I’m surprised. Am I the only one with this issue? Am I missing something?
Issue Analytics
- State:
- Created 4 years ago
- Comments:14 (12 by maintainers)
Top GitHub Comments
Oops, I forgot about that and I consider myself a chatops freak. Will continue there. blush
I know we’re all busy, but this needs to be shouted from the rooftops somewhere not buried in an unrelated issue, it’s gold!
I do the update in the view.
I added a method in
TableSchema
to factorize the update logic. The point is to avoid writing:where the list of attributes can be known thanks to the schema.