From 4262ff7673bfcd1ff68c01aa09e2ea3484a6c11a Mon Sep 17 00:00:00 2001 From: John Chilton Date: Mon, 15 Dec 2014 00:20:35 -0500 Subject: [PATCH] Implement a detailed break down of data table fields via API. Originally this approach was laid out by Kyle Ellrott in this pull request (https://bitbucket.org/galaxy/galaxy-central/pull-request/531/add-downloads-to-tool-data-api/diff). The changes to galaxy.tools.data are entirely his contribution, I only reworked the API and endpoint slightly and did some stylistic fixes and refactoring. --- lib/galaxy/tools/data/__init__.py | 63 ++++++++++++++++++++++ lib/galaxy/webapps/galaxy/api/tool_data.py | 16 ++++++ lib/galaxy/webapps/galaxy/buildapp.py | 1 + test/api/test_tool_data.py | 8 +++ 4 files changed, 88 insertions(+) diff --git a/lib/galaxy/tools/data/__init__.py b/lib/galaxy/tools/data/__init__.py index edb471dbadc..4cfc408211f 100644 --- a/lib/galaxy/tools/data/__init__.py +++ b/lib/galaxy/tools/data/__init__.py @@ -9,7 +9,11 @@ to modify the tool configurations. import logging import os import os.path +import re import string +import hashlib + +from glob import glob from galaxy import util from galaxy.util.odict import odict @@ -340,6 +344,13 @@ class TabularToolDataTable( ToolDataTable, Dictifiable ): def get_fields( self ): return self.data + def get_field(self, value): + rval = None + for i in self.get_named_fields_list(): + if i['value'] == value: + rval = TabularToolDataField(i) + return rval + def get_named_fields_list( self ): rval = [] named_colums = self.get_column_name_list() @@ -580,6 +591,58 @@ class TabularToolDataTable( ToolDataTable, Dictifiable ): return rval +class TabularToolDataField(Dictifiable, object): + + dict_collection_visible_keys = [] + + def __init__(self, data): + self.data = data + + def __getitem__(self, key): + return self.data[key] + + def get_base_path(self): + return os.path.normpath(os.path.abspath( self.data['path'] )) + + def get_base_dir(self): + path = self.get_base_path() + if not os.path.isdir(path): + path = os.path.dirname(path) + return path + + def clean_base_dir(self, path): + return re.sub( "^" + self.get_base_dir() + r"/*", "", path ) + + def get_files(self): + return glob( self.get_base_path() + "*" ) + + def get_filesize_map(self, rm_base_dir=False): + out = {} + for path in self.get_files(): + if rm_base_dir: + out[self.clean_base_dir(path)] = os.path.getsize(path) + else: + out[path] = os.path.getsize(path) + return out + + def get_fingerprint(self): + sha1 = hashlib.sha1() + fmap = self.get_filesize_map(True) + for k in sorted(fmap.keys()): + sha1.update(k) + sha1.update(str(fmap[k])) + return sha1.hexdigest() + + def to_dict(self): + rval = super(TabularToolDataField, self).to_dict() + rval['name'] = self.data['value'] + rval['fields'] = self.data + rval['base_dir'] = self.get_base_dir(), + rval['files'] = self.get_filesize_map(True) + rval['fingerprint'] = self.get_fingerprint() + return rval + + def expand_here_template(content, here=None): if here and content: content = string.Template(content).safe_substitute( { "__HERE__": here }) diff --git a/lib/galaxy/webapps/galaxy/api/tool_data.py b/lib/galaxy/webapps/galaxy/api/tool_data.py index 585b9878c37..24945481dbf 100644 --- a/lib/galaxy/webapps/galaxy/api/tool_data.py +++ b/lib/galaxy/webapps/galaxy/api/tool_data.py @@ -64,6 +64,22 @@ class ToolData( BaseAPIController ): return data_table.remove_entry(split_values) + @web.require_admin + @expose_api + def show_field( self, trans, id, value, **kwds ): + """ + GET /api/tool_data//fields/ + + Get information about a partiular field in a tool_data table + """ + return self._data_table_field( id, value ).to_dict() + + def _data_table_field( self, id, value ): + out = self._data_table(id).get_field(value) + if out is None: + raise exceptions.ObjectNotFound("No such field %s in data table %s." % (value, id)) + return out + def _data_table( self, id ): try: return self._data_tables[id] diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 661c338be42..afae14b7d27 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -171,6 +171,7 @@ def populate_api_routes( webapp, app ): webapp.mapper.resource( 'dataset', 'datasets', path_prefix='/api' ) webapp.mapper.resource( 'tool_data', 'tool_data', path_prefix='/api' ) + webapp.mapper.connect( '/api/tool_data/{id:.+?}/fields/{value:.+?}', action='show_field', controller="tool_data" ) webapp.mapper.resource( 'dataset_collection', 'dataset_collections', path_prefix='/api/') webapp.mapper.resource( 'sample', 'samples', path_prefix='/api' ) webapp.mapper.resource( 'request', 'requests', path_prefix='/api' ) diff --git a/test/api/test_tool_data.py b/test/api/test_tool_data.py index 60cca5dd9e8..f30d354dabc 100644 --- a/test/api/test_tool_data.py +++ b/test/api/test_tool_data.py @@ -28,3 +28,11 @@ class ToolDataApiTestCase( api.ApiTestCase ): assert first_entry[0] == "data1" assert first_entry[1] == "data1name" assert first_entry[2].endswith("test/functional/tool-data/data1/entry.txt") + + def test_show_field(self): + show_field_response = self._get( "tool_data/testalpha/fields/data1", admin=True ) + self._assert_status_code_is( show_field_response, 200 ) + field = show_field_response.json() + self._assert_has_keys( field, "files", "name", "fields", "fingerprint", "base_dir" ) + files = field[ "files" ] + assert len( files ) == 2