flask-marshmallow: how to mark all fields as optional only when the method is PUT

13.1k views Asked by At

I am making a flask restful API, what I'm having trouble with is marshmallow-sqlalchemy, and webargs.

In short here is my sqlalchemy model:

class User(Model):
    id = Column(String, primary_key=True)
    name = Column(String(64), nullable=False)
    email = Column(String(120), nullable=False)
    password = Column(String(128))
    creation_date = Column(DateTime, default=datetime.utcnow)

and this is my schema:

class UserSchema(ModelSchema):
    class Meta:
        model = User
        strict = True
        sqla_session = db.session

user_schema = UserSchema()

and an example of my routes using flask-classful and webargs:

class UserView(FlaskView):
    trailing_slash = False
    model = User
    schema = user_schema

    @use_kwargs(schema.fields)
    def post(self, **kwargs):
        try:
            entity = self.model()

            for d in kwargs:
                if kwargs[d] is not missing:
                    entity.__setattr__(d, kwargs[d])
    
            db.session.add(entity)
            db.session.commit()
            o = self.schema.dump(entity).data
            return jsonify({'{}'.format(self.model.__table__.name): o})

        except IntegrityError:
            return jsonify({'message': '{} exist in the database. choose another id'
                   .format(self.model.__table__.name)}), 409


    @use_kwargs(schema.fields)
    def put(self, id, **kwargs):
        entity = self.model.query.filter_by(id=id).first_or_404()

        for d in kwargs:
            if kwargs[d] is not missing:
                entity.__setattr__(d, kwargs[d])

        db.session.commit()
        o = self.schema.dump(entity).data
        return jsonify({'{}'.format(self.model.__table__.name): o})

UserView.register(app)

The problem:
As you can see in my sqlalchemy model, some fields are not nullable, thus my marshmallow schemda marks them as required. My get, index, delete and post methods all work perfectly. But I included post for one reason:

when I try to post a new user with no name for example, a 422 http code is raised because name field is required, which is something I want and it is done perfectly.

BUT when editing fields with put request, I wish EVERYTHING in my schema becomes optional.. right now if I wanted to update a user, I must provide not only the id.. but ALL other information required by default even if I didn't change them at all.

In short, how to mark all fields as "optional" when the method is "put"?

EDIT: just like the solution provided by @Mekicha, I made the following changes:

Change the schema to make the required fields in my model accept the value None. like this:

class UserSchema(ModelSchema):
    class Meta:
        model = User
        ...
    name = fields.Str(missing=None, required=True)
    email = fields.Email(missing=None, required=True)
    ...

change my put and post method condition from this:

if kwargs[d] is not missing:

to this:

if kwargs[d] is not missing and kwargs[d] is not None:
2

There are 2 answers

0
Mekicha On BEST ANSWER

Since you want to make the fields optional during put, how about setting the missing attribute for the fields. From the doc:

missing is used for deserialization if the field is not found in the input data

I think a combination of missing and allow_none(which defaults to True when missing=None) as pointed out here: https://github.com/marshmallow-code/marshmallow/blob/dev/src/marshmallow/fields.py#L89 should work for you

0
Jérôme On

You could instantiate the schema with partial=True to make all fields optional.