-
Notifications
You must be signed in to change notification settings - Fork 51
/
Copy path__init__.py
138 lines (113 loc) · 3.57 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
from __future__ import annotations
import copy
import enum
import re
import typing as t
import jsonschema
import jsonschema.validators
import regress
from .implementations import (
validate_rfc3339,
validate_rfc5321,
validate_rfc6531,
validate_time,
)
# all known format strings except for a selection from draft3 which have either
# been renamed or removed:
# - color
# - host-name
# - ip-address
KNOWN_FORMATS: tuple[str, ...] = (
"date",
"date-time",
"duration",
"email",
"hostname",
"idn-email",
"idn-hostname",
"ipv4",
"ipv6",
"iri",
"iri-reference",
"json-pointer",
"regex",
"relative-json-pointer",
"time",
"uri",
"uri-reference",
"uri-template",
"uuid",
)
class EmailImplementation:
def __init__(self) -> None:
pass
def check_format_email(self, instance: t.Any) -> bool:
if not isinstance(instance, str):
return True
return validate_rfc5321(instance)
def check_format_idn_email(self, instance: t.Any) -> bool:
if not isinstance(instance, str):
return True
return validate_rfc6531(instance)
class RegexVariantName(enum.Enum):
default = "default"
python = "python"
class RegexImplementation:
def __init__(self, variant: RegexVariantName) -> None:
self.variant = variant
def check_format(self, instance: t.Any) -> bool:
if not isinstance(instance, str):
return True
try:
if self.variant == RegexVariantName.default:
regress.Regex(instance)
else:
re.compile(instance)
# something is wrong with RegressError getting into the published types
# needs investigation... for now, ignore the error
except (regress.RegressError, re.error): # type: ignore[attr-defined]
return False
return True
class FormatOptions:
def __init__(
self,
*,
enabled: bool = True,
regex_variant: RegexVariantName = RegexVariantName.default,
disabled_formats: tuple[str, ...] = (),
) -> None:
self.enabled = enabled
self.regex_variant = regex_variant
self.disabled_formats = disabled_formats
def get_base_format_checker(schema_dialect: str | None) -> jsonschema.FormatChecker:
# resolve the dialect, if given, to a validator class
# default to the latest draft
validator_class = jsonschema.validators.validator_for(
{} if schema_dialect is None else {"$schema": schema_dialect},
default=jsonschema.Draft202012Validator,
)
return validator_class.FORMAT_CHECKER
def make_format_checker(
opts: FormatOptions,
schema_dialect: str | None = None,
) -> jsonschema.FormatChecker | None:
if not opts.enabled:
return None
# copy the base checker
base_checker = get_base_format_checker(schema_dialect)
checker = copy.deepcopy(base_checker)
# replace the regex check
del checker.checkers["regex"]
email_impl = EmailImplementation()
regex_impl = RegexImplementation(opts.regex_variant)
checker.checks("email")(email_impl.check_format_email)
checker.checks("idn-email")(email_impl.check_format_idn_email)
checker.checks("regex")(regex_impl.check_format)
checker.checks("date-time")(validate_rfc3339)
checker.checks("time")(validate_time)
# remove the disabled checks, which may include the regex check
for checkname in opts.disabled_formats:
if checkname not in checker.checkers:
continue
del checker.checkers[checkname]
return checker