Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 23 additions & 17 deletions api/api.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import webapp2
import webapp2_extras.routes

from .download import Download
from .handlers.collectionshandler import CollectionsHandler
from .handlers.confighandler import Config, Version
from .handlers.containerhandler import ContainerHandler
from .handlers.dataexplorerhandler import DataExplorerHandler
from .handlers.devicehandler import DeviceHandler
from .handlers.grouphandler import GroupHandler
from .handlers.listhandler import FileListHandler, NotesListHandler, PermissionsListHandler, TagsListHandler
from .handlers.refererhandler import AnalysesHandler
from .handlers.reporthandler import ReportHandler
from .handlers.resolvehandler import ResolveHandler
from .handlers.roothandler import RootHandler
from .handlers.schemahandler import SchemaHandler
from .handlers.userhandler import UserHandler
from .jobs.handlers import BatchHandler, JobsHandler, JobHandler, GearsHandler, GearHandler, RulesHandler, RuleHandler
from .upload import Upload
from .web.base import RequestHandler
from .download import Download
from .handlers.abstractcontainerhandler import AbstractContainerHandler
from .handlers.collectionshandler import CollectionsHandler
from .handlers.confighandler import Config, Version
from .handlers.containerhandler import ContainerHandler
from .handlers.dataexplorerhandler import DataExplorerHandler
from .handlers.devicehandler import DeviceHandler
from .handlers.grouphandler import GroupHandler
from .handlers.listhandler import FileListHandler, NotesListHandler, PermissionsListHandler, TagsListHandler
from .handlers.refererhandler import AnalysesHandler
from .handlers.reporthandler import ReportHandler
from .handlers.resolvehandler import ResolveHandler
from .handlers.roothandler import RootHandler
from .handlers.schemahandler import SchemaHandler
from .handlers.userhandler import UserHandler
from .jobs.handlers import BatchHandler, JobsHandler, JobHandler, GearsHandler, GearHandler, RulesHandler, RuleHandler
from .upload import Upload
from .web.base import RequestHandler
from . import config


Expand Down Expand Up @@ -183,6 +184,11 @@ def prefix(path, routes):
route('/<cid:site>/rules/<rid:{cid}>', RuleHandler, m=['GET', 'PUT', 'DELETE']),


# Abstract container

route('/containers/<cid:{fname}><extra:.*>', AbstractContainerHandler, h='handle'),


# Groups

route('/groups', GroupHandler, h='get_all', m=['GET']),
Expand Down
66 changes: 66 additions & 0 deletions api/handlers/abstractcontainerhandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from webapp2 import Request

from .. import config
from ..web import base
from ..web.errors import APINotFoundException


# Efficiently search in multiple collections
CONTAINER_SEARCH_JS = r"""
(function searchContainer(_id) {
if (/^[a-f\d]{24}$/i.test(_id)) {
_id = ObjectId(_id);
}
return {
"groups": db.getCollection("groups").findOne({"_id" : _id}, {"_id": 1}),
"projects": db.getCollection("projects").findOne({"_id" : _id}, {"_id": 1}),
"sessions": db.getCollection("sessions").findOne({"_id" : _id}, {"_id": 1}),
"acquisitions": db.getCollection("acquisitions").findOne({"_id" : _id}, {"_id": 1}),
"analyses": db.getCollection("analyses").findOne({"_id" : _id}, {"_id": 1}),
"collections": db.getCollection("collections").findOne({"_id" : _id}, {"_id": 1})
}
})("%s");
"""


class AbstractContainerHandler(base.RequestHandler):
"""
Asbtract handler that removes the need to know a container's noun before performing an action.
"""

# pylint: disable=unused-argument
def handle(self, cid, extra):
"""
Dispatch a request from /containers/x/... to its proper destination.
For example:
/containers/x/files --> x is a project ID --> /projects/x/files
"""

# Run command; check result
command = config.db.command('eval', CONTAINER_SEARCH_JS % cid)
result = command.get('retval')

if command.get('ok') != 1.0 or result is None:
self.abort(500, 'Error running db command')

# Find which container type was found, if any
cont_name = None
for key in result.keys():
if result[key] is not None:
cont_name = key
break
else:
raise APINotFoundException('No container ' + cid + ' found')

# Create new request instance using destination URI (eg. replace containers with cont_name)
destination_environ = self.request.environ
for key in 'PATH_INFO', 'REQUEST_URI':
destination_environ[key] = destination_environ[key].replace('containers', cont_name, 1)
destination_request = Request(destination_environ)

# Apply SciTranRequest attrs
destination_request.id = self.request.id
destination_request.logger = self.request.logger

# Dispatch the destination request
self.app.router.dispatch(destination_request, self.response)
1 change: 0 additions & 1 deletion api/web/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,3 @@ class FileFormException(Exception):
# Payload for a POST or PUT does not match input json schema
class InputValidationException(Exception):
pass

31 changes: 31 additions & 0 deletions tests/integration_tests/python/test_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1331,3 +1331,34 @@ def test_container_delete_tag(data_builder, default_payload, as_root, as_admin,
# test that the (now) empty group can be deleted
assert as_root.delete('/groups/' + group).ok

def test_abstract_containers(data_builder, as_admin, file_form):
group = data_builder.create_group()
project = data_builder.create_project()
session = data_builder.create_session()
acquisition = data_builder.create_acquisition()
analysis = as_admin.post('/sessions/' + session + '/analyses', files=file_form(
'analysis.csv', meta={'label': 'no-job', 'inputs': [{'name': 'analysis.csv'}]})).json()['_id']
collection = data_builder.create_collection()

for cont in (collection, analysis, acquisition, session, project, group):
r = as_admin.post('/containers/' + cont + '/tags', json={'value': 'abstract1'})
assert r.ok

r = as_admin.get('/containers/' + cont)
assert r.ok
assert r.json()['tags'] == ['abstract1']

r = as_admin.put('/containers/' + cont + '/tags/abstract1', json={'value': 'abstract2'})
assert r.ok

r = as_admin.get('/containers/' + cont + '/tags/abstract2')
assert r.ok
assert r.json() == 'abstract2'

# /analyses/x does not support DELETE (yet?)
for cont in (collection, acquisition, session, project, group):
r = as_admin.delete('/containers/' + cont)
assert r.ok

r = as_admin.get('/containers/' + cont)
assert r.status_code == 404