diff --git a/api/api.py b/api/api.py index d6a8f21843d2e42a41701ecf3340b43924aca372..c14227dbf1ee133e67b02f23a092fbe65225983f 100644 --- a/api/api.py +++ b/api/api.py @@ -1,8 +1,10 @@ import os import copy import json +import pytz import webapp2 -import bson.json_util +import datetime +import bson.objectid import webapp2_extras.routes from . import core @@ -98,16 +100,25 @@ for cls in [ ]: cls.post_schema = copy.deepcopy(schema_dict[cls.__name__.lower()]) cls.put_schema = copy.deepcopy(cls.post_schema) - cls.put_schema['properties'].pop('_id') + cls.put_schema['properties'].pop('_id', None) cls.put_schema.pop('required') +def custom_json_serializer(obj): + if isinstance(obj, bson.objectid.ObjectId): + return str(obj) + elif isinstance(obj, datetime.datetime): + return pytz.timezone('UTC').localize(obj).isoformat() + raise TypeError(repr(obj) + " is not JSON serializable") + + def dispatcher(router, request, response): rv = router.default_dispatcher(request, response) if rv is not None: - response.write(json.dumps(rv, default=bson.json_util.default)) + response.write(json.dumps(rv, default=custom_json_serializer)) response.headers['Content-Type'] = 'application/json; charset=utf-8' + try: import newrelic.agent app = newrelic.agent.WSGIApplicationWrapper(webapp2.WSGIApplication(routes)) diff --git a/api/base.py b/api/base.py index f2bc2c8fb89be77dbf67768ff1ebb157549f2244..51251595b80012c3ac151b50b024099480ffc2f1 100644 --- a/api/base.py +++ b/api/base.py @@ -32,11 +32,11 @@ class RequestHandler(webapp2.RequestHandler): # User (oAuth) authentication if access_token and self.app.config['oauth2_id_endpoint']: - token_request_time = datetime.datetime.now() + token_request_time = datetime.datetime.utcnow() cached_token = self.app.db.authtokens.find_one({'_id': access_token}) if cached_token: self.uid = cached_token['uid'] - log.debug('looked up cached token in %dms' % ((datetime.datetime.now() - token_request_time).total_seconds() * 1000.)) + log.debug('looked up cached token in %dms' % ((datetime.datetime.utcnow() - token_request_time).total_seconds() * 1000.)) else: r = requests.get(self.app.config['oauth2_id_endpoint'], headers={'Authorization': 'Bearer ' + access_token}) if r.status_code == 200: @@ -45,7 +45,7 @@ class RequestHandler(webapp2.RequestHandler): if not self.uid: self.abort(400, 'OAuth2 token resolution did not return email address') self.app.db.authtokens.replace_one({'_id': access_token}, {'uid': self.uid, 'timestamp': datetime.datetime.utcnow()}, upsert=True) - log.debug('looked up remote token in %dms' % ((datetime.datetime.now() - token_request_time).total_seconds() * 1000.)) + log.debug('looked up remote token in %dms' % ((datetime.datetime.utcnow() - token_request_time).total_seconds() * 1000.)) else: headers = {'WWW-Authenticate': 'Bearer realm="%s", error="invalid_token", error_description="Invalid OAuth2 token."' % self.app.config['site_id']} self.abort(401, 'invalid oauth2 token', headers=headers) diff --git a/api/collections.py b/api/collections.py index ab7acb2e98220667019f7d8d87f59ab5ec53d160..9eacbc4dba87278fe9e450ebd1b2d7058b9d2c3d 100644 --- a/api/collections.py +++ b/api/collections.py @@ -303,10 +303,7 @@ class CollectionSessions(sessions.Sessions): projection['permissions'] = {'$elemMatch': {'_id': self.uid, 'site': self.source_site}} sessions = list(self.dbc.find(query, projection)) # avoid permissions checking by not using ContainerList._get() for sess in sessions: - sess['_id'] = str(sess['_id']) # do this manually, since not going through ContainerList._get() sess['subject_code'] = sess.pop('subject', {}).get('code', '') # FIXME when subject is pulled out of session - sess.setdefault('timestamp', datetime.datetime.utcnow()) - sess['timestamp'], sess['timezone'] = util.format_timestamp(sess['timestamp'], sess.get('timezone')) if self.debug: for sess in sessions: sid = str(sess['_id']) @@ -342,9 +339,7 @@ class CollectionAcquisitions(acquisitions.Acquisitions): projection['permissions'] = {'$elemMatch': {'_id': self.uid, 'site': self.source_site}} acquisitions = list(self.dbc.find(query, projection)) for acq in acquisitions: - acq['_id'] = str(acq['_id']) # do this manually, since not going through ContainerList._get() acq.setdefault('timestamp', datetime.datetime.utcnow()) - acq['timestamp'], acq['timezone'] = util.format_timestamp(acq['timestamp'], acq.get('timezone')) if self.debug: for acq in acquisitions: aid = str(acq['_id']) diff --git a/api/containers.py b/api/containers.py index 7d3847d4283ecf1df7bbc6123a2b08cbada418f8..090243dc89c5de3c0914a6e5cbac9be8b9bbd604 100644 --- a/api/containers.py +++ b/api/containers.py @@ -137,9 +137,7 @@ class ContainerList(base.RequestHandler): projection['permissions'] = {'$elemMatch': {'_id': uid or self.uid, 'site': self.source_site}} containers = list(self.dbc.find(query, projection)) for container in containers: - container['_id'] = str(container['_id']) container.setdefault('timestamp', datetime.datetime.utcnow()) - container['timestamp'], container['timezone'] = util.format_timestamp(container['timestamp'], container.get('timezone')) # TODO json serializer should do this container['attachment_count'] = len([f for f in container.get('files', []) if f.get('flavor') == 'attachment']) return containers @@ -174,11 +172,7 @@ class Container(base.RequestHandler): if self.request.GET.get('paths', '').lower() in ('1', 'true'): for fileinfo in container['files']: fileinfo['path'] = str(_id)[-3:] + '/' + str(_id) + '/' + fileinfo['filename'] - container['_id'] = str(container['_id']) container.setdefault('timestamp', datetime.datetime.utcnow()) - container['timestamp'], container['timezone'] = util.format_timestamp(container['timestamp'], container.get('timezone')) # TODO json serializer should do this - for note in container.get('notes', []): - note['timestamp'], _ = util.format_timestamp(note['timestamp']) # TODO json serializer should do this return container, user_perm def _put(self, _id): @@ -250,7 +244,7 @@ class Container(base.RequestHandler): if self.request.GET.get('info', '').lower() in ('1', 'true'): try: with zipfile.ZipFile(filepath) as zf: - return [(zi.filename, zi.file_size, util.format_timestamp(datetime.datetime(*zi.date_time))[0]) for zi in zf.infolist()] + return [(zi.filename, zi.file_size, datetime.datetime(*zi.date_time)) for zi in zf.infolist()] except zipfile.BadZipfile: self.abort(400, 'not a zip file') elif self.request.GET.get('comment', '').lower() in ('1', 'true'): diff --git a/api/jobs.py b/api/jobs.py index e88d730476dd357f0133244f8116b89ebf8b1f94..721e10598c33d0381acfc530ea4f975ce900a269 100644 --- a/api/jobs.py +++ b/api/jobs.py @@ -164,14 +164,6 @@ def queue_job(db, algorithm_id, container_type, container_id, filename, filehash log.info('Running %s as job %s to process %s %s' % (algorithm_id, str(_id), container_type, container_id)) return _id -def serialize_job(job): - if job: - job['_id'] = str(job['_id']) - job['created'] = util.format_timestamp(job['created']) - job['modified'] = util.format_timestamp(job['modified']) - - return job - class Jobs(base.RequestHandler): """Provide /jobs API routes.""" @@ -184,8 +176,6 @@ class Jobs(base.RequestHandler): self.abort(401, 'Request requires superuser') results = list(self.app.db.jobs.find()) - for result in results: - result = serialize_job(result) return results @@ -229,7 +219,7 @@ class Jobs(base.RequestHandler): if result == None: self.abort(400, 'No jobs to process') - return serialize_job(result) + return result class Job(base.RequestHandler): @@ -240,7 +230,7 @@ class Job(base.RequestHandler): self.abort(401, 'Request requires superuser') result = self.app.db.jobs.find_one({'_id': bson.ObjectId(_id)}) - return serialize_job(result) + return result def put(self, _id): """ diff --git a/api/users.py b/api/users.py index 06fe0f673978c6139ed3ff04573557f3aba81e33..15477a96f78aec16a852bb1930446b1c19d660ae 100644 --- a/api/users.py +++ b/api/users.py @@ -61,9 +61,6 @@ class Users(base.RequestHandler): if self.public_request: self.abort(403, 'must be logged in to retrieve User list') users = list(self.dbc.find({}, {'preferences': False})) - for user in users: - user['created'], _ = util.format_timestamp(user['created']) # TODO json serializer should do this - user['modified'], _ = util.format_timestamp(user['modified']) # TODO json serializer should do this if self.debug: for user in users: user['debug'] = {} @@ -108,8 +105,6 @@ class User(base.RequestHandler): user = self.dbc.find_one({'_id': _id}, projection or None) if not user: self.abort(404, 'no such User') - user['created'], _ = util.format_timestamp(user['created']) # TODO json serializer should do this - user['modified'], _ = util.format_timestamp(user['modified']) # TODO json serializer should do this if self.debug and (self.superuser_request or _id == self.uid): user['debug'] = {} user['debug']['groups'] = self.uri_for('groups', _id, _full=True) + '?' + self.request.query_string @@ -184,9 +179,6 @@ class Groups(base.RequestHandler): query = {'roles._id': self.uid} projection += ['roles.$'] groups = list(self.app.db.groups.find(query, projection)) - for group in groups: - group['created'], _ = util.format_timestamp(group['created']) # TODO json serializer should do this - group['modified'], _ = util.format_timestamp(group['modified']) # TODO json serializer should do this if self.debug: for group in groups: group['debug'] = {} @@ -219,8 +211,6 @@ class Group(base.RequestHandler): group = self.app.db.groups.find_one({'_id': _id, 'roles': {'$elemMatch': {'_id': self.uid, 'access': 'admin'}}}) if not group: self.abort(403, 'User ' + self.uid + ' is not an admin of Group ' + _id) - group['created'], _ = util.format_timestamp(group['created']) # TODO json serializer should do this - group['modified'], _ = util.format_timestamp(group['modified']) # TODO json serializer should do this if self.debug: group['debug'] = {} group['debug']['projects'] = self.uri_for('g_projects', gid=group['_id'], _full=True) + '?' + self.request.query_string diff --git a/api/util.py b/api/util.py index 81683b8140d4b32f746d9ce2854b90b1284889c3..dc9fb2b29003dde7c71f29de5f99f3428b2f78ad 100644 --- a/api/util.py +++ b/api/util.py @@ -299,10 +299,5 @@ def guess_filetype(filepath, mimetype): return subtype -def format_timestamp(timestamp, tzname=None): - timezone = pytz.timezone(tzname or 'UTC') - return timezone.localize(timestamp).isoformat(), timezone.zone - - def parse_timestamp(iso_timestamp): return dateutil.parser.parse(iso_timestamp)