В данной статье я расскажу вам историю "как я до этого дошёл" и мы рассмотрим основные преимущества данной библиотеки. Все полезные ссылки вы найдёте в конце статьи.
История создания
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 (проверка типов данных), что так же послужило хорошим опытом.
Ссылки
Комментарии (12)
SemakMillev
06.05.2022 07:44Подскажите, а что с производительностью? Что быстрее если банально сравнить
if type(var) == str
Или var.stringType?
gurkin33 Автор
06.05.2022 08:14+1# Я думаю, быстрее и практичнее сделать так: if isinstance(var, str):
Конечно же, если вам надо проверить только тип данных, то вам не надо использовать сторонние библиотеки, а пользоваться встроенными функциями. Данная библиотека нужна для проверки многих параметров. Например, username должен быть:
Тип str (
if not isinstance(var, str): # return error
)Длинна от 4 до 64 включительно(
if not 4 <= len(var) <= 64: # return error
)Не содержать пробелы (
if ' ' in var: # return error
)Содержать только латинские буквы или цифры (
if not var.isalnum(): # return error
)Не быть равным admin или root (
if var in ['admin', 'root']: # return error
)
Как вы можете заметить задача усложнилась. Кроме того, что вам надо написать много
if
, вам так же надо продумать сообщения, которые надо отправить пользователю. Вот здесь может пригодиться библиотека валидации. :)
pashkatrick
06.05.2022 10:07в 3.10 версии питона появился паттерн матчер, видел примеры как раз на валидации входных данных(сам не использовал). Хотелось бы посмотреть в сравнении с вашим решеним в разрезе простота/удобство/скорость.
gurkin33 Автор
06.05.2022 11:59+1Как я понимаю, "паттерн матчер" это что-то типа switch/case (например, у PHP или Bash). Как по мне, то это немного для других целей, не для валидации.
Но! Если вы что-то можете сделать быстро средствами самого Python, то я рекомендую не прибегать к использованию библиотек, гораздо быстрее решить простые задачи локальными средствами. А уже если вам надо произвести сложную задачу, то чтобы "не изобретать велосипед", я рекомендую использовать дополнительные библиотеки. В моём комментарии выше, есть пример, где входящее значение должно пройти 5 правил и это только для username, а надо проверить еще пароль, email, имя, фамилию, т.п. В таком случае лучше использовать универсальное решение.
Tirael78
Давно существует attrs, которая качественно и полностью закрывает все потребности в области валидация в python. Кроме того, в ней можно гибко настроить не только валидацию, но и преобразование данных, без чего в больших проектах не обойтись.
С другой стороны, вы попрактиковались, получив таким образом собственный опыт, это тоже отлично
ArsenAbakarov
Все же attrs задумывался как фреймворк для замены обычных классов (не буду голословен, тут hynec это сказал)
Для валидации обычно используется pydantic (по крайней мере там, где я успел поработать)
Tirael78
Поверьте это не так. Мы использовали в ряде проектов и pydantic, в итоге отказались от него из соображений скорости, возможности гибкой настройки. Тот факт, что attrs ещё и позволяет на выходе получать удобные модели , только повышает удобство его использования.
Ещё раз отдельно сфокусирую внимание на возможности гибкой настройки в attrs, не буду скрывать, мы дописывали свои модели валидации и конвертации для attrs, и да, там есть ряд нюансов, которые , при активном использовании, устранить можно только доработкой. Возможно мы засушим свои наработки в общий репозиторий.
ArsenAbakarov
Замечательно, было бы интересно посмотреть
googoosik
Можно пожалуйста поподробнее о том, в чем именно вам не хватило гибкости pydantic?
danilovmy
Я не знаю почему именно у автора выше пайдантик не зашёл, но в моих проектах он не «заходит» вообще, потому что не умеет работать с ошибками и в некоторых случаях сериализации медленнее работает. Я писал об этом на Хабре. Для простых задач он подходит, это видно по любви к педантику у начинающих и на микро АПИ, где не хочется тратить время на подумать а надо, чтоб работало. Для сложных задач валидации полей или нелинейных сериализаторов — пайдантик превращается в «тыкву».
AnthonyMikh
Это в скором времени вполне могут поправить.
gurkin33 Автор
Что касается преобразования или сериализации, это важная тема, выходящая за рамки данной библиотеки статьи, возможно, тут пригодится attrs.
A так же соглашусь, что библиотек, которые способны валидировать данные много, но не все ориентированы на конечного пользователя (обычного пользователя). Например, не всем может быть понятна надпись:
Гораздо понятнее будет:
Все зависит от поставленных задач, а так же удобства использования при разработке.