-
Notifications
You must be signed in to change notification settings - Fork 124
/
Copy pathvalidation.py
175 lines (142 loc) · 6.36 KB
/
validation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
"""Functionality related to validation of BIDSLayouts and BIDS projects."""
from pathlib import Path
import json
import re
import warnings
from ..utils import listify
from ..exceptions import BIDSValidationError, BIDSDerivativesValidationError
MANDATORY_BIDS_FIELDS = {
"Name": {"Name": "Example dataset"},
"BIDSVersion": {"BIDSVersion": "1.0.2"},
}
MANDATORY_DERIVATIVES_FIELDS = {
**MANDATORY_BIDS_FIELDS,
"GeneratedBy": {
"GeneratedBy": [{"Name": "Example pipeline"}]
},
}
EXAMPLE_BIDS_DESCRIPTION = {
k: val[k] for val in MANDATORY_BIDS_FIELDS.values() for k in val}
EXAMPLE_DERIVATIVES_DESCRIPTION = {
k: val[k] for val in MANDATORY_DERIVATIVES_FIELDS.values() for k in val}
DEFAULT_LOCATIONS_TO_IGNORE = {
re.compile(r"^/(code|models|sourcedata|stimuli)"),
re.compile(r'/\.'),
}
def absolute_path_deprecation_warning():
warnings.warn("The absolute_paths argument will be removed from PyBIDS "
"in 0.14. You can easily access the relative path of "
"BIDSFile objects via the .relpath attribute (instead of "
".path). Switching to this pattern is strongly encouraged, "
"as the current implementation of relative path handling "
"is known to produce query failures in certain edge cases.")
def indexer_arg_deprecation_warning():
warnings.warn("The ability to pass arguments to BIDSLayout that control "
"indexing is likely to be removed in future; possibly as "
"early as PyBIDS 0.14. This includes the `config_filename`, "
"`ignore`, `force_index`, and `index_metadata` arguments. "
"The recommended usage pattern is to initialize a new "
"BIDSLayoutIndexer with these arguments, and pass it to "
"the BIDSLayout via the `indexer` argument.")
def validate_root(root, validate):
# Validate root argument and make sure it contains mandatory info
try:
root = Path(root)
except TypeError:
raise TypeError("root argument must be a pathlib.Path (or a type that "
"supports casting to pathlib.Path, such as "
"string) specifying the directory "
"containing the BIDS dataset.")
root = root.absolute()
if not root.exists():
raise ValueError("BIDS root does not exist: %s" % root)
target = root / 'dataset_description.json'
if not target.exists():
if validate:
raise BIDSValidationError(
"'dataset_description.json' is missing from project root."
" Every valid BIDS dataset must have this file."
"\nExample contents of 'dataset_description.json': \n%s" %
json.dumps(EXAMPLE_BIDS_DESCRIPTION)
)
else:
description = None
else:
err = None
try:
with open(target, 'r', encoding='utf-8') as desc_fd:
description = json.load(desc_fd)
except (UnicodeDecodeError, json.JSONDecodeError) as e:
description = None
err = e
if validate:
if description is None:
raise BIDSValidationError(
"'dataset_description.json' is not a valid json file."
" There is likely a typo in your 'dataset_description.json'."
"\nExample contents of 'dataset_description.json': \n%s" %
json.dumps(EXAMPLE_BIDS_DESCRIPTION)
) from err
for k in MANDATORY_BIDS_FIELDS:
if k not in description:
raise BIDSValidationError(
"Mandatory %r field missing from "
"'dataset_description.json'."
"\nExample: %s" % (k, MANDATORY_BIDS_FIELDS[k])
)
return root, description
def validate_derivative_path(path, **kwargs):
# Collect all paths that contain a dataset_description.json
dd = Path(path) / 'dataset_description.json'
description = json.loads(dd.read_text(encoding='utf-8'))
pipeline_names = [pipeline["Name"]
for pipeline in description.get("GeneratedBy", [])
if "Name" in pipeline]
if pipeline_names:
pipeline_name = pipeline_names[0]
elif "PipelineDescription" in description:
warnings.warn("The PipelineDescription field was superseded "
"by GeneratedBy in BIDS 1.4.0. You can use "
"``pybids upgrade`` to update your derivative "
"dataset.")
pipeline_name = description["PipelineDescription"].get("Name")
else:
pipeline_name = None
if pipeline_name is None:
raise BIDSDerivativesValidationError(
"Every valid BIDS-derivatives dataset must "
"have a GeneratedBy.Name field set "
"inside 'dataset_description.json'. "
f"\nExample: {MANDATORY_DERIVATIVES_FIELDS['GeneratedBy']}"
)
return pipeline_name
def _sort_patterns(patterns, root):
"""Return sorted patterns, from more specific to more general."""
regexes = [patt for patt in patterns if hasattr(patt, "search")]
paths = [
str((root / patt).absolute())
for patt in listify(patterns)
if not hasattr(patt, "search")
]
# Sort patterns from general to specific
paths.sort(key=len)
# Combine and return (note path patterns are reversed, specific first)
return [Path(p) for p in reversed(paths)] + regexes
def validate_indexing_args(ignore, force_index, root):
if ignore is None:
ignore = list(
DEFAULT_LOCATIONS_TO_IGNORE - set(force_index or [])
)
# root has already been validated to be a directory
ignore = _sort_patterns(ignore, root)
force_index = _sort_patterns(force_index or [], root)
# Derivatives get special handling; they shouldn't be indexed normally
for entry in force_index:
condi = (isinstance(entry, str) and
str(entry.resolve()).startswith('derivatives'))
if condi:
msg = ("Do not pass 'derivatives' in the force_index "
"list. To index derivatives, either set "
"derivatives=True, or use add_derivatives().")
raise ValueError(msg)
return ignore, force_index