Skip to content

Commit

Permalink
feat: Add support for items as array to get_schema_fields
Browse files Browse the repository at this point in the history
  • Loading branch information
jpmckinney committed Dec 17, 2024
1 parent f3d6a21 commit 2115181
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 21 deletions.
8 changes: 8 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

1.3.2 (2024-12-17)
------------------

Added
~~~~~

- :func:`ocdskit.schema.get_schema_fields`: Add support for ``items`` as array.

1.3.1 (2024-12-16)
------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
author = "Open Contracting Partnership"

# The short X.Y version
version = "1.3.1"
version = "1.3.2"
# The full version, including alpha/beta/rc tags
release = version

Expand Down
51 changes: 36 additions & 15 deletions ocdskit/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,27 @@ def get_schema_fields(

if items := schema.get('items'):
# `items` advances the pointer and sets array context (for the next level only).
yield from get_schema_fields(
items,
f'{pointer}/items',
path_components,
definition,
deprecated,
whole_list_merge=whole_list_merge,
array=True,
)
if isinstance(items, dict):
yield from get_schema_fields(
items,
f'{pointer}/items',
path_components,
definition,
deprecated,
whole_list_merge=whole_list_merge,
array=True,
)
else:
for i, subschema in enumerate(items):
yield from get_schema_fields(
subschema,
f'{pointer}/items/{i}',
path_components,
definition,
deprecated,
whole_list_merge=whole_list_merge,
array=True,
)

for keyword in ('anyOf', 'allOf', 'oneOf'):
if elements := schema.get(keyword):
Expand Down Expand Up @@ -152,7 +164,7 @@ def get_schema_fields(
prop_pointer = f'{pointer}/properties/{name}'
prop_path_components = (*path_components, name)
prop_deprecated = _deprecated(subschema)
prop_items = subschema.get('items', {})
prop_codelist, prop_open_codelist = _codelist(subschema)

# To date, codelist and openCodelist in OCDS aren't set on `items`.
yield Field(
Expand All @@ -163,8 +175,8 @@ def get_schema_fields(
definition=definition,
deprecated_self=prop_deprecated,
deprecated=deprecated or prop_deprecated,
codelist=subschema.get('codelist') or prop_items.get('codelist', ''),
open_codelist=subschema.get('openCodelist') or prop_items.get('openCodelist', False),
codelist=prop_codelist,
open_codelist=prop_open_codelist,
multilingual=name in multilingual,
required=name in required,
merge_by_id=name == 'id' and array and not whole_list_merge,
Expand All @@ -186,7 +198,7 @@ def get_schema_fields(
prop_pointer = f'{pointer}/patternProperties/{name}'
prop_path_components = (*path_components, name)
prop_deprecated = _deprecated(subschema)
prop_items = subschema.get('items', {})
prop_codelist, prop_open_codelist = _codelist(subschema)

yield Field(
name=name,
Expand All @@ -196,8 +208,8 @@ def get_schema_fields(
definition=definition,
deprecated_self=prop_deprecated,
deprecated=deprecated or prop_deprecated,
codelist=subschema.get('codelist') or prop_items.get('codelist', ''),
open_codelist=subschema.get('openCodelist') or prop_items.get('openCodelist', False),
codelist=prop_codelist,
open_codelist=prop_open_codelist,
pattern=True,
# `patternProperties` can't be multilingual, required, or "id".
)
Expand All @@ -222,6 +234,15 @@ def get_schema_fields(
yield from get_schema_fields(subschema, f'/{keyword}/{name}', definition=name)


def _codelist(subschema):
if codelist := subschema.get('codelist'):
return codelist, subschema.get('openCodelist', False)
# The behavior hasn't been decided if `items` is an array (e.g. with conflicting codelist-related values).
if (items := subschema.get('items')) and isinstance(items, dict):
return items.get('codelist', ''), items.get('openCodelist', False)
return '', False


def _deprecated(value):
return value.get('deprecated') or (hasattr(value, '__reference__') and value.__reference__.get('deprecated')) or {}

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "ocdskit"
version = "1.3.1"
version = "1.3.2"
authors = [{name = "Open Contracting Partnership", email = "[email protected]"}]
description = "A suite of command-line tools for working with OCDS data"
readme = "README.rst"
Expand Down
4 changes: 2 additions & 2 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
import ocdskit.util


def path(filename):
return os.path.join('tests', 'fixtures', filename)
def path(*args):
return os.path.join('tests', 'fixtures', *args)


def read(filename, mode='rt', **kwargs):
Expand Down
4 changes: 2 additions & 2 deletions tests/commands/test_mapping_sheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,13 @@ def test_command_oc4ids(capsys, monkeypatch):

def test_command_bods(capsys, monkeypatch):
assert_command(capsys, monkeypatch, main,
['mapping-sheet', '--order-by', 'path', path('bods/person-statement.json')],
['mapping-sheet', '--order-by', 'path', path('bods', 'person-statement.json')],
'mapping-sheet_bods.csv')


def test_command_sedl(capsys, monkeypatch):
assert_command(capsys, monkeypatch, main,
['mapping-sheet', path('sedl-schema.json')],
['mapping-sheet', path('sedl', 'schema-alpha.json')],
'mapping-sheet_sedl.csv')


Expand Down
106 changes: 106 additions & 0 deletions tests/fixtures/schema-items-array.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
{
"properties": {
"json_schema_example_fields": {
"type": "object",
"properties": {
"minimum": {
"minimum": 2
},
"minimum2": {
"exclusiveMinimum": true,
"minimum": 2
},
"maximum": {
"maximum": 2
},
"maximum2": {
"exclusiveMaximum": true,
"maximum": 2
},
"minItems": {
"type": "array",
"minItems": 2
},
"maxItems": {
"type": "array",
"maxItems": 2
},
"minLength": {
"type": "string",
"minLength": 2
},
"maxLength": {
"type": "string",
"maxLength": 2
},
"maxProperties": {
"type": "object",
"maxProperties": 2
},
"multipleOf": {
"type": "number",
"multipleOf": 3
},
"not": {
"not": {
"type": "string"
}
},
"anyOf": {
"anyOf": [
{"type": "array"},
{"type": "object"}
]
},
"allOf": {
"anyOf": [
{"type": "array"},
{"type": "object"}
]
},
"oneOf": {
"oneOf": [
{"type": "array"},
{"type": "object"}
]
},
"oneOf2": {
"oneOf": [
{"type": "number"},
{"type": "integer"}
]
},
"additionalItems": {
"type": "array",
"items": [{
"type": "string"
}],
"additionalItems": false
},
"additionalProperties": {
"type": "object",
"additionalProperties": false
},
"additionalProperties2": {
"type": "object",
"patternProperties": {
"okay": {
"type": "string"
}
},
"additionalProperties": false
},
"dependencies": {
"type": "object",
"dependencies": {
"b": ["a"]
}
},
"format": {
"type": "string",
"format": "email"
}
}
}
}
}
File renamed without changes.
29 changes: 29 additions & 0 deletions tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,35 @@
from tests import load


def test_items_array():
schema = load("schema-items-array.json")

assert {field.path_components for field in get_schema_fields(schema)} == {
("json_schema_example_fields", "additionalItems"),
("json_schema_example_fields", "additionalProperties"),
("json_schema_example_fields", "additionalProperties2"),
("json_schema_example_fields", "additionalProperties2", "okay"),
("json_schema_example_fields", "allOf"),
("json_schema_example_fields", "anyOf"),
("json_schema_example_fields", "dependencies"),
("json_schema_example_fields", "format"),
("json_schema_example_fields", "maximum"),
("json_schema_example_fields", "maximum2"),
("json_schema_example_fields", "maxItems"),
("json_schema_example_fields", "maxLength"),
("json_schema_example_fields", "maxProperties"),
("json_schema_example_fields", "minimum"),
("json_schema_example_fields", "minimum2"),
("json_schema_example_fields", "minItems"),
("json_schema_example_fields", "minLength"),
("json_schema_example_fields", "multipleOf"),
("json_schema_example_fields", "not"),
("json_schema_example_fields", "oneOf"),
("json_schema_example_fields", "oneOf2"),
("json_schema_example_fields",),
}


@pytest.mark.parametrize(
("path", "expected"),
[
Expand Down

0 comments on commit 2115181

Please sign in to comment.