2

I'm trying to validate all data being sent to my api. My url structure contains a variable within it, /api/v2/users/<string:username>/collections/, as well as actual query string parameters, all of which need to be passed through validation.

The full url looks like this: https://127.0.0.1:5000/api/v2/users/<string:username>/collections/?page=5&per_page=10

The two variables that need to be validated are: username page, and per_page. The easy solution would be to change my url structure, but I'm wondering if it's possible to accomplish what I need and keep the simplicity that I currently have without adding additional validation within my resource class. If it's possible, how do you do it?

class UserCollections(Resource):
    @use_args({
        'username': fields.Str(
            required=True,
            validate=username_length,
            error_messages=dict(
                required='Username is required.',
                validator_failed='Username can be between 3 and 25 characters.',
            )
        ),
        'page': fields.Int(
            #required=True,
            missing=1,
            validate=feed_minmax_pages,
            error_messages=dict(
                validator_failed='Maximum number of pages reached.',
            )
        ),
        'per_page': fields.Int(
            #required=True,
            missing=5,
            validate=validate.Range(min=5,max=25),
            error_messages=dict(
                validator_failed='Test number of pages reached.',
            )
        ),
    }, locations=('query',))

    def get(self, args, username):
        print(args)
        print(username)

        return default_schema(
            data={},
            http_status=200
        )

When I run the code, I get a validation error for username because it doesn't exist in the args.

stwhite
  • 3,156
  • 4
  • 37
  • 70

1 Answers1

5

After poking around for a while I came across the solution, which I now see in the webargs flaskparser documentation

This can easily be used in addition to other use_args location params within the arguments. It appears that the request method function get, post, etc still requires that you pass in that url variable. In my case it was <username>.

class UserCollections(Resource):
    @use_args({
        'username': fields.Str(
            location='view_args',
            required=True,
            validate=username_length,
            error_messages=dict(
                required='Username is required.',
                validator_failed='Username can be between 3 and 25 characters.',
            )
        ),
        'page': fields.Int(
            location='query',
            missing=1,
            validate=feed_minmax_pages,
            error_messages=dict(
                validator_failed='Maximum number of pages reached.',
            )
        ),
        'per_page': fields.Int(
            location='query',
            missing=5,
            validate=validate.Range(min=5,max=25),
            error_messages=dict(
                validator_failed='Test number of pages reached.',
            )
        ),
    })

    def get(self, args, username):
        print(args) # access with args['username']
        print(username) # would be nice to not have a second of the same var

        return default_schema(
            data={},
            http_status=200
        )
stwhite
  • 3,156
  • 4
  • 37
  • 70
  • Webargs 6.x and later no longer support searching multiple locations in the same `@use_args` or `@use_kwargs` decorator. Instead, the above answer needs to be modified to use multiple `@use_args()` calls. `@use_args({'username': fields.Str(...)}, location='view_args'); @use_args({'page': fields.Int(...)}, location-'query'); def(get...)` – Brian H. Sep 12 '22 at 14:36