From 6d99fd8c017f13b1840e3f7a9c0a74ce79b23f4f Mon Sep 17 00:00:00 2001 From: Stephen Rosen Date: Mon, 23 Dec 2024 16:39:32 -0600 Subject: [PATCH] Support patternProperties in regex variants --- docs/usage.rst | 9 +-- src/check_jsonschema/regex_variants.py | 73 ++++++++++++++++++++++ src/check_jsonschema/schema_loader/main.py | 5 +- 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index e9216986e..7de004e75 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -187,7 +187,8 @@ Example usage: ~~~~~~~~~~~~~~~~~~ Set a mode for handling of the ``"regex"`` value for ``"format"`` and the mode -for ``"pattern"`` interpretation. The modes are as follows: +for ``"pattern"`` and ``"patternProperties"`` interpretation. +The modes are as follows: .. list-table:: Regex Options :widths: 15 30 @@ -202,12 +203,6 @@ for ``"pattern"`` interpretation. The modes are as follows: * - python - Use Python regex syntax. -.. note:: - - This only controls the regex mode used for ``format`` and ``pattern``. - ``patternProperties`` is not currently controlled, and always uses the - Python engine. - Other Options -------------- diff --git a/src/check_jsonschema/regex_variants.py b/src/check_jsonschema/regex_variants.py index 276230d10..6e4f70fdf 100644 --- a/src/check_jsonschema/regex_variants.py +++ b/src/check_jsonschema/regex_variants.py @@ -19,6 +19,14 @@ def pattern_keyword( self, validator: t.Any, pattern: str, instance: str, schema: t.Any ) -> t.Iterator[jsonschema.ValidationError]: ... + def patternProperties_keyword( + self, + validator: t.Any, + patternProperties: dict[str, t.Any], + instance: dict[str, t.Any], + schema: t.Any, + ) -> t.Iterator[jsonschema.ValidationError]: ... + class RegexImplementation: """ @@ -40,6 +48,9 @@ def __init__(self, variant: RegexVariantName) -> None: self.check_format = self._real_implementation.check_format self.pattern_keyword = self._real_implementation.pattern_keyword + self.patternProperties_keyword = ( + self._real_implementation.patternProperties_keyword + ) class _UnicodeRegressImplementation: @@ -65,6 +76,27 @@ def pattern_keyword( if not regress_pattern.find(instance): yield jsonschema.ValidationError(f"{instance!r} does not match {pattern!r}") + def patternProperties_keyword( + self, + validator: t.Any, + patternProperties: dict[str, t.Any], + instance: dict[str, t.Any], + schema: t.Any, + ) -> t.Iterator[jsonschema.ValidationError]: + if not validator.is_type(instance, "object"): + return + + for pattern, subschema in patternProperties.items(): + regress_pattern = regress.Regex(pattern, flags="u") + for k, v in instance.items(): + if regress_pattern.find(k): + yield from validator.descend( + v, + subschema, + path=k, + schema_path=pattern, + ) + class _NonunicodeRegressImplementation: def check_format(self, instance: t.Any) -> bool: @@ -89,6 +121,27 @@ def pattern_keyword( if not regress_pattern.find(instance): yield jsonschema.ValidationError(f"{instance!r} does not match {pattern!r}") + def patternProperties_keyword( + self, + validator: t.Any, + patternProperties: dict[str, t.Any], + instance: dict[str, t.Any], + schema: t.Any, + ) -> t.Iterator[jsonschema.ValidationError]: + if not validator.is_type(instance, "object"): + return + + for pattern, subschema in patternProperties.items(): + regress_pattern = regress.Regex(pattern) + for k, v in instance.items(): + if regress_pattern.find(k): + yield from validator.descend( + v, + subschema, + path=k, + schema_path=pattern, + ) + class _PythonImplementation: def check_format(self, instance: t.Any) -> bool: @@ -112,3 +165,23 @@ def pattern_keyword( 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}") + + def patternProperties_keyword( + self, + validator: t.Any, + patternProperties: dict[str, t.Any], + instance: dict[str, t.Any], + schema: t.Any, + ) -> t.Iterator[jsonschema.ValidationError]: + if not validator.is_type(instance, "object"): + return + + for pattern, subschema in patternProperties.items(): + for k, v in instance.items(): + if re.search(pattern, k): + yield from validator.descend( + v, + subschema, + path=k, + schema_path=pattern, + ) diff --git a/src/check_jsonschema/schema_loader/main.py b/src/check_jsonschema/schema_loader/main.py index 6e817088c..f48de1cab 100644 --- a/src/check_jsonschema/schema_loader/main.py +++ b/src/check_jsonschema/schema_loader/main.py @@ -52,7 +52,10 @@ def _extend_with_pattern_implementation( ) -> type[jsonschema.Validator]: return jsonschema.validators.extend( validator_class, - {"pattern": regex_impl.pattern_keyword}, + { + "pattern": regex_impl.pattern_keyword, + "patternProperties": regex_impl.patternProperties_keyword, + }, )