Skip to content

Commit

Permalink
Optimize regex implementations & fix tool configs
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sirosen committed Nov 2, 2024
1 parent 7bf7e19 commit d5249de
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 29 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
exclude = .git,.tox,__pycache__,dist,.venv*,docs,build
max-line-length = 90
# black related: W503/W504 conflict, black causes E203
ignore = W503,W504,E203,B019
extend-ignore = W503,W504,E203,B019
84 changes: 56 additions & 28 deletions src/check_jsonschema/regex_variants.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,68 @@ class RegexVariantName(enum.Enum):
python = "python"


class _ConcreteImplementation(t.Protocol):
def check_format(self, instance: t.Any) -> bool: ...

def pattern_keyword(
self, validator: t.Any, pattern: str, instance: str, schema: t.Any
) -> t.Iterator[jsonschema.ValidationError]: ...


class RegexImplementation:
"""
A high-level interface for getting at the different possible
implementations of regex behaviors.
"""

_real_implementation: _ConcreteImplementation

def __init__(self, variant: RegexVariantName) -> None:
self.variant = variant

if self.variant == RegexVariantName.default:
self._real_implementation = _RegressImplementation()
else:
self._real_implementation = _PythonImplementation()

self.check_format = self._real_implementation.check_format
self.pattern_keyword = self._real_implementation.pattern_keyword


class _RegressImplementation:
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)
regress.Regex(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]
except regress.RegressError: # type: ignore[attr-defined]
return False
return True

def pattern_keyword(
self, validator: t.Any, pattern: str, instance: str, schema: t.Any
) -> t.Iterator[jsonschema.ValidationError]:
if not validator.is_type(instance, "string"):
return

try:
regress_pattern = regress.Regex(pattern)
except regress.RegressError: # type: ignore[attr-defined]
yield jsonschema.ValidationError(f"pattern {pattern!r} failed to compile")
if not regress_pattern.find(instance):
yield jsonschema.ValidationError(f"{instance!r} does not match {pattern!r}")


class _PythonImplementation:
def check_format(self, instance: t.Any) -> bool:
if not isinstance(instance, str):
return True
try:
re.compile(instance)
except re.error:
return False
return True

def pattern_keyword(
Expand All @@ -37,25 +81,9 @@ def pattern_keyword(
if not validator.is_type(instance, "string"):
return

if self.variant == RegexVariantName.default:
try:
regress_pattern = regress.Regex(pattern)
except regress.RegressError: # type: ignore[attr-defined]
yield jsonschema.ValidationError(
f"pattern {pattern!r} failed to compile"
)
if not regress_pattern.find(instance):
yield jsonschema.ValidationError(
f"{instance!r} does not match {pattern!r}"
)
else:
try:
re_pattern = re.compile(pattern)
except re.error:
yield jsonschema.ValidationError(
f"pattern {pattern!r} failed to compile"
)
if not re_pattern.search(instance):
yield jsonschema.ValidationError(
f"{instance!r} does not match {pattern!r}"
)
try:
re_pattern = re.compile(pattern)
except re.error:
yield jsonschema.ValidationError(f"pattern {pattern!r} failed to compile")
if not re_pattern.search(instance):
yield jsonschema.ValidationError(f"{instance!r} does not match {pattern!r}")
4 changes: 4 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,22 @@ deps =
format: jsonschema[format]
commands =
coverage run -m pytest {posargs:--junitxml={envdir}/pytest.xml}
depends = cov_clean

[testenv:cov_clean]
description = "erase coverage data to prepare for a new run"
deps = coverage
skip_install = true
commands = coverage erase
depends =

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

[testenv:mypy]
description = "check type annotations with mypy"
Expand All @@ -52,6 +55,7 @@ deps = mypy
types-requests
click
commands = mypy src/ {posargs}
depends =

[testenv:pyright]
description = "check type annotations with pyright"
Expand Down

0 comments on commit d5249de

Please sign in to comment.