fw-gear-session-validator 0.2.2__py3-none-any.whl
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.
- fw_gear_session_validator/__init__.py +8 -0
- fw_gear_session_validator/errors.py +322 -0
- fw_gear_session_validator/main.py +104 -0
- fw_gear_session_validator/parser.py +138 -0
- fw_gear_session_validator/pdf_report.py +221 -0
- fw_gear_session_validator/session.py +32 -0
- fw_gear_session_validator/utils.py +103 -0
- fw_gear_session_validator/validation.py +94 -0
- fw_gear_session_validator-0.2.2.dist-info/METADATA +780 -0
- fw_gear_session_validator-0.2.2.dist-info/RECORD +12 -0
- fw_gear_session_validator-0.2.2.dist-info/WHEEL +4 -0
- fw_gear_session_validator-0.2.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simple JSONSchema Error Handler
|
|
3
|
+
|
|
4
|
+
A lightweight approach that extracts key error information
|
|
5
|
+
|
|
6
|
+
This approach:
|
|
7
|
+
1. Extracts validator type
|
|
8
|
+
2. Extracts schema path
|
|
9
|
+
3. Extracts instance path
|
|
10
|
+
4. Cleans error.message by removing instance representation
|
|
11
|
+
5. Extracts description from schema by walking up schema path
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Any, Dict, Optional, Sequence, Tuple
|
|
16
|
+
|
|
17
|
+
from jsonschema.exceptions import ValidationError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _resolve_path(root: Any, path: Sequence[Any]) -> Any:
|
|
21
|
+
"""Resolve a path within a nested data structure.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
root: The root data structure (dict/list)
|
|
25
|
+
path: Sequence of keys/indices to traverse
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
The value at the specified path
|
|
29
|
+
"""
|
|
30
|
+
node = root
|
|
31
|
+
for key in path:
|
|
32
|
+
node = node[key]
|
|
33
|
+
return node
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def nearest_description(
|
|
37
|
+
schema_root: dict, abs_schema_path: Sequence[Any]
|
|
38
|
+
) -> Tuple[Optional[str], Optional[Sequence[Any]]]:
|
|
39
|
+
"""Find the nearest description by walking up the schema path.
|
|
40
|
+
|
|
41
|
+
JSONSchema validators may fail at a node that doesn't have a description,
|
|
42
|
+
but a parent node (e.g., an allOf element) often does. This function
|
|
43
|
+
walks up from the failing node to find the nearest description.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
schema_root: The root schema dictionary
|
|
47
|
+
abs_schema_path: The absolute schema path from the validation error
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Tuple of (description string or None, path where found or None)
|
|
51
|
+
"""
|
|
52
|
+
# Start at the failing node and walk up to the root
|
|
53
|
+
for i in range(len(abs_schema_path), -1, -1):
|
|
54
|
+
sub_path = abs_schema_path[:i]
|
|
55
|
+
try:
|
|
56
|
+
node = _resolve_path(schema_root, sub_path)
|
|
57
|
+
except (KeyError, IndexError, TypeError):
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
if isinstance(node, dict):
|
|
61
|
+
desc = node.get("description")
|
|
62
|
+
if isinstance(desc, str) and desc.strip():
|
|
63
|
+
return desc, sub_path
|
|
64
|
+
|
|
65
|
+
return None, None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def create_simple_error_message(
|
|
69
|
+
error: ValidationError, schema_root: Optional[dict] = None
|
|
70
|
+
) -> Dict[str, Any]:
|
|
71
|
+
"""
|
|
72
|
+
Create a simple, clean error message from ValidationError.
|
|
73
|
+
|
|
74
|
+
This extracts the essential information and cleans the error message
|
|
75
|
+
by removing the instance representation that causes 1MB messages.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
error: ValidationError from jsonschema
|
|
79
|
+
schema_root: The root schema dictionary (optional, for description extraction)
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Dict with keys:
|
|
83
|
+
- error_message: Cleaned error message without instance repr
|
|
84
|
+
- instance_path: Path in the data where error occurred
|
|
85
|
+
- label: Human-facing label (falls back to $ref)
|
|
86
|
+
- description: Human-readable description from schema (if found)
|
|
87
|
+
- schema_definition: Tail of validator_value[$ref] if present
|
|
88
|
+
- schema_path: Path in the schema where error occurred
|
|
89
|
+
- validator_type: Dict with 'type' and 'details' (details vary by type)
|
|
90
|
+
- validator_value: The constraint value from schema
|
|
91
|
+
"""
|
|
92
|
+
# 1. Extract validator type
|
|
93
|
+
validator_type_str = error.validator
|
|
94
|
+
|
|
95
|
+
# 2. Extract schema path (where in schema the error is defined)
|
|
96
|
+
schema_path = list(error.absolute_schema_path)
|
|
97
|
+
schema_path_str = ".".join(str(p) for p in schema_path) if schema_path else "root"
|
|
98
|
+
|
|
99
|
+
# 3. Extract instance path (where in data the error occurred)
|
|
100
|
+
instance_path = list(error.absolute_path)
|
|
101
|
+
instance_path_str = (
|
|
102
|
+
".".join(str(p) for p in instance_path) if instance_path else "root"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# 4. Get error message
|
|
106
|
+
original_message = error.message
|
|
107
|
+
|
|
108
|
+
# 5. Clean the message by removing instance representation
|
|
109
|
+
cleaned_message = clean_error_message(original_message, error.instance)
|
|
110
|
+
|
|
111
|
+
# 6. Extract validator value
|
|
112
|
+
validator_value = error.validator_value
|
|
113
|
+
|
|
114
|
+
# 7. Extract $ref and schema_definition if present
|
|
115
|
+
# NOTE: schema_definition is only populated when validator_value contains a $ref.
|
|
116
|
+
# For direct constraints (e.g., maximum: -9, pattern: "^[A-Z]+$"), schema_definition
|
|
117
|
+
# will remain None since they don't reference a definition.
|
|
118
|
+
schema_definition = None
|
|
119
|
+
label = None
|
|
120
|
+
if isinstance(validator_value, dict) and "$ref" in validator_value:
|
|
121
|
+
ref_value = validator_value["$ref"]
|
|
122
|
+
# Extract definition name from $ref path (e.g., "#/$defs/T1_acquisition" -> "T1_acquisition")
|
|
123
|
+
if "/" in ref_value:
|
|
124
|
+
schema_definition = ref_value.split("/")[-1]
|
|
125
|
+
else:
|
|
126
|
+
schema_definition = ref_value
|
|
127
|
+
label = schema_definition # Use definition as label
|
|
128
|
+
|
|
129
|
+
# 8. Generate label if not already set
|
|
130
|
+
if label is None:
|
|
131
|
+
label = _generate_label(validator_type_str, validator_value, instance_path)
|
|
132
|
+
|
|
133
|
+
# 9. Build validator_type with details
|
|
134
|
+
validator_type_obj = {
|
|
135
|
+
"type": validator_type_str,
|
|
136
|
+
"details": _extract_validator_details(error),
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# 10. Extract description from schema by walking up the schema path
|
|
140
|
+
description = None
|
|
141
|
+
if schema_root is not None:
|
|
142
|
+
description, _ = nearest_description(schema_root, schema_path)
|
|
143
|
+
|
|
144
|
+
# 11. Build result
|
|
145
|
+
result = {
|
|
146
|
+
"error_message": cleaned_message,
|
|
147
|
+
"instance_path": instance_path_str,
|
|
148
|
+
"label": label,
|
|
149
|
+
"schema_path": schema_path_str,
|
|
150
|
+
"validator_type": validator_type_obj,
|
|
151
|
+
"validator_value": validator_value,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Add schema_definition if present
|
|
155
|
+
if schema_definition is not None:
|
|
156
|
+
result["schema_definition"] = schema_definition
|
|
157
|
+
|
|
158
|
+
# Add description if found
|
|
159
|
+
if description is not None:
|
|
160
|
+
result["description"] = description
|
|
161
|
+
|
|
162
|
+
return result
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def clean_error_message(message: str, instance: Any) -> str:
|
|
166
|
+
"""
|
|
167
|
+
Clean error message by removing the instance representation.
|
|
168
|
+
|
|
169
|
+
JSONSchema error messages often follow patterns like:
|
|
170
|
+
"<instance repr> <description>"
|
|
171
|
+
"None of <instance repr> are valid under the given schema"
|
|
172
|
+
|
|
173
|
+
This function removes the instance repr part.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
message: Original error message from jsonschema
|
|
177
|
+
instance: The instance value that failed validation
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Cleaned error message
|
|
181
|
+
"""
|
|
182
|
+
# NEW SIMPLIFIED APPROACH: Remove instance repr from message
|
|
183
|
+
instance_repr = repr(instance)
|
|
184
|
+
|
|
185
|
+
# Check if instance repr is in the message
|
|
186
|
+
if instance_repr in message:
|
|
187
|
+
# Replace the instance repr with a placeholder
|
|
188
|
+
if isinstance(instance, list):
|
|
189
|
+
placeholder = f"[array of {len(instance)} items]"
|
|
190
|
+
elif isinstance(instance, dict):
|
|
191
|
+
placeholder = f"{{object with {len(instance)} keys}}"
|
|
192
|
+
else:
|
|
193
|
+
placeholder = f"<{type(instance).__name__}>"
|
|
194
|
+
|
|
195
|
+
message = message.replace(instance_repr, placeholder)
|
|
196
|
+
|
|
197
|
+
return message
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _extract_validator_details(error: ValidationError) -> Dict[str, Any]: # noqa: PLR0912
|
|
201
|
+
"""
|
|
202
|
+
Extract validator-specific details using a hybrid approach:
|
|
203
|
+
- Custom logic for validators that need special handling
|
|
204
|
+
- Dynamic extraction of all constraint keywords from schema
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
error: ValidationError from jsonschema
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Dict with details extracted from error and schema
|
|
211
|
+
"""
|
|
212
|
+
validator_type = error.validator
|
|
213
|
+
validator_value = error.validator_value
|
|
214
|
+
instance = error.instance
|
|
215
|
+
schema = error.schema
|
|
216
|
+
|
|
217
|
+
details = {}
|
|
218
|
+
|
|
219
|
+
# Step 1: Custom handling for validators that need special logic
|
|
220
|
+
if validator_type == "required":
|
|
221
|
+
# Calculate which specific fields are missing
|
|
222
|
+
if isinstance(validator_value, list) and isinstance(instance, dict):
|
|
223
|
+
missing = [field for field in validator_value if field not in instance]
|
|
224
|
+
details["missing"] = missing
|
|
225
|
+
|
|
226
|
+
elif validator_type == "type":
|
|
227
|
+
# Show expected vs found type
|
|
228
|
+
details["expected"] = validator_value
|
|
229
|
+
details["found"] = type(instance).__name__
|
|
230
|
+
|
|
231
|
+
elif validator_type == "pattern":
|
|
232
|
+
# Show pattern and preview of actual value
|
|
233
|
+
details["pattern"] = validator_value
|
|
234
|
+
if isinstance(instance, str):
|
|
235
|
+
details["preview"] = instance[:50] # First 50 chars
|
|
236
|
+
|
|
237
|
+
elif validator_type in ("anyOf", "oneOf", "allOf"):
|
|
238
|
+
# Count number of alternatives
|
|
239
|
+
if isinstance(validator_value, list):
|
|
240
|
+
details["alternatives"] = len(validator_value)
|
|
241
|
+
|
|
242
|
+
elif validator_type == "enum":
|
|
243
|
+
# Show allowed values
|
|
244
|
+
details["allowed_values"] = validator_value
|
|
245
|
+
details["found"] = instance
|
|
246
|
+
|
|
247
|
+
elif validator_type == "const":
|
|
248
|
+
# Show expected constant value
|
|
249
|
+
details["expected"] = validator_value
|
|
250
|
+
details["found"] = instance
|
|
251
|
+
|
|
252
|
+
# Step 2: Add instance size/length if applicable
|
|
253
|
+
if isinstance(instance, (list, dict, str)):
|
|
254
|
+
if "found" not in details: # Don't overwrite if already set
|
|
255
|
+
details["found"] = len(instance)
|
|
256
|
+
|
|
257
|
+
# Step 3: DYNAMIC EXTRACTION - Get all constraint keywords from schema
|
|
258
|
+
# Exclude structural/metadata keys that don't represent constraints
|
|
259
|
+
metadata_keys = {
|
|
260
|
+
"$schema",
|
|
261
|
+
"$id",
|
|
262
|
+
"$ref",
|
|
263
|
+
"$defs",
|
|
264
|
+
"definitions",
|
|
265
|
+
"title",
|
|
266
|
+
"description",
|
|
267
|
+
"examples",
|
|
268
|
+
"default",
|
|
269
|
+
"comment",
|
|
270
|
+
"type",
|
|
271
|
+
"properties",
|
|
272
|
+
"items",
|
|
273
|
+
"additionalProperties",
|
|
274
|
+
"patternProperties",
|
|
275
|
+
"additionalItems",
|
|
276
|
+
"required",
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if isinstance(schema, dict):
|
|
280
|
+
for key, value in schema.items():
|
|
281
|
+
# Only add if:
|
|
282
|
+
# 1. Not a metadata/structural key
|
|
283
|
+
# 2. Not already in details (don't overwrite custom logic)
|
|
284
|
+
# 3. Not the current validator's keyword (avoid duplication with validator_value)
|
|
285
|
+
if (
|
|
286
|
+
key not in metadata_keys
|
|
287
|
+
and key not in details
|
|
288
|
+
and key != validator_type
|
|
289
|
+
):
|
|
290
|
+
details[key] = value
|
|
291
|
+
|
|
292
|
+
return details
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _generate_label(
|
|
296
|
+
validator_type: str, validator_value: Any, instance_path: list
|
|
297
|
+
) -> str:
|
|
298
|
+
"""
|
|
299
|
+
Generate a human-facing label for the error.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
validator_type: Type of validator that failed
|
|
303
|
+
validator_value: The constraint value
|
|
304
|
+
instance_path: Path to the instance that failed
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Human-readable label string
|
|
308
|
+
"""
|
|
309
|
+
# Use the last element of instance_path if available
|
|
310
|
+
if instance_path:
|
|
311
|
+
return str(instance_path[-1])
|
|
312
|
+
|
|
313
|
+
# Fallback to validator type
|
|
314
|
+
if validator_type == "required":
|
|
315
|
+
if isinstance(validator_value, list) and validator_value:
|
|
316
|
+
return f"missing_{validator_value[0]}"
|
|
317
|
+
elif validator_type == "type":
|
|
318
|
+
return "type_mismatch"
|
|
319
|
+
elif validator_type == "contains":
|
|
320
|
+
return "contains_validation"
|
|
321
|
+
|
|
322
|
+
return validator_type
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Main module."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Tuple
|
|
7
|
+
|
|
8
|
+
import flywheel
|
|
9
|
+
from flywheel import Client as FWClient
|
|
10
|
+
|
|
11
|
+
from fw_gear_session_validator.parser import RunOptions
|
|
12
|
+
from fw_gear_session_validator.pdf_report import generate_pdf_report
|
|
13
|
+
from fw_gear_session_validator.session import build_session_dict
|
|
14
|
+
from fw_gear_session_validator.utils import (
|
|
15
|
+
check_for_qc_tags,
|
|
16
|
+
handle_existing_qc_tags,
|
|
17
|
+
save_report,
|
|
18
|
+
tag_session_qc_result,
|
|
19
|
+
)
|
|
20
|
+
from fw_gear_session_validator.validation import validate_json, validate_schema
|
|
21
|
+
|
|
22
|
+
log = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run(
|
|
26
|
+
destination: dict,
|
|
27
|
+
output_dir: Path,
|
|
28
|
+
schema_path: Path,
|
|
29
|
+
client: FWClient,
|
|
30
|
+
options: RunOptions,
|
|
31
|
+
) -> Tuple[int, list[dict], flywheel.Session]:
|
|
32
|
+
"""Run the session validation.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
destination: The destination container information.
|
|
36
|
+
output_dir: The output directory for saving reports.
|
|
37
|
+
schema_path: Path to the validation schema file.
|
|
38
|
+
client: The Flywheel client to interact with the API.
|
|
39
|
+
options: RunOptions with configurations and optional PDF links.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Tuple
|
|
43
|
+
e_code (int): Exit code of the run.
|
|
44
|
+
validation_report (list[dict]): The validation report.
|
|
45
|
+
session_ (flywheel.Session): The Flywheel session object.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# Grab the destination container and parent to make sure we are at the
|
|
50
|
+
# session level
|
|
51
|
+
run_level = destination.parent["type"]
|
|
52
|
+
if run_level != "session":
|
|
53
|
+
raise RuntimeError(
|
|
54
|
+
f"Cannot run at {run_level} level. Please run at session level."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
session_id = destination.parent["id"]
|
|
58
|
+
session_ = client.get_session(session_id)
|
|
59
|
+
log.info(f"Checking if session {session_.label} needs validation.")
|
|
60
|
+
|
|
61
|
+
# Check for existing session-validator tags
|
|
62
|
+
qc_tags = check_for_qc_tags(session_)
|
|
63
|
+
# If session is already validated, log and exit
|
|
64
|
+
if not handle_existing_qc_tags(session_, qc_tags, options.force_rerun):
|
|
65
|
+
return None, None, None
|
|
66
|
+
|
|
67
|
+
# Check if schema is valid
|
|
68
|
+
log.debug(f"Loading schema from {schema_path}")
|
|
69
|
+
with open(schema_path, "r") as schema_file:
|
|
70
|
+
schema = json.load(schema_file)
|
|
71
|
+
log.info("Validating schema...")
|
|
72
|
+
if not validate_schema(schema):
|
|
73
|
+
raise ValueError("Invalid JSON schema. Please check the schema file.")
|
|
74
|
+
|
|
75
|
+
log.info(f"Validating session {session_.label}.")
|
|
76
|
+
|
|
77
|
+
# Build session dictionary for validation
|
|
78
|
+
log.debug("Building session dictionary.")
|
|
79
|
+
session_dict = build_session_dict(session_)
|
|
80
|
+
|
|
81
|
+
validation_report, validation_report_meta = validate_json(session_dict, schema)
|
|
82
|
+
|
|
83
|
+
if options.save_validation_report:
|
|
84
|
+
log.info("Validation complete. Saving JSON report.")
|
|
85
|
+
save_report(output_dir, validation_report)
|
|
86
|
+
else:
|
|
87
|
+
log.info("Validation complete. Not saving JSON report as per configuration.")
|
|
88
|
+
|
|
89
|
+
if options.save_pdf_report:
|
|
90
|
+
log.info("Generating PDF report.")
|
|
91
|
+
generate_pdf_report(
|
|
92
|
+
output_dir, session_.label, validation_report, options.pdf_links
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
log.info("Not generating PDF report as per configuration.")
|
|
96
|
+
|
|
97
|
+
# Tag session as PASS or FAIL
|
|
98
|
+
log.info("Tagging session with QC result.")
|
|
99
|
+
tag_session_qc_result(session_, passed=not len(validation_report))
|
|
100
|
+
|
|
101
|
+
# Return error code
|
|
102
|
+
# Return validation_report and session container for creating
|
|
103
|
+
# qc metadata object in run.py
|
|
104
|
+
return 0, validation_report_meta, session_
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Parser module to parse gear config.json."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from fw_gear import GearContext as GearToolkitContext
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class PdfLinks:
|
|
12
|
+
"""PDF report link metadata."""
|
|
13
|
+
|
|
14
|
+
session_path: str
|
|
15
|
+
job_url: str
|
|
16
|
+
session_url: str
|
|
17
|
+
schema_url: str
|
|
18
|
+
schema_filename: str
|
|
19
|
+
schema_version: int
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class RunOptions:
|
|
24
|
+
"""Configuration options."""
|
|
25
|
+
|
|
26
|
+
save_validation_report: bool = False
|
|
27
|
+
save_pdf_report: bool = False
|
|
28
|
+
force_rerun: bool = False
|
|
29
|
+
pdf_links: Optional[PdfLinks] = None
|
|
30
|
+
|
|
31
|
+
def validate(self) -> None:
|
|
32
|
+
"""Validate options."""
|
|
33
|
+
if self.save_pdf_report and self.pdf_links is None:
|
|
34
|
+
raise ValueError("save_pdf_report=True requires pdf_links.")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def build_pdf_links(gear_context: GearToolkitContext, destination: dict) -> PdfLinks:
|
|
38
|
+
"""Build all PDF report link metadata.
|
|
39
|
+
|
|
40
|
+
Constructs session path, job URL, session URL, and schema info for PDF reports.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
gear_context: The gear context containing client and config info.
|
|
44
|
+
destination: The destination analysis container.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
PdfLinks dataclass with all link metadata.
|
|
48
|
+
"""
|
|
49
|
+
client = gear_context.client
|
|
50
|
+
session_id = destination.parent["id"]
|
|
51
|
+
session = client.get_session(session_id)
|
|
52
|
+
|
|
53
|
+
# Extract site URL (shared across all URL builders)
|
|
54
|
+
api_host = client._fw.api_client.configuration.host
|
|
55
|
+
site_url = api_host.rsplit("/api", 1)[0]
|
|
56
|
+
|
|
57
|
+
# Build session path: fw://group/project/subject/session
|
|
58
|
+
group = session.parents.group
|
|
59
|
+
project = client.get_project(session.parents.project)
|
|
60
|
+
subject = client.get_subject(session.parents.subject)
|
|
61
|
+
session_path = f"fw://{group}/{project.label}/{subject.label}/{session.label}"
|
|
62
|
+
|
|
63
|
+
# Build job URL
|
|
64
|
+
job_id = gear_context.config.job.get("id")
|
|
65
|
+
job_url = f"{site_url}/#/jobs/{job_id}" if job_id else ""
|
|
66
|
+
|
|
67
|
+
# Build session URL
|
|
68
|
+
session_url = (
|
|
69
|
+
f"{site_url}/#/projects/{session.parents.project}/sessions/{session_id}"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Build schema info
|
|
73
|
+
schema_url, schema_filename, schema_version = "", "", 0
|
|
74
|
+
schema_input = gear_context.config.get_input("validation_schema")
|
|
75
|
+
if schema_input:
|
|
76
|
+
container_type = schema_input.get("hierarchy", {}).get("type", "")
|
|
77
|
+
container_id = schema_input.get("hierarchy", {}).get("id", "")
|
|
78
|
+
schema_filename = schema_input.get("location", {}).get("name", "")
|
|
79
|
+
file_id = schema_input.get("object", {}).get("file_id", "")
|
|
80
|
+
|
|
81
|
+
if file_id and container_id:
|
|
82
|
+
try:
|
|
83
|
+
file_info = client.get_file(file_id)
|
|
84
|
+
schema_version = getattr(file_info, "version", 0)
|
|
85
|
+
except Exception:
|
|
86
|
+
pass # Version is optional
|
|
87
|
+
|
|
88
|
+
if container_type and container_id:
|
|
89
|
+
schema_url = f"{site_url}/#/{container_type}s/{container_id}"
|
|
90
|
+
|
|
91
|
+
return PdfLinks(
|
|
92
|
+
session_path=session_path,
|
|
93
|
+
job_url=job_url,
|
|
94
|
+
session_url=session_url,
|
|
95
|
+
schema_url=schema_url,
|
|
96
|
+
schema_filename=schema_filename,
|
|
97
|
+
schema_version=schema_version,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def parse_config(
|
|
102
|
+
gear_context: GearToolkitContext,
|
|
103
|
+
) -> Tuple[dict, Path, Path, RunOptions]:
|
|
104
|
+
"""Parse context config.json object.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
gear_context (GearToolkitContext): The gear context containing configuration.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
tuple:
|
|
111
|
+
destination: The destination container information.
|
|
112
|
+
output_dir: The output directory for saving reports.
|
|
113
|
+
schema_path: Path to the validation schema file.
|
|
114
|
+
options: RunOptions with flags and optional PDF links.
|
|
115
|
+
"""
|
|
116
|
+
save_validation_report = gear_context.config.opts.get(
|
|
117
|
+
"save_validation_report", False
|
|
118
|
+
)
|
|
119
|
+
force_rerun = gear_context.config.opts.get("force_rerun", False)
|
|
120
|
+
save_pdf_report = gear_context.config.opts.get("save_pdf_report", False)
|
|
121
|
+
schema_path = gear_context.config.get_input_path("validation_schema")
|
|
122
|
+
if not schema_path:
|
|
123
|
+
raise ValueError("Validation schema is required.")
|
|
124
|
+
|
|
125
|
+
destination = gear_context.client.get_analysis(
|
|
126
|
+
gear_context.config.destination["id"]
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
pdf_links = build_pdf_links(gear_context, destination) if save_pdf_report else None
|
|
130
|
+
|
|
131
|
+
options = RunOptions(
|
|
132
|
+
save_validation_report=save_validation_report,
|
|
133
|
+
save_pdf_report=save_pdf_report,
|
|
134
|
+
force_rerun=force_rerun,
|
|
135
|
+
pdf_links=pdf_links,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return destination, Path(gear_context.output_dir), Path(schema_path), options
|