Respect Validation на Python

В данной статье я расскажу вам историю «как я до этого дошёл» и мы рассмотрим основные преимущества данной библиотеки. Все полезные ссылки вы найдёте в конце статьи.

9323ff891e0ada4efbdbeecd9835171a.jpeg

История создания

6 или 7 лет назад я познакомился с библиотекой respect/validation на PHP. Был в восторге от её архитектуры и простоты использования, она до сих пор работает в нескольких моих проектах. Шли годы и я пересел на Python. Иногда, приходится писать API, а значит валидировать данные, которые прислал пользователь. И вот валидация стала для меня болью, я перепробовал несколько библиотек и никто не мог дать мне простоты и гибкости (моё мнение может отличаться от мнения других людей :)) , пробовал писать что-то своё, но все заканчивалось фиаско. В конце концов, я решил «не изобретать велосипед», а просто переписать любимую библиотеку под другой язык. Решение оказалось удачным, уже начал интегрировать её в свои проекты — доволен, как слон.

Основные преимущества

Далее я представлю вашему вниманию список преимуществ. Преимущества могут показаться вам абстрактными, потому что я не буду сравнивать эту библиотеку с другими, дабы никого не обидеть.

  • Цепочки правил. Рассмотрим пример:

    v.stringType().alnum().noWhitespace().length(4,64).validate('gurkin33')

    Вы можете с лёгкостью проследить логику проверки, взглянув на цепочку правил. Слева направо — значение должно иметь тип данных str, содержать только буквы или цифры, длинна от 4 до 64.

  • Никаких дополнительных импортов — вам не надо импортировать дополнительные пакеты или модули, достаточно импортировать один модуль валидатора:

    from respect_validation import Validator as v

    И никаких словарей с правилами проверки.

  • Библиотека имеет несколько логических операторов, которые могу быть использованы при создании цепочек проверки. Операторы:

# AllOf
# Все указанные цепочки правил должны быть удослетворены
v.AllOf(v.stringType(), v.alnum(), v.noWhitespace().length(4, 64)).validate('')
# Результат: False

# AnyOf
# Если любая из указанных цепочек правил будет валидна, 
# то закончить проверку удовлетворительно
v.AnyOf(v.stringType(), v.alnum(), v.noWhitespace().length(4, 64)).validate('')
# Результат: True

# NoneOf
# Если никакая цепочта не удовлетворяет условия,
# то закончить проверку удовлетворительно
v.NoneOf(v.stringType(), v.alnum(), v.noWhitespace().length(4, 64)).validate('')

# OneOf
# Только одна цепочка правил должна быть удовлетворена
v.OneOf(v.stringType(), v.alnum(), v.noWhitespace().length(4, 64)).validate('')
# Результат: True

# When
# Аналог if .. then .. else
v.When(
	v.stringType(),  # если тип str, то проверить .. иначе перейти к другой цепочке
  v.alnum().length(4, 64),  # строка содержит только буквы и цифры, в пределах указанной длины
  v.intType().max(100),  # число с максимальным значением 100
).validate(10)
# Результат: True
  • Оператору Not я хочу уделить особое внимание. Любую цепочку правил можно инвертировать с помощью данного оператора. При этом, так же поменяются и выводимые сообщения, если проверка не пройдена (о сообщениях чуть дальше).

v.Not(v.intVal().positive()).validate(-1.5)
# Результат: True
  • Так же я хочу выделить правила Optional и Nullable. Данные правила помогают мне фильтровать данные для базы данных — когда разрешено None, но если что-то есть, то будьте добры соблюдайте правила.

# Optional
# Если значение None или '', то игнорировать цепочку правил внутри
v.optional(v.alpha().length(3, 10)).validate('')
# Результат: True

# Nullable
# Если значение None, то игнорировать цепочку правил внутри
v.nullable(v.alpha().length(3, 10)).validate(None)
# Результат: True
  • На данный момент библиотека содержит более 130 правил, их должно хватить для стандартных проверок. Важно! Большинство правил могут работать с несколькими типами данных поэтому, если вы точно знаете какой тип данных должен быть, то я рекомендую начинать цепочку правил именно с указания типа данных. Полный список правил тут.

  • Очень просто создать свой пакет правил и использовать его по необходимости. Я хочу еще раз подчеркнуть, не класс, не метод, а именно пакет (package) правил или несколько пакетов. Больше информации об этой возможности тут.

  • Сообщения об ошибках. Все стандартные правила уже имеют шаблоны сообщений об ошибке. И их как минимум два для каждого правила! Первый стандартный, а второй на случай, если вы захотите использовать оператор Not для данного правила. Существует несколько типов результатов проверки:

    • True/False, при использовании метода .validate(input_value)

    • Вернуть исключение (exception) на первом не удовлетворенном правиле или None (при успехе). Для этого надо использовать метод .check(input_value)

    • Вернуть исключение (exception), пройдясь по всем правилам или None (при успехе). Для этого надо использовать метод .claim(input_value)

    В данном случае мы рассмотрим последний пример, с использованием метода claim():

try:
    v.alnum().noWhitespace().length(1, 15)\
    .claim('really messed up screen#name')
except NestedValidationException as exception:
		# у класса NestedValidationException есть два метода 
    # для сбора сообщений об ошибках
    # первый get_full_message(), вернёт нам сообщения в виде
    # отформатированного текста
    print(exception.get_full_message())
    
    # get_messages() вернёт нам словарь, где ключи (keys) 
    # это имена правил, которые были не удовлетворены
    print(exception.get_messages())

Результат:

# get_full_message()
- All of the required rules must pass for "really messed up screen#name"
  - "really messed up screen#name" must contain only letters (a-z) and digits (0-9)
  - "really messed up screen#name" must not contain whitespace
  - "really messed up screen#name" must have a length between 1 and 15
  
 # get_messages()
 {
  'alnum': ['"really messed up screen#name" must contain only letters (a-z) and digits (0-9)'], 
  'noWhitespace': ['"really messed up screen#name" must not contain whitespace'], 
  'length': ['"really messed up screen#name" must have a length between 1 and 15']
}

Интеграция с flask

Как бонус, я добавил дополнительный класс FormValidation, который может упростить процесс проверки входящих данных. На моей практике, в большинстве случаев, frontend присылает json, который далее трансформируется в словарь (dict). Именно получившийся словарь можно пропустить через FormValidator и получить понятные сообщения, которые далее можно транслировать пользователю на web форму. Пример работы flask и FormValidator тут.

Заключение

В итоге, я получил желанный результат, теперь у меня есть «та самая» библиотека.

Python и PHP имеют массу различий, но каждый из языков по своему прекрасен, со своей «изюменкой». Они имеют разную логику, следовательно разные подходы к решению задач, в определённых моментах. Именно по этому, архитектуру изначальной библиотеки сохранить удалось, но реализация имеет массу отличий, хотя при использовании вы должны получить одинаковый результат.

Библиотека прошла через ряд тестов pytest (проверка логики исполнения), flake8 (проверка синтаксиса) и mypy (проверка типов данных), что так же послужило хорошим опытом.

Ссылки

Репозиторий: https://github.com/gurkin33/respect_validation

Документация: https://gurkin33.github.io/respect_validation/

PyPI: https://pypi.org/project/respect-validation/

© Habrahabr.ru