question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to PUT with marshmallow-sqlalchemy + webargs?

See original GitHub issue

Hi 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:closed
  • Created 4 years ago
  • Comments:14 (12 by maintainers)

github_iconTop GitHub Comments

1reaction
antgelcommented, Oct 2, 2019

Copying my messages from Slack convos:

Oops, I forgot about that and I consider myself a chatops freak. Will continue there. blush

this ‘how to do apis in python’ question comes up every few months or so on reddit and elsewhere. i think my current recommendations are something like: you want to use python and don’t have opinions on tooling? use DRF you want openapi support and are more comfortable with flask? use ~flask-rest-api~ flask-smorest you have experience with and like DRF but prefer flask and sqlalchemy? try flask-resty

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!

1reaction
lafrechcommented, Oct 2, 2019

I do the update in the view.

I added a method in TableSchema to factorize the update logic. The point is to avoid writing:

    item.attr_1 = data['attr_1']
    item.attr_2 = data['attr_2']

where the list of attributes can be known thanks to the schema.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Advanced Usage - webargs 8.2.0 documentation
This section includes guides for advanced usage patterns. Custom Location Handlers#. To add your own custom location handler, write a function that receives...
Read more >
flask-marshmallow: how to mark all fields as optional only ...
I am making a flask restful api, what I'm having trouble with is marshmallow-sqlalchemy, and webargs.
Read more >
webargs - Read the Docs
Go on to Advanced Usage to learn how to add custom location handlers, use marshmallow Schemas, and more. • See the Framework Support...
Read more >
How To Marshal Data With Flask
The first package is Marshmallow-SQLAlchemy. To use it on its own, we can just import its special schema classes: pip install marshmallow- ...
Read more >
marshmallow-code - Bountysource
[ ] Add request parsing by integrating with webargs , which now uses marshmallow. ... I'm using marshmallow-sqlalchemy 0.23.1 and SQLAlchemy 1.3.15, ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found