datamodel-code-generator icon indicating copy to clipboard operation
datamodel-code-generator copied to clipboard

Support for adding validator decorator

Open sarangsbabu367 opened this issue 3 years ago • 2 comments

Is your feature request related to a problem? Please describe. pydantic supports the provision to add a validator decorator for fields. Is there anyway to represent this in jsonschema and data-model-generator will generate this. This can be treated as a custom-path where decorator code will be present in the given path and during the model generation this method needs to be attached to the model(Not sure about this).

Describe the solution you'd like

  • Support for specifying validator in jsonschema.
  • Need a provision to specify validators in the jsonschema and data-model generator should recognize this.
from pydantic import BaseModel, ValidationError, validator

class UserModel(BaseModel):
    name: str

    @validator('name', pre=True)
    def name_must_contain_space(cls, v):
        if ' ' not in v:
            raise ValueError('must contain a space')
        return v.title()
  • validator name and args can be described in jsonschema and path to the code can be made custom, since it will contain other logic.
type: object
properties:
  name:
    type: string
    x-validator:
      type: object
      properties:
        name:
          const: name_must_contain_space
        path:
          const: a.b.c._name_must_contain_space
        args:
          pre:
            const: True
  • With the above representation the validator logic can be declared in another method and a simple reference needs to be added in model. Like,
from pydantic import BaseModel, ValidationError, validator
from a.b.c import _name_must_contain_space

class UserModel(BaseModel):
    name: str

    @validator('name', pre=True)
    def name_must_contain_space(cls, v, values, **kwargs):
        return _name_must_contain_space(cls, v, values, **kwargs)

Describe alternatives you've considered

  • For implementing this behaviour, generated models needs to be extended and need to add this validator method in the extended model.

sarangsbabu367 avatar Jul 23 '21 05:07 sarangsbabu367

@sarangsbabu367 Thank you for your suggestion. I feel it's a good idea. I will implement it when I finish other issues.

koxudaxi avatar Jul 23 '21 15:07 koxudaxi

@koxudaxi May i open a pr for this ?

  1. Can you suggest some points to improve the validator representation in yaml, like currently there is an option for customTypePath can we use a similar name like customValidator ?
  2. Can we reduce the yaml format(minimalize) ?
Person:
  type: object
  customValidator:
    type: object
    properties:
      path: a.b.c.person_root_validator
      args:
        pre: True
  properties:
    name:
      type: string
      customValidator:
        type: object
        properties:
          path: a.b.c.name_must_contain_space
          args:
            pre: True
            always: True

In the above example, we can support an option for root-level validator. And option to give a validator name can be removed since there wont be much usecase(validator method name will be same as given by client)

sarangsbabu367 avatar Jan 21 '22 02:01 sarangsbabu367

Isn't it possible to just not "override" the "@validator" code whenever we generate the pydantic objects again?

Just adding "@validator" logic to the spec would not provide the flexibility that some examples might need. ie: in this case, we would want to validate if a given field matches a set of regexs.

openapi: 3.0.2

info:
  title: Example 🐶
  version: 1.0.0

paths: {}

components:
  schemas:
    example.Job:
      title: Job
      type: object
      properties:
        schedule:
          title: Schedule
          type: string
          description: Frequency of cronjob. (@once to run 1 time, None to not run automatically)
          example: 0 1 * * 1
# example.py
(...)

# added manually ----->
def compile_match_cron_expression(expression: str):
    cron_re = re.compile(
        r"(@(once|annually|yearly|monthly|weekly|daily|hourly|reboot))|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})"
    )
    return cron_re.match(expression) or None
# <---------------------

class Job(BaseModel):
    schedule: Optional[str] = Field(
        None,
        description='Frequency of cronjob. (@once to run 1 time, None to not run automatically)',
        example='0 1 * * 1',
        title='Schedule',
    )
    
    # added manually ----->
    @validator("schedule")
    def schedule_is_cron(cls, v):
        if v:
            assert compile_match_cron_expression(v), "Value provided is not a valid cron expression."
        return v
    # <---------------------

This could also solve this problem. Is this something that datamodel-codegen allows today?

andreffs18 avatar Oct 08 '22 11:10 andreffs18