Skip to content

Commit 2032725

Browse files
committed
Optimize regex implementations & fix tool configs
Change RegexImplementation to dispatch between variants at init time, so that each call to the relevant methods is faster and does not re-check the variant. Minor tool config fixes: - flake8 ignore vs extend-ignore (whoops) - tox.ini depends needed to run in parallel
1 parent 819d834 commit 2032725

File tree

3 files changed

+61
-29
lines changed

3 files changed

+61
-29
lines changed

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
exclude = .git,.tox,__pycache__,dist,.venv*,docs,build
33
max-line-length = 90
44
# black related: W503/W504 conflict, black causes E203
5-
ignore = W503,W504,E203,B019
5+
extend-ignore = W503,W504,E203,B019

src/check_jsonschema/regex_variants.py

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,68 @@ class RegexVariantName(enum.Enum):
1111
python = "python"
1212

1313

14+
class _ConcreteImplementation(t.Protocol):
15+
def check_format(self, instance: t.Any) -> bool: ...
16+
17+
def pattern_keyword(
18+
self, validator: t.Any, pattern: str, instance: str, schema: t.Any
19+
) -> t.Iterator[jsonschema.ValidationError]: ...
20+
21+
1422
class RegexImplementation:
23+
"""
24+
A high-level interface for getting at the different possible
25+
implementations of regex behaviors.
26+
"""
27+
28+
_real_implementation: _ConcreteImplementation
29+
1530
def __init__(self, variant: RegexVariantName) -> None:
1631
self.variant = variant
1732

33+
if self.variant == RegexVariantName.default:
34+
self._real_implementation = _RegressImplementation()
35+
else:
36+
self._real_implementation = _PythonImplementation()
37+
38+
self.check_format = self._real_implementation.check_format
39+
self.pattern_keyword = self._real_implementation.pattern_keyword
40+
41+
42+
class _RegressImplementation:
1843
def check_format(self, instance: t.Any) -> bool:
1944
if not isinstance(instance, str):
2045
return True
21-
2246
try:
23-
if self.variant == RegexVariantName.default:
24-
regress.Regex(instance)
25-
else:
26-
re.compile(instance)
47+
regress.Regex(instance)
2748
# something is wrong with RegressError getting into the published types
2849
# needs investigation... for now, ignore the error
29-
except (regress.RegressError, re.error): # type: ignore[attr-defined]
50+
except regress.RegressError: # type: ignore[attr-defined]
3051
return False
52+
return True
3153

54+
def pattern_keyword(
55+
self, validator: t.Any, pattern: str, instance: str, schema: t.Any
56+
) -> t.Iterator[jsonschema.ValidationError]:
57+
if not validator.is_type(instance, "string"):
58+
return
59+
60+
try:
61+
regress_pattern = regress.Regex(pattern)
62+
except regress.RegressError: # type: ignore[attr-defined]
63+
yield jsonschema.ValidationError(f"pattern {pattern!r} failed to compile")
64+
if not regress_pattern.find(instance):
65+
yield jsonschema.ValidationError(f"{instance!r} does not match {pattern!r}")
66+
67+
68+
class _PythonImplementation:
69+
def check_format(self, instance: t.Any) -> bool:
70+
if not isinstance(instance, str):
71+
return True
72+
try:
73+
re.compile(instance)
74+
except re.error:
75+
return False
3276
return True
3377

3478
def pattern_keyword(
@@ -37,25 +81,9 @@ def pattern_keyword(
3781
if not validator.is_type(instance, "string"):
3882
return
3983

40-
if self.variant == RegexVariantName.default:
41-
try:
42-
regress_pattern = regress.Regex(pattern)
43-
except regress.RegressError: # type: ignore[attr-defined]
44-
yield jsonschema.ValidationError(
45-
f"pattern {pattern!r} failed to compile"
46-
)
47-
if not regress_pattern.find(instance):
48-
yield jsonschema.ValidationError(
49-
f"{instance!r} does not match {pattern!r}"
50-
)
51-
else:
52-
try:
53-
re_pattern = re.compile(pattern)
54-
except re.error:
55-
yield jsonschema.ValidationError(
56-
f"pattern {pattern!r} failed to compile"
57-
)
58-
if not re_pattern.search(instance):
59-
yield jsonschema.ValidationError(
60-
f"{instance!r} does not match {pattern!r}"
61-
)
84+
try:
85+
re_pattern = re.compile(pattern)
86+
except re.error:
87+
yield jsonschema.ValidationError(f"pattern {pattern!r} failed to compile")
88+
if not re_pattern.search(instance):
89+
yield jsonschema.ValidationError(f"{instance!r} does not match {pattern!r}")

tox.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,22 @@ deps =
3030
format: jsonschema[format]
3131
commands =
3232
coverage run -m pytest {posargs:--junitxml={envdir}/pytest.xml}
33+
depends = cov_clean
3334

3435
[testenv:cov_clean]
3536
description = "erase coverage data to prepare for a new run"
3637
deps = coverage
3738
skip_install = true
3839
commands = coverage erase
40+
depends =
3941

4042
[testenv:cov]
4143
description = "combine and report coverage data"
4244
deps = coverage
4345
skip_install = true
4446
commands_pre = - coverage combine
4547
commands = coverage report --skip-covered
48+
depends = py{,38,39,310,311,312}{,-mindeps,-format,-json5,-pyjson5,-disable_orjson}
4649

4750
[testenv:mypy]
4851
description = "check type annotations with mypy"
@@ -51,6 +54,7 @@ deps = mypy
5154
types-requests
5255
click
5356
commands = mypy src/ {posargs}
57+
depends =
5458

5559
[testenv:pyright]
5660
description = "check type annotations with pyright"

0 commit comments

Comments
 (0)