Gen3SchemaDev 2.3.6__tar.gz → 2.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/PKG-INFO +16 -2
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/README.md +11 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/pyproject.toml +2 -2
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/cli.py +59 -1
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/converter.py +20 -8
- gen3schemadev-2.5.0/src/gen3schemadev/refs.py +204 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/schema/schema_templates/program.yaml +2 -1
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/schema/schema_templates/project.yaml +4 -2
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/validators/rule_validator.py +6 -3
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/LICENSE +0 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/__init__.py +0 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/ddvis.py +0 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/schema/__init__.py +0 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/schema/gen3_template.py +0 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/schema/input_schema.py +0 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/schema/schema_templates/_definitions.yaml +0 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/schema/schema_templates/_settings.yaml +0 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/schema/schema_templates/_terms.yaml +0 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/schema/schema_templates/core_metadata_collection.yaml +0 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/schema/schema_templates/gen3_metaschema.yml +0 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/utils.py +0 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/validators/input_validator.py +0 -0
- {gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/validators/metaschema_validator.py +0 -0
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Gen3SchemaDev
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Tool for data modelling in Gen3
|
|
5
5
|
License: Apache 2.0
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Author: JoshuaHarris391
|
|
8
8
|
Author-email: harjo391@gmail.com
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.9.5,<4.0
|
|
10
10
|
Classifier: License :: Other/Proprietary License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
14
17
|
Requires-Dist: check-jsonschema (>=0.34.1,<0.35.0)
|
|
@@ -32,6 +35,17 @@ This repository aims to provide the documentation, learning materials, and softw
|
|
|
32
35
|
- [Guide to creating your first dictionary](docs/gen3schemadev/first_dictionary.md)
|
|
33
36
|
- [Troubleshooting](docs/gen3schemadev/troubleshooting.md)
|
|
34
37
|
|
|
38
|
+
### Fixing `$ref` properties in an existing dictionary
|
|
39
|
+
|
|
40
|
+
JSON Schema draft-04 (which Gen3 uses) ignores any keyword sitting as a direct sibling of `$ref`, so properties written as `{description: ..., $ref: _definitions.yaml#/X}` lose their description in the data-dictionary viewer. The `fix-refs` command rewrites such properties in place, moving the `$ref` into an `allOf` list so annotations survive resolution:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
gen3schemadev fix-refs -y path/to/dictionary --dry-run # preview changes
|
|
44
|
+
gen3schemadev fix-refs -y path/to/dictionary # rewrite in place
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The command is idempotent and never touches bare refs, refs already inside `allOf`/`anyOf`/`oneOf`, the `properties: {$ref: ...}` merge construct, or `_definitions.yaml`/`_terms.yaml`/`_settings.yaml`.
|
|
48
|
+
|
|
35
49
|
|
|
36
50
|
## Deep dive into Gen3 Data Modelling
|
|
37
51
|
*Special Thanks to Marion Shadbolt for providing the [source material](https://github.com/AustralianBioCommons/umccr-dictionary/tree/main/docs/schemas)*
|
|
@@ -12,6 +12,17 @@ This repository aims to provide the documentation, learning materials, and softw
|
|
|
12
12
|
- [Guide to creating your first dictionary](docs/gen3schemadev/first_dictionary.md)
|
|
13
13
|
- [Troubleshooting](docs/gen3schemadev/troubleshooting.md)
|
|
14
14
|
|
|
15
|
+
### Fixing `$ref` properties in an existing dictionary
|
|
16
|
+
|
|
17
|
+
JSON Schema draft-04 (which Gen3 uses) ignores any keyword sitting as a direct sibling of `$ref`, so properties written as `{description: ..., $ref: _definitions.yaml#/X}` lose their description in the data-dictionary viewer. The `fix-refs` command rewrites such properties in place, moving the `$ref` into an `allOf` list so annotations survive resolution:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
gen3schemadev fix-refs -y path/to/dictionary --dry-run # preview changes
|
|
21
|
+
gen3schemadev fix-refs -y path/to/dictionary # rewrite in place
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The command is idempotent and never touches bare refs, refs already inside `allOf`/`anyOf`/`oneOf`, the `properties: {$ref: ...}` merge construct, or `_definitions.yaml`/`_terms.yaml`/`_settings.yaml`.
|
|
25
|
+
|
|
15
26
|
|
|
16
27
|
## Deep dive into Gen3 Data Modelling
|
|
17
28
|
*Special Thanks to Marion Shadbolt for providing the [source material](https://github.com/AustralianBioCommons/umccr-dictionary/tree/main/docs/schemas)*
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "Gen3SchemaDev"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.5.0"
|
|
4
4
|
description = "Tool for data modelling in Gen3"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "JoshuaHarris391",email = "harjo391@gmail.com"}
|
|
7
7
|
]
|
|
8
8
|
license = {text = "Apache 2.0"}
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.9.5,<4.0"
|
|
11
11
|
dependencies = [
|
|
12
12
|
"setuptools>=70.0",
|
|
13
13
|
"gen3-validator (>=2.0.0,<3.0.0)",
|
|
@@ -20,6 +20,7 @@ from gen3schemadev.validators.metaschema_validator import validate_schema_with_m
|
|
|
20
20
|
from importlib.metadata import version
|
|
21
21
|
from gen3schemadev.ddvis import visualise_with_docker
|
|
22
22
|
from gen3schemadev.validators.rule_validator import RuleValidator
|
|
23
|
+
from gen3schemadev.refs import WRAPPED, SKIP_REASONS, fix_yaml_dir
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
def main():
|
|
@@ -142,7 +143,28 @@ def main():
|
|
|
142
143
|
action="store_true",
|
|
143
144
|
help="Set logging level to DEBUG"
|
|
144
145
|
)
|
|
145
|
-
|
|
146
|
+
|
|
147
|
+
# Create 'fix-refs' subcommand
|
|
148
|
+
fixrefs_parser = subparsers.add_parser(
|
|
149
|
+
"fix-refs",
|
|
150
|
+
help="Rewrite properties with '$ref' siblings into draft-04-safe allOf form (in place)"
|
|
151
|
+
)
|
|
152
|
+
fixrefs_parser.add_argument(
|
|
153
|
+
"-y", "--yamldir",
|
|
154
|
+
required=True,
|
|
155
|
+
help="Directory of Gen3 YAML files to rewrite in place (recursive)"
|
|
156
|
+
)
|
|
157
|
+
fixrefs_parser.add_argument(
|
|
158
|
+
"--dry-run",
|
|
159
|
+
action="store_true",
|
|
160
|
+
help="Report what would change without writing files"
|
|
161
|
+
)
|
|
162
|
+
fixrefs_parser.add_argument(
|
|
163
|
+
"--debug",
|
|
164
|
+
action="store_true",
|
|
165
|
+
help="Set logging level to DEBUG"
|
|
166
|
+
)
|
|
167
|
+
|
|
146
168
|
args = parser.parse_args()
|
|
147
169
|
|
|
148
170
|
# Handle case where no command is provided
|
|
@@ -306,5 +328,41 @@ def main():
|
|
|
306
328
|
output_path = args.output or "input_example.yaml"
|
|
307
329
|
write_yaml(init_yaml, output_path)
|
|
308
330
|
|
|
331
|
+
elif args.command == "fix-refs":
|
|
332
|
+
if not os.path.isdir(args.yamldir):
|
|
333
|
+
print(f"ERROR: Directory not found: {args.yamldir}", file=sys.stderr)
|
|
334
|
+
sys.exit(1)
|
|
335
|
+
|
|
336
|
+
if args.dry_run:
|
|
337
|
+
print(f"Dry run: scanning {args.yamldir} (no files will be written)")
|
|
338
|
+
else:
|
|
339
|
+
print(f"Fixing $ref sibling annotations in: {args.yamldir}")
|
|
340
|
+
|
|
341
|
+
reports = fix_yaml_dir(args.yamldir, dry_run=args.dry_run)
|
|
342
|
+
|
|
343
|
+
total_wrapped = 0
|
|
344
|
+
files_changed = 0
|
|
345
|
+
for report in reports:
|
|
346
|
+
print(report["path"])
|
|
347
|
+
if report["skipped_file"]:
|
|
348
|
+
print(f" skipped file ({report['skipped_file']})")
|
|
349
|
+
continue
|
|
350
|
+
if not report["changes"]:
|
|
351
|
+
print(" no $ref properties found")
|
|
352
|
+
continue
|
|
353
|
+
for prop_name, action, preserved in report["changes"]:
|
|
354
|
+
if action == WRAPPED:
|
|
355
|
+
total_wrapped += 1
|
|
356
|
+
print(f" wrapped: {prop_name} (preserved: {', '.join(preserved)})")
|
|
357
|
+
else:
|
|
358
|
+
print(f" skipped ({SKIP_REASONS[action]}): {prop_name}")
|
|
359
|
+
if report["rewritten"] or (args.dry_run and any(c[1] == WRAPPED for c in report["changes"])):
|
|
360
|
+
files_changed += 1
|
|
361
|
+
|
|
362
|
+
files_unchanged = len(reports) - files_changed
|
|
363
|
+
verb = "would be wrapped" if args.dry_run else "wrapped"
|
|
364
|
+
print(f"\nSummary: {total_wrapped} properties {verb} in {files_changed} files; "
|
|
365
|
+
f"{files_unchanged} files unchanged.")
|
|
366
|
+
|
|
309
367
|
if __name__ == "__main__":
|
|
310
368
|
main()
|
|
@@ -11,10 +11,14 @@ Expected Data Structure:
|
|
|
11
11
|
- links: List of link objects with attributes: child, parent, multiplicity
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
14
16
|
from dataclasses import dataclass, asdict
|
|
15
17
|
from typing import Protocol, runtime_checkable, Dict, Any, List
|
|
16
18
|
import logging
|
|
17
19
|
|
|
20
|
+
from gen3schemadev.refs import wrap_ref_siblings
|
|
21
|
+
|
|
18
22
|
|
|
19
23
|
logger = logging.getLogger(__name__)
|
|
20
24
|
|
|
@@ -486,7 +490,11 @@ def format_datetime(prop_dict: dict) -> dict:
|
|
|
486
490
|
Formats a property dictionary with a 'type' of 'datetime' to use a $ref
|
|
487
491
|
to the Gen3 _definitions.yaml#/datetime definition.
|
|
488
492
|
|
|
489
|
-
|
|
493
|
+
Annotations such as 'description' are preserved by wrapping the $ref in
|
|
494
|
+
an allOf list: under JSON Schema draft-04, keywords sitting as direct
|
|
495
|
+
siblings of $ref are ignored during resolution, so they must live beside
|
|
496
|
+
allOf instead. A datetime property with no annotations becomes a bare
|
|
497
|
+
$ref. If the property is not of type 'datetime', it is returned unchanged.
|
|
490
498
|
|
|
491
499
|
Example input:
|
|
492
500
|
{
|
|
@@ -499,7 +507,10 @@ def format_datetime(prop_dict: dict) -> dict:
|
|
|
499
507
|
Example output:
|
|
500
508
|
{
|
|
501
509
|
"collection_date": {
|
|
502
|
-
"
|
|
510
|
+
"description": "Date and time of collection (datetime)",
|
|
511
|
+
"allOf": [
|
|
512
|
+
{"$ref": "_definitions.yaml#/datetime"}
|
|
513
|
+
]
|
|
503
514
|
}
|
|
504
515
|
}
|
|
505
516
|
|
|
@@ -516,16 +527,17 @@ def format_datetime(prop_dict: dict) -> dict:
|
|
|
516
527
|
try:
|
|
517
528
|
if len(prop_dict) != 1:
|
|
518
529
|
raise ValueError("Expected a single property dictionary")
|
|
519
|
-
|
|
530
|
+
|
|
520
531
|
first_key = next(iter(prop_dict))
|
|
521
532
|
value = prop_dict[first_key]
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
533
|
+
|
|
534
|
+
if isinstance(value, dict) and value.get('type') == 'datetime':
|
|
535
|
+
ref_prop = {k: v for k, v in value.items() if k != 'type'}
|
|
536
|
+
ref_prop['$ref'] = "_definitions.yaml#/datetime"
|
|
537
|
+
formatted_props, _ = wrap_ref_siblings(ref_prop, first_key)
|
|
526
538
|
else:
|
|
527
539
|
formatted_props = value
|
|
528
|
-
|
|
540
|
+
|
|
529
541
|
output = {first_key: formatted_props}
|
|
530
542
|
return output
|
|
531
543
|
except Exception as e:
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Utilities for making ``$ref`` properties safe under JSON Schema draft-04.
|
|
2
|
+
|
|
3
|
+
Gen3 compiles data dictionaries with draft-04 semantics, where any keyword
|
|
4
|
+
sitting as a direct sibling of ``$ref`` is ignored during resolution. A
|
|
5
|
+
property written as::
|
|
6
|
+
|
|
7
|
+
atrial_fibrillation:
|
|
8
|
+
description: "Self-reported atrial fibrillation."
|
|
9
|
+
$ref: "_definitions.yaml#/enum_yes_no"
|
|
10
|
+
|
|
11
|
+
loses its ``description`` (and ``termDef``, ``term``, etc.) when resolved by
|
|
12
|
+
strict tooling such as dictionaryutils, so the data-dictionary viewer shows
|
|
13
|
+
"No Description". The fix is structural: keep the annotations at the top
|
|
14
|
+
level and move the ``$ref`` into an ``allOf`` list::
|
|
15
|
+
|
|
16
|
+
atrial_fibrillation:
|
|
17
|
+
description: "Self-reported atrial fibrillation."
|
|
18
|
+
allOf:
|
|
19
|
+
- $ref: "_definitions.yaml#/enum_yes_no"
|
|
20
|
+
|
|
21
|
+
This module is the single source of truth for that transformation. It is
|
|
22
|
+
used by the schema generator (``converter.format_datetime``), the rule
|
|
23
|
+
validator, and the ``gen3schemadev fix-refs`` CLI command.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import fnmatch
|
|
29
|
+
import logging
|
|
30
|
+
import os
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
# Actions recorded for each property examined by the transform.
|
|
35
|
+
WRAPPED = "wrapped"
|
|
36
|
+
BARE_REF = "bare-ref"
|
|
37
|
+
ALREADY_WRAPPED = "already-wrapped"
|
|
38
|
+
NO_REF = "no-ref"
|
|
39
|
+
PROPERTIES_MERGE = "properties-merge"
|
|
40
|
+
|
|
41
|
+
# Human-readable reasons for skipped properties, keyed by action.
|
|
42
|
+
SKIP_REASONS = {
|
|
43
|
+
BARE_REF: "bare $ref, no siblings to preserve",
|
|
44
|
+
ALREADY_WRAPPED: "already wrapped in allOf/anyOf/oneOf",
|
|
45
|
+
PROPERTIES_MERGE: "$ref key in 'properties' is a Gen3 properties-merge",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_COMBINATORS = ("allOf", "anyOf", "oneOf")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def has_ref(prop) -> bool:
|
|
52
|
+
"""
|
|
53
|
+
Return True if a property definition references another schema, either
|
|
54
|
+
via a top-level ``$ref`` or via a ``$ref`` inside an allOf/anyOf/oneOf
|
|
55
|
+
list item.
|
|
56
|
+
"""
|
|
57
|
+
if not isinstance(prop, dict):
|
|
58
|
+
return False
|
|
59
|
+
if "$ref" in prop:
|
|
60
|
+
return True
|
|
61
|
+
for combinator in _COMBINATORS:
|
|
62
|
+
items = prop.get(combinator)
|
|
63
|
+
if isinstance(items, list):
|
|
64
|
+
if any(isinstance(item, dict) and "$ref" in item for item in items):
|
|
65
|
+
return True
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def wrap_ref_siblings(prop, prop_name: str = "") -> "tuple":
|
|
70
|
+
"""
|
|
71
|
+
Rewrite a single property definition so sibling annotations survive
|
|
72
|
+
draft-04 ``$ref`` resolution.
|
|
73
|
+
|
|
74
|
+
Returns a ``(new_prop, action)`` tuple. The transformation only fires
|
|
75
|
+
when the property dict has a top-level ``$ref`` alongside at least one
|
|
76
|
+
other key; in that case the siblings keep their original order and the
|
|
77
|
+
``$ref`` moves into an ``allOf`` list appended last. All other shapes
|
|
78
|
+
are returned unchanged:
|
|
79
|
+
|
|
80
|
+
- not a dict, or no ``$ref`` anywhere -> ``NO_REF``
|
|
81
|
+
- ``$ref`` only inside allOf/anyOf/oneOf -> ``ALREADY_WRAPPED`` (idempotent)
|
|
82
|
+
- ``{"$ref": ...}`` with no siblings -> ``BARE_REF`` (nothing is lost)
|
|
83
|
+
"""
|
|
84
|
+
if not isinstance(prop, dict) or not has_ref(prop):
|
|
85
|
+
return prop, NO_REF
|
|
86
|
+
|
|
87
|
+
if "$ref" not in prop:
|
|
88
|
+
return prop, ALREADY_WRAPPED
|
|
89
|
+
|
|
90
|
+
if len(prop) == 1:
|
|
91
|
+
return prop, BARE_REF
|
|
92
|
+
|
|
93
|
+
new_prop = {k: v for k, v in prop.items() if k != "$ref"}
|
|
94
|
+
if isinstance(new_prop.get("allOf"), list):
|
|
95
|
+
# Rare edge: the property already has an allOf next to the $ref.
|
|
96
|
+
# Append rather than overwrite so existing constraints are kept.
|
|
97
|
+
new_prop["allOf"] = new_prop["allOf"] + [{"$ref": prop["$ref"]}]
|
|
98
|
+
else:
|
|
99
|
+
new_prop["allOf"] = [{"$ref": prop["$ref"]}]
|
|
100
|
+
logger.debug(f"Wrapped $ref of property '{prop_name}' in allOf.")
|
|
101
|
+
return new_prop, WRAPPED
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def fix_schema(schema: dict) -> "tuple":
|
|
105
|
+
"""
|
|
106
|
+
Apply ``wrap_ref_siblings`` to every property of a node schema.
|
|
107
|
+
|
|
108
|
+
Only the direct values of ``schema["properties"]`` are examined:
|
|
109
|
+
sibling-``$ref`` annotations occur at the property level in Gen3 node
|
|
110
|
+
schemas, and a shallow walk cannot accidentally rewrite the Gen3
|
|
111
|
+
properties-merge construct (a ``$ref`` *key* directly inside the
|
|
112
|
+
``properties`` block), which is recorded as ``PROPERTIES_MERGE`` and
|
|
113
|
+
left untouched.
|
|
114
|
+
|
|
115
|
+
Returns ``(new_schema, changes)`` where changes is a list of
|
|
116
|
+
``(prop_name, action, preserved_keys)`` tuples. ``preserved_keys`` names
|
|
117
|
+
the sibling annotation keys kept for ``WRAPPED`` entries and is an empty
|
|
118
|
+
tuple otherwise.
|
|
119
|
+
"""
|
|
120
|
+
changes = []
|
|
121
|
+
props = schema.get("properties") if isinstance(schema, dict) else None
|
|
122
|
+
if not isinstance(props, dict):
|
|
123
|
+
return schema, changes
|
|
124
|
+
|
|
125
|
+
new_props = {}
|
|
126
|
+
for prop_name, prop in props.items():
|
|
127
|
+
if prop_name == "$ref":
|
|
128
|
+
new_props[prop_name] = prop
|
|
129
|
+
changes.append((prop_name, PROPERTIES_MERGE, ()))
|
|
130
|
+
continue
|
|
131
|
+
new_prop, action = wrap_ref_siblings(prop, prop_name)
|
|
132
|
+
new_props[prop_name] = new_prop
|
|
133
|
+
preserved = ()
|
|
134
|
+
if action == WRAPPED:
|
|
135
|
+
preserved = tuple(k for k in prop.keys() if k != "$ref")
|
|
136
|
+
if action != NO_REF:
|
|
137
|
+
changes.append((prop_name, action, preserved))
|
|
138
|
+
|
|
139
|
+
new_schema = dict(schema)
|
|
140
|
+
new_schema["properties"] = new_props
|
|
141
|
+
return new_schema, changes
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def fix_yaml_dir(yaml_dir: str, dry_run: bool = False) -> list:
|
|
145
|
+
"""
|
|
146
|
+
Rewrite every YAML node schema under ``yaml_dir`` (recursively) so that
|
|
147
|
+
``$ref`` sibling annotations survive draft-04 resolution.
|
|
148
|
+
|
|
149
|
+
Files whose basename starts with an underscore (``_definitions.yaml``,
|
|
150
|
+
``_terms.yaml``, ``_settings.yaml``) are skipped entirely: adding
|
|
151
|
+
structure or descriptions to shared definitions would force them onto
|
|
152
|
+
every referencing property. Files with no changes are never rewritten,
|
|
153
|
+
which keeps the operation idempotent. With ``dry_run=True`` nothing is
|
|
154
|
+
written at all.
|
|
155
|
+
|
|
156
|
+
Returns a list of per-file report dicts::
|
|
157
|
+
|
|
158
|
+
{"path": <relative path>, "skipped_file": <reason or None>,
|
|
159
|
+
"changes": [(prop_name, action, preserved_keys), ...],
|
|
160
|
+
"rewritten": <bool>}
|
|
161
|
+
"""
|
|
162
|
+
from gen3schemadev.utils import load_yaml, write_yaml
|
|
163
|
+
|
|
164
|
+
reports = []
|
|
165
|
+
for root, _dirs, files in os.walk(yaml_dir):
|
|
166
|
+
for fname in sorted(files):
|
|
167
|
+
if not (fnmatch.fnmatch(fname, "*.yaml") or fnmatch.fnmatch(fname, "*.yml")):
|
|
168
|
+
continue
|
|
169
|
+
path = os.path.join(root, fname)
|
|
170
|
+
rel_path = os.path.relpath(path, yaml_dir)
|
|
171
|
+
|
|
172
|
+
if fname.startswith("_"):
|
|
173
|
+
reports.append({
|
|
174
|
+
"path": rel_path,
|
|
175
|
+
"skipped_file": "definitions/terms/settings file",
|
|
176
|
+
"changes": [],
|
|
177
|
+
"rewritten": False,
|
|
178
|
+
})
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
schema = load_yaml(path)
|
|
182
|
+
if not isinstance(schema, dict):
|
|
183
|
+
reports.append({
|
|
184
|
+
"path": rel_path,
|
|
185
|
+
"skipped_file": "not a mapping, no properties to fix",
|
|
186
|
+
"changes": [],
|
|
187
|
+
"rewritten": False,
|
|
188
|
+
})
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
new_schema, changes = fix_schema(schema)
|
|
192
|
+
wrapped = [c for c in changes if c[1] == WRAPPED]
|
|
193
|
+
rewritten = False
|
|
194
|
+
if wrapped and not dry_run:
|
|
195
|
+
write_yaml(new_schema, path)
|
|
196
|
+
rewritten = True
|
|
197
|
+
|
|
198
|
+
reports.append({
|
|
199
|
+
"path": rel_path,
|
|
200
|
+
"skipped_file": None,
|
|
201
|
+
"changes": changes,
|
|
202
|
+
"rewritten": rewritten,
|
|
203
|
+
})
|
|
204
|
+
return reports
|
{gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/schema/schema_templates/project.yaml
RENAMED
|
@@ -35,9 +35,10 @@ properties:
|
|
|
35
35
|
type:
|
|
36
36
|
type: string
|
|
37
37
|
id:
|
|
38
|
-
$ref: _definitions.yaml#/UUID
|
|
39
38
|
systemAlias: node_id
|
|
40
39
|
description: UUID for the project.
|
|
40
|
+
allOf:
|
|
41
|
+
- $ref: _definitions.yaml#/UUID
|
|
41
42
|
name:
|
|
42
43
|
type: string
|
|
43
44
|
description: Display name/brief description for the project.
|
|
@@ -62,10 +63,11 @@ properties:
|
|
|
62
63
|
description: The ID of the source providing support/grant resources.
|
|
63
64
|
type: string
|
|
64
65
|
programs:
|
|
65
|
-
$ref: _definitions.yaml#/to_one
|
|
66
66
|
description: 'Indicates that the Study is logically part of the indicated Study.
|
|
67
67
|
|
|
68
68
|
'
|
|
69
|
+
allOf:
|
|
70
|
+
- $ref: _definitions.yaml#/to_one
|
|
69
71
|
state:
|
|
70
72
|
description: 'The possible states a project can be in. All but `open` are
|
|
71
73
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# validates gen3 bundled jsonschema (.json) by testing gen3 specific business rules
|
|
2
2
|
import logging
|
|
3
3
|
from gen3schemadev.converter import link_suffix
|
|
4
|
+
from gen3schemadev.refs import has_ref
|
|
4
5
|
|
|
5
6
|
logger = logging.getLogger(__name__)
|
|
6
7
|
|
|
@@ -188,10 +189,12 @@ class RuleValidator:
|
|
|
188
189
|
)
|
|
189
190
|
continue
|
|
190
191
|
|
|
191
|
-
# Skip if property definition contains a $ref (i.e., is an alias/reference)
|
|
192
|
-
|
|
192
|
+
# Skip if property definition contains a $ref (i.e., is an alias/reference),
|
|
193
|
+
# either top-level or wrapped in allOf/anyOf/oneOf
|
|
194
|
+
if has_ref(value):
|
|
193
195
|
logger.debug(
|
|
194
|
-
f"Skipping property '{key}' because it contains a '$ref'
|
|
196
|
+
f"Skipping property '{key}' because it contains a '$ref' "
|
|
197
|
+
"(top-level or inside allOf/anyOf/oneOf)."
|
|
195
198
|
)
|
|
196
199
|
continue
|
|
197
200
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/schema/schema_templates/_settings.yaml
RENAMED
|
File without changes
|
{gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/schema/schema_templates/_terms.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gen3schemadev-2.3.6 → gen3schemadev-2.5.0}/src/gen3schemadev/validators/metaschema_validator.py
RENAMED
|
File without changes
|