Skip to content

Messages

Basic

Ensurance Messages

Passby a string.

>>> z.ensure(lambda x: len(x) > 5 , message='Too short').parse("hello")
Traceback (most recent call last):
zangar.exceptions.ValidationError: [{'msgs': ['Too short']}]

Passby a function.

>>> z.ensure(lambda x: len(x) > 5 , message=lambda x: f'The ${x} is too short').parse("hello")
Traceback (most recent call last):
zangar.exceptions.ValidationError: [{'msgs': ['The $hello is too short']}]

Transformation Messages

Passby a string.

>>> z.transform(int, message='Invalid integer').parse('a')
Traceback (most recent call last):
zangar.exceptions.ValidationError: [{'msgs': ['Invalid integer']}]

Passby a function.

>>> z.transform(int, message=lambda x: f'Invalid integer: {x!r}').parse('a')
Traceback (most recent call last):
zangar.exceptions.ValidationError: [{'msgs': ["Invalid integer: 'a'"]}]

Type messages

API like z.int and z.to.int are implemented based on ensure and transform, and they also support the above-mentioned custom message mechanism.

z.str(message='Invalid string')
z.int(message='Invalid integer')
z.float(message='Invalid float')
z.to.str(message='Invalid string')
z.to.int(message='Invalid integer')
...

Required field message

>>> z.object({
...     'username': z.field(z.str()).required(message='Username is required.'),
... }).parse({})
Traceback (most recent call last):
zangar.exceptions.ValidationError: [{'loc': ['username'], 'msgs': ['Username is required.']}]

Warning

If the required field message is a function, the function's argument will be a None.

Different messages

Message can be in any form you need, not just a string.

Note

In practical scenarios, the frontend often needs the backend to provide an error code rather than an exact error message. This is because the frontend may have its own multilingual settings or custom text requirements.

import enum
import json


class ErrorCode(enum.Enum):
    INVALID = 1000, 'Invalid value.'
    REQUIRED = 1001, 'This field is required.'
    IS_EMAIL = 1002, 'Must provide a valid email address.'

def custom_default(o):
    if isinstance(o, enum.Enum):
        return {'code': o.value[0], 'description': o.value[1]}
    raise TypeError(f'Cannot serialize object of {type(obj)}')

schema = z.object({
    'username': z.field(z.str()).required(message=ErrorCode.REQUIRED)
})
>>> try:
...     schema.parse({})
... except z.ValidationError as e:
...     print(json.dumps(e.format_errors(), default=custom_default, indent=2))
[
  {
    "loc": [
      "username"
    ],
    "msgs": [
      {
        "code": 1001,
        "description": "This field is required."
      }
    ]
  }
]

Modify default messages

The default messages in Zangar's validation methods are all string types. Overriding these default messages one by one can be costly. Therefore, Zangar offers a context object that allows modifying default messages wherever needed.

import zangar as z

class MyDefaultMessages(z.DefaultMessages):
    def default(self, name: str, value, ctx: dict):
        if name == 'field_required':
            return {
                'error': 1001,
                'reason': 'This field is required.'
            }
        return {
            'error': 1000,
            'reason': super().default(name, value, ctx),
        }
>>> with MyDefaultMessages():
...     try:
...         z.object({
...             'username': z.str().min(6),
...             'password': z.str()
...         }).parse({'username': 'user'})
...     except z.ValidationError as e:
...         print(json.dumps(e.format_errors(), indent=2))
[
  {
    "loc": [
      "username"
    ],
    "msgs": [
      {
        "error": 1000,
        "reason": "The minimum length of the string is 6"
      }
    ]
  },
  {
    "loc": [
      "password"
    ],
    "msgs": [
      {
        "error": 1001,
        "reason": "This field is required."
      }
    ]
  }
]

This is the source code for Zangar DefaultMessages.default.

    def default(self, name: str, value: Any, ctx: dict):
        if name == "field_required":
            return "This field is required"

        if name == "type_check":
            return f"Expected {ctx['expected_type'].__name__}, received {type(value).__name__}"

        if name == "type_convertion":
            return (
                f"Cannot convert the value {value!r} to {ctx['expected_type'].__name__}"
            )

        if name == "transform_failed":
            return str(ctx["exc"])

        if name == "str_min":
            return f"The minimum length of the string is {ctx['min']}"

        if name == "str_max":
            return f"The maximum length of the string is {ctx['max']}"

        if name == "number_gte":
            return f"The value should be greater than or equal to {ctx['gte']}"

        if name == "number_gt":
            return f"The value should be greater than {ctx['gt']}"

        if name == "number_lte":
            return f"The value should be less than or equal to {ctx['lte']}"

        if name == "number_lt":
            return f"The value should be less than {ctx['lt']}"

        if name == "datetime_is_aware":
            return "The datetime should be aware"

        if name == "datetime_is_naive":
            return "The datetime should be naive"

        return "Invalid value"