Skip to content

Commit 0861725

Browse files
committed
create utils.schema module and refactor functions into it
1 parent e41035f commit 0861725

File tree

6 files changed

+115
-70
lines changed

6 files changed

+115
-70
lines changed

src/ssvc/_mixins.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
SCHEMA_VERSION,
4747
)
4848
from ssvc.utils.field_specs import NamespaceString, VersionString
49-
from ssvc.utils.misc import filename_friendly, order_schema
49+
from ssvc.utils.misc import filename_friendly
50+
from ssvc.utils.schema import order_schema
5051

5152

5253
class _Versioned(BaseModel):

src/ssvc/doctools.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
from ssvc.registry import get_registry
5252
from ssvc.registry.base import SsvcObjectRegistry, get_all
5353
from ssvc.selection import SelectionList
54-
from ssvc.utils.misc import filename_friendly, order_schema
54+
from ssvc.utils.misc import filename_friendly
55+
from ssvc.utils.schema import order_schema
5556

5657
logger = logging.getLogger(__name__)
5758

src/ssvc/registry_demo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
from ssvc.registry import get_registry
2828
from ssvc.registry.base import SsvcObjectRegistry
29-
from ssvc.utils.misc import order_schema
29+
from ssvc.utils.schema import order_schema
3030

3131
logger = logging.getLogger(__name__)
3232

src/ssvc/selection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
)
4444
from ssvc.decision_points.base import DecisionPoint
4545
from ssvc.utils.field_specs import TargetIdList, VersionString
46-
from ssvc.utils.misc import order_schema, strip_nullable_anyof
46+
from ssvc.utils.schema import order_schema, strip_nullable_anyof
4747

4848
SCHEMA_VERSION = "2.0.0"
4949

src/ssvc/utils/misc.py

Lines changed: 0 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -25,39 +25,6 @@
2525
import re
2626
import secrets
2727

28-
from ssvc.utils.defaults import SCHEMA_ORDER
29-
30-
31-
def reorder_title_first(obj):
32-
if isinstance(obj, dict):
33-
if "title" in obj:
34-
reordered = {"title": obj["title"]}
35-
for k, v in obj.items():
36-
if k != "title":
37-
reordered[k] = reorder_title_first(v)
38-
return reordered
39-
else:
40-
return {k: reorder_title_first(v) for k, v in obj.items()}
41-
elif isinstance(obj, list):
42-
return [reorder_title_first(item) for item in obj]
43-
else:
44-
return obj
45-
46-
47-
def order_schema(schema: dict) -> dict:
48-
# create a new dict with the preferred order of fields first
49-
ordered_schema = {k: schema[k] for k in (SCHEMA_ORDER) if k in schema}
50-
51-
# add the rest of the fields in their original order
52-
other_keys = [k for k in schema if k not in ordered_schema]
53-
for k in other_keys:
54-
ordered_schema[k] = schema[k]
55-
56-
# recursively move "title" to the front of any nested objects
57-
ordered_schema = reorder_title_first(ordered_schema)
58-
59-
return ordered_schema
60-
6128

6229
def obfuscate_dict(data: dict) -> tuple[dict, dict]:
6330
"""Given a dictionary, obfuscate its keys by replacing them with random strings.
@@ -118,36 +85,3 @@ def filename_friendly(name: str, replacement="_", to_lower=True) -> str:
11885
return name
11986

12087

121-
def strip_nullable_anyof(schema: dict) -> dict:
122-
"""Recursively rewrite schema to drop `anyOf` [string, null] constructs."""
123-
if isinstance(schema, dict):
124-
# If schema has "anyOf"
125-
if "anyOf" in schema:
126-
anyof: list[dict[str, Any]] = schema["anyOf"]
127-
string_schema = None
128-
has_null = False
129-
130-
for option in anyof:
131-
if option.get("type") == "string":
132-
string_schema = option
133-
elif option.get("type") == "null":
134-
has_null = True
135-
136-
# Replace with string schema if this was the pattern
137-
if string_schema and has_null and len(anyof) == 2:
138-
# Preserve the title if it was in the parent
139-
title = schema.get("title")
140-
schema = dict(string_schema) # copy
141-
if title:
142-
schema["title"] = title
143-
# Drop any default:null
144-
schema.pop("default", None)
145-
146-
# Recurse into nested dicts/lists
147-
for key, value in list(schema.items()):
148-
schema[key] = strip_nullable_anyof(value)
149-
150-
elif isinstance(schema, list):
151-
return [strip_nullable_anyof(item) for item in schema]
152-
153-
return schema

src/ssvc/utils/schema.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/env python
2+
"""
3+
Utility functions for SSVC json schema handling.
4+
"""
5+
6+
# Copyright (c) 2025 Carnegie Mellon University.
7+
# NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE
8+
# ENGINEERING INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS.
9+
# CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND,
10+
# EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT
11+
# NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR
12+
# MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE
13+
# OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE
14+
# ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM
15+
# PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
16+
# Licensed under a MIT (SEI)-style license, please see LICENSE or contact
17+
# [email protected] for full terms.
18+
# [DISTRIBUTION STATEMENT A] This material has been approved for
19+
# public release and unlimited distribution. Please see Copyright notice
20+
# for non-US Government use and distribution.
21+
# This Software includes and/or makes use of Third-Party Software each
22+
# subject to its own license.
23+
# DM24-0278
24+
25+
from ssvc.utils.defaults import SCHEMA_ORDER
26+
27+
28+
# Copyright (c) 2025 Carnegie Mellon University.
29+
# NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE
30+
# ENGINEERING INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS.
31+
# CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND,
32+
# EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT
33+
# NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR
34+
# MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE
35+
# OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE
36+
# ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM
37+
# PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
38+
# Licensed under a MIT (SEI)-style license, please see LICENSE or contact
39+
# [email protected] for full terms.
40+
# [DISTRIBUTION STATEMENT A] This material has been approved for
41+
# public release and unlimited distribution. Please see Copyright notice
42+
# for non-US Government use and distribution.
43+
# This Software includes and/or makes use of Third-Party Software each
44+
# subject to its own license.
45+
# DM24-0278
46+
def reorder_title_first(obj):
47+
if isinstance(obj, dict):
48+
if "title" in obj:
49+
reordered = {"title": obj["title"]}
50+
for k, v in obj.items():
51+
if k != "title":
52+
reordered[k] = reorder_title_first(v)
53+
return reordered
54+
else:
55+
return {k: reorder_title_first(v) for k, v in obj.items()}
56+
elif isinstance(obj, list):
57+
return [reorder_title_first(item) for item in obj]
58+
else:
59+
return obj
60+
61+
62+
def order_schema(schema: dict) -> dict:
63+
# create a new dict with the preferred order of fields first
64+
ordered_schema = {k: schema[k] for k in (SCHEMA_ORDER) if k in schema}
65+
66+
# add the rest of the fields in their original order
67+
other_keys = [k for k in schema if k not in ordered_schema]
68+
for k in other_keys:
69+
ordered_schema[k] = schema[k]
70+
71+
# recursively move "title" to the front of any nested objects
72+
ordered_schema = reorder_title_first(ordered_schema)
73+
74+
return ordered_schema
75+
76+
77+
def strip_nullable_anyof(schema: dict) -> dict:
78+
"""Recursively rewrite schema to drop `anyOf` [string, null] constructs."""
79+
if isinstance(schema, dict):
80+
# If schema has "anyOf"
81+
if "anyOf" in schema:
82+
anyof: list[dict[str, Any]] = schema["anyOf"]
83+
string_schema = None
84+
has_null = False
85+
86+
for option in anyof:
87+
if option.get("type") == "string":
88+
string_schema = option
89+
elif option.get("type") == "null":
90+
has_null = True
91+
92+
# Replace with string schema if this was the pattern
93+
if string_schema and has_null and len(anyof) == 2:
94+
# Preserve the title if it was in the parent
95+
title = schema.get("title")
96+
schema = dict(string_schema) # copy
97+
if title:
98+
schema["title"] = title
99+
# Drop any default:null
100+
schema.pop("default", None)
101+
102+
# Recurse into nested dicts/lists
103+
for key, value in list(schema.items()):
104+
schema[key] = strip_nullable_anyof(value)
105+
106+
elif isinstance(schema, list):
107+
return [strip_nullable_anyof(item) for item in schema]
108+
109+
return schema

0 commit comments

Comments
 (0)