Skip to content
Snippets Groups Projects
Commit d6950114 authored by Gunnar Schaefer's avatar Gunnar Schaefer
Browse files

preliminary schema and some cleanup

parent c518a637
No related branches found
No related tags found
No related merge requests found
# @author: Gunnar Schaefer
import nimsapi
app = nimsapi.app
......@@ -9,9 +9,43 @@ import nimsapiutil
class Epochs(nimsapiutil.NIMSRequestHandler):
json_schema = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'Epoch List',
'type': 'array',
'items': {
'title': 'Epoch',
'type': 'object',
'properties': {
'_id': {
'title': 'Database ID',
},
'timestamp': {
'title': 'Timestamp',
},
'datatype': {
'title': 'Datatype',
'type': 'string',
},
'series': {
'title': 'Series',
'type': 'integer',
},
'acquisition': {
'title': 'Acquisition',
'type': 'integer',
},
'description': {
'title': 'Description',
'type': 'string',
},
}
}
}
def count(self, iid):
"""Return the number of Epochs."""
self.response.write('epochs count\n')
self.response.write(json.dumps(self.app.db.epochs.count()))
def post(self, iid):
"""Create a new Epoch."""
......@@ -39,6 +73,142 @@ class Epochs(nimsapiutil.NIMSRequestHandler):
class Epoch(nimsapiutil.NIMSRequestHandler):
json_schema = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'Epoch',
'type': 'object',
'properties': {
'_id': {
'title': 'Database ID',
},
'session': {
'title': 'Session ID',
},
'timestamp': {
'title': 'Timestamp',
},
'session_uid': {
'title': 'DICOM UID',
'type': 'string',
},
'datatype': {
'title': 'Datatype',
'type': 'string',
},
'series': {
'title': 'Series',
'type': 'integer',
},
'acquisition': {
'title': 'Acquisition',
'type': 'integer',
},
'description': {
'title': 'Description',
'type': 'string',
'maxLength': 64,
},
'protocol': {
'title': 'Protocol',
'type': 'string',
'maxLength': 64,
},
'rx_coil': {
'title': 'Coil',
'type': 'string',
'maxLength': 64,
},
'device': {
'title': 'Device',
'type': 'string',
'maxLength': 64,
},
'size': {
'title': 'Size',
'type': 'array',
'items': {
'type': 'integer',
}
},
'acquisition_matrix': {
'title': 'Acquisition Matrix',
'type': 'array',
'items': {
'type': 'number',
}
},
'fov': {
'title': 'Field of View',
'type': 'array',
'items': {
'type': 'number',
}
},
'mm_per_voxel': {
'title': 'mm per Voxel',
'type': 'array',
'items': {
'type': 'number',
}
},
'flip_angle': {
'title': 'Flip Angle',
'type': 'integer',
},
'num_averages': {
'title': 'Averages',
'type': 'integer',
},
'num_bands': {
'title': 'Bands',
'type': 'integer',
},
'num_echos': {
'title': 'Echos',
'type': 'integer',
},
'num_slices': {
'title': 'Slices',
'type': 'integer',
},
'num_timepoints': {
'title': 'Time Points',
'type': 'integer',
},
'pixel_bandwidth': {
'title': 'Pixel Bandwidth',
'type': 'number',
},
'prescribed_duration': {
'title': 'Prescribed Duration',
'type': 'number',
},
'duration': {
'title': 'Duration',
'type': 'number',
},
'slice_encode_undersample': {
'title': 'Slice Encode Undersample',
'type': 'integer',
},
'te': {
'title': 'Te',
'type': 'number',
},
'tr': {
'title': 'Tr',
'type': 'number',
},
'files': {
'title': 'Files',
'type': 'array',
'items': nimsapiutil.NIMSRequestHandler.file_schema,
'uniqueItems': True,
},
},
'required': ['_id'],
}
def get(self, iid, eid):
"""Return one Epoch, conditionally with details."""
epoch = self.app.db.epochs.find_one({'_id': bson.objectid.ObjectId(eid)})
......
......@@ -9,9 +9,35 @@ import nimsapiutil
class Experiments(nimsapiutil.NIMSRequestHandler):
json_schema = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'Experiment List',
'type': 'array',
'items': {
'title': 'Experiment',
'type': 'object',
'properties': {
'_id': {
'title': 'Database ID',
},
'firstname': {
'title': 'First Name',
'type': 'string',
},
'lastname': {
'title': 'Last Name',
'type': 'string',
},
'email_hash': {
'type': 'string',
},
}
}
}
def count(self, iid):
"""Return the number of Experiments."""
self.response.write('%d experiments\n' % self.app.db.experiments.count())
self.response.write(json.dumps(self.app.db.experiments.count()))
def post(self, iid):
"""Create a new Experiment."""
......@@ -38,6 +64,41 @@ class Experiments(nimsapiutil.NIMSRequestHandler):
class Experiment(nimsapiutil.NIMSRequestHandler):
json_schema = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'Experiment',
'type': 'object',
'properties': {
'_id': {
'title': 'Database ID',
},
'timestamp': {
'title': 'Timestamp',
},
'group': {
'title': 'Group',
'type': 'string',
},
'name': {
'title': 'Name',
'type': 'string',
'maxLength': 32,
},
'permissions': {
'title': 'Permissions',
'type': 'object',
'minProperties': 1,
},
'files': {
'title': 'Files',
'type': 'array',
'items': nimsapiutil.NIMSRequestHandler.file_schema,
'uniqueItems': True,
},
},
'required': ['_id', 'group', 'name'],
}
def get(self, iid, xid):
"""Return one Experiment, conditionally with details."""
experiment = self.app.db.experiments.find_one({'_id': bson.objectid.ObjectId(xid)})
......
......@@ -34,8 +34,44 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
def get(self):
"""Return API documentation"""
resources = [
('/experiments', 'list of experiments'),
('/collections', 'list of collections'),
('/experiments/<xid>/sessions', 'list of sessions for one experiment'),
('/collections/<xid>/sessions', 'list of sessions for one collection'),
('/sessions/<sid>/epochs', 'list of epochs for one session'),
('', ''),
('/experiments/<xid>', 'details for one experiment'),
('/collections/<xid>', 'details for one collection'),
('/sessions/<sid>', 'details for one session'),
('/epochs/<eid>', 'details for one epoch'),
('', ''),
('/users', 'list of users'),
('/groups', 'list of groups'),
('/users/<uid>', 'details for one user'),
('/groups/<gid>', 'details for one group'),
]
self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
self.response.write('nimsapi - {0}\n'.format(self.app.config['site_id']))
self.response.write('<html>\n')
self.response.write('<head>\n')
self.response.write('<title>NIMSAPI</title>\n')
self.response.write('<style type="text/css">\n')
self.response.write('.tftable {font-size:12px;color:#333333;width:100%;border-width: 1px;border-color: #a9a9a9;border-collapse: collapse;}\n')
self.response.write('.tftable th {font-size:12px;background-color:#b8b8b8;border-width: 1px;padding: 8px;border-style: solid;border-color: #a9a9a9;text-align:left;}\n')
self.response.write('.tftable tr {background-color:#cdcdcd;}\n')
self.response.write('.tftable td {font-size:12px;border-width: 1px;padding: 8px;border-style: solid;border-color: #a9a9a9;}\n')
self.response.write('</style>\n')
self.response.write('</head>\n')
self.response.write('<body>\n')
self.response.write('<p>nimsapi - ' + self.app.config['site_id'] + '</p>\n')
self.response.write('<table class="tftable" border="1">\n')
self.response.write('<tr><th>Resource</th><th>Description</th></tr>\n')
for r, d in resources:
r = r.replace('<', '&#60;').replace('>', '&#62;')
self.response.write('<tr><td>%s</td><td>%s</td></tr>\n' % (r, d))
self.response.write('</table>\n')
self.response.write('</body>\n')
self.response.write('</html>\n')
def upload(self):
# TODO add security: either authenticated user or machine-to-machine CRAM
......@@ -46,7 +82,7 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
with nimsutil.TempDir(prefix='.tmp', dir=stage_path) as tempdir_path:
hash_ = hashlib.md5()
upload_filepath = os.path.join(tempdir_path, filename)
log.info(os.path.basename(upload_filepath))
log.info('receiving upload ' + os.path.basename(upload_filepath))
with open(upload_filepath, 'wb') as upload_file:
for chunk in iter(lambda: self.request.body_file.read(2**20), ''):
hash_.update(chunk)
......@@ -55,7 +91,7 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
self.abort(400, 'Content-MD5 mismatch.')
if not tarfile.is_tarfile(upload_filepath) and not zipfile.is_zipfile(upload_filepath):
self.abort(415)
os.rename(upload_filepath, os.path.join(stage_path, str(uuid.uuid1()) + '_' + fid)) # add UUID to prevent clobbering files
os.rename(upload_filepath, os.path.join(stage_path, str(uuid.uuid1()) + '_' + filename)) # add UUID to prevent clobbering files
def download(self):
paths = []
......@@ -72,6 +108,42 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
class Users(nimsapiutil.NIMSRequestHandler):
json_schema = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'User List',
'type': 'array',
'items': {
'title': 'User',
'type': 'object',
'properties': {
'_id': {
'title': 'Database ID',
'type': 'string',
},
'firstname': {
'title': 'First Name',
'type': 'string',
'default': '',
},
'lastname': {
'title': 'Last Name',
'type': 'string',
'default': '',
},
'email': {
'title': 'Email',
'type': 'string',
'format': 'email',
'default': '',
},
'email_hash': {
'type': 'string',
'default': '',
},
}
}
}
def count(self, iid):
"""Return the number of Users."""
self.response.write('%d users\n' % self.app.db.users.count())
......@@ -93,6 +165,43 @@ class Users(nimsapiutil.NIMSRequestHandler):
class User(nimsapiutil.NIMSRequestHandler):
json_schema = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'User',
'type': 'object',
'properties': {
'_id': {
'title': 'Database ID',
'type': 'string',
},
'firstname': {
'title': 'First Name',
'type': 'string',
'default': '',
},
'lastname': {
'title': 'Last Name',
'type': 'string',
'default': '',
},
'email': {
'title': 'Email',
'type': 'string',
'format': 'email',
'default': '',
},
'email_hash': {
'type': 'string',
'default': '',
},
'superuser': {
'title': 'Superuser',
'type': 'boolean',
},
},
'required': ['_id'],
}
def get(self, iid, uid):
"""Return User details."""
user = self.app.db.users.find_one({'_id': uid})
......@@ -127,6 +236,22 @@ class User(nimsapiutil.NIMSRequestHandler):
class Groups(nimsapiutil.NIMSRequestHandler):
json_schema = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'Group List',
'type': 'array',
'items': {
'title': 'Group',
'type': 'object',
'properties': {
'_id': {
'title': 'Database ID',
'type': 'string',
},
}
}
}
def count(self, iid):
"""Return the number of Groups."""
self.response.write('%d groups\n' % self.app.db.groups.count())
......@@ -148,6 +273,46 @@ class Groups(nimsapiutil.NIMSRequestHandler):
class Group(nimsapiutil.NIMSRequestHandler):
json_schema = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'Group',
'type': 'object',
'properties': {
'_id': {
'title': 'Database ID',
'type': 'string',
},
'pis': {
'title': 'PIs',
'type': 'array',
'default': [],
'items': {
'type': 'string',
},
'uniqueItems': True,
},
'admins': {
'title': 'Admins',
'type': 'array',
'default': [],
'items': {
'type': 'string',
},
'uniqueItems': True,
},
'memebers': {
'title': 'Members',
'type': 'array',
'default': [],
'items': {
'type': 'string',
},
'uniqueItems': True,
},
},
'required': ['_id'],
}
def get(self, iid, gid):
"""Return Group details."""
group = self.app.db.groups.find_one({'_id': gid})
......@@ -207,8 +372,8 @@ class ArgumentParser(argparse.ArgumentParser):
super(ArgumentParser, self).__init__()
self.add_argument('uri', help='NIMS DB URI')
self.add_argument('stage_path', help='path to staging area')
self.add_argument('--pubkey', default='internims/NIMSpubkey.pub', help='path to ssl pubkey')
self.add_argument('-u', '--uid', default='local', help='site uid')
self.add_argument('-k', '--pubkey', help='path to public SSL key')
self.add_argument('-u', '--uid', default='local', help='site UID')
self.add_argument('-f', '--logfile', help='path to log file')
self.add_argument('-l', '--loglevel', default='info', help='path to log file')
self.add_argument('-q', '--quiet', action='store_true', default=False, help='disable console logging')
......@@ -218,36 +383,53 @@ routes = [
webapp2_extras.routes.PathPrefixRoute(r'/nimsapi', [
webapp2.Route(r'/download', NIMSAPI, handler_method='download', methods=['GET']),
webapp2.Route(r'/dump', NIMSAPI, handler_method='dump', methods=['GET']),
webapp2.Route(r'/upload/<fid>', NIMSAPI, handler_method='upload', methods=['PUT']),
webapp2.Route(r'/upload', NIMSAPI, handler_method='upload', methods=['PUT']),
webapp2.Route(r'/remotes', Remotes),
]),
# webapp2_extras.routes.PathPrefixRoute has bug, variable MUST have regex
]),
webapp2_extras.routes.PathPrefixRoute(r'/nimsapi/<iid:[^/]+>', [
webapp2.Route(r'/users', Users),
webapp2.Route(r'/users/count', Users, handler_method='count', methods=['GET']),
webapp2.Route(r'/users/listschema', Users, handler_method='schema', methods=['GET']),
webapp2.Route(r'/users/schema', User, handler_method='schema', methods=['GET']),
webapp2.Route(r'/users/<uid>', User),
webapp2.Route(r'/groups', Groups),
webapp2.Route(r'/groups/count', Groups, handler_method='count', methods=['GET']),
webapp2.Route(r'/groups/listschema', Groups, handler_method='schema', methods=['GET']),
webapp2.Route(r'/groups/schema', Group, handler_method='schema', methods=['GET']),
webapp2.Route(r'/groups/<gid>', Group),
webapp2.Route(r'/experiments', experiments.Experiments),
webapp2.Route(r'/experiments/count', experiments.Experiments, handler_method='count', methods=['GET']),
webapp2.Route(r'/experiments/listschema', experiments.Experiments, handler_method='schema', methods=['GET']),
webapp2.Route(r'/experiments/schema', experiments.Experiment, handler_method='schema', methods=['GET']),
webapp2.Route(r'/experiments/<xid:[0-9a-f]{24}>', experiments.Experiment),
webapp2.Route(r'/experiments/<xid:[0-9a-f]{24}>/sessions', sessions.Sessions),
webapp2.Route(r'/sessions/count', sessions.Sessions, handler_method='count', methods=['GET']),
webapp2.Route(r'/sessions/listschema', sessions.Sessions, handler_method='schema', methods=['GET']),
webapp2.Route(r'/sessions/schema', sessions.Session, handler_method='schema', methods=['GET']),
webapp2.Route(r'/sessions/<sid:[0-9a-f]{24}>', sessions.Session),
webapp2.Route(r'/sessions/<sid:[0-9a-f]{24}>/move', sessions.Session, handler_method='move'),
webapp2.Route(r'/sessions/<sid:[0-9a-f]{24}>/epochs', epochs.Epochs),
webapp2.Route(r'/epochs/count', epochs.Epochs, handler_method='count', methods=['GET']),
webapp2.Route(r'/epochs/listschema', epochs.Epochs, handler_method='schema', methods=['GET']),
webapp2.Route(r'/epochs/schema', epochs.Epoch, handler_method='schema', methods=['GET']),
webapp2.Route(r'/epochs/<eid:[0-9a-f]{24}>', epochs.Epoch),
]),
]
app = webapp2.WSGIApplication(routes, debug=True)
if __name__ == '__main__':
args = ArgumentParser().parse_args()
nimsutil.configure_log(args.logfile, not args.quiet, args.loglevel)
from paste import httpserver
app = webapp2.WSGIApplication(routes, debug=True, config=dict(stage_path=args.stage_path, site_id=args.uid, pubkey=args.pubkey))
app.config = dict(stage_path=args.stage_path, site_id=args.uid, pubkey=args.pubkey)
app.db = (pymongo.MongoReplicaSetClient(args.uri) if 'replicaSet' in args.uri else pymongo.MongoClient(args.uri)).get_default_database()
httpserver.serve(app, host=httpserver.socket.gethostname(), port='8080')
# import nimsapi, webapp2, pymongo, bson.json_util
# nimsapi.app.db = pymongo.MongoClient('mongodb://nims:cnimr750@slice.stanford.edu/nims').get_default_database()
# response = webapp2.Request.blank('/nimsapi/local/users').get_response(nimsapi.app)
# response.status
# response.body
......@@ -17,11 +17,12 @@ import nimsapi
import nimsutil
logfile = '/var/local/log/nimsapi.log'
db_uri = 'mongodb://nimsfs.stanford.edu,nimsbk.stanford.edu/nims?replicaSet=cni'
db_uri = 'mongodb://nims:cnimr750@cnifs.stanford.edu,cnibk.stanford.edu/nims?replicaSet=cni'
stage_path = '/scratch/upload'
nimsutil.configure_log(logfile, False)
db_client = pymongo.MongoReplicaSetClient(db_uri) if 'replicaSet' in db_uri else pymongo.MongoClient(db_uri)
application = webapp2.WSGIApplication(nimsapi.nimsapi.routes, debug=False, config=dict(stage_path=stage_path))
application = nimsapi.app
application.config = dict(stage_path=stage_path))
application.db = db_client.get_default_database()
# @author: Gunnar Schaefer
# @author: Kevin S. Hahn
# @author: Gunnar Schaefer, Kevin S. Hahn
import base64
import datetime
import json
import base64
import socket # socket.gethostname()
import logging
import requests
import webapp2
import socket # socket.gethostname()
import datetime
import requests
import bson.json_util
from Crypto.Hash import HMAC
from Crypto.Random import random
logging.basicConfig(level=logging.INFO)
log = logging.getLogger('nimsapi')
class NIMSRequestHandler(webapp2.RequestHandler):
"""fetches pubkey from own self.db.remotes. needs to be aware of OWN site uid"""
json_schema = None
file_schema = {
'title': 'File',
'type': 'object',
'properties': {
'datakind': {
'title': 'Data Kind',
'type': 'string',
},
'datatype': {
'title': 'Data Type',
'type': 'string',
},
'filetype': {
'title': 'File Type',
'type': 'string',
},
'filename': {
'title': 'File Name',
'type': 'string',
},
'ext': {
'title': 'File Name Extension',
'type': 'string',
},
'md5': {
'title': 'MD5',
'type': 'string',
},
'size': {
'title': 'Size',
'type': 'integer',
},
}
}
def __init__(self, request=None, response=None):
webapp2.RequestHandler.__init__(self, request, response)
# call initialize, isntead of __init__
self.initialize(request, response)
self.request.remote_user = self.request.get('user', None) # FIXME: auth system should set REMOTE_USER
self.userid = self.request.remote_user or '@public'
self.user = self.app.db.users.find_one({'_id': self.userid})
......@@ -29,18 +66,17 @@ class NIMSRequestHandler(webapp2.RequestHandler):
self.target_id = request.route_kwargs['iid'] # for what site is the request meant
except KeyError:
self.target_id = 'local' # change to site_id?
self.useragent = self.request.headers['user-agent'] # browser or internimsP2P request
self.site_id = self.app.config['site_id'] # what is THIS site
self.pubkey = open(self.app.config['pubkey']).read()
self.pubkey = open(self.app.config['pubkey']).read() if self.app.config['pubkey'] is not None else None
# requests coming from another NIMS instance are dealt with differently
if self.useragent.startswith('NIMS Instance'):
logging.info("request from '{0}', interNIMS p2p initiated".format(self.useragent))
if self.request.user_agent.startswith('NIMS Instance'):
log.info("request from '{0}', interNIMS p2p initiated".format(self.request.user_agent))
try:
authinfo = self.request.headers['authorization']
challenge_id, digest = base64.b64decode(authinfo).split()
user, remote_site = challenge_id.split(':')
# logging.info('{0} {1} {2}'.format(user, remote_site, digest))
# log.info('{0} {1} {2}'.format(user, remote_site, digest))
projection = {'_id': False, 'pubkey': True}
remote_pubkey = self.app.db.remotes.find_one({'_id': remote_site}, projection)['pubkey']
# get the challenge from db.challenges
......@@ -52,7 +88,7 @@ class NIMSRequestHandler(webapp2.RequestHandler):
h = HMAC.new(remote_pubkey, challenge)
self.expected = base64.b64encode('%s %s' % (challenge_id, h.hexdigest()))
if self.expected == authinfo:
logging.info('CRAM successful')
log.info('CRAM successful')
else:
self.abort(403, 'Not Authorized: cram failed')
except KeyError, e:
......@@ -71,14 +107,14 @@ class NIMSRequestHandler(webapp2.RequestHandler):
def dispatch(self):
"""dispatching and request forwarding"""
if self.target_id in ['local', self.site_id]:
logging.info('{0} delegating to local {1}'.format(socket.gethostname(), self.request.url))
log.info('{0} delegating to local {1}'.format(socket.gethostname(), self.request.url))
super(NIMSRequestHandler, self).dispatch()
else:
logging.info('{0} delegating to remote {1}'.format(socket.gethostname(), self.target_id))
log.info('{0} delegating to remote {1}'.format(socket.gethostname(), self.target_id))
# is target registered?
target = self.app.db.remotes.find_one({'_id': self.target_id}, {'_id':False, 'hostname':True})
if not target:
logging.info('remote host {0} not in auth list. DENIED'.format(self.target_id))
log.info('remote host {0} not in auth list. DENIED'.format(self.target_id))
self.abort(403, 'forbidden: site is not registered with interNIMS')
self.cid = self.userid + ':' + self.site_id
reqheaders = dict(self.request.headers)
......@@ -94,12 +130,15 @@ class NIMSRequestHandler(webapp2.RequestHandler):
if r.status_code == 401:
challenge = base64.b64decode(r.headers['www-authenticate'])
# logging.info('challenge {0} recieved'.format(challenge))
# log.info('challenge {0} recieved'.format(challenge))
h = HMAC.new(self.pubkey, challenge)
response = base64.b64encode('%s %s' % (self.cid, h.hexdigest()))
# logging.info('sending: {0} {1}'.format(self.cid, h.hexdigest()))
# log.info('sending: {0} {1}'.format(self.cid, h.hexdigest()))
#adjust the request and try again
reqheaders['authorization'] = response
r = requests.request(method=self.request.method, url=target_api, params=reqparams, data=self.request.body, headers=reqheaders, cookies=self.request.cookies)
self.response.write(r.content)
def schema(self, *args, **kwargs):
self.response.write(json.dumps(self.json_schema, default=bson.json_util.default))
......@@ -9,9 +9,31 @@ import nimsapiutil
class Sessions(nimsapiutil.NIMSRequestHandler):
json_schema = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'Session List',
'type': 'array',
'items': {
'title': 'Session',
'type': 'object',
'properties': {
'_id': {
'title': 'Database ID',
},
'timestamp': {
'title': 'Timestamp',
},
'subject': {
'title': 'Subject Code',
'type': 'string',
},
}
}
}
def count(self, iid):
"""Return the number of Sessions."""
self.response.write('sessions count\n')
self.response.write(json.dumps(self.app.db.sessions.count()))
def post(self, iid):
"""Create a new Session"""
......@@ -36,6 +58,54 @@ class Sessions(nimsapiutil.NIMSRequestHandler):
class Session(nimsapiutil.NIMSRequestHandler):
json_schema = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'Session',
'type': 'object',
'properties': {
'_id': {
'title': 'Database ID',
},
'experiment': {
'title': 'Experiment ID',
},
'timestamp': {
'title': 'Timestamp',
},
'uid': {
'title': 'DICOM UID',
'type': 'string',
},
'firstname': {
'title': 'First Name',
'type': 'string',
},
'lastname': {
'title': 'Last Name',
'type': 'string',
},
'patient_id': {
'title': 'Patient ID',
'type': 'string',
},
'subject': {
'title': 'Subject Code',
'type': 'string',
},
'exam': {
'title': 'Exam Number',
'type': 'integer',
},
'files': {
'title': 'Files',
'type': 'array',
'items': nimsapiutil.NIMSRequestHandler.file_schema,
'uniqueItems': True,
},
},
'required': ['_id', 'experiment', 'uid', 'patient_id', 'subject'],
}
def get(self, iid, sid):
"""Return one Session, conditionally with details."""
session = self.app.db.sessions.find_one({'_id': bson.objectid.ObjectId(sid)})
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment