Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
validators.py733 linesDownload Raw Back to wtforms
1import ipaddress2import math3import re4import uuid5 6__all__ = (7    "DataRequired",8    "data_required",9    "Email",10    "email",11    "EqualTo",12    "equal_to",13    "IPAddress",14    "ip_address",15    "InputRequired",16    "input_required",17    "Length",18    "length",19    "NumberRange",20    "number_range",21    "Optional",22    "optional",23    "Regexp",24    "regexp",25    "URL",26    "url",27    "AnyOf",28    "any_of",29    "NoneOf",30    "none_of",31    "MacAddress",32    "mac_address",33    "UUID",34    "ValidationError",35    "StopValidation",36    "readonly",37    "ReadOnly",38    "disabled",39    "Disabled",40)41 42 43class ValidationError(ValueError):44    """45    Raised when a validator fails to validate its input.46    """47 48    def __init__(self, message="", *args, **kwargs):49        ValueError.__init__(self, message, *args, **kwargs)50 51 52class StopValidation(Exception):53    """54    Causes the validation chain to stop.55 56    If StopValidation is raised, no more validators in the validation chain are57    called. If raised with a message, the message will be added to the errors58    list.59    """60 61    def __init__(self, message="", *args, **kwargs):62        Exception.__init__(self, message, *args, **kwargs)63 64 65class EqualTo:66    """67    Compares the values of two fields.68 69    :param fieldname:70        The name of the other field to compare to.71    :param message:72        Error message to raise in case of a validation error. Can be73        interpolated with `%(other_label)s` and `%(other_name)s` to provide a74        more helpful error.75    """76 77    def __init__(self, fieldname, message=None):78        self.fieldname = fieldname79        self.message = message80 81    def __call__(self, form, field):82        try:83            other = form[self.fieldname]84        except KeyError as exc:85            raise ValidationError(86                field.gettext("Invalid field name '%s'.") % self.fieldname87            ) from exc88        if field.data == other.data:89            return90 91        d = {92            "other_label": hasattr(other, "label")93            and other.label.text94            or self.fieldname,95            "other_name": self.fieldname,96        }97        message = self.message98        if message is None:99            message = field.gettext("Field must be equal to %(other_name)s.")100 101        raise ValidationError(message % d)102 103 104class Length:105    """106    Validates the length of a string.107 108    :param min:109        The minimum required length of the string. If not provided, minimum110        length will not be checked.111    :param max:112        The maximum length of the string. If not provided, maximum length113        will not be checked.114    :param message:115        Error message to raise in case of a validation error. Can be116        interpolated using `%(min)d` and `%(max)d` if desired. Useful defaults117        are provided depending on the existence of min and max.118 119    When supported, sets the `minlength` and `maxlength` attributes on widgets.120    """121 122    def __init__(self, min=-1, max=-1, message=None):123        assert (124            min != -1 or max != -1125        ), "At least one of `min` or `max` must be specified."126        assert max == -1 or min <= max, "`min` cannot be more than `max`."127        self.min = min128        self.max = max129        self.message = message130        self.field_flags = {}131        if self.min != -1:132            self.field_flags["minlength"] = self.min133        if self.max != -1:134            self.field_flags["maxlength"] = self.max135 136    def __call__(self, form, field):137        length = field.data and len(field.data) or 0138        if length >= self.min and (self.max == -1 or length <= self.max):139            return140 141        if self.message is not None:142            message = self.message143 144        elif self.max == -1:145            message = field.ngettext(146                "Field must be at least %(min)d character long.",147                "Field must be at least %(min)d characters long.",148                self.min,149            )150        elif self.min == -1:151            message = field.ngettext(152                "Field cannot be longer than %(max)d character.",153                "Field cannot be longer than %(max)d characters.",154                self.max,155            )156        elif self.min == self.max:157            message = field.ngettext(158                "Field must be exactly %(max)d character long.",159                "Field must be exactly %(max)d characters long.",160                self.max,161            )162        else:163            message = field.gettext(164                "Field must be between %(min)d and %(max)d characters long."165            )166 167        raise ValidationError(message % dict(min=self.min, max=self.max, length=length))168 169 170class NumberRange:171    """172    Validates that a number is of a minimum and/or maximum value, inclusive.173    This will work with any comparable number type, such as floats and174    decimals, not just integers.175 176    :param min:177        The minimum required value of the number. If not provided, minimum178        value will not be checked.179    :param max:180        The maximum value of the number. If not provided, maximum value181        will not be checked.182    :param message:183        Error message to raise in case of a validation error. Can be184        interpolated using `%(min)s` and `%(max)s` if desired. Useful defaults185        are provided depending on the existence of min and max.186 187    When supported, sets the `min` and `max` attributes on widgets.188    """189 190    def __init__(self, min=None, max=None, message=None):191        self.min = min192        self.max = max193        self.message = message194        self.field_flags = {}195        if self.min is not None:196            self.field_flags["min"] = self.min197        if self.max is not None:198            self.field_flags["max"] = self.max199 200    def __call__(self, form, field):201        data = field.data202        if (203            data is not None204            and not math.isnan(data)205            and (self.min is None or data >= self.min)206            and (self.max is None or data <= self.max)207        ):208            return209 210        if self.message is not None:211            message = self.message212 213        # we use %(min)s interpolation to support floats, None, and214        # Decimals without throwing a formatting exception.215        elif self.max is None:216            message = field.gettext("Number must be at least %(min)s.")217 218        elif self.min is None:219            message = field.gettext("Number must be at most %(max)s.")220 221        else:222            message = field.gettext("Number must be between %(min)s and %(max)s.")223 224        raise ValidationError(message % dict(min=self.min, max=self.max))225 226 227class Optional:228    """229    Allows empty input and stops the validation chain from continuing.230 231    If input is empty, also removes prior errors (such as processing errors)232    from the field.233 234    :param strip_whitespace:235        If True (the default) also stop the validation chain on input which236        consists of only whitespace.237 238    Sets the `optional` attribute on widgets.239    """240 241    def __init__(self, strip_whitespace=True):242        if strip_whitespace:243            self.string_check = lambda s: s.strip()244        else:245            self.string_check = lambda s: s246 247        self.field_flags = {"optional": True}248 249    def __call__(self, form, field):250        if (251            not field.raw_data252            or isinstance(field.raw_data[0], str)253            and not self.string_check(field.raw_data[0])254        ):255            field.errors[:] = []256            raise StopValidation()257 258 259class DataRequired:260    """261    Checks the field's data is 'truthy' otherwise stops the validation chain.262 263    This validator checks that the ``data`` attribute on the field is a 'true'264    value (effectively, it does ``if field.data``.) Furthermore, if the data265    is a string type, a string containing only whitespace characters is266    considered false.267 268    If the data is empty, also removes prior errors (such as processing errors)269    from the field.270 271    **NOTE** this validator used to be called `Required` but the way it behaved272    (requiring coerced data, not input data) meant it functioned in a way273    which was not symmetric to the `Optional` validator and furthermore caused274    confusion with certain fields which coerced data to 'falsey' values like275    ``0``, ``Decimal(0)``, ``time(0)`` etc. Unless a very specific reason276    exists, we recommend using the :class:`InputRequired` instead.277 278    :param message:279        Error message to raise in case of a validation error.280 281    Sets the `required` attribute on widgets.282    """283 284    def __init__(self, message=None):285        self.message = message286        self.field_flags = {"required": True}287 288    def __call__(self, form, field):289        if field.data and (not isinstance(field.data, str) or field.data.strip()):290            return291 292        if self.message is None:293            message = field.gettext("This field is required.")294        else:295            message = self.message296 297        field.errors[:] = []298        raise StopValidation(message)299 300 301class InputRequired:302    """303    Validates that input was provided for this field.304 305    Note there is a distinction between this and DataRequired in that306    InputRequired looks that form-input data was provided, and DataRequired307    looks at the post-coercion data. This means that this validator only checks308    whether non-empty data was sent, not whether non-empty data was coerced309    from that data. Initially populated data is not considered sent.310 311    Sets the `required` attribute on widgets.312    """313 314    def __init__(self, message=None):315        self.message = message316        self.field_flags = {"required": True}317 318    def __call__(self, form, field):319        if field.raw_data and field.raw_data[0]:320            return321 322        if self.message is None:323            message = field.gettext("This field is required.")324        else:325            message = self.message326 327        field.errors[:] = []328        raise StopValidation(message)329 330 331class Regexp:332    """333    Validates the field against a user provided regexp.334 335    :param regex:336        The regular expression string to use. Can also be a compiled regular337        expression pattern.338    :param flags:339        The regexp flags to use, for example re.IGNORECASE. Ignored if340        `regex` is not a string.341    :param message:342        Error message to raise in case of a validation error.343    """344 345    def __init__(self, regex, flags=0, message=None):346        if isinstance(regex, str):347            regex = re.compile(regex, flags)348        self.regex = regex349        self.message = message350 351    def __call__(self, form, field, message=None):352        match = self.regex.match(field.data or "")353        if match:354            return match355 356        if message is None:357            if self.message is None:358                message = field.gettext("Invalid input.")359            else:360                message = self.message361 362        raise ValidationError(message)363 364 365class Email:366    """367    Validates an email address. Requires email_validator package to be368    installed. For ex: pip install wtforms[email].369 370    :param message:371        Error message to raise in case of a validation error.372    :param granular_message:373        Use validation failed message from email_validator library374        (Default False).375    :param check_deliverability:376        Perform domain name resolution check (Default False).377    :param allow_smtputf8:378        Fail validation for addresses that would require SMTPUTF8379        (Default True).380    :param allow_empty_local:381        Allow an empty local part (i.e. @example.com), e.g. for validating382        Postfix aliases (Default False).383    """384 385    def __init__(386        self,387        message=None,388        granular_message=False,389        check_deliverability=False,390        allow_smtputf8=True,391        allow_empty_local=False,392    ):393        self.message = message394        self.granular_message = granular_message395        self.check_deliverability = check_deliverability396        self.allow_smtputf8 = allow_smtputf8397        self.allow_empty_local = allow_empty_local398 399    def __call__(self, form, field):400        try:401            import email_validator402        except ImportError as exc:  # pragma: no cover403            raise Exception(404                "Install 'email_validator' for email validation support."405            ) from exc406 407        try:408            if field.data is None:409                raise email_validator.EmailNotValidError()410            email_validator.validate_email(411                field.data,412                check_deliverability=self.check_deliverability,413                allow_smtputf8=self.allow_smtputf8,414                allow_empty_local=self.allow_empty_local,415            )416        except email_validator.EmailNotValidError as e:417            message = self.message418            if message is None:419                if self.granular_message:420                    message = field.gettext(e)421                else:422                    message = field.gettext("Invalid email address.")423            raise ValidationError(message) from e424 425 426class IPAddress:427    """428    Validates an IP address.429 430    :param ipv4:431        If True, accept IPv4 addresses as valid (default True)432    :param ipv6:433        If True, accept IPv6 addresses as valid (default False)434    :param message:435        Error message to raise in case of a validation error.436    """437 438    def __init__(self, ipv4=True, ipv6=False, message=None):439        if not ipv4 and not ipv6:440            raise ValueError(441                "IP Address Validator must have at least one of ipv4 or ipv6 enabled."442            )443        self.ipv4 = ipv4444        self.ipv6 = ipv6445        self.message = message446 447    def __call__(self, form, field):448        value = field.data449        valid = False450        if value:451            valid = (self.ipv4 and self.check_ipv4(value)) or (452                self.ipv6 and self.check_ipv6(value)453            )454 455        if valid:456            return457 458        message = self.message459        if message is None:460            message = field.gettext("Invalid IP address.")461        raise ValidationError(message)462 463    @classmethod464    def check_ipv4(cls, value):465        try:466            address = ipaddress.ip_address(value)467        except ValueError:468            return False469 470        if not isinstance(address, ipaddress.IPv4Address):471            return False472 473        return True474 475    @classmethod476    def check_ipv6(cls, value):477        try:478            address = ipaddress.ip_address(value)479        except ValueError:480            return False481 482        if not isinstance(address, ipaddress.IPv6Address):483            return False484 485        return True486 487 488class MacAddress(Regexp):489    """490    Validates a MAC address.491 492    :param message:493        Error message to raise in case of a validation error.494    """495 496    def __init__(self, message=None):497        pattern = r"^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$"498        super().__init__(pattern, message=message)499 500    def __call__(self, form, field):501        message = self.message502        if message is None:503            message = field.gettext("Invalid Mac address.")504 505        super().__call__(form, field, message)506 507 508class URL(Regexp):509    """510    Simple regexp based url validation. Much like the email validator, you511    probably want to validate the url later by other means if the url must512    resolve.513 514    :param require_tld:515        If true, then the domain-name portion of the URL must contain a .tld516        suffix.  Set this to false if you want to allow domains like517        `localhost`.518    :param allow_ip:519        If false, then give ip as host will fail validation520    :param message:521        Error message to raise in case of a validation error.522    """523 524    def __init__(self, require_tld=True, allow_ip=True, message=None):525        regex = (526            r"^[a-z]+://"527            r"(?P<host>[^\/\?:]+)"528            r"(?P<port>:[0-9]+)?"529            r"(?P<path>\/.*?)?"530            r"(?P<query>\?.*)?$"531        )532        super().__init__(regex, re.IGNORECASE, message)533        self.validate_hostname = HostnameValidation(534            require_tld=require_tld, allow_ip=allow_ip535        )536 537    def __call__(self, form, field):538        message = self.message539        if message is None:540            message = field.gettext("Invalid URL.")541 542        match = super().__call__(form, field, message)543        if not self.validate_hostname(match.group("host")):544            raise ValidationError(message)545 546 547class UUID:548    """549    Validates a UUID.550 551    :param message:552        Error message to raise in case of a validation error.553    """554 555    def __init__(self, message=None):556        self.message = message557 558    def __call__(self, form, field):559        message = self.message560        if message is None:561            message = field.gettext("Invalid UUID.")562        try:563            uuid.UUID(field.data)564        except ValueError as exc:565            raise ValidationError(message) from exc566 567 568class AnyOf:569    """570    Compares the incoming data to a sequence of valid inputs.571 572    :param values:573        A sequence of valid inputs.574    :param message:575        Error message to raise in case of a validation error. `%(values)s`576        contains the list of values.577    :param values_formatter:578        Function used to format the list of values in the error message.579    """580 581    def __init__(self, values, message=None, values_formatter=None):582        self.values = values583        self.message = message584        if values_formatter is None:585            values_formatter = self.default_values_formatter586        self.values_formatter = values_formatter587 588    def __call__(self, form, field):589        if field.data in self.values:590            return591 592        message = self.message593        if message is None:594            message = field.gettext("Invalid value, must be one of: %(values)s.")595 596        raise ValidationError(message % dict(values=self.values_formatter(self.values)))597 598    @staticmethod599    def default_values_formatter(values):600        return ", ".join(str(x) for x in values)601 602 603class NoneOf:604    """605    Compares the incoming data to a sequence of invalid inputs.606 607    :param values:608        A sequence of invalid inputs.609    :param message:610        Error message to raise in case of a validation error. `%(values)s`611        contains the list of values.612    :param values_formatter:613        Function used to format the list of values in the error message.614    """615 616    def __init__(self, values, message=None, values_formatter=None):617        self.values = values618        self.message = message619        if values_formatter is None:620            values_formatter = self.default_values_formatter621        self.values_formatter = values_formatter622 623    def __call__(self, form, field):624        if field.data not in self.values:625            return626 627        message = self.message628        if message is None:629            message = field.gettext("Invalid value, can't be any of: %(values)s.")630 631        raise ValidationError(message % dict(values=self.values_formatter(self.values)))632 633    @staticmethod634    def default_values_formatter(v):635        return ", ".join(str(x) for x in v)636 637 638class HostnameValidation:639    """640    Helper class for checking hostnames for validation.641 642    This is not a validator in and of itself, and as such is not exported.643    """644 645    hostname_part = re.compile(r"^(xn-|[a-z0-9_]+)(-[a-z0-9_-]+)*$", re.IGNORECASE)646    tld_part = re.compile(r"^([a-z]{2,20}|xn--([a-z0-9]+-)*[a-z0-9]+)$", re.IGNORECASE)647 648    def __init__(self, require_tld=True, allow_ip=False):649        self.require_tld = require_tld650        self.allow_ip = allow_ip651 652    def __call__(self, hostname):653        if self.allow_ip and (654            IPAddress.check_ipv4(hostname) or IPAddress.check_ipv6(hostname)655        ):656            return True657 658        # Encode out IDNA hostnames. This makes further validation easier.659        try:660            hostname = hostname.encode("idna")661        except UnicodeError:662            pass663 664        # Turn back into a string in Python 3x665        if not isinstance(hostname, str):666            hostname = hostname.decode("ascii")667 668        if len(hostname) > 253:669            return False670 671        # Check that all labels in the hostname are valid672        parts = hostname.split(".")673        for part in parts:674            if not part or len(part) > 63:675                return False676            if not self.hostname_part.match(part):677                return False678 679        if self.require_tld and (len(parts) < 2 or not self.tld_part.match(parts[-1])):680            return False681 682        return True683 684 685class ReadOnly:686    """687    Set a field readonly.688 689    Validation fails if the form data is different than the690    field object data, or if unset, from the field default data.691    """692 693    def __init__(self):694        self.field_flags = {"readonly": True}695 696    def __call__(self, form, field):697        if field.data != field.object_data:698            raise ValidationError(field.gettext("This field cannot be edited"))699 700 701class Disabled:702    """703    Set a field disabled.704 705    Validation fails if the form data has any value.706    """707 708    def __init__(self):709        self.field_flags = {"disabled": True}710 711    def __call__(self, form, field):712        if field.raw_data is not None:713            raise ValidationError(714                field.gettext("This field is disabled and cannot have a value")715            )716 717 718email = Email719equal_to = EqualTo720ip_address = IPAddress721mac_address = MacAddress722length = Length723number_range = NumberRange724optional = Optional725input_required = InputRequired726data_required = DataRequired727regexp = Regexp728url = URL729any_of = AnyOf730none_of = NoneOf731readonly = ReadOnly732disabled = Disabled733 
codekingpro/portable-devtools · Team Ai