strawberry-graphql / strawberry

A GraphQL library for Python that leverages type annotations 🍓
https://strawberry.rocks
MIT License
4.03k stars 535 forks source link

Field level data validators and pre/post processors #788

Open la4de opened 3 years ago

la4de commented 3 years ago

It would be very useful to have possibility to define field level validators especially for those fields which do not have resolvers. My proposal is to add validators parameter to field to strawberry.field and validator decorator. This idea came to my mind when I was designing API for strawberry-graphql-django package (https://github.com/strawberry-graphql/strawberry-graphql-django/issues/10). Soon we realized that this could be core feature of strawberry library.

I implemented prototype code and made draft pull request #809.

Validators could be used for following purposes

Option 1

I'd propose following syntax for validators

def permission_validator(value, info):
    if not info.context.request.user.has_permission():
        raise Exception('Permission denied')
    return value

@strawberry.type
class User:
    name: str = strawberry.field(validators=[permission_validator])

Option 2

I'd also propose decorator syntax, which is quite similar with existing field resolver syntax. Strawberry field would provide validator decorator which could be then used to tie the field together with validator function.

@strawberry.input
class UserInput:
    name: str = strawberry.field()

    @name.validator
    def name_validator(value, info):
        if 'bad' in value:
            raise ValueError('name contains bad word')
      return value.title()

Option 3 (not doable)

def permission_validator(value, info):
    if not info.context.request.user.has_permission():
        raise Exception('Permission denied')
    return value

@strawberry.type
class User:
    # this does not work
    @strawberry.validator(permission_validator)
    name: str = strawberry.field()

Upvote & Fund

Fund with Polar

BryceBeagle commented 3 years ago

I still think that maybe it's possible do add this functionality without first-classing support for validation, through other techniques such as user-defined function decorators (this gets a bit tricky with input types w/o resolvers, so maybe not?).

However, if we do want to first-class validation, I wonder if we should take a similar approach that Python's @property decorator does the getter and setter functions have the same symbol name:

####### Property

class SomeClass:
    @property                     # implicitly my_field.getter
    def my_field() -> bool:
        return True

    @my_field.setter
    def my_field(value: bool):
        print(f"Set my_field={value}"

####### Strawberry

@strawberry.type
class User:
    @strawberry.field             # implicitly name.resolver
    def name() -> str:
        return "First Last"

    @name.validator
    def name(value):
        if 'bad' in value:
            raise ValueError('name contains bad word')
        return value.title()

Here, the same function symbol name is used for both the resolver and validator functions for the name field.

I think this might actually just be the standard practice in Python, and not actually a strict requirement with the property system; but it could be something for us to follow precedent

la4de commented 3 years ago

I added 3rd option. I think I personally would like implement both 2nd and 3rd. Any opinions?

BryceBeagle commented 3 years ago

Unfortunately, I don't think you can use decorators around assignments/expressions, only function/class definitions.

la4de commented 3 years ago

Unfortunately, I don't think you can use decorators around assignments/expressions, only function/class definitions.

~Decorator can add validator functions to the internal field definition structure. Generic field resolver can then iterate validators and execute them later. See: https://github.com/la4de/strawberry/commit/04b0ca2a0fe3e6b0a2dc65cd835846bc743ade67. I think this should be doable.~ 

You are right, it is not possible to do that. :)

patrick91 commented 3 years ago

don't know if this helps, but attrs has a concept of validators: https://www.attrs.org/en/stable/init.html#validators

jkimbo commented 3 years ago

don't know if this helps, but attrs has a concept of validators: https://www.attrs.org/en/stable/init.html#validators

I like this API. I think it would be a nice feature if Strawberry implemented it as well. (It's option 1 and 2 in the examples)

la4de commented 3 years ago

I implemented prototype code and made draft pull request #809.