2025, Dec 17 09:01
Переиспользуемые поля Marshmallow для Flask‑Smorest: один класс вместо копипаста
Как в Marshmallow и Flask-Smorest вынести повторяющиеся поля в собственный класс: общие метаданные и Regexp-валидация, меньше копипаста и документация API.
Когда вы создаете API на Flask с использованием flask-smorest и Marshmallow, нередко приходится повторять одни и те же ограничения для входных данных на нескольких эндпоинтах. Раз за разом копировать один и тот же тип поля с одинаковыми метаданными и валидатором RegExp — шумно и чревато ошибками при последующих правках.
Повторяющиеся поля схем: как это обычно выглядит
Ниже показан типичный паттерн, когда каждая схема заново объявляет одну и ту же строку с теми же правилами валидации и теми же описательными метаданными.
from marshmallow import Schema, fields, validate
class EndpointA:
class InPayload(Schema):
req_code = fields.Str(
metadata={'description': 'Request Number', 'example': 'REQUEST12345'},
validate=validate.Regexp(
regex=r"^REQUEST\d{3,9}$",
error="Input string didn't match required format - REQUEST12345"
)
)
class EndpointB:
class InPayload(Schema):
req_code = fields.Str(
metadata={'description': 'Request Number', 'example': 'REQUEST12345'},
validate=validate.Regexp(
regex=r"^REQUEST\d{3,9}$",
error="Input string didn't match required format - REQUEST12345"
)
)
В чем реальная проблема
Каждый эндпоинт повторяет одну и ту же настройку поля: тип, метаданные и встроенный validate.Regexp с одинаковыми параметрами. Даже если вынести экземпляр валидатора в вспомогательный объект и ссылаться на него из каждой схемы, вам все равно приходится заново прописывать конфигурацию поля и метаданные. Итог — дублирование, а дублирование ведет к рассинхронизации.
Аккуратное решение: переиспользуемое поле
В Marshmallow можно упаковать тип, метаданные и валидаторы в собственный подкласс Field. Вы один раз описываете общее поведение, а затем используете это поле в нужных местах. Так сохраняется встроенный валидатор, неизменными остаются и регулярное выражение, и текст ошибки, а шаблонный код из схем исчезает.
from marshmallow import Schema, fields, validate
class ReqNumField(fields.String):
def __init__(self, *args, **kwargs):
super().__init__(
*args,
metadata={'description': 'Request Number', 'example': 'REQUEST12345'},
validate=validate.Regexp(
regex=r"^REQUEST\d{3,9}$",
error="Input string didn't match required format - REQUEST12345"
),
**kwargs
)
class ServiceOne:
class InputSchema(Schema):
req_code = ReqNumField()
class ServiceTwo:
class InputSchema(Schema):
req_code = ReqNumField()
Подход не меняет семантику: поле по‑прежнему строковое, паттерн тот же — ^REQUEST\d{3,9}$, текст ошибки не меняется, а описательные метаданные остаются привязаны к полю. В любом месте, где вы используете ReqNumField, вы автоматически получаете согласованную валидацию и документацию.
Почему это важно
Централизация валидатора и метаданных убирает бесконечный копипаст между схемами. Если формат когда‑нибудь поменяется, править придется только в одном месте. Поскольку поле несет свои метаданные, сгенерированная документация API остается согласованной без дополнительных усилий. И так как решение опирается на встроенный validate.Regexp из Marshmallow, сохраняется стандартное поведение, к которому вы и стремились.
Итоги
Когда нескольким эндпоинтам нужны одинаковые правила валидации, упакуйте их в собственное поле Marshmallow и переиспользуйте. Держите логику и регулярку там, где им место, избегайте дублирования в схемах, а flask-smorest сам поднимет единую документацию из одной-единственной декларации.