From b1bbfa832199611ad7e9df7c8077ad93b508df4d Mon Sep 17 00:00:00 2001
From: Nathaniel Kofalt <nathaniel@kofalt.com>
Date: Wed, 7 Sep 2016 14:53:12 -0500
Subject: [PATCH] Add gear suggestion endpoint

---
 api/api.py                  |  1 +
 api/dao/containerstorage.py | 14 ++++++++++++++
 api/jobs/gears.py           | 31 +++++++++++++++++++++++++++++--
 api/jobs/handlers.py        | 18 +++++++++++++++---
 4 files changed, 59 insertions(+), 5 deletions(-)

diff --git a/api/api.py b/api/api.py
index dee8d8e5..b2d19c3a 100644
--- a/api/api.py
+++ b/api/api.py
@@ -125,6 +125,7 @@ routes = [
     webapp2_extras.routes.PathPrefixRoute(r'/api/gears', [
         webapp2.Route(r'/<:[^/]+>',            GearHandler),
         webapp2.Route(r'/<:[^/]+>/invocation', GearHandler, handler_method='get_invocation'),
+        webapp2.Route(r'/<:[^/]+>/suggest/<:[^/]+>/<:[^/]+>', GearHandler, handler_method='suggest'),
     ]),
     webapp2.Route(r'/api/rules',             RulesHandler),
     webapp2.Route(r'/api/groups',                                   grouphandler.GroupHandler, handler_method='get_all', methods=['GET']),
diff --git a/api/dao/containerstorage.py b/api/dao/containerstorage.py
index 175b9f26..37105127 100644
--- a/api/dao/containerstorage.py
+++ b/api/dao/containerstorage.py
@@ -122,3 +122,17 @@ class GroupStorage(ContainerStorage):
             },
             upsert=True)
 
+def inflate_container(cr):
+    """
+    Given a container reference, inflate its hierarchy into a map.
+    Eeventually, this might want to deduplicate with logic in hierarchy.py.
+    """
+
+    if cr.type != 'session':
+        raise Exception('Only sessions are supported for inflation right now')
+
+    oid = bson.ObjectId(cr.id)
+    root = ContainerStorage('sessions', True).exec_op('GET', oid, projection={'permissions': 0})
+    root['acquisitions'] = ContainerStorage('acquisitions', True).exec_op('GET', query={'session': oid}, projection={'permissions': 0})
+
+    return root
diff --git a/api/jobs/gears.py b/api/jobs/gears.py
index f236370b..664573f0 100644
--- a/api/jobs/gears.py
+++ b/api/jobs/gears.py
@@ -2,10 +2,15 @@
 Gears
 """
 
+import bson.objectid
+import jsonschema
+from jsonschema import Draft4Validator
+import gear_tools
+
 from .. import config
 from .jobs import Job
+from ..dao.containerstorage import inflate_container
 
-import gear_tools
 
 log = config.log
 
@@ -51,9 +56,31 @@ def get_gear_by_name(name):
     # Mongo returns the full document: { '_id' : 'gears', 'gear_list' : [ { .. } ] }, so strip that out
     return gear_doc[SINGLETON_KEY][0]
 
-def get_invocation(gear):
+def get_invocation_schema(gear):
     return gear_tools.derive_invocation_schema(gear['manifest'])
 
+def suggest_container(gear, cr):
+    """
+    Given a container reference, suggest files that would work well for each input on a gear.
+    """
+
+    root = inflate_container(cr)
+    invocation_schema = get_invocation_schema(gear)
+
+    schemas = {}
+    for x in gear['manifest']['inputs']:
+        schema = gear_tools.isolate_file_invocation(invocation_schema, x)
+        schemas[x] = Draft4Validator(schema)
+
+    # It would be nice to have use a visitor here instead of manual key loops.
+    for acq in root['acquisitions']:
+        for f in acq.get('files', []):
+            f['suggested'] = {}
+            for x in schemas:
+                f['suggested'][x] = schemas[x].is_valid({})
+
+    return root
+
 def insert_gear(doc):
     config.db.singletons.update(
         {"_id" : "gears"},
diff --git a/api/jobs/handlers.py b/api/jobs/handlers.py
index b7a52de9..a2ab54a1 100644
--- a/api/jobs/handlers.py
+++ b/api/jobs/handlers.py
@@ -5,11 +5,11 @@ API request handlers for the jobs module
 import json
 import StringIO
 
-from ..dao.containerutil import create_filereference_from_dictionary, create_containerreference_from_dictionary, create_containerreference_from_filereference
+from ..dao.containerutil import create_filereference_from_dictionary, create_containerreference_from_dictionary, create_containerreference_from_filereference, ContainerReference
 from .. import base
 from .. import config
 
-from .gears import get_gears, get_gear_by_name, get_invocation, remove_gear, upsert_gear
+from .gears import get_gears, get_gear_by_name, get_invocation_schema, remove_gear, upsert_gear, suggest_container
 from .jobs import Job
 from .queue import Queue
 
@@ -47,7 +47,19 @@ class GearHandler(base.RequestHandler):
             self.abort(403, 'Request requires login')
 
         gear = get_gear_by_name(_id)
-        return get_invocation(gear)
+        return get_invocation_schema(gear)
+
+    def suggest(self, _id, cont_name, cid):
+
+        if self.public_request:
+            self.abort(403, 'Request requires login')
+
+        cr = ContainerReference(cont_name, cid)
+        if not self.superuser_request:
+            cr.check_access(self.uid, 'ro')
+
+        gear = get_gear_by_name(_id)
+        return suggest_container(gear, cr)
 
     def post(self, _id):
         """Upsert an entire gear document."""
-- 
GitLab