PKrMl~~fastgenomics/__init__.py""" FASTGenomics python helper """ from get_version import get_version __version__ = get_version(__file__) del get_version PKUSNFR R fastgenomics/app.pyimport jsonschema import json from pathlib import Path from .defaults import DEFAULT_APP_DIR from .parameters import FGParameters class FGApp(object): # files that are checked for existence that are not otherwise # explicitly used by the FGApp _mandatory_files = ["LICENSE", "LICENSE-THIRD-PARTY", "manifest.json"] def __init__(self, app_dir=DEFAULT_APP_DIR): if isinstance(app_dir, str): app_dir = Path(app_dir) self.app_dir = app_dir self.check_files() self.manifest = self.get_manifest() self.application = self.manifest["application"] self.default_parameters = FGParameters(self.application["parameters"]) self.app_type = self.application["type"] self.inputs = self.application["input"] self.outputs = self.application["output"] assert self.app_type in ["Calculation", "Visualization"] def get_manifest(self): manifest_file = self.app_dir / "manifest.json" manifest = json.loads(manifest_file.read_bytes()) check_manifest(manifest) return manifest def check_files(self): if not self.app_dir.exists(): raise FileNotFoundError(f"Could not find the App directory {self.app_dir}") not_found = [] for f in self._mandatory_files: absolute_path = self.app_dir / f if not absolute_path.exists(): not_found.append(f) if not_found: msg = [f'Could not find the following files in "{self.app_dir}":'] msg += [f"- {file}" for file in not_found] raise FileNotFoundError("\n".join(msg)) def check_manifest(config: dict): """ Asserts that the manifest (``manifest.json``) matches our JSON-Schema. If not a :py:exc:`jsonschema.ValidationError` will be raised. """ schema_file = Path(__file__).parent / "schemes" / "manifest_schema.json" schema = json.loads(schema_file.read_text()) jsonschema.validate(config, schema) input_types = config["application"]["input"] output_types = config["application"]["output"] def err_msg(x, y): return "'{}'-type not supported for {}-operations.".format(x, y) if input_types is not None: for name, properties in input_types.items(): if properties["type"] == "output_only": raise RuntimeError(err_msg(properties["type"], "input")) if output_types is not None: for name, properties in output_types.items(): if properties["type"] == "dataset_manifest": raise RuntimeError(err_msg(properties["type"], "output")) PKdsNa fastgenomics/data.pyfrom pathlib import Path from logging import getLogger import json from .defaults import DEFAULT_DATA_ROOT, INPUT_FILE_MAPPING logger = getLogger(f"fastgenomics.data") DATA_SUBDIRS = ["data", "config", "output", "summary"] MANDATORY_SUBDIRS = ["data", "config"] class FGData(object): """This class stores the paths to data structured according to the fastgenomics specification. It also loads the input file mappings and checks if the files exist. """ _subdirs = DATA_SUBDIRS _mandatory_files = MANDATORY_SUBDIRS def __init__( self, data_root=DEFAULT_DATA_ROOT, env_input_file_mapping=INPUT_FILE_MAPPING ): if isinstance(data_root, str): data_root = Path(data_root) self.root = data_root self.env_input_file_mapping = env_input_file_mapping self.check_files() self.paths = self.get_paths() self.input_file_mapping = self.get_input_file_mapping() self.parameters = self.get_parameters() def get_input_file_mapping(self): mapping_file = self.paths["config"] / "input_file_mapping.json" if self.env_input_file_mapping is not None and mapping_file.exists(): raise RuntimeError( f'Environmental variable INPUT_FILE_MAPPING was defined and a mapping file "{mapping_file}" was found. Please use just one of the abouve to define the input file mapping.' ) elif self.env_input_file_mapping is not None: logger.debug( 'Loading the file mapping from an environmental variable "INPUT_FILE_MAPPING".' ) mapping = self.env_input_file_mapping elif mapping_file.exists(): logger.debug( f'Loading the input file mapping from a file "{mapping_file}". The current version of FASTGenomics runtime uses an environmental variable INPUT_FILE_MAPPING to pass the information on file mapping and the file "input_file_mapping.json" should only be used for testing.' ) mapping = json.loads(mapping_file.read_text()) else: raise RuntimeError( f'Environmental variable INPUT_FILE_MAPPING was undefined and a mapping file was not found under "{mapping_file}". Please use one or the other to define the file mapping.' ) logger.debug(f'File mapping is: "{mapping}"') return mapping def get_paths(self): return {dir: self.root / dir for dir in self._subdirs} def get_parameters(self): params_file = self.paths["config"] / "parameters.json" if params_file.exists(): return json.loads(params_file.read_text()) else: return {} def check_files(self): if not self.root.exists(): raise FileNotFoundError( f"Could not find the data directory under {self.root}" ) not_found = [] for f in self._mandatory_files: absolute_path = self.root / f if not absolute_path.exists(): not_found.append(f) if not_found: msg = [ f'Could not find the following files or directories in "{self.root}":' ] msg += [f"- {file}" for file in not_found] raise FileNotFoundError("\n".join(msg)) PKnSNQllfastgenomics/defaults.pyimport os import json from pathlib import Path def get_mapping_from_env(var): mapping = os.environ.get(var, None) if mapping is None: return None else: return { key: os.path.expandvars(name) for key, name in json.loads(mapping).items() } # set default paths DEFAULT_APP_DIR = Path(os.path.expandvars(os.environ.get("FG_APP_DIR", "/app"))) DEFAULT_DATA_ROOT = Path( os.path.expandvars(os.environ.get("FG_DATA_ROOT", "/fastgenomics")) ) INPUT_FILE_MAPPING = get_mapping_from_env("INPUT_FILE_MAPPING") # summary key for the FGProcess.output SUMMARY_KEY = "summary" PK#oONFZfastgenomics/deprecated.pyfrom deprecated import deprecated from .process import FGProcess _PROCESS = None def get_process(): if _PROCESS is None: raise NameError( f"call set_paths(app_dir, data_root) before accessing the global process." ) else: return _PROCESS @deprecated(reason="Use FGProcess instead") def set_paths(app_dir, data_root): global _PROCESS _PROCESS = FGProcess(app_dir, data_root) _PROCESS.data.paths["app"] = app_dir @deprecated(reason="Use FGProcess.parameters instead") def get_parameter(key): return get_process().parameters[key] @deprecated(reason="Use FGProcess.input instead") def get_input_path(filename): return get_process().input[filename].path @deprecated(reason="Use FGProcess.output instead") def get_output_path(filename): return get_process().output[filename].path @deprecated(reason="Use FGProcess.summary or FGProcess.output['summary'] instead") def get_summary_path(): return get_process().output["summary"].path @deprecated(reason="Use FGProcess.data.paths instead") def get_paths(): return {**get_process().data.paths, "app": get_process().app.app_dir} @deprecated(reason="Use FGProcess.app.manifest instead") def get_app_manifest(): return get_process().app.manifest @deprecated(reason="Use FGProcess.parameters instead") def get_parameters(): return { name: param.value for name, param in get_process().parameters.parameter.items() } PKiYN # fastgenomics/io.py# coding: utf-8 from collections import OrderedDict from pathlib import Path import textwrap from logging import getLogger import os.path from .defaults import SUMMARY_KEY logger = getLogger("fastgenomics.common") class Files(object): def __getitem__(self, key): if key not in self.files: raise KeyError(f'Key "{key}" not defined in manifest.json of the App') else: return self.files[key] def __contains__(self, key): return key in self.files def __repr__(self): output = "Files:\n" for file in self.files.values(): output += textwrap.indent(file.__repr__(), " ") + "\n" return output def keys(self): return self.files.keys() class FilesInput(Files): def __init__(self, specs, root, mapping): self.files = OrderedDict() for name, spec in specs.items(): path = None isoptional = "optional" in spec and spec["optional"] if name in mapping: path = Path(root) / mapping[name] if not path.exists(): raise FileNotFoundError(f"File {name}: not found under {path}") elif isoptional: path = None else: raise KeyError( f'Non-optional file "{name}" missing from input_file_mapping.json' ) self.files[name] = FileInput( name=name, type=spec["type"], usage=spec["usage"], path=path, optional=isoptional, ) not_in_manifest = set(mapping) - set(specs) if not_in_manifest: logger.warning( f"Ignoring files defined in input_file_mapping.json: {not_in_manifest}" ) logger.debug(f"Files found in the manifest: {list(mapping)}") logger.debug(f"Files found in the input_file_mapping.json: {list(specs)}") class FilesOutput(Files): def __init__(self, specs, root, summary_path): self.files = OrderedDict() for name, spec in specs.items(): self.files[name] = FileOutput( name=name, type=spec["type"], usage=spec["usage"], path=Path(root) / spec["file_name"], ) self.files[SUMMARY_KEY] = FileOutput( name="summary", type="summary", usage="Summary file", path=summary_path ) class File(object): def __init__(self, name, type, usage, path): self.name = name self.type = type self.usage = usage self.path = path def __repr__(self): output = [f'"{arg.capitalize()}": {val}' for arg, val in vars(self).items()] return "\n".join(output) class FileInput(File): def __init__(self, name, type, usage, path, optional=False): super().__init__(name, type, usage, path) self.optional = optional def __repr__(self): return "\n".join( [super().__repr__(), "IO type: Input", f"Optional: {self.optional}"] ) class FileOutput(File): def __repr__(self): return "\n".join([super().__repr__(), "IO type: Output"]) PK#oON^&fastgenomics/parameters.pyimport copy class Parameter: type_mapping = { "float": (int, float), "integer": int, "bool": bool, "list": list, "dict": dict, "string": str, "enum": object, } def __init__(self, param: dict, name): self.name = name self.description = param["description"] self.type = self.type_mapping[param["type"]] self.enum = param.get("enum") self.optional = param.get("optional", False) self.value = param["default"] @property def value(self): return self._value @value.setter def value(self, val): if self.optional and val is None: self._value = val return if not isinstance(val, self.type): raise ValueError( f"Setting parameter {self.name} of type {self.type} with value {val}." ) elif self.enum and val not in self.enum: raise ValueError( f"Setting parameter {self.name} with a value {val} that is not in {self.enum}." ) self._value = val class FGParameters(object): def __init__(self, parameters: dict): self.parameter = {} for name, spec in parameters.items(): self.parameter[name] = Parameter(spec, name) def __getitem__(self, key): return self.parameter[key].value def keys(self): return self.parameter.keys() def update(self, values): for name, value in values.items(): if name not in self.parameter: raise KeyError(f"Parameter {name} not found in manifest.json.") self.parameter[name].value = value def copy(self): return copy.deepcopy(self) def check(self, name, checker, error_message): if not checker(self[name]): raise ValueError(f'Parameter "{name}" failed validation: {error_message}') PKsrRN*Vfastgenomics/process.pyfrom .app import FGApp from .data import FGData from .io import FilesInput, FilesOutput from .summary import FGSummary from .defaults import DEFAULT_APP_DIR, DEFAULT_DATA_ROOT, SUMMARY_KEY from logging import getLogger class FGProcess(object): def __init__(self, app_dir=DEFAULT_APP_DIR, data_dir=DEFAULT_DATA_ROOT): self.data = FGData(data_dir) self.app = FGApp(app_dir) self.parameters = self.app.default_parameters.copy() self.parameters.update(self.data.parameters) self.logger = getLogger(f"fastgenomics.process") # log the updated parameter values info = "\n".join(f"{k}:{v.value}" for k, v in self.parameters.parameter.items()) self.logger.info(f"Parameters: \n{info}") self.input = FilesInput( specs=self.app.inputs, root=self.data.paths["data"], mapping=self.data.input_file_mapping, ) self.output = FilesOutput( specs=self.app.outputs, root=self.data.paths["output"], summary_path=self.data.paths[SUMMARY_KEY] / "summary.md", ) self.summary = FGSummary( output=self.output[SUMMARY_KEY], params=self.parameters ) PK#oON£fastgenomics/summary.pyimport jinja2 from pathlib import Path class FGSummary(object): def __init__(self, output, params): if output.type != "summary": raise TypeError(f'Expected a summary file but got "{output.type}"') self.output = output.path self.params = params self.footnote = self.summary_footnote() self.template = None def summary_footnote(self): footnote = "\n### Parameters\n" for name, param in self.params.parameter.items(): if param.type == str: value = f'"{param.value}"' else: value = param.value footnote += f"* __{name}__ = `{value}` _({param.description})_\n" return footnote def render_template(self, temp_file, **kwargs): summary = temp_file.read_text() + self.footnote temp = jinja2.Template(summary, undefined=jinja2.StrictUndefined) return temp.render(kwargs) def write(self, **kwargs): if self.template is None: raise AttributeError( "Please specify the template before calling `write` by setting the `template` parameter." ) template = Path(self.template) if not template.exists(): raise FileNotFoundError( f'Could not find the summary template under "{template}". You can change the template location by modifying the `template` argument.' ) summary = self.render_template(template, **kwargs) self.output.write_text(summary) PKIgYNfastgenomics/testing.pydef cleanoutput(fg): """Deletes all the output files. Useful for cleanup before tests.""" for file in fg.output.keys(): path = fg.output[file].path if path.exists(): path.unlink() PK#oON!fastgenomics/external/__init__.pyPKK}NKR:: fastgenomics/external/anndata.pytry: import pandas as pd import numpy as np import scipy.sparse as sp from anndata import AnnData except ImportError as e: msg = f"Could not import some of the necessary modules ({e.name}). Please make sure to install anndata (https://github.com/theislab/anndata) with all its dependencies correctly (e.g. pandas, numpy, scipy)." raise ImportError(msg, name=e.name, path=e.path) raise e GENE_METADATA_DTYPE = {"entrez_id": str} CELL_METADATA_DTYPE = {"cell_id": str} EXPRESSION_MATRIX_DTYPE = { "entrez_id": "category", "cell_id": "category", "expression": np.float64, } def check_file_type(file, content_type): """Returns a path if the file is of requested FASTGenomics type, otherwise throws a TypeError.""" if file.type != content_type: raise TypeError( f'File "{file.name}" is of type "{file.type}" but expected "{content_type}".' ) def read(expr, cell_meta=None, gene_meta=None): """Reads an anndata object by composing three files together: `expression_matrix`, `cell_metadata` and `gene_metadata`.""" check_file_type(expr, content_type="expression_matrix") expr = pd.read_csv(expr.path, dtype=EXPRESSION_MATRIX_DTYPE) obs = read_cell_metadata(cell_meta, expr) var = read_gene_metadata(gene_meta, expr) counts = read_sparse_matrix(expr, obs, var) adata = AnnData(counts, obs=obs, var=var, dtype="float64") return adata def read_cell_metadata(cell_meta, expr): expr_cell_id = expr.cell_id.unique() if cell_meta is None: df = pd.DataFrame(data={"cell_id": expr_cell_id}) else: check_file_type(cell_meta, content_type="cell_metadata") df = pd.read_csv(cell_meta.path, dtype=CELL_METADATA_DTYPE) df["cell_id"] = df["cell_id"].astype("category") df.set_index("cell_id", inplace=True, drop=False) missing_ids = set(expr_cell_id) - set(df.index) if missing_ids: raise Exception( f'Some cell_id\'s were present in the expression matrix but not in "{cell_meta.path}": {missing_ids}.' ) return df def read_gene_metadata(gene_meta, expr): expr_entrez_id = expr.entrez_id.unique() if gene_meta is None: df = pd.DataFrame(data={"entrez_id": expr_entrez_id}) else: check_file_type(gene_meta, content_type="gene_metadata") df = pd.read_csv(gene_meta.path, dtype=GENE_METADATA_DTYPE) df["entrez_id"] = df["entrez_id"].astype("category") df.set_index("entrez_id", inplace=True, drop=False) missing_ids = set(expr_entrez_id) - set(df.index) if missing_ids: raise Exception( f'Some entrez_id\'s were present in the expression matrix but not in "{gene_meta.path}": {missing_ids}.' ) return df def read_sparse_matrix(expr, obs, var): cell_idx = pd.DataFrame( dict(cell_id=obs.index, cell_idx=np.arange(obs.shape[0])) ).set_index("cell_id") entrez_idx = pd.DataFrame( dict(entrez_id=var.index, entrez_idx=np.arange(var.shape[0])) ).set_index("entrez_id") expr = expr.merge(cell_idx, on="cell_id", copy=False) expr = expr.merge(entrez_idx, on="entrez_id", copy=False) counts = sp.coo_matrix( (expr.expression, (expr.cell_idx, expr.entrez_idx)), shape=(obs.shape[0], var.shape[0]), ).tocsr() return counts # Writing def write_exprs_csv(adata, csv_file): mat = adata.X.tocoo() df = pd.DataFrame.from_dict( dict( cell_id=adata.obs_names[mat.row], entrez_id=adata.var_names[mat.col], expression=mat.data, ) ) df.to_csv(csv_file, index=False) def write(adata, expr=None, cell_meta=None, gene_meta=None): if expr is not None: check_file_type(expr, content_type="expression_matrix") write_exprs_csv(adata, expr.path) if cell_meta is not None: check_file_type(cell_meta, content_type="cell_metadata") adata.obs.to_csv(cell_meta.path, index=False) if gene_meta is not None: check_file_type(gene_meta, content_type="gene_metadata") adata.var.to_csv(gene_meta.path, index=False) PKZM988)fastgenomics/schemes/manifest_schema.json{ "$schema": "http://json-schema.org/draft-04/schema", "required": [ "application", "schema" ], "definitions": { "input_entry": { "type": "object", "properties": { "type": { "description": "FASTGenomics Type of the file", "enum": [ "expression_matrix", "gene_metadata", "cell_metadata", "coordinates", "assignments", "data_quality", "batch_effects", "dense_matrix", "heatmap_info", "dataset_manifest", "output_only" ], "type": "string" }, "usage": { "description": "short description of the file usage", "examples": ["gene expression matrix", "classification of cells"], "type": "string" }, "optional": { "description": "marks an input file as optional, e.g., it doesn't have to be defined in the input_file_mapping nor be existing", "type": "boolean" } }, "required": ["type", "usage"] }, "output_entry": { "allOf": [ { "$ref": "#/definitions/input_entry"}, { "properties": { "file_name": { "description": "plain filename of the output-file without directory", "type": "string", "pattern": "^[a-zA-Z0-9_.]+$" } }, "required": ["file_name"] } ] }, "parameter_entry": { "type": "object", "properties": { "type": { "description": "Type of the value of the parameter", "enum": ["string", "integer", "float", "bool", "list", "dict", "enum"] }, "optional": { "description": "Accept null as parameter value in addition to values of the given type?", "type": "boolean" }, "description": { "description": "Description of the parameter", "type": "string" }, "enum": { "description": "Valid values of an enum type", "type": "array" }, "default": { "description": "Default value of the parameter" } }, "required": ["type", "description", "default"], "if": { "properties": { "type": { "enum": ["enum"] } } }, "then": { "required": ["enum"] } } }, "type": "object", "properties": { "schema": { "type": "string", "enum": ["1.0.0"], "description": "The version of the schema itself." }, "application": { "type": "object", "properties": { "author": { "type": "object", "properties": { "email": { "description": "E-mail address of app developer", "examples": ["john.doe@fastgenomics.org"], "type": "string" }, "name": { "description": "Name of the app developer", "examples": ["Jon Doe"], "type": "string" }, "organisation": { "description": "Organization of the developer", "examples": ["FASTGenomics"], "type": "string" } } }, "name": { "description": "The name of the application", "examples": ["Hello Genomics Sample App"], "type": "string" }, "type": { "description": "Type of the application", "enum": ["Calculation", "Visualization"] }, "description": { "description": "Description of the application - can be markdown", "type": "string" }, "demands": { "type": "array", "items": { "description": "Demands on the runtime environment", "enum": ["GPU"] } }, "input": { "type": "object", "patternProperties": { "^[a-zA-Z0-9_.]+$": { "$ref": "#/definitions/input_entry" } } }, "output": { "type": "object", "patternProperties": { "^[a-zA-Z0-9_.]+$": { "$ref": "#/definitions/output_entry" } } }, "parameters": { "type": "object", "patternProperties": { "^[a-zA-Z0-9_.]+$": { "$ref": "#/definitions/parameter_entry" } } } }, "required": ["author", "name", "type", "description", "demands", "input", "output", "parameters"] } } } PKZMB,fastgenomics/templates/docker-compose.yml.j2version: '3' # this file can be used to showcase the environment an app would see. # This file is for local development purposes only. FASTGenomics does not need to see this file. services: {{ app_name }}: build: context: . image: {% if docker_registry %}{{ docker_registry.rstrip('/') + '/' }}{% endif %}{{ app_name }}:dev volumes: - ./{{ sample_dir }}/config:/fastgenomics/config/:ro - ./{{ sample_dir }}/data:/fastgenomics/data/:ro {%- if app_type == 'Calculation' %} - ./{{ sample_dir }}/output:/fastgenomics/output/ - ./{{ sample_dir }}/summary:/fastgenomics/summary/ {% elif app_type == 'Visualization' %} ports: - "8000:8000" {% endif %} PKZMsb1fastgenomics/templates/input_file_mapping.json.j2{ {% for file_key, path_val in file_mapping.items() %} "{{ file_key }}": "{{ path_val }}"{{ "," if not loop.last }} {% endfor -%} } PKZM(~--$fastgenomics-2.0.7.dist-info/LICENSEMIT License Copyright (c) 2017 FASTGenomics Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!H>*RQ"fastgenomics-2.0.7.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,rzd&Y)r$[)T&UrPK!HpHg%fastgenomics-2.0.7.dist-info/METADATAX[s~ǯ@3(l8n6g II@]$EJ>I/9O҉T81(U5n! IJIv[0 w~{CM˭[/eVJs9*Εv2/[V(+2*JdaՏWןyfOݝy}3Pل'p"q YJiG7 {+mbT{%4;qcuτj$lx+W2eZ~넫,L7|oNq1dX) z,HeϫT"DXC^MRd㸂Fd(B "SOw ]©boɜ~ᗗTbs'U E_*M]u 䓞N*ipUHiH n貘kQV"S *s]D4zR /]UMx*K#@x4xѢΒ%hN7Rţ8 =]H7]$'"+]U:3{pS.UG7 yqTяD4Gg_@Ig_w)T#hO7(GޣCuRQ[[끐"oFJJvA'<*t!NA}pF}-.҇@ Z).rvPE1W}j;lJ;.Czlϸ[}.̈"Y i\ı^PvD8˄ONlBW|+$ncd/Efrely4sHoue(L%ST#sFCr!a.nR:2 jyYW4VWSf^~ֿ$󙰀@oHAK[w7NfZ?0q67:!󖫼w?܄X]T/mFnrq+$U(N`墼QuEa?n"qvK">U5Ux9 C(JBLESۘr5F*$b Dn S]0IԠDL6q/&aL!2 @4 p$ s$IhrQ9*A36c.LKc sOq3 棛?OF(" O"KqEMrWp=#vEa!MMzPIPZ=81'79 tQðG(:Ltf et#FT{clnvln8uw{v7=uQh؊ c_qkC}f$@d(ЂyFs6Ig{(ir Viܖ:IJ[`e"@dm5 P8y$ Q,]5T-h~nWdߌ0vUZt\g^565n` Y=)L\ehb=g5ǹi"ul[})T#τY@ME`WC*rCEd"o&|ÿ'] x^fgIpIN>6%?]BS r,06ˮ"1~|ZYAyTDk@Z=qO>}Ac㵟Iifrhk + 17Nx ;*GBD`Wm6K*+bX7\P@H-ά;7kXe*Bc׆v=A=tT'v1RiWPFG#2!'CN 5"vV혔϶5= @waKaH˅(U'O,UZJctk{{É&a L?%> štwJtg*p> tq0h0Ä> m0FZ4@nF݆%ЂJPK!H$pr2#fastgenomics-2.0.7.dist-info/RECORD}˒H}? X/^ xA7D =!Ǟ;?2=NaݠZ3TpB2. $]a_XPo8 jhq5bӜdmV7E=dSw8Jj}7 ճ7Q ;2jPp@բ`DƝx;6쐪)Fnչ$Yیn&yC|TU^1f9,$PtV&Frt*VrVv:5ؾLfS$"{Lu|7+j_ơWVڮ}z)7xn9wgO~_M ϸoh6mϜr.l!\ ˉ/jQQGM3lj'EUN{Ϝxj}wKn+vMUB,EdT ,h+Q ?PKrMl~~fastgenomics/__init__.pyPKUSNFR R fastgenomics/app.pyPKdsNa 7 fastgenomics/data.pyPKnSNQllsfastgenomics/defaults.pyPK#oONFZfastgenomics/deprecated.pyPKiYN # !fastgenomics/io.pyPK#oON^&-fastgenomics/parameters.pyPKsrRN*V5fastgenomics/process.pyPK#oON£:fastgenomics/summary.pyPKIgYN@fastgenomics/testing.pyPK#oON!Afastgenomics/external/__init__.pyPKK}NKR:: Bfastgenomics/external/anndata.pyPKZM988)Rfastgenomics/schemes/manifest_schema.jsonPKZMB,efastgenomics/templates/docker-compose.yml.j2PKZMsb1hfastgenomics/templates/input_file_mapping.json.j2PKZM(~--$hfastgenomics-2.0.7.dist-info/LICENSEPK!H>*RQ"]mfastgenomics-2.0.7.dist-info/WHEELPK!HpHg%mfastgenomics-2.0.7.dist-info/METADATAPK!H$pr2#vfastgenomics-2.0.7.dist-info/RECORDPKz