Skip to content

Commit 5182fa1

Browse files
committed
Initial classification commit
1 parent 696ad10 commit 5182fa1

File tree

5 files changed

+189
-4
lines changed

5 files changed

+189
-4
lines changed

api/api.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .handlers.confighandler import Config, Version
88
from .handlers.containerhandler import ContainerHandler
99
from .handlers.devicehandler import DeviceHandler
10+
from .handlers.modalityhandler import ModalityHandler
1011
from .handlers.grouphandler import GroupHandler
1112
from .handlers.listhandler import AnalysesHandler, ListHandler, FileListHandler, NotesListHandler, PermissionsListHandler, TagsListHandler
1213
from .handlers.reporthandler import ReportHandler
@@ -167,6 +168,14 @@ def prefix(path, routes):
167168
route('/<device_id:[^/]+>', DeviceHandler, m=['GET']),
168169
]),
169170

171+
# Modalities
172+
173+
route( '/modalities', ModalityHandler, h='get_all', m=['GET']),
174+
route( '/modalities', ModalityHandler, m=['POST']),
175+
prefix('/modalities', [
176+
route('/<modality_name:[^/]+>', ModalityHandler, m=['GET', 'PUT', 'DELETE']),
177+
]),
178+
170179

171180
# Groups
172181

@@ -244,7 +253,7 @@ def prefix(path, routes):
244253
route('/packfile', FileListHandler, h='packfile', m=['POST']),
245254
route('/packfile-end', FileListHandler, h='packfile_end'),
246255
route('/<list_name:files>', FileListHandler, m=['POST']),
247-
route('/<list_name:files>/<name:{fname}>', FileListHandler, m=['GET', 'DELETE']),
256+
route('/<list_name:files>/<name:{fname}>', FileListHandler, m=['GET', 'DELETE', 'PUT']),
248257
route('/<list_name:files>/<name:{fname}>/info', FileListHandler, h='get_info', m=['GET']),
249258

250259

api/auth/listauth.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def default_sublist(handler, container):
2020
access = _get_access(handler.uid, handler.user_site, container)
2121
def g(exec_op):
2222
def f(method, _id, query_params=None, payload=None, exclude_params=None):
23+
log.debug('Im actually in here')
2324
if method == 'GET' and container.get('public', False):
2425
min_access = -1
2526
elif method == 'GET':

api/dao/liststorage.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def get_container(self, _id, query_params=None):
4646
log.debug('query {}'.format(query))
4747
return self.dbc.find_one(query, projection)
4848

49-
def exec_op(self, action, _id=None, query_params=None, payload=None, exclude_params=None):
49+
def exec_op(self, action, _id=None, query_params=None, payload=None, exclude_params=None, replace_classification=False):
5050
"""
5151
Generic method to exec an operation.
5252
The request is dispatched to the corresponding private methods.
@@ -63,7 +63,7 @@ def exec_op(self, action, _id=None, query_params=None, payload=None, exclude_par
6363
if action == 'DELETE':
6464
return self._delete_el(_id, query_params)
6565
if action == 'PUT':
66-
return self._update_el(_id, query_params, payload, exclude_params)
66+
return self._update_el(_id, query_params, payload, exclude_params, replace_classification=replace_classification)
6767
if action == 'POST':
6868
return self._create_el(_id, payload, exclude_params)
6969
raise ValueError('action should be one of GET, POST, PUT, DELETE')
@@ -342,3 +342,31 @@ def inflate_job_info(analysis):
342342

343343
analysis['job'] = job
344344
return analysis
345+
346+
347+
class FileStorage(ListStorage):
348+
349+
def _update_el(self, _id, query_params, payload, exclude_params, replace_classification=False):
350+
mod_elem = {}
351+
update = {}
352+
353+
# If we want to add to the classification lists rather than replace
354+
# the entirity of the classification map, use $addToSet.
355+
# This allows some endpoints to only make additive changes
356+
if not replace_classification:
357+
classification = payload.get('classification')
358+
if classification:
359+
add_to_set = {}
360+
for k,array in classification.items():
361+
add_to_set[self.list_name + '.$.classification.' + k] = array
362+
update['$addToSet'] = add_to_set
363+
364+
for k,v in payload.items():
365+
mod_elem[self.list_name + '.$.' + k] = v
366+
query = {
367+
'_id': _id,
368+
self.list_name: {'$elemMatch': query_params}
369+
}
370+
update['$set'] = mod_elem
371+
372+
return self.dbc.update_one(query, update)

api/handlers/listhandler.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from ..dao import liststorage
1919
from ..dao import APIStorageException
2020
from ..dao import hierarchy
21+
from .modalityhandler import ModalityHandler
2122
from ..web.request import log_access, AccessType
2223

2324

@@ -38,7 +39,7 @@ def initialize_list_configurations():
3839
'input_schema_file': 'tag.json'
3940
},
4041
'files': {
41-
'storage': liststorage.ListStorage,
42+
'storage': liststorage.FileStorage,
4243
'permchecker': listauth.default_sublist,
4344
'use_object_id': True,
4445
'storage_schema_file': 'file.json',
@@ -484,6 +485,37 @@ def post(self, cont_name, list_name, **kwargs):
484485

485486
return upload.process_upload(self.request, upload.Strategy.targeted, container_type=cont_name, id_=_id, origin=self.origin)
486487

488+
def put(self, cont_name, list_name, **kwargs):
489+
_id = kwargs.pop('cid')
490+
permchecker, storage, mongo_validator, payload_validator, keycheck = self._initialize_request(cont_name, list_name, _id, query_params=kwargs)
491+
492+
payload = self.request.json_body
493+
payload_validator(payload, 'PUT')
494+
if not set(payload.keys()).issubset({'info', 'modality', 'classification'}):
495+
self.abort(400, 'Can only update info, modality an classification keys on file.')
496+
497+
classification = payload.get('classification')
498+
if classification:
499+
modality = payload.get('modality')
500+
if not modality:
501+
file_obj = storage.exec_op('GET', _id, query_params=kwargs)
502+
modality = file_obj.get('modality')
503+
504+
if not ModalityHandler.check_classification(modality, classification):
505+
self.abort(400, 'Classification does not match allowable values for modality {}.'.format(modality))
506+
507+
rc = self.is_true('replace_classification')
508+
509+
try:
510+
result = keycheck(mongo_validator(permchecker(storage.exec_op)))('PUT', _id=_id, query_params=kwargs, payload=payload)
511+
except APIStorageException as e:
512+
self.abort(400, e.message)
513+
# abort if the query of the update wasn't able to find any matching documents
514+
if result.matched_count == 0:
515+
self.abort(404, 'Element not updated in list {} of container {} {}'.format(storage.list_name, storage.cont_name, _id))
516+
else:
517+
return {'modified':result.modified_count}
518+
487519
def delete(self, cont_name, list_name, **kwargs):
488520
# Overriding base class delete to audit action before completion
489521
_id = kwargs.pop('cid')

api/handlers/modalityhandler.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import datetime as dt
2+
3+
from ..web import base
4+
from .. import config
5+
from .. import util
6+
from ..auth import require_drone, require_login, require_superuser
7+
from ..dao import containerstorage, APINotFoundException
8+
from ..validators import validate_data
9+
10+
log = config.log
11+
12+
13+
class ModalityHandler(base.RequestHandler):
14+
15+
def __init__(self, request=None, response=None):
16+
super(ModalityHandler, self).__init__(request, response)
17+
self.storage = containerstorage.ContainerStorage('modalities', use_object_id=False)
18+
19+
@require_login
20+
def get(self, modality_name):
21+
return self.storage.get_container(modality_name)
22+
23+
@require_login
24+
def get_all(self):
25+
return self.storage.get_all_el(None, None, None)
26+
27+
@require_superuser
28+
def post(self):
29+
payload = self.request.json_body
30+
# Clean this up when validate_data method is fixed to use new schemas
31+
# POST unnecessary, used to avoid run-time modification of schema
32+
#validate_data(payload, 'modality.json', 'input', 'POST', optional=True)
33+
34+
result = self.storage.create_el(payload)
35+
if result.acknowledged:
36+
return {'_id': result.inserted_id}
37+
else:
38+
self.abort(400, 'Modality not inserted')
39+
40+
@require_superuser
41+
def put(self, modality_name):
42+
payload = self.request.json_body
43+
# Clean this up when validate_data method is fixed to use new schemas
44+
# POST unnecessary, used to avoid run-time modification of schema
45+
#validate_data(payload, 'modality.json', 'input', 'POST', optional=True)
46+
47+
result = self.storage.update_el(modality_name, payload)
48+
if result.matched_count == 1:
49+
return {'modified': result.modified_count}
50+
else:
51+
raise APINotFoundException('Modality with name {} not found, modality not updated'.format(modality_name))
52+
53+
@require_superuser
54+
def delete(self, modality_name):
55+
result = self.storage.delete_el(modality_name)
56+
if result.deleted_count == 1:
57+
return {'deleted': result.deleted_count}
58+
else:
59+
raise APINotFoundException('Modality with name {} not found, modality not deleted'.format(modality_name))
60+
61+
@staticmethod
62+
def check_classification(modality_name, classification_map):
63+
"""
64+
Given a modality name and a proposed classification map,
65+
ensure:
66+
- that a modality exists with that name and has a classification
67+
map
68+
- all keys in the classification_map exist in the
69+
`classifications` map on the modality object
70+
- all the values in the arrays in the classification_map
71+
exist in the modality's classifications map
72+
73+
For example:
74+
Modality = {
75+
"_id" = "Example_modality",
76+
"classifications": {
77+
"Example1": ["Blue", "Green"]
78+
"Example2": ["one", "two"]
79+
}
80+
}
81+
82+
Returns True:
83+
classification_map = {
84+
"Example1": ["Blue"],
85+
"custom": ["anything"]
86+
}
87+
88+
Returns False:
89+
classification_map = {
90+
"Example1": ["Red"], # "Red" is not allowed
91+
"Example2": ["one", "two"]
92+
}
93+
"""
94+
try:
95+
modality = containerstorage.ContainerStorage('modalities', use_object_id=False).get_container(modality_name)
96+
except APINotFoundException:
97+
if classification_map.keys() == ['custom']:
98+
# for unknown modalities allow only list of custom values
99+
return True
100+
else:
101+
return False
102+
103+
classifications = modality.get('classifications')
104+
if not classifications:
105+
return False
106+
107+
for k,array in classification_map.iteritems():
108+
if k == 'custom':
109+
# any unique value is allowed in custom list
110+
continue
111+
possible_values = classifications.get(k, [])
112+
if not set(array).issubset(set(possible_values)):
113+
return False
114+
115+
return True

0 commit comments

Comments
 (0)