Skip to content

Commit 2115181

Browse files
committed
feat: Add support for items as array to get_schema_fields
1 parent f3d6a21 commit 2115181

File tree

9 files changed

+185
-21
lines changed

9 files changed

+185
-21
lines changed

docs/changelog.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
Changelog
22
=========
33

4+
1.3.2 (2024-12-17)
5+
------------------
6+
7+
Added
8+
~~~~~
9+
10+
- :func:`ocdskit.schema.get_schema_fields`: Add support for ``items`` as array.
11+
412
1.3.1 (2024-12-16)
513
------------------
614

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
author = "Open Contracting Partnership"
2424

2525
# The short X.Y version
26-
version = "1.3.1"
26+
version = "1.3.2"
2727
# The full version, including alpha/beta/rc tags
2828
release = version
2929

ocdskit/schema.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,27 @@ def get_schema_fields(
112112

113113
if items := schema.get('items'):
114114
# `items` advances the pointer and sets array context (for the next level only).
115-
yield from get_schema_fields(
116-
items,
117-
f'{pointer}/items',
118-
path_components,
119-
definition,
120-
deprecated,
121-
whole_list_merge=whole_list_merge,
122-
array=True,
123-
)
115+
if isinstance(items, dict):
116+
yield from get_schema_fields(
117+
items,
118+
f'{pointer}/items',
119+
path_components,
120+
definition,
121+
deprecated,
122+
whole_list_merge=whole_list_merge,
123+
array=True,
124+
)
125+
else:
126+
for i, subschema in enumerate(items):
127+
yield from get_schema_fields(
128+
subschema,
129+
f'{pointer}/items/{i}',
130+
path_components,
131+
definition,
132+
deprecated,
133+
whole_list_merge=whole_list_merge,
134+
array=True,
135+
)
124136

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

157169
# To date, codelist and openCodelist in OCDS aren't set on `items`.
158170
yield Field(
@@ -163,8 +175,8 @@ def get_schema_fields(
163175
definition=definition,
164176
deprecated_self=prop_deprecated,
165177
deprecated=deprecated or prop_deprecated,
166-
codelist=subschema.get('codelist') or prop_items.get('codelist', ''),
167-
open_codelist=subschema.get('openCodelist') or prop_items.get('openCodelist', False),
178+
codelist=prop_codelist,
179+
open_codelist=prop_open_codelist,
168180
multilingual=name in multilingual,
169181
required=name in required,
170182
merge_by_id=name == 'id' and array and not whole_list_merge,
@@ -186,7 +198,7 @@ def get_schema_fields(
186198
prop_pointer = f'{pointer}/patternProperties/{name}'
187199
prop_path_components = (*path_components, name)
188200
prop_deprecated = _deprecated(subschema)
189-
prop_items = subschema.get('items', {})
201+
prop_codelist, prop_open_codelist = _codelist(subschema)
190202

191203
yield Field(
192204
name=name,
@@ -196,8 +208,8 @@ def get_schema_fields(
196208
definition=definition,
197209
deprecated_self=prop_deprecated,
198210
deprecated=deprecated or prop_deprecated,
199-
codelist=subschema.get('codelist') or prop_items.get('codelist', ''),
200-
open_codelist=subschema.get('openCodelist') or prop_items.get('openCodelist', False),
211+
codelist=prop_codelist,
212+
open_codelist=prop_open_codelist,
201213
pattern=True,
202214
# `patternProperties` can't be multilingual, required, or "id".
203215
)
@@ -222,6 +234,15 @@ def get_schema_fields(
222234
yield from get_schema_fields(subschema, f'/{keyword}/{name}', definition=name)
223235

224236

237+
def _codelist(subschema):
238+
if codelist := subschema.get('codelist'):
239+
return codelist, subschema.get('openCodelist', False)
240+
# The behavior hasn't been decided if `items` is an array (e.g. with conflicting codelist-related values).
241+
if (items := subschema.get('items')) and isinstance(items, dict):
242+
return items.get('codelist', ''), items.get('openCodelist', False)
243+
return '', False
244+
245+
225246
def _deprecated(value):
226247
return value.get('deprecated') or (hasattr(value, '__reference__') and value.__reference__.get('deprecated')) or {}
227248

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "ocdskit"
7-
version = "1.3.1"
7+
version = "1.3.2"
88
authors = [{name = "Open Contracting Partnership", email = "data@open-contracting.org"}]
99
description = "A suite of command-line tools for working with OCDS data"
1010
readme = "README.rst"

tests/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
import ocdskit.util
1212

1313

14-
def path(filename):
15-
return os.path.join('tests', 'fixtures', filename)
14+
def path(*args):
15+
return os.path.join('tests', 'fixtures', *args)
1616

1717

1818
def read(filename, mode='rt', **kwargs):

tests/commands/test_mapping_sheet.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,13 @@ def test_command_oc4ids(capsys, monkeypatch):
103103

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

109109

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

115115

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
{
2+
"properties": {
3+
"json_schema_example_fields": {
4+
"type": "object",
5+
"properties": {
6+
"minimum": {
7+
"minimum": 2
8+
},
9+
"minimum2": {
10+
"exclusiveMinimum": true,
11+
"minimum": 2
12+
},
13+
"maximum": {
14+
"maximum": 2
15+
},
16+
"maximum2": {
17+
"exclusiveMaximum": true,
18+
"maximum": 2
19+
},
20+
"minItems": {
21+
"type": "array",
22+
"minItems": 2
23+
},
24+
"maxItems": {
25+
"type": "array",
26+
"maxItems": 2
27+
},
28+
"minLength": {
29+
"type": "string",
30+
"minLength": 2
31+
},
32+
"maxLength": {
33+
"type": "string",
34+
"maxLength": 2
35+
},
36+
"maxProperties": {
37+
"type": "object",
38+
"maxProperties": 2
39+
},
40+
"multipleOf": {
41+
"type": "number",
42+
"multipleOf": 3
43+
},
44+
"not": {
45+
"not": {
46+
"type": "string"
47+
}
48+
},
49+
"anyOf": {
50+
"anyOf": [
51+
{"type": "array"},
52+
{"type": "object"}
53+
]
54+
},
55+
"allOf": {
56+
"anyOf": [
57+
{"type": "array"},
58+
{"type": "object"}
59+
]
60+
},
61+
"oneOf": {
62+
"oneOf": [
63+
{"type": "array"},
64+
{"type": "object"}
65+
]
66+
},
67+
"oneOf2": {
68+
"oneOf": [
69+
{"type": "number"},
70+
{"type": "integer"}
71+
]
72+
},
73+
"additionalItems": {
74+
"type": "array",
75+
"items": [{
76+
"type": "string"
77+
}],
78+
"additionalItems": false
79+
},
80+
"additionalProperties": {
81+
"type": "object",
82+
"additionalProperties": false
83+
},
84+
"additionalProperties2": {
85+
"type": "object",
86+
"patternProperties": {
87+
"okay": {
88+
"type": "string"
89+
}
90+
},
91+
"additionalProperties": false
92+
},
93+
"dependencies": {
94+
"type": "object",
95+
"dependencies": {
96+
"b": ["a"]
97+
}
98+
},
99+
"format": {
100+
"type": "string",
101+
"format": "email"
102+
}
103+
}
104+
}
105+
}
106+
}
File renamed without changes.

tests/test_schema.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,35 @@
55
from tests import load
66

77

8+
def test_items_array():
9+
schema = load("schema-items-array.json")
10+
11+
assert {field.path_components for field in get_schema_fields(schema)} == {
12+
("json_schema_example_fields", "additionalItems"),
13+
("json_schema_example_fields", "additionalProperties"),
14+
("json_schema_example_fields", "additionalProperties2"),
15+
("json_schema_example_fields", "additionalProperties2", "okay"),
16+
("json_schema_example_fields", "allOf"),
17+
("json_schema_example_fields", "anyOf"),
18+
("json_schema_example_fields", "dependencies"),
19+
("json_schema_example_fields", "format"),
20+
("json_schema_example_fields", "maximum"),
21+
("json_schema_example_fields", "maximum2"),
22+
("json_schema_example_fields", "maxItems"),
23+
("json_schema_example_fields", "maxLength"),
24+
("json_schema_example_fields", "maxProperties"),
25+
("json_schema_example_fields", "minimum"),
26+
("json_schema_example_fields", "minimum2"),
27+
("json_schema_example_fields", "minItems"),
28+
("json_schema_example_fields", "minLength"),
29+
("json_schema_example_fields", "multipleOf"),
30+
("json_schema_example_fields", "not"),
31+
("json_schema_example_fields", "oneOf"),
32+
("json_schema_example_fields", "oneOf2"),
33+
("json_schema_example_fields",),
34+
}
35+
36+
837
@pytest.mark.parametrize(
938
("path", "expected"),
1039
[

0 commit comments

Comments
 (0)