Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How can I write a custom validator? #716

Open
AbdoDabbas opened this issue Sep 20, 2019 · 10 comments
Open

How can I write a custom validator? #716

AbdoDabbas opened this issue Sep 20, 2019 · 10 comments

Comments

@AbdoDabbas
Copy link

AbdoDabbas commented Sep 20, 2019

Is there a way to write a validator for a custom field type that I can use in the API model (in except decorator).
I mean like this:

api.model('a_name', {
    'email': MyEmailType(required=True)
})

I tried to inherit from fields.String and did it like:

EMAIL_REGEX = re.compile(r'\S+@\S+\.\S+')

class Email(fields.String):
    """
    Email field
    """
    __schema_type__ = 'string'
    __schema_format__ = 'email'
    __schema_example__ = 'email@domain.com'
 
    def validate(self, value):
        if not value:
            return False if self.required else True
        if not EMAIL_REGEX.match(value):
            return False
        return True
@AbdoDabbas
Copy link
Author

I tried to apply this with no luck:
https://aviaryan.com/blog/gsoc/restplus-validation-custom-fields

@JBarrioGarcia
Copy link

I don't know how to do a custom validator, but you can use "pattern" to validate the string:

api.model('a_name', {
'email': fields.String(required=True, pattern='\S+@\S+.\S+')
})

I hope it helps

@j5awry
Copy link
Collaborator

j5awry commented Sep 30, 2019

@AbdoDabbas , reading what you implemented, and your described problem, did you implement the last step? In the example you posted, they created another function, validate_payload(). While the description is incomplete, I inferred that this method was then called on each endpoint after serializing the payload. something like:

@route("/test")
class MyResource(Resource):
     @api.expect(MY_MODEL, validate=False)
     def post(self):
          payload = api.payload
          validate_payload(payload, MY_MODEL)

This is based on the code in the flask_restplus Resource class: https://github.com/noirbizarre/flask-restplus/blob/master/flask_restplus/resource.py#L75

I'm not having good luck on peeking where validate_payload is called though.

@AbdoDabbas
Copy link
Author

@JBarrioGarcia I tried your way, the problem now is the error message is strange, it says:

'string' does not match '\\\\S+@\\\\S+.\\\\S+'

If 'string' can be resolved using the "title", I didn't find how to replace the pattern in the message to be something I want.
So the idea is, is there a way to replace the error message?

@andreixk
Copy link

andreixk commented Nov 1, 2019

+1. the pattern=... solution works, but it gives a really ugly message and confuses users
there's a similar problem here, but it's an odd-ball solution.
This thread describes exactly the expected behaviour of a custom field, but for whatever reason it's not working. Just wondering if there's been any work done on this?

@AbdoDabbas
Copy link
Author

I was able somehow to work around this by doing two things:

  1. Depending on pydantic package, I defined the model I'm expecting in the request, along with the validations I need to do.
  2. I wrote a custom function to convert the validation errors that pydantic throws and throw them again using the same schema that flask uses when you define your api.model.

Here:
https://gist.github.com/AbdoDabbas/54f1f0ad2312e95e5a43da7f062ef577
you can find a snippet code of what I mean.

Maybe pydantic integration can be built inside flask-restplus, I was going to do it specifically with directives (response, expect), but didn't know how to override it or change.

@andreixk
Copy link

andreixk commented Nov 1, 2019

@AbdoDabbas It seems you're doing validation after flask-restplus already had a crack at it and passed it. You can definitely do validation inside the endpoints, but that pollutes your routes and creates a fractured logic - some validation happens before, some after. Besides, the @api.expect with validate=True should do exactly that already.

@AbdoDabbas
Copy link
Author

AbdoDabbas commented Nov 1, 2019

@AbdoDabbas It seems you're doing validation after flask-restplus already had a crack at it and passed it. You can definitely do validation inside the endpoints, but that pollutes your routes and creates a fractured logic - some validation happens before, some after. Besides, the @api.expect with validate=True should do exactly that already.

Actually I need to do it before, but using api model validation way will not give me what I need and it's limited as you saw in the previous comments.

I think it may work better using the before_request decorator, I will try it later.

@andreixk
Copy link

andreixk commented Nov 10, 2019

@AbdoDabbas I created a PR addressing this issue in a more standardized way (imho). If you don't want to wait, you can just update your own copy of the flask_restplus/model.py file and use it exactly the way you described in your post. Only difference, instead of returning False, you'd have to raise Exception to fail validation.

@AbdoDabbas
Copy link
Author

@andreixk
Actually I'm raising an exception already:
https://gist.github.com/AbdoDabbas/54f1f0ad2312e95e5a43da7f062ef577
This link explains the idea, we can use it in flask-restplus I think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants