wemake-services / wemake-django-rest

Create Django REST APIs the right way, no magic intended
MIT License
11 stars 1 forks source link

Most pythonic validator ever #2

Closed orsinium closed 3 years ago

orsinium commented 5 years ago

Key principles:

  1. Use type hinting and dataclass
  2. Use enum.Enum for choices.
  3. Avoid useless field(). We can set up field params as tuple.
  4. Move error messages from validator. They too verbose and can be reused.
  5. Set up validator for field not only as callable object.
import re
from datetime import date
from enum import Enum
from typing import List

from wemake import default, optional, value, Error, errors

errors.register('title', '{name} ({value}) should start with capital letter')
# ^ Example: "First Name (first_name) should start with capital letter."

class Sex(Enum):
    FEMALE = 0
    MALE = 1

@dataclass
class Author:
    first_name: str = 'First Name', value.istitle(), Error('title')
    # ^ 1. If first parameter is string, it's field verbose name.
    # ^ 2. `value` is lambda replacement with MagicMock behaivor
    # ^     (https://github.com/kachayev/fn.py).
    # ^ 3. We can pass Error(slug) that will be returned if validator returns False.
    #       If we need more than one error, validator can return Error object itself.
    last_name: str = 'Last Name', value.istitle(), optional, Error('title')
    # ^ optional parameter
    sex: str = Sex, default(Sex.FEMALE)
    # ^ 1. enum.Enum will be interpreted as choice.
    # ^ 2. Also we can pass default value via `default(...)`.
    # ^ 3. If verbose name missed, it will be automatically generated from field name.
    hometown: str
    # ^ Yeah, you can miss any params, of course.
    birthday: date = lambda d: 0 < (date.today() - d) < 150 * 365, optional
    # ^ Sometimes we have complicated validator that can't be covered by `value`

@dataclass
class Book:
    title: str
    authors: List[Author] = len(value) >= 1
    # ^ We can create many-to-many relation in type hints format.
    isbn: str = 'ISBN', re.compile(r'[0-9-]+'), 'International Standard Book Number'
    # ^ 1. Regexp object also will be interpreted as validator
    # ^ 2. Str not in first position will be interpreted as help text.
sobolevn commented 5 years ago

@orsinium thanks! This looks interesting.

Please, take a look at the proof of concept that I have done today (wrote in less than an hour compared to the wasted day yesterday 😞 ).

proofit404 commented 5 years ago

I like user-defined errors like for title.

My only concern about it global nature.

orsinium commented 5 years ago

Good point, thank you. I will think about namespaces for errors or something like this.

proofit404 commented 5 years ago

By the way, I'm looking forward to using it as an option to context contract in the stories library. Would be cool if it will be a separate package from the rest.

orsinium commented 5 years ago

Thank you! I'm going to make it after dephell. I will notify you when I start it.

proofit404 commented 5 years ago

I found this gem on the internet: https://www.youtube.com/watch?v=7CcbEGVpnMg

I think we should strongly consider similar possibilities in this library design.

orsinium commented 3 years ago

The syntax in the description doesn't work with type annotations, braces are required.