koxudaxi / datamodel-code-generator

Pydantic model and dataclasses.dataclass generator for easy conversion of JSON, OpenAPI, JSON Schema, and YAML data sources.
https://koxudaxi.github.io/datamodel-code-generator/
MIT License
2.77k stars 304 forks source link

Support for adding validator decorator #464

Open sarangsbabu367 opened 3 years ago

sarangsbabu367 commented 3 years ago

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

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.
```yaml
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
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

koxudaxi commented 3 years ago

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

sarangsbabu367 commented 2 years ago

@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)

andreffs18 commented 2 years ago

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?