Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
=== UNRELEASED

* Fixed `$` in `patternProperties` matching before a trailing newline, which also made a `pattern` with the same regex accept a trailing newline
* Added support of dependentRequired and dependentSchemas from draft-2019

=== 2.22.3 (2026-11-01)

Expand Down
9 changes: 6 additions & 3 deletions fastjsonschema/draft04.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,20 +664,23 @@ def generate_dependencies(self):
Since draft 06 definition can be boolean or empty array. True and empty array
means nothing, False means that key cannot be there at all.
"""
self._generate_dependencies('dependencies')

def _generate_dependencies(self, rule):
self.create_variable_is_dict()
with self.l('if {variable}_is_dict:'):
is_empty = True
for key, values in self._definition["dependencies"].items():
for key, values in self._definition[rule].items():
if values == [] or values is True:
continue
is_empty = False
with self.l('if "{}" in {variable}:', self.e(key)):
if values is False:
self.exc('{} in {name} must not be there', self.e(key), rule='dependencies')
self.exc('{} in {name} must not be there', self.e(key), rule=rule)
elif isinstance(values, list):
for value in values:
with self.l('if "{}" not in {variable}:', self.e(value)):
self.exc('{name} missing dependency {} for {}', self.e(value), self.e(key), rule='dependencies')
self.exc('{name} missing dependency {} for {}', self.e(value), self.e(key), rule=rule)
else:
code_len = len(self._code)
self.generate_func_code_block(values, self._variable, self._variable_name, clear_variables=True)
Expand Down
54 changes: 54 additions & 0 deletions fastjsonschema/draft2019.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from collections import OrderedDict

from .draft07 import CodeGeneratorDraft07


Expand All @@ -13,3 +15,55 @@ class CodeGeneratorDraft2019(CodeGeneratorDraft07):
r')\Z'
),
})

def __init__(
self,
definition,
resolver=None,
formats={},
use_default=True,
use_formats=True,
detailed_exceptions=True,
fast_fail=True
):
super().__init__(definition, resolver, formats, use_default, use_formats, detailed_exceptions, fast_fail)
# Like dependencies, check them before properties generates default values.
keywords = list(self._json_keywords_to_function.items())
index = [key for key, _ in keywords].index('dependencies') + 1
keywords[index:index] = [
('dependentRequired', self.generate_dependent_required),
('dependentSchemas', self.generate_dependent_schemas),
]
self._json_keywords_to_function = OrderedDict(keywords)

def generate_dependent_required(self):
"""
Means when object has property, it needs to have also other properties.

.. code-block:: python

{
'dependentRequired': {
'bar': ['foo'],
},
}

Valid object is containing only foo, both bar and foo or none of them, but not
object with only bar.
"""
self._generate_dependencies('dependentRequired')

def generate_dependent_schemas(self):
"""
Means when object has property, the whole object needs to be valid against
the schema.

.. code-block:: python

{
'dependentSchemas': {
'bar': {'required': ['foo']},
},
}
"""
self._generate_dependencies('dependentSchemas')
2 changes: 0 additions & 2 deletions tests/json_schema/test_draft2019.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ def pytest_generate_tests(metafunc):
'anchor.json',
'content.json',
'defs.json',
'dependentRequired.json',
'dependentSchemas.json',
'maxContains.json',
'minContains.json',
'recursiveRef.json',
Expand Down
16 changes: 16 additions & 0 deletions tests/test_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,19 @@ def test_dependencies_annotation_only_schema():
validator = fastjsonschema.compile(schema)
assert validator({'foo': 1}) == {'foo': 1}
assert validator({'bar': 1}) == {'bar': 1}


@pytest.mark.parametrize('keyword, definition', [
('dependentRequired', {'foo': ['bar']}),
('dependentSchemas', {'foo': False}),
])
def test_dependent_keywords_checked_before_defaults(keyword, definition):
validator = fastjsonschema.compile({
'$schema': 'https://json-schema.org/draft/2019-09/schema',
'properties': {'foo': {'default': 1}},
keyword: definition,
})
assert validator({}) == {'foo': 1}
with pytest.raises(JsonSchemaValueException) as exc:
validator({'foo': 1})
assert exc.value.rule == keyword