Source code for edify.integrations.django

"""Django integration — validate model / form fields against an edify :class:`Pattern`.

Requires the ``django`` extra: ``pip install edify[django]``. Django ships no type
information, so the module is loaded through :func:`importlib.import_module` and its
one used entry point is described by a local :class:`Protocol`.
"""

from __future__ import annotations

import re
from importlib import import_module
from typing import Protocol, cast

from edify.pattern.composition import Pattern


class _RegexValidator(Protocol):
    regex: re.Pattern[str]
    message: str
    code: str

    def __call__(self, value: object) -> None: ...


class _RegexValidatorFactory(Protocol):
    def __call__(self, *, regex: str, message: str, code: str) -> _RegexValidator: ...


[docs] def pattern_validator( pattern: Pattern, message: str | None = None, code: str = "invalid", ) -> _RegexValidator: """Return a :class:`django.core.validators.RegexValidator` pinned to ``pattern``. Args: pattern: The :class:`edify.Pattern` a field must match anywhere in. message: The error message the validator raises on rejection. Defaults to a message that reproduces the pattern's regex string. code: The Django validation-error code the validator raises on rejection. """ validators_module = import_module("django.core.validators") regex_validator_factory = cast(_RegexValidatorFactory, validators_module.RegexValidator) resolved_message = ( message if message is not None else f"value does not match {pattern.to_regex_string()}" ) return regex_validator_factory( regex=pattern.to_regex_string(), message=resolved_message, code=code, )