girder-jsonforms 2.1.1__tar.gz → 2.1.2__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.
- {girder_jsonforms-2.1.1/girder_jsonforms.egg-info → girder_jsonforms-2.1.2}/PKG-INFO +1 -1
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/jq.py +68 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/models/project.py +117 -7
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/rest/aimdl.py +3 -2
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/web_client/dist/girder-plugin-jsonforms.umd.cjs +3 -3
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2/girder_jsonforms.egg-info}/PKG-INFO +1 -1
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/setup.py +1 -1
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/.github/workflows/release.yml +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/.gitignore +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/LICENSE +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/README.md +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/doc/file-uploads.md +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/doc/preload.md +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/__init__.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/asgi.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/__init__.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/announcement.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/events.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/google_drive.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/igsn_client.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/igsn_vocab.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/locks.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/mail.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/metadata_dates.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/orcid.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/project_helpers.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/project_tasks.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/response_cache.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/lib/timing.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/mail_assets/caimee-shield-80.png +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/mail_templates/_projectsFooter.mako +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/mail_templates/_projectsFooter.txt.mako +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/mail_templates/_projectsHeader.mako +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/mail_templates/projectDecision.mako +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/mail_templates/projectDecision.txt.mako +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/mail_templates/projectSubmitted.mako +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/mail_templates/projectSubmitted.txt.mako +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/models/__init__.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/models/deposition.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/models/entry.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/models/form.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/rest/__init__.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/rest/deposition.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/rest/entry.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/rest/form.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/rest/project.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/schemas/__init__.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/schemas/datacite-v4.5.json +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/scripts/__init__.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/scripts/migrate_metadata_dates.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/scripts/preview_mail.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/settings.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/web_client/dist/style.css +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/worker_plugin/__init__.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/worker_plugin/amdee.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/worker_plugin/folder_ops.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/worker_plugin/handle_enumsource.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/worker_plugin/htmax.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/worker_plugin/igsn_registry.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/worker_plugin/orcid.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/worker_plugin/projects.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/worker_plugin/pull_related_ids.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms/worker_plugin/relate_entry_to_igsn.py +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms.egg-info/SOURCES.txt +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms.egg-info/dependency_links.txt +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms.egg-info/entry_points.txt +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms.egg-info/not-zip-safe +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms.egg-info/requires.txt +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/girder_jsonforms.egg-info/top_level.txt +0 -0
- {girder_jsonforms-2.1.1 → girder_jsonforms-2.1.2}/setup.cfg +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
1
2
|
from typing import Any
|
|
2
3
|
|
|
3
4
|
|
|
@@ -43,6 +44,73 @@ def get_value(json_data: dict, key: str) -> dict:
|
|
|
43
44
|
return json_data
|
|
44
45
|
|
|
45
46
|
|
|
47
|
+
class JqPathError(ValueError):
|
|
48
|
+
"""A path that does not fit the shape of the data it was applied to."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def iter_values(json_data: Any, key: str) -> Iterator[tuple[str, Any]]:
|
|
52
|
+
"""Resolve a path that may contain ``[]`` ("for each element").
|
|
53
|
+
|
|
54
|
+
Yields ``(path, value)`` pairs, where ``path`` is ``key`` with every ``[]``
|
|
55
|
+
replaced by the index it matched, in the same spelling ``find_key_paths``
|
|
56
|
+
emits (``a.[0].b``), so an emitted path can be fed straight back into
|
|
57
|
+
``get_value``. A path with no ``[]`` yields at most one pair, which makes
|
|
58
|
+
this a generalization of ``get_value`` rather than a separate notion.
|
|
59
|
+
|
|
60
|
+
Nested iteration is supported: ``a.[].b.[].c`` iterates the outer array and
|
|
61
|
+
then the inner one for each outer element.
|
|
62
|
+
|
|
63
|
+
Missing data is skipped: an element that lacks the trailing key, or an
|
|
64
|
+
explicit index past the end of a list, yields nothing while its siblings
|
|
65
|
+
still yield, with their original indices intact. A path that disagrees with
|
|
66
|
+
the shape of the data -- ``[]`` or ``[3]`` applied to anything that is not a
|
|
67
|
+
list -- raises ``JqPathError`` instead, because no amount of data fixes it.
|
|
68
|
+
|
|
69
|
+
Zero matches is therefore not an error here, and a typo'd key is
|
|
70
|
+
indistinguishable from an empty array at this layer. Callers that care
|
|
71
|
+
should report how many of the elements they expected actually matched.
|
|
72
|
+
"""
|
|
73
|
+
yield from _iter_values(json_data, key.split("."), "")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _join(prefix: str, segment: str) -> str:
|
|
77
|
+
return f"{prefix}.{segment}" if prefix else segment
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _iter_values(
|
|
81
|
+
node: Any, segments: list[str], prefix: str
|
|
82
|
+
) -> Iterator[tuple[str, Any]]:
|
|
83
|
+
if not segments:
|
|
84
|
+
yield prefix, node
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
head, rest = segments[0], segments[1:]
|
|
88
|
+
|
|
89
|
+
if head == "[]":
|
|
90
|
+
if not isinstance(node, list):
|
|
91
|
+
raise JqPathError(
|
|
92
|
+
f"{prefix or '<root>'} is a {type(node).__name__}, not a list, "
|
|
93
|
+
"so [] cannot iterate it"
|
|
94
|
+
)
|
|
95
|
+
for index, item in enumerate(node):
|
|
96
|
+
yield from _iter_values(item, rest, _join(prefix, f"[{index}]"))
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
if head.startswith("[") and head.endswith("]"):
|
|
100
|
+
if not isinstance(node, list):
|
|
101
|
+
raise JqPathError(
|
|
102
|
+
f"{prefix or '<root>'} is a {type(node).__name__}, not a list, "
|
|
103
|
+
f"so {head} cannot index it"
|
|
104
|
+
)
|
|
105
|
+
index = int(head[1:-1])
|
|
106
|
+
if index < len(node):
|
|
107
|
+
yield from _iter_values(node[index], rest, _join(prefix, head))
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
if isinstance(node, dict) and head in node:
|
|
111
|
+
yield from _iter_values(node[head], rest, _join(prefix, head))
|
|
112
|
+
|
|
113
|
+
|
|
46
114
|
def set_value(json_data: dict, key: str, value: Any) -> None:
|
|
47
115
|
for path in key.split(".")[:-1]:
|
|
48
116
|
if path.startswith("["):
|
|
@@ -63,6 +63,28 @@ project_schema = {
|
|
|
63
63
|
"lastName": {"type": "string"},
|
|
64
64
|
"orcidId": {"type": "string"},
|
|
65
65
|
"email": {"type": "string", "format": "email"},
|
|
66
|
+
# Day-to-day contact. Defaults to the PI in the UI but can be
|
|
67
|
+
# moved to anyone, so it is its own flag rather than derived
|
|
68
|
+
# from `role`.
|
|
69
|
+
"isPointOfContact": {"type": "boolean"},
|
|
70
|
+
# Whether this person is physically coming to AIMD-L; some
|
|
71
|
+
# members only take part remotely.
|
|
72
|
+
"onSite": {"type": "boolean"},
|
|
73
|
+
# Career stage. Orthogonal to `role`, which is the access
|
|
74
|
+
# level (see _role_to_access_level in lib/events.py).
|
|
75
|
+
"status": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"enum": [
|
|
78
|
+
"faculty",
|
|
79
|
+
"staff",
|
|
80
|
+
"postdoc",
|
|
81
|
+
"grad",
|
|
82
|
+
"undergrad",
|
|
83
|
+
"other",
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
# Only set when it differs from the project's organization.
|
|
87
|
+
"institution": {"type": "string"},
|
|
66
88
|
},
|
|
67
89
|
"required": ["email", "role"],
|
|
68
90
|
"additionalProperties": False,
|
|
@@ -93,8 +115,93 @@ project_schema = {
|
|
|
93
115
|
"enum": ["integrated", "singleInstrument", "development"],
|
|
94
116
|
"default": "integrated",
|
|
95
117
|
},
|
|
96
|
-
|
|
97
|
-
|
|
118
|
+
# Affiliation of the applicant. Unranked: the numbering of the `priority`
|
|
119
|
+
# list it replaces implied an ordering the lab never allocated on.
|
|
120
|
+
"accessCategory": {
|
|
121
|
+
"type": "string",
|
|
122
|
+
"enum": [
|
|
123
|
+
"jhu",
|
|
124
|
+
"external-academic",
|
|
125
|
+
"external-corporate",
|
|
126
|
+
"external-government",
|
|
127
|
+
"external-foreign",
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
# Home institution/company. Asked of external applicants only.
|
|
131
|
+
"organization": {"type": "string"},
|
|
132
|
+
# How the data this project generates has to be handled -- it describes
|
|
133
|
+
# the material coming into the lab, not the proposal documents. Advisory:
|
|
134
|
+
# access still defaults to the project's own members (lib/events.py).
|
|
135
|
+
"dataClassification": {
|
|
136
|
+
"type": "string",
|
|
137
|
+
"enum": [
|
|
138
|
+
"open",
|
|
139
|
+
"confidential-proprietary",
|
|
140
|
+
"confidential-controlled",
|
|
141
|
+
"opt-out",
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
"assistanceRequired": {"type": "boolean"},
|
|
145
|
+
# Free text on purpose: "3 days", "2 half-days", "~1 week".
|
|
146
|
+
"daysRequested": {"type": "string"},
|
|
147
|
+
# The single-instrument proposal, written inline instead of uploaded.
|
|
148
|
+
"experimentPlan": {"type": "string"},
|
|
149
|
+
"safety": {
|
|
150
|
+
"type": "object",
|
|
151
|
+
"properties": {
|
|
152
|
+
"sampleHazards": {
|
|
153
|
+
"type": "array",
|
|
154
|
+
"items": {
|
|
155
|
+
"type": "string",
|
|
156
|
+
"enum": [
|
|
157
|
+
"none",
|
|
158
|
+
"toxic",
|
|
159
|
+
"flammable",
|
|
160
|
+
"energetic",
|
|
161
|
+
"biosafety",
|
|
162
|
+
"radioactive",
|
|
163
|
+
"other",
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
"default": [],
|
|
167
|
+
},
|
|
168
|
+
"otherHazards": {
|
|
169
|
+
"type": "array",
|
|
170
|
+
"items": {
|
|
171
|
+
"type": "string",
|
|
172
|
+
"enum": [
|
|
173
|
+
"none",
|
|
174
|
+
"laser",
|
|
175
|
+
"high-temperature",
|
|
176
|
+
"high-voltage",
|
|
177
|
+
"user-equipment",
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
"default": [],
|
|
181
|
+
},
|
|
182
|
+
"description": {"type": "string"},
|
|
183
|
+
},
|
|
184
|
+
"additionalProperties": False,
|
|
185
|
+
},
|
|
186
|
+
"funding": {
|
|
187
|
+
"type": "object",
|
|
188
|
+
"properties": {
|
|
189
|
+
"grants": {
|
|
190
|
+
"type": "array",
|
|
191
|
+
"items": {
|
|
192
|
+
"type": "object",
|
|
193
|
+
"properties": {
|
|
194
|
+
"agency": {"type": "string"},
|
|
195
|
+
"grantNumber": {"type": "string"},
|
|
196
|
+
},
|
|
197
|
+
"additionalProperties": False,
|
|
198
|
+
},
|
|
199
|
+
"default": [],
|
|
200
|
+
},
|
|
201
|
+
# JHU internal budget/IO number.
|
|
202
|
+
"internalBudgetNumber": {"type": "string"},
|
|
203
|
+
},
|
|
204
|
+
"additionalProperties": False,
|
|
98
205
|
},
|
|
99
206
|
"status": {
|
|
100
207
|
"type": "string",
|
|
@@ -181,20 +288,27 @@ class Project(AccessControlledModel):
|
|
|
181
288
|
level=AccessType.READ,
|
|
182
289
|
fields=(
|
|
183
290
|
"_id",
|
|
291
|
+
"accessCategory",
|
|
292
|
+
"assistanceRequired",
|
|
184
293
|
"created",
|
|
185
294
|
"creatorId",
|
|
295
|
+
"dataClassification",
|
|
296
|
+
"daysRequested",
|
|
186
297
|
"description",
|
|
298
|
+
"experimentPlan",
|
|
187
299
|
"files",
|
|
300
|
+
"funding",
|
|
188
301
|
"instruments",
|
|
189
302
|
"name",
|
|
190
303
|
"metadata",
|
|
191
304
|
"members",
|
|
192
305
|
"orcidResourceUrl",
|
|
193
|
-
"
|
|
306
|
+
"organization",
|
|
194
307
|
"projectId",
|
|
195
308
|
"projectType",
|
|
196
309
|
"public",
|
|
197
310
|
"publicFlags",
|
|
311
|
+
"safety",
|
|
198
312
|
"samples",
|
|
199
313
|
"submissionFolderId",
|
|
200
314
|
"status",
|
|
@@ -231,10 +345,6 @@ class Project(AccessControlledModel):
|
|
|
231
345
|
doc["instruments"] = []
|
|
232
346
|
if "projectType" not in doc:
|
|
233
347
|
doc["projectType"] = "integrated"
|
|
234
|
-
try:
|
|
235
|
-
doc["priority"] = int(doc.get("priority", 0))
|
|
236
|
-
except (ValueError, TypeError):
|
|
237
|
-
raise ValidationException("Priority must be an integer")
|
|
238
348
|
try:
|
|
239
349
|
self.validator(project_schema).validate(doc)
|
|
240
350
|
except jsonschema.ValidationError as ve:
|
|
@@ -544,7 +544,7 @@ class BaseLabResource(Resource):
|
|
|
544
544
|
@filtermodel(model=Item)
|
|
545
545
|
@autoDescribeRoute(
|
|
546
546
|
Description("Get a list of items with a specific data type.")
|
|
547
|
-
.param("dataType", "The data type to filter items by.", required=
|
|
547
|
+
.param("dataType", "The data type to filter items by.", required=False)
|
|
548
548
|
.param(
|
|
549
549
|
"baseParentId",
|
|
550
550
|
"The ID of the parent collection to count items in.",
|
|
@@ -596,8 +596,9 @@ class BaseLabResource(Resource):
|
|
|
596
596
|
base_parent = self._get_base_parent(baseParentType, baseParentId, user)
|
|
597
597
|
q = {
|
|
598
598
|
"meta.igsn": {"$exists": True},
|
|
599
|
-
"meta.data_type": dataType,
|
|
600
599
|
}
|
|
600
|
+
if dataType:
|
|
601
|
+
q["meta.data_type"] = dataType
|
|
601
602
|
filters = filters or {}
|
|
602
603
|
try:
|
|
603
604
|
filters = sanitize_query(filters)
|