From f60314ffbad18592f3d5a1e899710903a3b226db Mon Sep 17 00:00:00 2001 From: Colin Diesh Date: Thu, 7 Dec 2023 14:38:50 -0500 Subject: [PATCH] Re-create data folder, change to importlib for static files (#82) --- jbrowse_jupyter/__init__.py | 5 +- jbrowse_jupyter/dev_server.py | 71 ++--- jbrowse_jupyter/jbrowse_config.py | 450 ++++++++++++++---------------- jbrowse_jupyter/tracks.py | 307 +++++++++++--------- jbrowse_jupyter/util.py | 17 +- 5 files changed, 440 insertions(+), 410 deletions(-) diff --git a/jbrowse_jupyter/__init__.py b/jbrowse_jupyter/__init__.py index 663b81a..0185855 100644 --- a/jbrowse_jupyter/__init__.py +++ b/jbrowse_jupyter/__init__.py @@ -1,5 +1,6 @@ # flake8: noqa from .jbrowse_config import JBrowseConfig, create -from .util import launch, create_component +from .util import launch, create_component from .dev_server import serve -__all__ = ['JBrowseConfig', 'create', 'launch', 'create_component', 'serve'] + +__all__ = ["JBrowseConfig", "create", "launch", "create_component", "serve"] diff --git a/jbrowse_jupyter/dev_server.py b/jbrowse_jupyter/dev_server.py index de6ad91..24881d0 100644 --- a/jbrowse_jupyter/dev_server.py +++ b/jbrowse_jupyter/dev_server.py @@ -3,7 +3,7 @@ import re from http.server import HTTPServer, SimpleHTTPRequestHandler -''' +""" This server takes inspiration from multiple solutions to provide: * CORS enabled HTTPServer - https://gist.github.com/acdha/925e9ffc3d74ad59c3ea @@ -11,16 +11,16 @@ - https://github.com/danvk/RangeHTTPServer/blob/master/RangeHTTPServer * Adding specific directory - https://stackoverflow.com/a/46332163 -''' +""" # CREDIT FOR ENABLING RANGE REQUEST HTTP SERVER: # lines 20-31, 34,37-52, 59-107 # https://github.com/danvk/RangeHTTPServer/blob/master/RangeHTTPServer -def copy_byte_range(infile, outfile, start=None, stop=None, bufsize=16*1024): - '''Like shutil.copyfileobj, but only copy a range of the streams. +def copy_byte_range(infile, outfile, start=None, stop=None, bufsize=16 * 1024): + """Like shutil.copyfileobj, but only copy a range of the streams. Both start and stop are inclusive. - ''' + """ if start is not None: infile.seek(start) while 1: @@ -31,7 +31,7 @@ def copy_byte_range(infile, outfile, start=None, stop=None, bufsize=16*1024): outfile.write(buf) -BYTE_RANGE_RE = re.compile(r'bytes=(\d+)-(\d+)?$') +BYTE_RANGE_RE = re.compile(r"bytes=(\d+)-(\d+)?$") def parse_byte_range(byte_range): @@ -39,31 +39,32 @@ def parse_byte_range(byte_range): Returns the two numbers in 'bytes=123-456' or throws ValueError. The last number or both numbers may be None. """ - if byte_range.strip() == '': + if byte_range.strip() == "": return None, None m = BYTE_RANGE_RE.match(byte_range) if not m: - raise ValueError('Invalid byte range %s' % byte_range) + raise ValueError("Invalid byte range %s" % byte_range) first, last = [x and int(x) for x in m.groups()] if last and last < first: - raise ValueError('Invalid byte range %s' % byte_range) + raise ValueError("Invalid byte range %s" % byte_range) return first, last -class CustomRequestHandler (SimpleHTTPRequestHandler): +class CustomRequestHandler(SimpleHTTPRequestHandler): """ Creating a small HTTP request server """ + def send_head(self): - if 'Range' not in self.headers: + if "Range" not in self.headers: self.range = None return SimpleHTTPRequestHandler.send_head(self) try: - self.range = parse_byte_range(self.headers['Range']) + self.range = parse_byte_range(self.headers["Range"]) except ValueError: - self.send_error(400, 'Invalid byte range') + self.send_error(400, "Invalid byte range") return None first, last = self.range @@ -72,28 +73,27 @@ def send_head(self): f = None ctype = self.guess_type(path) try: - f = open(path, 'rb') + f = open(path, "rb") except IOError: - self.send_error(404, 'File not found') + self.send_error(404, "File not found") return None fs = os.fstat(f.fileno()) file_len = fs[6] if first >= file_len: - self.send_error(416, 'Requested Range Not Satisfiable') + self.send_error(416, "Requested Range Not Satisfiable") return None self.send_response(206) - self.send_header('Content-type', ctype) + self.send_header("Content-type", ctype) if last is None or last >= file_len: last = file_len - 1 response_length = last - first + 1 - self.send_header('Content-Range', - 'bytes %s-%s/%s' % (first, last, file_len)) - self.send_header('Content-Length', str(response_length)) - self.send_header('Last-Modified', self.date_time_string(fs.st_mtime)) + self.send_header("Content-Range", "bytes %s-%s/%s" % (first, last, file_len)) + self.send_header("Content-Length", str(response_length)) + self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) self.end_headers() return f @@ -107,11 +107,11 @@ def copyfile(self, source, outputfile): copy_byte_range(source, outputfile, start, stop) def end_headers(self): - self.send_header('Access-Control-Allow-Origin', '*') - self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS') - self.send_header('Access-Control-Expose-Headers', '*') - self.send_header('Accept-Ranges', 'bytes') - self.send_header('Content-Type', 'application/octet-stream') + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS") + self.send_header("Access-Control-Expose-Headers", "*") + self.send_header("Accept-Ranges", "bytes") + self.send_header("Content-Type", "application/octet-stream") SimpleHTTPRequestHandler.end_headers(self) def translate_path(self, path): @@ -122,8 +122,9 @@ def translate_path(self, path): class DevServer(HTTPServer): - def __init__(self, base_path, server_address, - RequestHandlerClass=CustomRequestHandler): + def __init__( + self, base_path, server_address, RequestHandlerClass=CustomRequestHandler + ): self.base_path = base_path HTTPServer.__init__(self, server_address, RequestHandlerClass) @@ -144,11 +145,13 @@ def serve(data_path, **kwargs): the dev server, default to localhost """ print("=============================================") - print("Warning: \n" - "This is a development environment.\n" - "This is not recommended for production.") - port = kwargs.get('port', 8080) - host = kwargs.get('host', "localhost") + print( + "Warning: \n" + "This is a development environment.\n" + "This is not recommended for production." + ) + port = kwargs.get("port", 8080) + host = kwargs.get("host", "localhost") # data_path = kwargs.get('path', ".") # print('data', data_path) # dir_path = os.path.join(os.path.dirname(__file__), data_path) @@ -156,7 +159,7 @@ def serve(data_path, **kwargs): # print('relative ', os.path.relpath(data_path, os.getcwd())) # print('join', os.path.join(os.getcwd(), data_path)) httpd = DevServer(data_path, (host, port)) - server = f'http://{host}:{port}' + server = f"http://{host}:{port}" print("=============================================") print(f'Server is now running at \n "{server}"') httpd.serve_forever() diff --git a/jbrowse_jupyter/jbrowse_config.py b/jbrowse_jupyter/jbrowse_config.py index e49a869..a1bc2d6 100644 --- a/jbrowse_jupyter/jbrowse_config.py +++ b/jbrowse_jupyter/jbrowse_config.py @@ -1,6 +1,7 @@ import IPython from jbrowse_jupyter.util import ( - is_url, get_default, + is_url, + get_default, guess_file_name, get_name, ) @@ -10,7 +11,7 @@ check_track_data, get_from_config_adapter, guess_display_type, - make_url_colab_jupyter + make_url_colab_jupyter, ) @@ -31,26 +32,25 @@ def create(view_type="LGV", **kwargs): :raises TypeError: if view type is not `LGV` or `CGV` """ available_genomes = {"hg19", "hg38"} - conf = kwargs.get('conf', {}) - genome = kwargs.get('genome', "empty") + conf = kwargs.get("conf", {}) + genome = kwargs.get("genome", "empty") view = view_type # view type (LGV or CGV) # make it backwards compatible if view_type == "view" or view_type == "conf": view = "LGV" if view != "LGV" and view != "CGV": - raise TypeError(f'Currently not supporting view_type: {view}.') + raise TypeError(f"Currently not supporting view_type: {view}.") # configuration # 1) genomes available # 2) with own conf obj OR # 3) empty default config to customize) - no_configuration = (genome != "empty" and not conf) + no_configuration = genome != "empty" and not conf # Check passed genome is available message1 = "is not a valid default genome to view" message2 = "Choose from hg19 or hg38 or pass your own conf" if genome not in available_genomes and no_configuration: - raise TypeError( - f'"{genome}" {message1}.{message2}.') + raise TypeError(f'"{genome}" {message1}.{message2}.') # genome if genome in available_genomes: conf = get_default(genome, view) @@ -71,6 +71,7 @@ class JBrowseConfig: https://jbrowse.org/storybook/cgv/main/ """ + def __init__(self, view="LGV", conf=None): """ Initializes class. @@ -82,42 +83,34 @@ def __init__(self, view="LGV", conf=None): in_colab_notebook = False in_jupyter_notebook = False try: - import google.colab.output # noqa + import google.colab.output # noqa + in_colab_notebook = True - except: # noqa + except: # noqa in_colab_notebook = False try: - shell = IPython.get_ipython().__class__.__name__ # noqa - if shell == 'ZMQInteractiveShell': # noqa + shell = IPython.get_ipython().__class__.__name__ # noqa + if shell == "ZMQInteractiveShell": # noqa in_jupyter_notebook = True else: in_jupyter_notebook = False - except: # noqa + except: # noqa in_jupyter_notebook = False # ===================== view_default = { - "id": 'linearGenomeView', - "type": 'LinearGenomeView', - "tracks": [] + "id": "linearGenomeView", + "type": "LinearGenomeView", + "tracks": [], } if view != "LGV" and view == "CGV": - view_default = { - "id": 'circularView', - "type": 'CircularView', - "tracks": [] - } + view_default = {"id": "circularView", "type": "CircularView", "tracks": []} default = { "assembly": {}, "tracks": [], - "defaultSession": { - "name": "default-session", - "view": view_default - }, + "defaultSession": {"name": "default-session", "view": view_default}, "aggregateTextSearchAdapters": [], "location": "", - "configuration": { - "theme": {} - }, + "configuration": {"theme": {}}, } if conf is not None: for r in default.keys(): @@ -189,8 +182,9 @@ def get_assembly(self): def get_assembly_name(self): # Returns the assembly name. - assembly_error = "Can not get assembly name. " \ - "Please configure the assembly first." + assembly_error = ( + "Can not get assembly name. " "Please configure the assembly first." + ) if self.get_assembly(): return self.get_assembly()["name"] else: @@ -225,38 +219,37 @@ def set_assembly(self, assembly_data, **kwargs): :raises TypeError: adapter used for file type is not supported or recognized """ - overwrite = kwargs.get('overwrite', False) - indx = kwargs.get('index', "defaultIndex") - err = 'assembly is already set, set overwrite to True to overwrite' + overwrite = kwargs.get("overwrite", False) + indx = kwargs.get("index", "defaultIndex") + err = "assembly is already set, set overwrite to True to overwrite" if self.get_assembly() and not overwrite: raise TypeError(err) - aliases = kwargs.get('aliases', []) - refname_aliases = kwargs.get('refname_aliases', {}) + aliases = kwargs.get("aliases", []) + refname_aliases = kwargs.get("refname_aliases", {}) if is_url(assembly_data): - if indx != 'defaultIndex': + if indx != "defaultIndex": if not is_url(indx) and not self.jupyter: - raise TypeError(f'Path for {assembly_data} ' - "is used in an unsupported environment." - "Paths are supported in Jupyter" - " notebooks and Jupyter lab." - "Please use a url for your assembly " - "data. You can check out our local " - "file support docs for more information") - assembly_adapter = guess_adapter_type(assembly_data, - 'uri', - indx, - **kwargs) - name = kwargs.get('name', get_name(assembly_data)) - if (assembly_adapter["type"] == "UNKNOWN"): + raise TypeError( + f"Path for {assembly_data} " + "is used in an unsupported environment." + "Paths are supported in Jupyter" + " notebooks and Jupyter lab." + "Please use a url for your assembly " + "data. You can check out our local " + "file support docs for more information" + ) + assembly_adapter = guess_adapter_type(assembly_data, "uri", indx, **kwargs) + name = kwargs.get("name", get_name(assembly_data)) + if assembly_adapter["type"] == "UNKNOWN": raise TypeError("Adapter type is not recognized") - if (assembly_adapter["type"] == "UNSUPPORTED"): + if assembly_adapter["type"] == "UNSUPPORTED": raise TypeError("Adapter type is not supported") assembly_config = { "name": name, "sequence": { "type": "ReferenceSequenceTrack", - "trackId": f'{name}-ReferenceSequenceTrack', - "adapter": assembly_adapter + "trackId": f"{name}-ReferenceSequenceTrack", + "adapter": assembly_adapter, }, "aliases": aliases, "refNameAliases": refname_aliases, @@ -264,40 +257,46 @@ def set_assembly(self, assembly_data, **kwargs): self.config["assembly"] = assembly_config else: if not self.jupyter: - raise TypeError(f'Path {assembly_data} for assembly data ' - "is used in an unsupported environment." - "Paths are supported in Jupyter notebooks" - " and Jupyter lab.Please use a url for " - "your assembly data. You can check out " - "our local file support docs for more " - "information") - if indx != 'defaultIndex' and not is_url(indx): + raise TypeError( + f"Path {assembly_data} for assembly data " + "is used in an unsupported environment." + "Paths are supported in Jupyter notebooks" + " and Jupyter lab.Please use a url for " + "your assembly data. You can check out " + "our local file support docs for more " + "information" + ) + if indx != "defaultIndex" and not is_url(indx): if not self.jupyter: - raise TypeError("Paths are used in an " - "unsupported environment." - "Paths are supported in Jupyter" - " notebooks and Jupyter lab." - "Please use a urls for your assembly" - " and index data. You can check out " - "our local file support docs for more" - " information") - assembly_adapter = guess_adapter_type(assembly_data, - 'localPath', - indx, - colab=self.colab, - nb_port=self.nb_port, - nb_host=self.nb_host) - name = kwargs.get('name', get_name(assembly_data)) - if (assembly_adapter["type"] == "UNKNOWN"): + raise TypeError( + "Paths are used in an " + "unsupported environment." + "Paths are supported in Jupyter" + " notebooks and Jupyter lab." + "Please use a urls for your assembly" + " and index data. You can check out " + "our local file support docs for more" + " information" + ) + assembly_adapter = guess_adapter_type( + assembly_data, + "localPath", + indx, + colab=self.colab, + nb_port=self.nb_port, + nb_host=self.nb_host, + ) + name = kwargs.get("name", get_name(assembly_data)) + if assembly_adapter["type"] == "UNKNOWN": raise TypeError("Adapter type is not recognized") - if (assembly_adapter["type"] == "UNSUPPORTED"): + if assembly_adapter["type"] == "UNSUPPORTED": raise TypeError("Adapter type is not supported") assembly_config = { "name": name, "sequence": { "type": "ReferenceSequenceTrack", - "trackId": f'{name}-ReferenceSequenceTrack', - "adapter": assembly_adapter + "trackId": f"{name}-ReferenceSequenceTrack", + "adapter": assembly_adapter, }, "aliases": aliases, "refNameAliases": refname_aliases, @@ -309,8 +308,8 @@ def set_assembly(self, assembly_data, **kwargs): def get_reference_track(self): # Returns the reference track for the configured assembly. assembly_name = self.get_assembly_name() - configuration = f'{assembly_name}-ReferenceSequenceTrack' - conf_str = f'{configuration}-LinearReferenceSequenceDisplay' + configuration = f"{assembly_name}-ReferenceSequenceTrack" + conf_str = f"{configuration}-LinearReferenceSequenceDisplay" return { "type": "ReferenceSequenceTrack", "configuration": configuration, @@ -331,17 +330,13 @@ def get_track_display(self, track): "type": track_type, "configuration": track_id, "displays": [ - { - "type": display_type, - "configuration": f'{track_id}-{display_type}' - } - ] + {"type": display_type, "configuration": f"{track_id}-{display_type}"} + ], } def get_track(self, track_name): # Return the list of track configurations with that name - tracks = [track for track in self.get_tracks() if track["name"] - == track_name] + tracks = [track for track in self.get_tracks() if track["name"] == track_name] return tracks def get_tracks(self): @@ -379,9 +374,9 @@ def add_df_track(self, track_data, name, **kwargs): raise TypeError("Can not add a data frame track to a CGV conf.") check_track_data(track_data) - overwrite = kwargs.get('overwrite', False) + overwrite = kwargs.get("overwrite", False) assembly_name = self.get_assembly_name() - track_id = kwargs.get('track_id', f'{assembly_name}-{name}') + track_id = kwargs.get("track_id", f"{assembly_name}-{name}") current_tracks = self.config["tracks"] # if score column is present => QuantitativeTrack, else FeatureTrack track_type = "FeatureTrack" @@ -394,18 +389,17 @@ def add_df_track(self, track_data, name, **kwargs): "trackId": track_id, "name": name, "assemblyNames": [assembly_name], - "adapter": adapter + "adapter": adapter, } err = ( f'track with trackId: "{track_id}" already exists in config.', - 'Set overwrite to True if you want to overwrite it.' + "Set overwrite to True if you want to overwrite it.", ) if track_id in self.tracks_ids_map.keys() and not overwrite: raise TypeError(err) if track_id in self.tracks_ids_map.keys() and overwrite: # delete track and overwrite it - current_tracks = [ - t for t in current_tracks if t["trackId"] != track_id] + current_tracks = [t for t in current_tracks if t["trackId"] != track_id] current_tracks.append(df_track_config) self.config["tracks"] = current_tracks @@ -437,136 +431,149 @@ def add_track(self, data, **kwargs): :raises TypeError: Paths are only supported in jupyter. """ if not data: - raise TypeError( - "Track data is required. None was provided.") + raise TypeError("Track data is required. None was provided.") if not self.get_assembly(): raise Exception("Please set the assembly before adding a track.") assembly_names = [self.get_assembly_name()] - name = kwargs.get('name', guess_file_name(data)) - index = kwargs.get('index', "defaultIndex") - overwrite = kwargs.get('overwrite', False) + name = kwargs.get("name", guess_file_name(data)) + index = kwargs.get("index", "defaultIndex") + overwrite = kwargs.get("overwrite", False) current_tracks = self.get_tracks() if is_url(data): # default to uri protocol until local files enabled - if not is_url(index) and index != 'defaultIndex': + if not is_url(index) and index != "defaultIndex": if not self.jupyter: - raise TypeError(f'Path {index} for index is used in an ' - "unsupported environment. Paths are " - "supported in Jupyter notebooks and Jupy" - "ter lab.Please use a url for your " - "assembly data. You can check out " - "our local file support docs for more " - "information") + raise TypeError( + f"Path {index} for index is used in an " + "unsupported environment. Paths are " + "supported in Jupyter notebooks and Jupy" + "ter lab.Please use a url for your " + "assembly data. You can check out " + "our local file support docs for more " + "information" + ) else: - adapter = guess_adapter_type(data, 'localPath', index, - colab=self.colab, - nb_port=self.nb_port, - nb_host=self.nb_host) + adapter = guess_adapter_type( + data, + "localPath", + index, + colab=self.colab, + nb_port=self.nb_port, + nb_host=self.nb_host, + ) else: - adapter = guess_adapter_type(data, 'uri', index) + adapter = guess_adapter_type(data, "uri", index) # adapter = guess_adapter_type(data, 'uri', index) - if (adapter["type"] == "UNKNOWN"): + if adapter["type"] == "UNKNOWN": raise TypeError("Adapter type is not recognized") - if (adapter["type"] == "UNSUPPORTED"): + if adapter["type"] == "UNSUPPORTED": raise TypeError("Adapter type is not supported") # get sequence adapter for cram adapter track if adapter["type"] == "CramAdapter": extra_config = self.get_assembly()["sequence"]["adapter"] adapter["sequenceAdapter"] = extra_config - t_type = kwargs.get('track_type', - guess_track_type(adapter["type"])) - supported_track_types = set({ - 'AlignmentsTrack', - 'QuantitativeTrack', - 'VariantTrack', - 'FeatureTrack', - 'ReferenceSequenceTrack' - }) + t_type = kwargs.get("track_type", guess_track_type(adapter["type"])) + supported_track_types = set( + { + "AlignmentsTrack", + "QuantitativeTrack", + "VariantTrack", + "FeatureTrack", + "ReferenceSequenceTrack", + } + ) if t_type not in supported_track_types: raise TypeError(f'Track type: "{t_type}" is not supported.') - default_track_id = f'{self.get_assembly_name()}-{name}' - track_id = kwargs.get('track_id', default_track_id) + default_track_id = f"{self.get_assembly_name()}-{name}" + track_id = kwargs.get("track_id", default_track_id) track_config = { "type": t_type, "trackId": track_id, "name": name, "assemblyNames": assembly_names, - "adapter": adapter + "adapter": adapter, } if track_id in self.tracks_ids_map.keys() and not overwrite: raise TypeError( ( f'track with trackId: "{track_id}" already exists in' - f'config. Set overwrite to True to overwrite it.') + f"config. Set overwrite to True to overwrite it." ) + ) if track_id in self.tracks_ids_map.keys() and overwrite: - current_tracks = [ - t for t in current_tracks if t["trackId"] != track_id] + current_tracks = [t for t in current_tracks if t["trackId"] != track_id] current_tracks.append(track_config) self.config["tracks"] = current_tracks self.tracks_ids_map[track_id] = track_config else: if not self.jupyter: - raise TypeError(f'Path {data} for track data ' - "is used in an unsupported environment." - "Paths are supported in Jupyter notebooks" - " and Jupyter lab.Please use a url for " - "your assembly data. You can check out " - "our local file support docs for more " - "information") - if not is_url(index) and index != 'defaultIndex': + raise TypeError( + f"Path {data} for track data " + "is used in an unsupported environment." + "Paths are supported in Jupyter notebooks" + " and Jupyter lab.Please use a url for " + "your assembly data. You can check out " + "our local file support docs for more " + "information" + ) + if not is_url(index) and index != "defaultIndex": if not self.jupyter: - raise TypeError(f'Path {index} for index is used in an ' - "unsupported environment.Paths are " - "supported in Jupyter notebooks and Jupyte" - "r lab.Please use a url for your assembly " - "data. You can check out our local file " - "support docs for more information") - adapter = guess_adapter_type(data, 'localPath', - index, - colab=self.colab, - nb_port=self.nb_port, - nb_host=self.nb_host - ) - if (adapter["type"] == "UNKNOWN"): + raise TypeError( + f"Path {index} for index is used in an " + "unsupported environment.Paths are " + "supported in Jupyter notebooks and Jupyte" + "r lab.Please use a url for your assembly " + "data. You can check out our local file " + "support docs for more information" + ) + adapter = guess_adapter_type( + data, + "localPath", + index, + colab=self.colab, + nb_port=self.nb_port, + nb_host=self.nb_host, + ) + if adapter["type"] == "UNKNOWN": raise TypeError("Adapter type is not recognized") - if (adapter["type"] == "UNSUPPORTED"): + if adapter["type"] == "UNSUPPORTED": raise TypeError("Adapter type is not supported") # get sequence adapter for cram adapter track if adapter["type"] == "CramAdapter": extra_config = self.get_assembly()["sequence"]["adapter"] adapter["sequenceAdapter"] = extra_config - t_type = kwargs.get('track_type', - guess_track_type(adapter["type"])) - supported_track_types = set({ - 'AlignmentsTrack', - 'QuantitativeTrack', - 'VariantTrack', - 'FeatureTrack', - 'ReferenceSequenceTrack' - }) + t_type = kwargs.get("track_type", guess_track_type(adapter["type"])) + supported_track_types = set( + { + "AlignmentsTrack", + "QuantitativeTrack", + "VariantTrack", + "FeatureTrack", + "ReferenceSequenceTrack", + } + ) if t_type not in supported_track_types: raise TypeError(f'Track type: "{t_type}" is not supported.') - default_track_id = f'{self.get_assembly_name()}-{name}' - track_id = kwargs.get('track_id', default_track_id) + default_track_id = f"{self.get_assembly_name()}-{name}" + track_id = kwargs.get("track_id", default_track_id) track_config = { "type": t_type, "trackId": track_id, "name": name, "assemblyNames": assembly_names, - "adapter": adapter + "adapter": adapter, } if track_id in self.tracks_ids_map.keys() and not overwrite: raise TypeError( ( f'track with trackId: "{track_id}" already exists in' - f'config. Set overwrite to True to overwrite it.') + f"config. Set overwrite to True to overwrite it." ) + ) if track_id in self.tracks_ids_map.keys() and overwrite: - current_tracks = [ - t for t in current_tracks if t["trackId"] != track_id] + current_tracks = [t for t in current_tracks if t["trackId"] != track_id] current_tracks.append(track_config) self.config["tracks"] = current_tracks @@ -589,27 +596,23 @@ def delete_track(self, track_id): current_tracks = self.get_tracks() if track_id not in self.tracks_ids_map.keys(): raise TypeError( - ( - f'track with trackId: "{track_id}" does not exist in' - f'config.') - ) + (f'track with trackId: "{track_id}" does not exist in' f"config.") + ) else: - new_tracks = [ - t for t in current_tracks if t["trackId"] != track_id] + new_tracks = [t for t in current_tracks if t["trackId"] != track_id] self.config["tracks"] = new_tracks # clear from default session default_sess = self.get_default_session() tracks_sess = default_sess["view"]["tracks"] - new_tracks_sess = [ - t for t in tracks_sess if t["configuration"] != track_id] - if (self.view == "CGV"): + new_tracks_sess = [t for t in tracks_sess if t["configuration"] != track_id] + if self.view == "CGV": self.config["defaultSession"] = { "name": "my session", "view": { "id": "circularView", "type": "CircularView", - "tracks": new_tracks_sess - } + "tracks": new_tracks_sess, + }, } else: self.config["defaultSession"] = { @@ -617,8 +620,8 @@ def delete_track(self, track_id): "view": { "id": "LinearGenomeView", "type": "LinearGenomeView", - "tracks": new_tracks_sess - } + "tracks": new_tracks_sess, + }, } # ======= location =========== @@ -632,7 +635,7 @@ def set_location(self, location): :param str location: location, syntax 'refName:start-end' :raises TypeError: if view is CGV, location not supported in CGV """ - if (self.view == 'CGV'): + if self.view == "CGV": raise TypeError("Location is not available to set on a CGV") else: self.config["location"] = location @@ -655,22 +658,21 @@ def set_default_session(self, tracks_ids, display_assembly=True): raise Exception(err) reference_track = {} tracks_configs = [] - if (display_assembly): + if display_assembly: reference_track = self.get_reference_track() tracks_configs.append(reference_track) - tracks_to_display = [ - t for t in self.get_tracks() if t["trackId"] in tracks_ids] + tracks_to_display = [t for t in self.get_tracks() if t["trackId"] in tracks_ids] # guess the display type for t in tracks_to_display: tracks_configs.append(self.get_track_display(t)) - if (self.view == "CGV"): + if self.view == "CGV": self.config["defaultSession"] = { "name": "my session", "view": { "id": "circularView", "type": "CircularView", - "tracks": tracks_configs - } + "tracks": tracks_configs, + }, } else: self.config["defaultSession"] = { @@ -678,8 +680,8 @@ def set_default_session(self, tracks_ids, display_assembly=True): "view": { "id": "LinearGenomeView", "type": "LinearGenomeView", - "tracks": tracks_configs - } + "tracks": tracks_configs, + }, } def get_default_session(self): @@ -691,8 +693,7 @@ def get_text_search_adapters(self): # Returns the aggregateTextSearchAdapters in the config return self.config["aggregateTextSearchAdapters"] - def add_text_search_adapter(self, ix, - ixx, meta, adapter_id=None): + def add_text_search_adapter(self, ix, ixx, meta, adapter_id=None): """ Adds an aggregate trix text search adapter. Currently not available for Circular Genome View @@ -715,56 +716,50 @@ def add_text_search_adapter(self, ix, if not self.get_assembly(): raise Exception(err) local = is_url(ix) and is_url(ixx) and is_url(meta) - if (local and not self.jupyter): - TypeError(f'Paths for "{ix},{ixx},and {meta}"' - " are used in an unsupported environment. Paths are " - "supported in Jupyter notebooks and Jupyter lab.Please" - " use a url for your assembly data. You can check out" - " our local file support docs for more information") + if local and not self.jupyter: + TypeError( + f'Paths for "{ix},{ixx},and {meta}"' + " are used in an unsupported environment. Paths are " + "supported in Jupyter notebooks and Jupyter lab.Please" + " use a url for your assembly data. You can check out" + " our local file support docs for more information" + ) if self.view == "CGV": raise TypeError("Text Searching not currently available in CGV") assembly_name = self.get_assembly_name() - default_id = f'{assembly_name}-{guess_file_name(ix)}-index' + default_id = f"{assembly_name}-{guess_file_name(ix)}-index" text_id = default_id if adapter_id is None else adapter_id text_search_adapter = { "type": "TrixTextSearchAdapter", "textSearchAdapterId": text_id, "ixFilePath": { - "uri": make_url_colab_jupyter( - ix, - colab=self.colab, - nb_host=self.nb_host, - nb_port=self.nb_port + "uri": make_url_colab_jupyter( + ix, colab=self.colab, nb_host=self.nb_host, nb_port=self.nb_port ), "locationType": "UriLocation", }, "ixxFilePath": { "uri": make_url_colab_jupyter( - ixx, - colab=self.colab, - nb_host=self.nb_host, - nb_port=self.nb_port + ixx, colab=self.colab, nb_host=self.nb_host, nb_port=self.nb_port ), "locationType": "UriLocation", }, "metaFilePath": { "uri": make_url_colab_jupyter( - meta, - colab=self.colab, - nb_host=self.nb_host, - nb_port=self.nb_port + meta, colab=self.colab, nb_host=self.nb_host, nb_port=self.nb_port ), "locationType": "UriLocation", }, - "assemblyNames": [assembly_name] + "assemblyNames": [assembly_name], } adapters = self.get_text_search_adapters() exists = [a for a in adapters if a["textSearchAdapterId"] == text_id] if len(exists) > 0: - raise TypeError("Adapter already exists for given adapterId: " - f'{text_id}.Provide a different adapter_id' - ) + raise TypeError( + "Adapter already exists for given adapterId: " + f"{text_id}.Provide a different adapter_id" + ) adapters.append(text_search_adapter) self.config["aggregateTextSearchAdapters"] = adapters @@ -773,8 +768,7 @@ def get_theme(self): subconfiguration = self.config["configuration"] return subconfiguration["theme"] - def set_theme(self, primary, - secondary=None, tertiary=None, quaternary=None): + def set_theme(self, primary, secondary=None, tertiary=None, quaternary=None): """ Sets the theme in the configuration. Accepts up to 4 hexadecimal colors. @@ -787,25 +781,11 @@ def set_theme(self, primary, :param str tertiary: (optional) tertiary color :param str quaternary: (optional) quaternary color """ - palette = { - "primary": { - "main": primary - } - } + palette = {"primary": {"main": primary}} if secondary: - palette["secondary"] = { - "main": secondary - } + palette["secondary"] = {"main": secondary} if tertiary: - palette["tertiary"] = { - "main": tertiary - } + palette["tertiary"] = {"main": tertiary} if quaternary: - palette["quaternary"] = { - "main": quaternary - } - self.config["configuration"] = { - "theme": { - "palette": palette - } - } + palette["quaternary"] = {"main": quaternary} + self.config["configuration"] = {"theme": {"palette": palette}} diff --git a/jbrowse_jupyter/tracks.py b/jbrowse_jupyter/tracks.py index f9f979d..74f201d 100644 --- a/jbrowse_jupyter/tracks.py +++ b/jbrowse_jupyter/tracks.py @@ -13,18 +13,17 @@ def make_location(location, protocol, **kwargs): :raises ValueError: if a protocol other than `uri` is used. """ - in_colab = kwargs.get('colab', False) - notebook_host = kwargs.get('nb_host', 8888) - notebook_port = kwargs.get('nb_port', "localhost") + in_colab = kwargs.get("colab", False) + notebook_host = kwargs.get("nb_host", 8888) + notebook_port = kwargs.get("nb_port", "localhost") if protocol == "uri": return {"uri": location, "locationType": "UriLocation"} elif protocol == "localPath": return { - "uri": make_url_colab_jupyter(location, - colab=in_colab, - nb_port=notebook_port, - nb_host=notebook_host), - "locationType": "UriLocation" + "uri": make_url_colab_jupyter( + location, colab=in_colab, nb_port=notebook_port, nb_host=notebook_host + ), + "locationType": "UriLocation", } else: raise TypeError(f"invalid protocol {protocol}") @@ -32,12 +31,12 @@ def make_location(location, protocol, **kwargs): def make_url_colab_jupyter(location, **kwargs): """Generates url from path based on env colab or jupyter""" - in_colab = kwargs.get('colab', False) - notebook_host = kwargs.get('nb_host', 8888) - notebook_port = kwargs.get('nb_port', "localhost") + in_colab = kwargs.get("colab", False) + notebook_host = kwargs.get("nb_host", 8888) + notebook_port = kwargs.get("nb_port", "localhost") if in_colab: return location - return f'http://{notebook_host}:{notebook_port}/files' + location + return f"http://{notebook_host}:{notebook_port}/files" + location def supported_track_type(track_type): @@ -103,8 +102,7 @@ def guess_track_type(adapter_type): return "FeatureTrack" -def guess_adapter_type(file_location, - protocol, index="defaultIndex", **kwargs): +def guess_adapter_type(file_location, protocol, index="defaultIndex", **kwargs): """ Creates location object given a location and a protocol. @@ -114,9 +112,9 @@ def guess_adapter_type(file_location, :return: the adapter track subconfiguration :rtype: obj """ - notebook_host = kwargs.get('nb_host', 8888) - notebook_port = kwargs.get('nb_port', "localhost") - in_colab = kwargs.get('colab', False) + notebook_host = kwargs.get("nb_host", 8888) + notebook_port = kwargs.get("nb_port", "localhost") + in_colab = kwargs.get("colab", False) bam = re.compile(r"\.bam$", re.IGNORECASE) bed = re.compile(r"\.bed$", re.IGNORECASE) bed_tabix = re.compile(r"\.bed\.b?gz$", re.IGNORECASE) @@ -142,17 +140,21 @@ def guess_adapter_type(file_location, if bool(re.search(bam, file_location)): return { "type": "BamAdapter", - "bamLocation": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "bamLocation": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), "index": { - "location": make_location(f"{file_location}.bai", - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "location": make_location( + f"{file_location}.bai", + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), "indexType": "CSI" if (index != "defaultIndex" and index.upper().endswith("CSI")) else "BAI", @@ -162,16 +164,20 @@ def guess_adapter_type(file_location, if bool(re.search(cram, file_location)): return { "type": "CramAdapter", - "cramLocation": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), - "craiLocation": make_location(f"{file_location}.crai", - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "cramLocation": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), + "craiLocation": make_location( + f"{file_location}.crai", + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), } # gff3 @@ -190,17 +196,21 @@ def guess_adapter_type(file_location, if bool(re.search(gff3_tabix, file_location)): return { "type": "Gff3TabixAdapter", - "gffGzLocation": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "gffGzLocation": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), "index": { - "location": make_location(f"{file_location}.tbi", - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "location": make_location( + f"{file_location}.tbi", + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), "indexType": "TBI", }, } @@ -209,11 +219,13 @@ def guess_adapter_type(file_location, if bool(re.search(vcf, file_location)): return { "type": "VcfAdapter", - "vcfLocation": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "vcfLocation": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), } # vcf idx @@ -226,17 +238,21 @@ def guess_adapter_type(file_location, if bool(re.search(vcf_gzp, file_location)): return { "type": "VcfTabixAdapter", - "vcfGzLocation": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "vcfGzLocation": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), "index": { - "location": make_location(f"{file_location}.tbi", - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "location": make_location( + f"{file_location}.tbi", + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), "indexType": "CSI" if (index != "defaultIndex" and index.upper().endswith("CSI")) else "TBI", @@ -247,11 +263,13 @@ def guess_adapter_type(file_location, if bool(re.search(big_wig, file_location)): return { "type": "BigWigAdapter", - "bigWigLocation": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "bigWigLocation": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), } # bed if bool(re.search(bed, file_location)): @@ -263,17 +281,21 @@ def guess_adapter_type(file_location, if bool(re.search(bed_tabix, file_location)): return { "type": "BedTabixAdapter", - "bedGzLocation": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "bedGzLocation": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), "index": { - "location": make_location(f"{file_location}.tbi", - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "location": make_location( + f"{file_location}.tbi", + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), "indexType": "CSI" if (index != "defaultIndex" and index.upper().endswith("CSI")) else "TBI", @@ -284,11 +306,13 @@ def guess_adapter_type(file_location, if bool(re.search(big_bed, file_location)): return { "type": "BigBedAdapter", - "bigBedLocation": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "bigBedLocation": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), } # fasta indexed @@ -296,47 +320,60 @@ def guess_adapter_type(file_location, fai = index if index != "defaultIndex" else f"{file_location}.fai" return { "type": "IndexedFastaAdapter", - "fastaLocation": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), - "faiLocation": make_location(fai, protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "fastaLocation": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), + "faiLocation": make_location( + fai, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), } # Bgzipped fasta if bool(re.search(fasta_gz, file_location)): return { "type": "BgzipFastaAdapter", - "fastaLocation": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), - "faiLocation": make_location(f"{file_location}.fai", - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), - "gziLocation": make_location(f"{file_location}.gzi", - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "fastaLocation": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), + "faiLocation": make_location( + f"{file_location}.fai", + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), + "gziLocation": make_location( + f"{file_location}.gzi", + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), } # twobit if bool(re.search(twobit, file_location)): return { "type": "TwoBitAdapter", - "twoBitLocation": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "twoBitLocation": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), } # sizes if bool(re.search(sizes, file_location)): @@ -347,43 +384,51 @@ def guess_adapter_type(file_location, if bool(re.search(nclist, file_location)): return { "type": "NCListAdapter", - "rootUrlTemplate": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "rootUrlTemplate": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), } # sparql if bool(re.search(sparql, file_location)): return { "type": "SPARQLAdapter", - "endpoint": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "endpoint": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), } # hic if bool(re.search(hic, file_location)): return { "type": "HicAdapter", - "hicLocation": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "hicLocation": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), } # paf if bool(re.search(paf, file_location)): return { "type": "PAFAdapter", - "pafLocation": make_location(file_location, - protocol, - colab=in_colab, - nb_host=notebook_host, - nb_port=notebook_port), + "pafLocation": make_location( + file_location, + protocol, + colab=in_colab, + nb_host=notebook_host, + nb_port=notebook_port, + ), } return { diff --git a/jbrowse_jupyter/util.py b/jbrowse_jupyter/util.py index 55a25a7..48b3a3c 100644 --- a/jbrowse_jupyter/util.py +++ b/jbrowse_jupyter/util.py @@ -1,7 +1,8 @@ import re import os import json -import pkg_resources +import importlib + import dash_jbrowse as jb from dash import html, Dash from urllib.parse import urlparse @@ -55,14 +56,14 @@ def get_name_regex(assembly_file): def get_default(name, view_type="LGV"): """Returns the configuration object given a genome name.""" - base = pkg_resources.resource_filename("jbrowse_jupyter") - file_name = f"{base}/{name}.json" if view_type == "CGV": - file_name = f"{base}/{name}_cgv.json" - conf = {} - with open(file_name) as json_data: - conf = json.load(json_data) - return conf + with importlib.resources.open_text( + "jbrowse_jupyter", f"{name}_cgv.json" + ) as file: + return json.load(file) + else: + with importlib.resources.open_text("jbrowse_jupyter", f"{name}.json") as file: + return json.load(file) def create_component(conf, **kwargs):