pallets-eco / wtforms

A flexible forms validation and rendering library for Python.
https://wtforms.readthedocs.io
BSD 3-Clause "New" or "Revised" License
1.51k stars 397 forks source link

It would be great if `validators.regexp` could use `re.fullmatch` #867

Open dougthor42 opened 3 weeks ago

dougthor42 commented 3 weeks ago

The regexp validator uses re.match:

https://github.com/pallets-eco/wtforms/blob/bbfb78c487a2d95dd012a6332d719a3d440498f9/src/wtforms/validators.py#L352

This means that it's not trivial to ensure that the entire string matches a regex, especially when that regex is complex.

I propose something like this:

class Regexp:
    """
    Validates the field against a user provided regexp.

    :param regex:
        The regular expression string to use. Can also be a compiled regular
        expression pattern.
    :param flags:
        The regexp flags to use, for example re.IGNORECASE. Ignored if
        `regex` is not a string.
    :param message:
        Error message to raise in case of a validation error.
    :param re_match_type
        "match" (default), "fullmatch", or "search"
    """

    def __init__(self, regex, flags=0, message=None, re_match_type="match":
        if isinstance(regex, str):
            regex = re.compile(regex, flags)
        self.regex = regex
        self.message = message
        self.re_match_type = re_match_type

    def __call__(self, form, field, message=None):
        if self.re_match_type == "match":
            match = self.regex.match(field.data or "")
        if self.re_match_type == "fullmatch":
            match = self.regex.fullmatch(field.data or "")
        elif self.re_match_type == "search":
            match = self.regex.search(field.data or "")
        else:
            raise ValueError("... but how???")

        if match:
            return match

        if message is None:
            if self.message is None:
                message = field.gettext("Invalid input.")
            else:
                message = self.message

        raise ValidationError(message)

Obviously I could make it more "intelligent" with getattr or something, and a real implementation would probably use an enum and do the validation elsewhere, but for the purposes of getting the idea across, the above is sufficient.

Thoughts? Would you mind if I implemented this?

Current workaround:

Make a custom validator akin to:

def validate_regexp_fullmatch(regex: str | re.Pattern) -> Callable:
    """A validator very similar to `wtforms.validators.regexp`, except that it uses 'fullmatch'."""
    message = "Regex Match failure"
    if isinstance(regex, str):
        regex = re.compile(regex)

    def _validate(form, field):
        match = regex.fullmatch(field.data or "")
        if match:
            return match

        raise wtforms.validators.ValidationError(message)

    return _validate

Environment

artempronevskiy commented 1 week ago

https://github.com/pallets-eco/wtforms/pull/871