Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_format.py547 linesDownload Raw Back to jsonschema
1from __future__ import annotations2 3from contextlib import suppress4from datetime import date, datetime5from uuid import UUID6import ipaddress7import re8import typing9import warnings10 11from jsonschema.exceptions import FormatError12 13_FormatCheckCallable = typing.Callable[[object], bool]14#: A format checker callable.15_F = typing.TypeVar("_F", bound=_FormatCheckCallable)16_RaisesType = type[Exception] | tuple[type[Exception], ...]17 18_RE_DATE = re.compile(r"^\d{4}-\d{2}-\d{2}$", re.ASCII)19 20 21class FormatChecker:22    """23    A ``format`` property checker.24 25    JSON Schema does not mandate that the ``format`` property actually do any26    validation. If validation is desired however, instances of this class can27    be hooked into validators to enable format validation.28 29    `FormatChecker` objects always return ``True`` when asked about30    formats that they do not know how to validate.31 32    To add a check for a custom format use the `FormatChecker.checks`33    decorator.34 35    Arguments:36 37        formats:38 39            The known formats to validate. This argument can be used to40            limit which formats will be used during validation.41 42    """43 44    checkers: dict[45        str,46        tuple[_FormatCheckCallable, _RaisesType],47    ] = {}  # noqa: RUF01248 49    def __init__(self, formats: typing.Iterable[str] | None = None):50        if formats is None:51            formats = self.checkers.keys()52        self.checkers = {k: self.checkers[k] for k in formats}53 54    def __repr__(self):55        return f"<FormatChecker checkers={sorted(self.checkers)}>"56 57    def checks(58        self, format: str, raises: _RaisesType = (),59    ) -> typing.Callable[[_F], _F]:60        """61        Register a decorated function as validating a new format.62 63        Arguments:64 65            format:66 67                The format that the decorated function will check.68 69            raises:70 71                The exception(s) raised by the decorated function when an72                invalid instance is found.73 74                The exception object will be accessible as the75                `jsonschema.exceptions.ValidationError.cause` attribute of the76                resulting validation error.77 78        """79 80        def _checks(func: _F) -> _F:81            self.checkers[format] = (func, raises)82            return func83 84        return _checks85 86    @classmethod87    def cls_checks(88        cls, format: str, raises: _RaisesType = (),89    ) -> typing.Callable[[_F], _F]:90        warnings.warn(91            (92                "FormatChecker.cls_checks is deprecated. Call "93                "FormatChecker.checks on a specific FormatChecker instance "94                "instead."95            ),96            DeprecationWarning,97            stacklevel=2,98        )99        return cls._cls_checks(format=format, raises=raises)100 101    @classmethod102    def _cls_checks(103        cls, format: str, raises: _RaisesType = (),104    ) -> typing.Callable[[_F], _F]:105        def _checks(func: _F) -> _F:106            cls.checkers[format] = (func, raises)107            return func108 109        return _checks110 111    def check(self, instance: object, format: str) -> None:112        """113        Check whether the instance conforms to the given format.114 115        Arguments:116 117            instance (*any primitive type*, i.e. str, number, bool):118 119                The instance to check120 121            format:122 123                The format that instance should conform to124 125        Raises:126 127            FormatError:128 129                if the instance does not conform to ``format``130 131        """132        if format not in self.checkers:133            return134 135        func, raises = self.checkers[format]136        result, cause = None, None137        try:138            result = func(instance)139        except raises as e:140            cause = e141        if not result:142            raise FormatError(f"{instance!r} is not a {format!r}", cause=cause)143 144    def conforms(self, instance: object, format: str) -> bool:145        """146        Check whether the instance conforms to the given format.147 148        Arguments:149 150            instance (*any primitive type*, i.e. str, number, bool):151 152                The instance to check153 154            format:155 156                The format that instance should conform to157 158        Returns:159 160            bool: whether it conformed161 162        """163        try:164            self.check(instance, format)165        except FormatError:166            return False167        else:168            return True169 170 171draft3_format_checker = FormatChecker()172draft4_format_checker = FormatChecker()173draft6_format_checker = FormatChecker()174draft7_format_checker = FormatChecker()175draft201909_format_checker = FormatChecker()176draft202012_format_checker = FormatChecker()177 178_draft_checkers: dict[str, FormatChecker] = dict(179    draft3=draft3_format_checker,180    draft4=draft4_format_checker,181    draft6=draft6_format_checker,182    draft7=draft7_format_checker,183    draft201909=draft201909_format_checker,184    draft202012=draft202012_format_checker,185)186 187 188def _checks_drafts(189    name=None,190    draft3=None,191    draft4=None,192    draft6=None,193    draft7=None,194    draft201909=None,195    draft202012=None,196    raises=(),197) -> typing.Callable[[_F], _F]:198    draft3 = draft3 or name199    draft4 = draft4 or name200    draft6 = draft6 or name201    draft7 = draft7 or name202    draft201909 = draft201909 or name203    draft202012 = draft202012 or name204 205    def wrap(func: _F) -> _F:206        if draft3:207            func = _draft_checkers["draft3"].checks(draft3, raises)(func)208        if draft4:209            func = _draft_checkers["draft4"].checks(draft4, raises)(func)210        if draft6:211            func = _draft_checkers["draft6"].checks(draft6, raises)(func)212        if draft7:213            func = _draft_checkers["draft7"].checks(draft7, raises)(func)214        if draft201909:215            func = _draft_checkers["draft201909"].checks(draft201909, raises)(216                func,217            )218        if draft202012:219            func = _draft_checkers["draft202012"].checks(draft202012, raises)(220                func,221            )222 223        # Oy. This is bad global state, but relied upon for now, until224        # deprecation. See #519 and test_format_checkers_come_with_defaults225        FormatChecker._cls_checks(226            draft202012 or draft201909 or draft7 or draft6 or draft4 or draft3,227            raises,228        )(func)229        return func230 231    return wrap232 233 234@_checks_drafts(name="idn-email")235@_checks_drafts(name="email")236def is_email(instance: object) -> bool:237    if not isinstance(instance, str):238        return True239    return "@" in instance240 241 242@_checks_drafts(243    draft3="ip-address",244    draft4="ipv4",245    draft6="ipv4",246    draft7="ipv4",247    draft201909="ipv4",248    draft202012="ipv4",249    raises=ipaddress.AddressValueError,250)251def is_ipv4(instance: object) -> bool:252    if not isinstance(instance, str):253        return True254    return bool(ipaddress.IPv4Address(instance))255 256 257@_checks_drafts(name="ipv6", raises=ipaddress.AddressValueError)258def is_ipv6(instance: object) -> bool:259    if not isinstance(instance, str):260        return True261    address = ipaddress.IPv6Address(instance)262    return not getattr(address, "scope_id", "")263 264 265with suppress(ImportError):266    from fqdn import FQDN267 268    @_checks_drafts(269        draft3="host-name",270        draft4="hostname",271        draft6="hostname",272        draft7="hostname",273        draft201909="hostname",274        draft202012="hostname",275        # fqdn.FQDN("") raises a ValueError due to a bug276        # however, it's not clear when or if that will be fixed, so catch it277        # here for now278        raises=ValueError,279    )280    def is_host_name(instance: object) -> bool:281        if not isinstance(instance, str):282            return True283        return FQDN(instance, min_labels=1).is_valid284 285 286with suppress(ImportError):287    # The built-in `idna` codec only implements RFC 3890, so we go elsewhere.288    import idna289 290    @_checks_drafts(291        draft7="idn-hostname",292        draft201909="idn-hostname",293        draft202012="idn-hostname",294        raises=(idna.IDNAError, UnicodeError),295    )296    def is_idn_host_name(instance: object) -> bool:297        if not isinstance(instance, str):298            return True299        idna.encode(instance)300        return True301 302 303try:304    import rfc3987305except ImportError:306    with suppress(ImportError):307        from rfc3986_validator import validate_rfc3986308 309        @_checks_drafts(name="uri")310        def is_uri(instance: object) -> bool:311            if not isinstance(instance, str):312                return True313            return validate_rfc3986(instance, rule="URI")314 315        @_checks_drafts(316            draft6="uri-reference",317            draft7="uri-reference",318            draft201909="uri-reference",319            draft202012="uri-reference",320            raises=ValueError,321        )322        def is_uri_reference(instance: object) -> bool:323            if not isinstance(instance, str):324                return True325            return validate_rfc3986(instance, rule="URI_reference")326 327    with suppress(ImportError):328        from rfc3987_syntax import is_valid_syntax as _rfc3987_is_valid_syntax329 330        @_checks_drafts(331            draft7="iri",332            draft201909="iri",333            draft202012="iri",334            raises=ValueError,335        )336        def is_iri(instance: object) -> bool:337            if not isinstance(instance, str):338                return True339            return _rfc3987_is_valid_syntax("iri", instance)340 341        @_checks_drafts(342            draft7="iri-reference",343            draft201909="iri-reference",344            draft202012="iri-reference",345            raises=ValueError,346        )347        def is_iri_reference(instance: object) -> bool:348            if not isinstance(instance, str):349                return True350            return _rfc3987_is_valid_syntax("iri_reference", instance)351 352else:353 354    @_checks_drafts(355        draft7="iri",356        draft201909="iri",357        draft202012="iri",358        raises=ValueError,359    )360    def is_iri(instance: object) -> bool:361        if not isinstance(instance, str):362            return True363        return rfc3987.parse(instance, rule="IRI")364 365    @_checks_drafts(366        draft7="iri-reference",367        draft201909="iri-reference",368        draft202012="iri-reference",369        raises=ValueError,370    )371    def is_iri_reference(instance: object) -> bool:372        if not isinstance(instance, str):373            return True374        return rfc3987.parse(instance, rule="IRI_reference")375 376    @_checks_drafts(name="uri", raises=ValueError)377    def is_uri(instance: object) -> bool:378        if not isinstance(instance, str):379            return True380        return rfc3987.parse(instance, rule="URI")381 382    @_checks_drafts(383        draft6="uri-reference",384        draft7="uri-reference",385        draft201909="uri-reference",386        draft202012="uri-reference",387        raises=ValueError,388    )389    def is_uri_reference(instance: object) -> bool:390        if not isinstance(instance, str):391            return True392        return rfc3987.parse(instance, rule="URI_reference")393 394 395with suppress(ImportError):396    from rfc3339_validator import validate_rfc3339397 398    @_checks_drafts(name="date-time")399    def is_datetime(instance: object) -> bool:400        if not isinstance(instance, str):401            return True402        return validate_rfc3339(instance.upper())403 404    @_checks_drafts(405        draft7="time",406        draft201909="time",407        draft202012="time",408    )409    def is_time(instance: object) -> bool:410        if not isinstance(instance, str):411            return True412        return is_datetime("1970-01-01T" + instance)413 414 415@_checks_drafts(name="regex", raises=re.error)416def is_regex(instance: object) -> bool:417    if not isinstance(instance, str):418        return True419    return bool(re.compile(instance))420 421 422@_checks_drafts(423    draft3="date",424    draft7="date",425    draft201909="date",426    draft202012="date",427    raises=ValueError,428)429def is_date(instance: object) -> bool:430    if not isinstance(instance, str):431        return True432    return bool(_RE_DATE.fullmatch(instance) and date.fromisoformat(instance))433 434 435@_checks_drafts(draft3="time", raises=ValueError)436def is_draft3_time(instance: object) -> bool:437    if not isinstance(instance, str):438        return True439    return bool(datetime.strptime(instance, "%H:%M:%S"))  # noqa: DTZ007440 441 442with suppress(ImportError):443    import webcolors444 445    @_checks_drafts(draft3="color", raises=(ValueError, TypeError))446    def is_css21_color(instance: object) -> bool:447        if isinstance(instance, str):448            try:449                webcolors.name_to_hex(instance)450            except ValueError:451                webcolors.normalize_hex(instance.lower())452        return True453 454 455with suppress(ImportError):456    import jsonpointer457 458    @_checks_drafts(459        draft6="json-pointer",460        draft7="json-pointer",461        draft201909="json-pointer",462        draft202012="json-pointer",463        raises=jsonpointer.JsonPointerException,464    )465    def is_json_pointer(instance: object) -> bool:466        if not isinstance(instance, str):467            return True468        return bool(jsonpointer.JsonPointer(instance))469 470    # TODO: I don't want to maintain this, so it471    #       needs to go either into jsonpointer (pending472    #       https://github.com/stefankoegl/python-json-pointer/issues/34) or473    #       into a new external library.474    @_checks_drafts(475        draft7="relative-json-pointer",476        draft201909="relative-json-pointer",477        draft202012="relative-json-pointer",478        raises=jsonpointer.JsonPointerException,479    )480    def is_relative_json_pointer(instance: object) -> bool:481        # Definition taken from:482        # https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01#section-3483        if not isinstance(instance, str):484            return True485        if not instance:486            return False487 488        non_negative_integer, rest = [], ""489        for i, character in enumerate(instance):490            if character.isdigit():491                # digits with a leading "0" are not allowed492                if i > 0 and int(instance[i - 1]) == 0:493                    return False494 495                non_negative_integer.append(character)496                continue497 498            if not non_negative_integer:499                return False500 501            rest = instance[i:]502            break503        return (rest == "#") or bool(jsonpointer.JsonPointer(rest))504 505 506with suppress(ImportError):507    import uri_template508 509    @_checks_drafts(510        draft6="uri-template",511        draft7="uri-template",512        draft201909="uri-template",513        draft202012="uri-template",514    )515    def is_uri_template(instance: object) -> bool:516        if not isinstance(instance, str):517            return True518        return uri_template.validate(instance)519 520 521with suppress(ImportError):522    import isoduration523 524    @_checks_drafts(525        draft201909="duration",526        draft202012="duration",527        raises=isoduration.DurationParsingException,528    )529    def is_duration(instance: object) -> bool:530        if not isinstance(instance, str):531            return True532        isoduration.parse_duration(instance)533        # FIXME: See bolsote/isoduration#25 and bolsote/isoduration#21534        return instance.endswith(tuple("DMYWHMS"))535 536 537@_checks_drafts(538    draft201909="uuid",539    draft202012="uuid",540    raises=ValueError,541)542def is_uuid(instance: object) -> bool:543    if not isinstance(instance, str):544        return True545    UUID(instance)546    return all(instance[position] == "-" for position in (8, 13, 18, 23))547 
codekingpro/portable-devtools · Team Ai