Flask-Restx Api.model(strict=True) allowing unspecified params

4.2k Views Asked by At

I have a simple Users resource with a put method to update all user information except user password. According to Flask-Restx docs when a model has set the strict and validation params to true, a validation error will be thrown if an unspecified param is provided in the request. However, this doesn't seem to be working for me.

Model definition:

from flask_restx import Namespace, Resource, fields, marshal

users_ns = Namespace("users")

user = users_ns.model(
    "user",
    {
        "user_name": fields.String(example="some_user", required=True),
        "email": fields.String(example="some.user@email", required=True),
        "is_admin": fields.Boolean(example="False"),
        "is_deactivated": fields.Boolean(example="False"),
        "created_date": fields.DateTime(example="2020-12-01T01:59:39.297904"),
        "last_modified_date": fields.DateTime(example="2020-12-01T01:59:39.297904"),
        "uri": fields.Url("api.user"),
    },
    strict=True,
)

user_post = users_ns.inherit(
    "user_post", user, {"password": fields.String(required=True)}
) # Used for when 

Resource and method definition:

from api.models import Users

class User(Resource):
    @users_ns.marshal_with(user)
    @users_ns.expect(user, validate=True)
    def put(self, id):
        """
        Update a specified user.
        """

        user = Users.query.get_or_404(id)
        body = request.get_json()

        user.update(body)

        return user

Failing Test:

def test_update_user_invalid_password_param(self, client, db):
        """ User endpoint should return 400 when user attempts to pass password param to update. """
        data = {
            "user_name": "some_user",
            "email": "[email protected]",
            "password": "newpassword",
        }

        response = client.put(url_for("api.user", id=1), json=data)

        assert response.status_code == 400

The response.status_code here is 200 because no validation error is thrown for the unspecified param passed in the body of the request.

Am I using the strict param improperly? Am I misunderstanding the behavior of strict?

UPDATED: I've added the test for strict model param from Flask-RestX repo (can be found here) for more context on expected behavior:

def test_api_payload_strict_verification(self, app, client):
        api = restx.Api(app, validate=True)
        ns = restx.Namespace("apples")
        api.add_namespace(ns)

        fields = ns.model(
            "Person",
            {
                "name": restx.fields.String(required=True),
                "age": restx.fields.Integer,
                "birthdate": restx.fields.DateTime,
            },
            strict=True,
        )

        @ns.route("/validation/")
        class Payload(restx.Resource):
            payload = None

            @ns.expect(fields)
            def post(self):
                Payload.payload = ns.payload
                return {}

        data = {
            "name": "John Doe",
            "agge": 15,  # typo
        }

        resp = client.post_json("/apples/validation/", data, status=400)
        assert re.match("Additional properties are not allowed \(u*'agge' was unexpected\)", resp["errors"][""])
2

There are 2 best solutions below

0
On BEST ANSWER

I resolved my issue by pulling the latest version of Flask-RESTX from Github. The strict parameter for models was merged after Flask-RESTX version 0.2.0 was released on Pypi in March of 2020 (see the closed issue in Flask-RESTX repo for more context). My confusion arose because the documentation appears to represent the latest state of master and not the last Pypi release.

2
On

It's been a while since I touched on this but from what I can tell, I don't think you are using the strict param correctly. From the documentation here, the :param bool strict is defined as

:param bool strict: validation should raise an error when there is param not provided in schema

But in your last snippet of code, you are trying to validate with the dictionary data with the body of the request.

If I recall well again, for this sort of task you need to use (and as you mentioned) a RequestParser. There's a good example of it here - flask - something more strict than @api.expect for input data?