Skip to content

Commit 199e348

Browse files
authored
ensure there are notes for all analyses (#1161)
1 parent dbe1c0d commit 199e348

File tree

3 files changed

+74
-1
lines changed

3 files changed

+74
-1
lines changed

store/backend/neurostore/resources/base.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Base Classes/functions for constructing views
33
"""
44

5+
import json
56
import re
67

78
from connexion.context import context
@@ -11,6 +12,7 @@
1112
abort_permission,
1213
abort_validation,
1314
abort_not_found,
15+
abort_unprocessable,
1416
)
1517

1618
from psycopg2 import errors
@@ -45,6 +47,12 @@
4547
from . import data as viewdata
4648

4749

50+
@parser.error_handler
51+
def handle_parser_error(err, req, schema, *, error_status_code, error_headers):
52+
detail = json.dumps(err.messages)
53+
abort_unprocessable(f"input does not conform to specification: {detail}")
54+
55+
4856
def create_user():
4957
from auth0.v3.authentication.users import Users
5058

@@ -128,10 +136,13 @@ def update_annotations(self, annotations):
128136
create_annotation_analyses = []
129137
for result in results:
130138
if result.analysis_id and not result.annotation_analysis_id:
139+
note_payload = result.note or self._build_default_note(result.note_keys)
140+
if note_payload is None:
141+
note_payload = {}
131142
params = {
132143
"analysis_id": result.analysis_id,
133144
"annotation_id": result.id,
134-
"note": result.note or {},
145+
"note": note_payload,
135146
"user_id": result.user_id,
136147
"study_id": result.study_id,
137148
"studyset_id": result.studyset_id,
@@ -215,6 +226,12 @@ def update_base_studies(self, base_studies):
215226
def eager_load(self, q, args):
216227
return q
217228

229+
@staticmethod
230+
def _build_default_note(note_keys):
231+
if not note_keys:
232+
return None
233+
return {key: None for key in note_keys.keys()}
234+
218235
def db_validation(self, record, data):
219236
"""
220237
Custom validation for database constraints.

store/backend/neurostore/schemas/data.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
pre_dump,
77
pre_load,
88
post_load,
9+
validates_schema,
910
EXCLUDE,
1011
ValidationError,
1112
)
@@ -662,6 +663,19 @@ def add_id(self, data, **kwargs):
662663
data["studyset"] = {"id": data.pop("studyset_id")}
663664
return data
664665

666+
@validates_schema
667+
def validate_notes(self, data, **kwargs):
668+
notes = data.get("annotation_analyses") or []
669+
invalid = {}
670+
671+
for idx, note in enumerate(notes):
672+
note_payload = note.get("note") if isinstance(note, dict) else None
673+
if not isinstance(note_payload, dict) or len(note_payload) == 0:
674+
invalid[idx] = ["note must include at least one field"]
675+
676+
if invalid:
677+
raise ValidationError({"notes": invalid})
678+
665679

666680
class BaseSnapshot(object):
667681
def __init__(self):

store/backend/neurostore/tests/api/test_annotations.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,48 @@ def test_post_blank_annotation(auth_client, ingest_neurosynth, session):
1818
assert annot.annotation_analyses[0].user_id == annot.user_id
1919

2020

21+
def test_blank_annotation_populates_note_fields(auth_client, ingest_neurosynth, session):
22+
dset = Studyset.query.first()
23+
note_keys = {"included": "boolean", "quality": "string"}
24+
payload = {
25+
"studyset": dset.id,
26+
"note_keys": note_keys,
27+
"name": "with defaults",
28+
}
29+
30+
resp = auth_client.post("/api/annotations/", data=payload)
31+
assert resp.status_code == 200
32+
33+
for note in resp.json()["notes"]:
34+
assert set(note["note"].keys()) == set(note_keys.keys())
35+
assert all(value is None for value in note["note"].values())
36+
37+
38+
def test_annotation_rejects_empty_note(auth_client, ingest_neurosynth, session):
39+
dset = Studyset.query.first()
40+
study = dset.studies[0]
41+
analysis = study.analyses[0]
42+
43+
payload = {
44+
"studyset": dset.id,
45+
"notes": [
46+
{
47+
"study": study.id,
48+
"analysis": analysis.id,
49+
"note": {},
50+
}
51+
],
52+
"note_keys": {"included": "boolean"},
53+
"name": "invalid annotation",
54+
}
55+
56+
resp = auth_client.post("/api/annotations/", data=payload)
57+
58+
assert resp.status_code == 422
59+
error = resp.json()
60+
assert "note must include at least one field" in error["detail"]
61+
62+
2163
def test_post_annotation(auth_client, ingest_neurosynth, session):
2264
dset = Studyset.query.first()
2365
# y for x in non_flat for y in x

0 commit comments

Comments
 (0)