From 88cbd545ac488460dc16aae424ddb544ce4eacea Mon Sep 17 00:00:00 2001 From: Zak King Date: Wed, 5 Dec 2018 13:22:48 -0800 Subject: [PATCH] cleaned up py urls --- py/escher/plots.py | 219 +++++++++++++++++++--------------- py/escher/tests/test_plots.py | 17 ++- py/escher/tests/test_urls.py | 35 +++--- py/escher/tests/test_utils.py | 4 +- py/escher/urls.py | 81 ++++--------- py/escher/util.py | 38 +----- py/escher/widget.py | 5 +- 7 files changed, 188 insertions(+), 211 deletions(-) diff --git a/py/escher/plots.py b/py/escher/plots.py index be622079..f0500913 100644 --- a/py/escher/plots.py +++ b/py/escher/plots.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- - from __future__ import print_function, unicode_literals from escher.urls import get_url, root_directory -from escher.version import __schema_version__, __map_model_version__ -from escher.util import query_yes_no, b64dump +from escher.util import b64dump from escher.widget import EscherWidget import os @@ -33,7 +31,7 @@ # server management def server_index(): - url = get_url('server_index', source='web', protocol='https') + url = get_url('server_index') try: download = urlopen(url) except URLError: @@ -68,7 +66,7 @@ def match_in_index(name, index, kind): if len(match) == 0: raise Exception('Could not find the %s %s on the server' % (kind, name)) org, name = match[0] - url = (get_url(kind + '_download', source='web', protocol='https') + + url = (get_url(kind + '_download') + '/'.join([url_escape(x, plus=False) for x in [org, name + '.json']])) warn('Downloading from %s' % (kind.title(), url)) try: @@ -78,18 +76,22 @@ def match_in_index(name, index, kind): data = _decode_response(download) return data + def model_json_for_name(model_name): return _json_for_name(model_name, 'model') + def map_json_for_name(map_name): return _json_for_name(map_name, 'map') # helper functions + def _get_an_id(): return (''.join(random.choice(string.ascii_lowercase) for _ in range(10))) + def _decode_response(download): """Decode the urllib.response.addinfourl response.""" data = download.read() @@ -103,7 +105,8 @@ def _decode_response(download): data = data.decode('utf-8') return data -def _load_resource(resource, name, safe=False): + +def _load_resource(resource, name): """Load a resource that could be a file, URL, or json string.""" # if it's a url, download it if resource.startswith('http://') or resource.startswith('https://'): @@ -120,8 +123,6 @@ def _load_resource(resource, name, safe=False): # check for error with long filepath (or URL) on Windows is_file = False if is_file: - if (safe): - raise Exception('Cannot load resource from file with safe mode enabled.') try: with open(resource, 'rb') as f: loaded_resource = f.read().decode('utf-8') @@ -134,7 +135,8 @@ def _load_resource(resource, name, safe=False): try: _ = json.loads(resource) except ValueError as err: - raise ValueError('Could not load %s. Not valid json, url, or filepath' % name) + raise ValueError('Could not load %s. Not valid json, url, or filepath' + % name) else: return resource raise Exception('Could not load %s.' % name) @@ -155,19 +157,20 @@ class Builder(object): :param map_json: - A JSON string, or a file path to a JSON file, or a URL specifying a JSON - file to be downloaded. + A JSON string, or a file path to a JSON file, or a URL specifying a + JSON file to be downloaded. :param model: A Cobra model. :param model_name: - A string specifying a model to be downloaded from the Escher web server. + A string specifying a model to be downloaded from the Escher web + server. :param model_json: - A JSON string, or a file path to a JSON file, or a URL specifying a JSON - file to be downloaded. + A JSON string, or a file path to a JSON file, or a URL specifying a + JSON file to be downloaded. :param embedded_css: @@ -180,8 +183,8 @@ class Builder(object): :param metabolite_data: - A dictionary with keys that correspond to metabolite ids and values that - will be mapped to metabolite nodes and labels. + A dictionary with keys that correspond to metabolite ids and values + that will be mapped to metabolite nodes and labels. :param gene_data: @@ -199,8 +202,7 @@ class Builder(object): :param safe: - If True, then loading files from the filesytem is not allowed. This is - to ensure the safety of using Builder within a web server. + Deprecated **Keyword Arguments** @@ -248,14 +250,14 @@ class Builder(object): """ def __init__(self, map_name=None, map_json=None, model=None, - model_name=None, model_json=None, embedded_css=None, - reaction_data=None, metabolite_data=None, gene_data=None, - local_host=None, id=None, safe=False, **kwargs): + model_name=None, model_json=None, embedded_css=None, + reaction_data=None, metabolite_data=None, gene_data=None, + local_host=None, id=None, safe=None, **kwargs): if local_host is not None: warn('The local_host option is deprecated') - - self.safe = safe + if safe is not None: + warn('The safe option is deprecated') # load the map self.map_name = map_name @@ -319,6 +321,7 @@ def __init__(self, map_name=None, map_json=None, model=None, 'cofactors', 'enable_tooltips', ] + def get_getter_setter(o): """Use a closure.""" # create local fget and fset functions @@ -341,7 +344,6 @@ def get_getter_setter(o): except AttributeError: print('Unrecognized keywork argument %s' % key) - def _load_model(self): """Load the model. @@ -353,17 +355,15 @@ def _load_model(self): try: import cobra.io except ImportError: - raise Exception(('The COBRApy package must be available to load ' - 'a COBRA model object')) + raise Exception(('The COBRApy package must be available to ' + 'load a COBRA model object')) self.loaded_model_json = cobra.io.to_json(self.model) elif self.model_json is not None: self.loaded_model_json = _load_resource(self.model_json, - 'model_json', - safe=self.safe) + 'model_json') elif self.model_name is not None: self.loaded_model_json = model_json_for_name(self.model_name) - def _load_map(self): """Load the map from input map_json using _load_resource, or, secondarily, from map_name. @@ -371,11 +371,91 @@ def _load_map(self): """ if self.map_json is not None: self.loaded_map_json = _load_resource(self.map_json, - 'map_json', - safe=self.safe) + 'map_json') elif self.map_name is not None: self.loaded_map_json = map_json_for_name(self.map_name) + def display_in_notebook(self, js_source=None, menu='zoom', + scroll_behavior='none', minified_js=None, + height=500, enable_editing=False): + """Embed the Map within the current IPython Notebook. + + :param string js_source: + + deprecated + + :param string menu: Menu bar options include: + + - *none* - No menu or buttons. + - *zoom* - Just zoom buttons. + - Note: The *all* menu option does not work in an IPython notebook. + + :param string scroll_behavior: Scroll behavior options: + + - *pan* - Pan the map. + - *zoom* - Zoom the map. + - *none* - (Default) No scroll events. + + :param Boolean minified_js: + + Deprectated. + + :param height: Height of the HTML container. + + :param Boolean enable_editing: Enable the map editing modes. + + """ + if js_source is not None: + warn('The js_source option is deprecated') + if minified_js is not None: + warn('The minified_js option is deprecated') + + # options + # TODO deduplicate + options = { + 'menu': menu, + 'enable_keys': enable_keys, + 'enable_editing': enable_editing, + 'scroll_behavior': scroll_behavior, + 'fill_screen': fill_screen, + 'never_ask_before_quit': never_ask_before_quit, + 'reaction_data': self.reaction_data, + 'metabolite_data': self.metabolite_data, + 'gene_data': self.gene_data, + } + # Add the specified options + for option in self.options: + val = getattr(self, option) + if val is None: + continue + options[option] = val + + return EscherWidget( + menu=menu, + scroll_behavior=scroll_behavior, + height=height, + enable_editing=enable_editing, + options=this.options, + embedded_css=this.embedded_css, + loaded_map_json=this.loaded_map_json, + loaded_model_json=this.loaded_model_json, + ) + + def display_in_browser(self, ip='127.0.0.1', port=7655, n_retries=50, + js_source='web', menu='all', scroll_behavior='pan', + enable_editing=True, enable_keys=True, + minified_js=True, never_ask_before_quit=False): + """Deprecated. + + We recommend using the Jupyter Widget (which now supports all Escher + features) or the save_html option to generate a standalone HTML file + that loads the map. + + """ + raise Exception(('display_in_browser is deprecated. We recommend using' + 'the Jupyter Widget (which now supports all Escher' + 'features) or the save_html option to generate a' + 'standalone HTML file that loads the map.')) def _get_html(self, js_source=None, menu='none', scroll_behavior='pan', html_wrapper=False, enable_editing=False, enable_keys=False, @@ -413,11 +493,9 @@ def _get_html(self, js_source=None, menu='none', scroll_behavior='pan', leave the page. By default, this message is displayed if enable_editing is True. - static_site_index_json: deprecated + static_site_index_json: Deprecated - protocol: The protocol can be 'http', 'https', or None which indicates a - 'protocol relative URL', as in //escher.github.io. Ignored if source is - local. + protocol: Deprecated ignore_bootstrap: Deprecated @@ -427,6 +505,8 @@ def _get_html(self, js_source=None, menu='none', scroll_behavior='pan', warn('The js_source option is deprecated') if static_site_index_json is not None: warn('The static_site_index_json option is deprecated') + if protocol is not None: + warn('The protocol option is deprecated') if ignore_bootstrap is not None: warn('The ignore_bootstrap option is deprecated') @@ -434,7 +514,8 @@ def _get_html(self, js_source=None, menu='none', scroll_behavior='pan', raise Exception('Bad value for menu: %s' % menu) if scroll_behavior not in ['pan', 'zoom', 'none']: - raise Exception('Bad value for scroll_behavior: %s' % scroll_behavior) + raise Exception('Bad value for scroll_behavior: %s' % + scroll_behavior) content = env.get_template('content.html') @@ -447,10 +528,11 @@ def _get_html(self, js_source=None, menu='none', scroll_behavior='pan', height = str(height) # for static site - map_download_url = get_url('map_download', url_source, None, protocol) - model_download_url = get_url('model_download', url_source, None, protocol) + map_download_url = get_url('map_download') + model_download_url = get_url('model_download') # options + # TODO deduplicate options = { 'menu': menu, 'enable_keys': enable_keys, @@ -492,63 +574,6 @@ def _get_html(self, js_source=None, menu='none', scroll_behavior='pan', return html - - def display_in_notebook(self, js_source=None, menu='zoom', scroll_behavior='none', - minified_js=None, height=500, enable_editing=False): - """Embed the Map within the current IPython Notebook. - - :param string js_source: - - deprecated - - :param string menu: Menu bar options include: - - - *none* - No menu or buttons. - - *zoom* - Just zoom buttons. - - Note: The *all* menu option does not work in an IPython notebook. - - :param string scroll_behavior: Scroll behavior options: - - - *pan* - Pan the map. - - *zoom* - Zoom the map. - - *none* - (Default) No scroll events. - - :param Boolean minified_js: - - Deprectated. - - :param height: Height of the HTML container. - - :param Boolean enable_editing: Enable the map editing modes. - - """ - if js_source is not None: - warn('The js_source option is deprecated') - if minified_js is not None: - warn('The minified_js option is deprecated') - - return EscherWidget( - menu=menu, - scroll_behavior=scroll_behavior, - height=height, - enable_editing=enable_editing, - ) - - def display_in_browser(self, ip='127.0.0.1', port=7655, n_retries=50, js_source='web', - menu='all', scroll_behavior='pan', enable_editing=True, enable_keys=True, - minified_js=True, never_ask_before_quit=False): - """Deprecated. - - We recommend using the Jupyter Widget (which now supports all Escher - features) or the save_html option to generate a standalone HTML file - that loads the map. - - """ - raise Exception(('display_in_browser is deprecated. We recommend using' - 'the Jupyter Widget (which now supports all Escher' - 'features) or the save_html option to generate a' - 'standalone HTML file that loads the map.')) - def save_html(self, filepath=None, overwrite=False, js_source=None, protocol=None, menu='all', scroll_behavior='pan', enable_editing=True, enable_keys=True, minified_js=True, @@ -624,8 +649,8 @@ def save_html(self, filepath=None, overwrite=False, js_source=None, else: os.makedirs(directory) # add dependencies to the directory - escher = get_url('escher_min' if minified_js else 'escher', 'local') - favicon = get_url('favicon', 'local') + escher = get_url('escher_min' if minified_js else 'escher') + favicon = get_url('favicon') for path in [escher, favicon]: if path is None: diff --git a/py/escher/tests/test_plots.py b/py/escher/tests/test_plots.py index 51433607..516258f0 100644 --- a/py/escher/tests/test_plots.py +++ b/py/escher/tests/test_plots.py @@ -65,6 +65,7 @@ def fin(): # server + @mark.web def test_server_index(): index = server_index() @@ -77,46 +78,55 @@ def test_server_index(): # model and maps + def test_model_json_for_name(tmpdir): models = tmpdir.mkdir('models') models.mkdir('Escherichia coli').join('iJO1366.json').write('"temp"') json = model_json_for_name('iJO1366', cache_dir=str(tmpdir)) assert json == '"temp"' + @mark.web def test_model_json_for_name_web(tmpdir): data = model_json_for_name('iJO1366', cache_dir=str(tmpdir)) assert 'reactions' in data assert 'metabolites' in data + def test_map_json_for_name(tmpdir): maps = tmpdir.mkdir('maps') maps.mkdir('Escherichia coli').join('iJO1366.Central metabolism.json').write('"temp"') json = map_json_for_name('iJO1366.Central metabolism', cache_dir=str(tmpdir)) assert json == '"temp"' + @mark.web def test_map_json_for_name_web(tmpdir): data = map_json_for_name('iJO1366.Central metabolism', cache_dir=str(tmpdir)) - root = get_url('escher_root', protocol='https').rstrip('/') + root = get_url('escher_root').rstrip('/') assert json.loads(data)[0]['schema'] == '/'.join([root, 'escher', 'jsonschema', __schema_version__ + '#']) + # helper functions + def test_load_resource_json(tmpdir): test_json = '{"r": "val"}' assert _load_resource(test_json, 'name') == test_json + def test_load_resource_long_json(tmpdir): # this used to fail on Windows with Python 3 test_json = '{"r": "' + ('val' * 100000) + '"}' assert _load_resource(test_json, 'name') == test_json + def test_load_resource_directory(tmpdir): directory = os.path.abspath(os.path.dirname(__file__)) assert _load_resource(join(directory, 'example.json'), 'name').strip() == '{"r": "val"}' + def test_load_resource_invalid_file(tmpdir): with raises(ValueError) as err: p = join(str(tmpdir), 'dummy') @@ -125,12 +135,14 @@ def test_load_resource_invalid_file(tmpdir): _load_resource(p, 'name') assert 'not a valid json file' in err.value + @mark.web def test_load_resource_web(tmpdir): - url = '/'.join([get_url('map_download', protocol='https'), + url = '/'.join([get_url('map_download'), 'Escherichia%20coli/iJO1366.Central%20metabolism.json']) _ = json.loads(_load_resource(url, 'name')) + def test_Builder(tmpdir): # ok with embedded_css arg b = Builder(map_json='{"r": "val"}', model_json='{"r": "val"}', embedded_css='') @@ -148,6 +160,7 @@ def test_Builder(tmpdir): b._get_html(menu='all') b._get_html(scroll_behavior='zoom') + @mark.web def test_Builder_download(): # download diff --git a/py/escher/tests/test_urls.py b/py/escher/tests/test_urls.py index 480651a9..58c303a5 100644 --- a/py/escher/tests/test_urls.py +++ b/py/escher/tests/test_urls.py @@ -1,37 +1,38 @@ from escher.urls import get_url, names, root_directory -from escher.version import __version__, __schema_version__, __map_model_version__ -import os +from escher.version import ( + __version__, + __schema_version__, + __map_model_version__, +) from os.path import join, exists - from pytest import raises + def test_online(): - url = get_url('escher', source='web', protocol='https') + url = get_url('escher') assert url == 'https://unpkg.com/escher@%s/dist/escher.js' % __version__ + def test_no_protocol(): - url = get_url('escher', 'web') + url = get_url('escher') assert url == '//unpkg.com/escher@%s/dist/escher.js' % __version__ + def test_local(): - url = get_url('escher_min', 'local') + url = get_url('escher_min') assert url == 'escher/static/escher/escher.min.js' assert exists(join(root_directory, url)) -def test_localhost(): - url = get_url('escher', source='local', local_host='http://localhost:7778/') - assert url == 'http://localhost:7778/escher/static/escher/escher.js' def test_download(): - url = get_url('server_index', source='local') - assert url == '../' + __schema_version__ + '/' + __map_model_version__ + '/index.json' - url = get_url('map_download', protocol='https') - assert url == 'https://escher.github.io/%s/%s/maps/' % (__schema_version__, __map_model_version__) + url = get_url('server_index') + assert url == ('../' + __schema_version__ + '/' + __map_model_version__ + + '/index.json') + url = get_url('map_download') + assert url == ('https://escher.github.io/%s/%s/maps/' % + (__schema_version__, __map_model_version__)) + def test_bad_url(): with raises(Exception): get_url('bad-name') - with raises(Exception): - get_url('d3', source='bad-source') - with raises(Exception): - get_url('d3', protocol='bad-protocol') diff --git a/py/escher/tests/test_utils.py b/py/escher/tests/test_utils.py index e0263b12..61694a7a 100644 --- a/py/escher/tests/test_utils.py +++ b/py/escher/tests/test_utils.py @@ -1,14 +1,16 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import base64 import json from escher.util import b64dump + def b64decode(str): return base64.b64decode(str.encode('utf-8')).decode('utf-8') + def test_b64dump(): assert b64decode(b64dump(None)) == 'null' accented_str = 'árvíztűrő tükörfúrógép' diff --git a/py/escher/urls.py b/py/escher/urls.py index bc655673..e7a4bbb6 100644 --- a/py/escher/urls.py +++ b/py/escher/urls.py @@ -15,39 +15,36 @@ 'escher_min': 'escher/static/escher/escher.min.js', 'logo': 'escher/static/img/escher-logo@2x.png', 'favicon': 'escher/static/img/favicon.ico', - 'homepage_js': 'escher/static/homepage/main.js', - 'homepage_css': 'escher/static/homepage/main.css', - 'server_index': '../%s/%s/index.json' % (__schema_version__, __map_model_version__), - 'map_download': '../%s/%s/maps/' % (__schema_version__, __map_model_version__), - 'model_download': '../%s/%s/models/' % (__schema_version__, __map_model_version__), } _escher_web = { 'server_index': '%s/%s/index.json' % (__schema_version__, __map_model_version__), 'map_download': '%s/%s/maps/' % (__schema_version__, __map_model_version__), 'model_download': '%s/%s/models/' % (__schema_version__, __map_model_version__), - 'favicon': 'escher/static/img/favicon.ico', -} - -_dependencies = { } _dependencies_cdn = { - 'escher': '//unpkg.com/escher@%s/dist/escher.js' % __version__, - 'escher_min': '//unpkg.com/escher@%s/dist/escher.min.js' % __version__, + 'escher': 'https://unpkg.com/escher@%s/dist/escher.js' % __version__, + 'escher_min': 'https://unpkg.com/escher@%s/dist/escher.min.js' % __version__, } _links = { - 'escher_root': '//escher.github.io/', - 'github': '//github.com/zakandrewking/escher/', - 'github_releases': '//github.com/zakandrewking/escher/releases', - 'documentation': '//escher.readthedocs.org/', + 'escher_root': 'https://escher.github.io/', + 'github': 'https://github.com/zakandrewking/escher', + 'github_releases': 'https://github.com/zakandrewking/escher/releases', + 'documentation': 'https://escher.readthedocs.org/', } # external dependencies -names = list(_escher_local.keys()) + list(_escher_web.keys()) + list(_dependencies.keys()) + list(_links.keys()) +names = ( + list(_escher_local.keys()) + + list(_escher_web.keys()) + + list(_dependencies.keys()) + + list(_links.keys()) +) + -def get_url(name, source='web', local_host=None, protocol=None): +def get_url(name): """Get a url. Arguments @@ -55,49 +52,15 @@ def get_url(name, source='web', local_host=None, protocol=None): name: The name of the URL. Options are available in urls.names. - source: Either 'web' or 'local'. Cannot be 'local' for external links. - - protocol: The protocol can be 'http', 'https', or None which indicates a - 'protocol relative URL', as in //escher.github.io. Ignored if source is - local. - - local_host: A host url, including the protocol. e.g. http://localhost:7778. - """ - if source not in ['web', 'local']: - raise Exception('Bad source: %s' % source) - if protocol not in [None, 'http', 'https']: - raise Exception('Bad protocol: %s' % protocol) - - if protocol is None: - protocol = '' - else: - protocol = protocol + ':' - - def apply_local_host(url): - return '/'.join([local_host.rstrip('/'), url.lstrip('/')]) - - # escher - if name in _escher_local and source == 'local': - if local_host is not None: - return apply_local_host(_escher_local[name]) + if name in _escher_local: return _escher_local[name] - elif name in _escher_web and source == 'web': - return protocol + '/'.join([_links['escher_root'].rstrip('/'), - _escher_web[name].lstrip('/')]) - # links + elif name in _escher_web: + return _links['escher_root'] + _escher_web[name] elif name in _links: - if source=='local': - raise Exception('Source cannot be "local" for external links') - return protocol + _links[name] - # local dependencies - elif name in _dependencies and source == 'local': - if local_host is not None: - return apply_local_host(_dependencies[name]) - return _dependencies[name] - # cdn dependencies - elif name in _dependencies_cdn and source == 'web': - return protocol + _dependencies_cdn[name] - - raise Exception('name not found') + return _links[name] + elif name in _dependencies_cdn: + return _dependencies_cdn[name] + else: + raise Exception('name not found') diff --git a/py/escher/util.py b/py/escher/util.py index 52332ac5..8db8d89e 100644 --- a/py/escher/util.py +++ b/py/escher/util.py @@ -1,40 +1,9 @@ +# -*- coding: utf-8 -*- +from __future__ import print_function, unicode_literals + import base64 import json -import sys - -# user input for python 2 and 3 -try: - import __builtin__ - input = getattr(__builtin__, 'raw_input') -except (ImportError, AttributeError): - pass - -def query_yes_no(question): - """Ask a yes/no question via input() and return their answer. - - Returns True for yes or False for no. - - Arguments - --------- - question: A string that is presented to the user. - - - Adapted from http://stackoverflow.com/questions/3041986/python-command-line-yes-no-input. - - """ - valid = {"yes": True, "y": True, "ye": True, - "no": False, "n": False} - prompt = " [y/n] " - - while True: - sys.stdout.write(question + prompt) - choice = input().lower() - try: - return valid[choice] - except KeyError: - sys.stdout.write("Please respond with 'yes' or 'no' " - "(or 'y' or 'n').\n") def b64dump(data): """Returns the base64 encoded dump of the input @@ -43,6 +12,7 @@ def b64dump(data): --------- data: Can be a dict, a (JSON or plain) string, or None + """ if isinstance(data, dict): data = json.dumps(data) diff --git a/py/escher/widget.py b/py/escher/widget.py index 92827aaf..d0352708 100644 --- a/py/escher/widget.py +++ b/py/escher/widget.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - from __future__ import print_function, unicode_literals from escher.version import __version__ @@ -9,6 +8,10 @@ class EscherWidget(widgets.DOMWidget): + def __init__(**kwargs): + """Create a widget. Passes all kwargs to Javascript.""" + pass + _view_name = Unicode('EscherMapView').tag(sync=True) _model_name = Unicode('EscherMapModel').tag(sync=True) _view_module = Unicode('jupyter-escher').tag(sync=True)