PK_MbQ<fastgenomics/__init__.py""" FASTGenomics python helper """ from get_version import get_version __version__ = get_version(__file__) del get_version PK_Mu` 11fastgenomics/_common.py""" FASTGenomics common IO helpers: Wraps manifest.json, input_file_mapping.json and parameters.json given by the FASTGenomics runtime. If you want to work without docker, you can set two environment variables to ease testing: ``APP_ROOT_DIR``: This path should contain manifest.json, normally this is /app. ``DATA_ROOT_DIR``: This path should contain you test data - normally, this is /fastgenomics. You can set them by environment variables or just call ``fastgenomics.common.set_paths(path_to_app, path_to_data_root)`` """ import os import json import typing as ty from pathlib import Path from logging import getLogger import jsonschema from ._resources import resource_bytes logger = getLogger('fastgenomics.common') # set default paths DEFAULT_APP_DIR = '/app' DEFAULT_DATA_ROOT = '/fastgenomics' # init cache _PATHS = {} _MANIFEST = {} _INPUT_FILE_MAPPING = {} ParameterValue = ty.Union[dict, list, bool, int, float, str, None] Parameters = ty.Dict[str, ParameterValue] PathsDict = ty.Dict[str, Path] FileMapping = ty.Dict[str, Path] class Parameter(ty.NamedTuple): """Parameter entry""" name: str type: str value: ty.Any # uses default for initialization or None default: ty.Any optional: bool enum: ty.Optional[ty.List[ty.Any]] description: str def check_paths(paths: PathsDict, raise_error: bool = True): """ checks, if main paths are existing along with the manifest.json and input_file_mapping.json """ # check base paths for dir_to_check in ['app', 'config', 'data']: path_entry = paths.get(dir_to_check) if path_entry is None or not path_entry.exists(): err_msg = f"Path to {path_entry} not found! Check paths!" if raise_error is True: raise FileNotFoundError(err_msg) else: logger.warning(err_msg) # check configs files paths_to_check = [paths['app'] / 'manifest.json'] if not os.environ.get('INPUT_FILE_MAPPING'): paths_to_check.append(paths['config'] / 'input_file_mapping.json') for path_entry in paths_to_check: if not path_entry.exists(): err_msg = f"`{path_entry}` does not exist! Please check paths and existence!" if raise_error is True: raise FileNotFoundError(err_msg) else: logger.warning(err_msg) def check_input_file_mapping(input_file_mapping: FileMapping): """checks the keys in input_file_mapping and existence of the files raises a KeyError on missing Key and FileNotFoundError on missing file """ manifest = get_app_manifest()["application"]['input'] not_in_manifest = set(input_file_mapping.keys()) - set(manifest.keys()) not_in_ifm = set(manifest.keys()) - set(input_file_mapping.keys()) optional = set([entry for entry, settings in manifest.items() if settings.get('optional', False) is True]) missing = not_in_ifm - optional # check keys if not_in_manifest: logger.warning(f"Ignoring Keys defined in input_file_mapping: {not_in_manifest}") if missing: raise KeyError(f"Non-optional keys not defined in input_file_mapping: {missing}") # check for existence for key, entry in input_file_mapping.items(): if not entry.exists(): if key in optional: logger.info(f"Optional file {entry} is not present and may cause an error - be aware!") else: raise FileNotFoundError(f"{entry}, defined in input_file_mapping, not found!") def str_to_path_file_mapping(relative_mapping: ty.Dict[str, str]) -> FileMapping: """maps the relative string paths given in input_file_mapping to absolute paths""" data_path = get_paths()['data'] absolute_mapping = {} for key, maybe_rel_path in relative_mapping.items(): expanded = Path(os.path.expandvars(maybe_rel_path)) if expanded.is_absolute(): absolute_mapping[key] = expanded else: absolute_mapping[key] = data_path / expanded return absolute_mapping def load_input_file_mapping() -> ty.Dict[str, str]: """helper function loading the input_file_mapping either from environment or from file""" # try to get input file mapping from environment empty_str = '{}' ifm_str = os.environ.get('INPUT_FILE_MAPPING', empty_str) source_str = "`INPUT_FILE_MAPPING` environment" # else use input_file_mapping file if ifm_str == empty_str: ifm_path = get_paths()['config'] / 'input_file_mapping.json' if not ifm_path.exists(): raise FileNotFoundError(f"Input file mapping {ifm_path} not found!") with open(get_paths()['config'] / 'input_file_mapping.json', encoding='utf-8') as f: ifm_str = f.read() source_str = ifm_path.name logger.info(f"Input file mapping loaded from {source_str}.") # decode json: try: ifm_dict = json.loads(ifm_str) except json.JSONDecodeError as e: e.msg = f"{source_str} is not valid JSON: {e.msg}" raise e return ifm_dict def get_input_file_mapping(check_mapping: bool = True) -> FileMapping: """returns the input_file_mapping either from environment `INPUT_FILE_MAPPING` or from config file""" global _INPUT_FILE_MAPPING if _INPUT_FILE_MAPPING: return _INPUT_FILE_MAPPING # load mapping ifm_dict = load_input_file_mapping() # convert into paths input_file_mapping = str_to_path_file_mapping(ifm_dict) # check existence if check_mapping: check_input_file_mapping(input_file_mapping) # update cache and return _INPUT_FILE_MAPPING = input_file_mapping return _INPUT_FILE_MAPPING def get_paths() -> PathsDict: """ safe getter for the runtime paths if paths are not initialized, it runs ``set_paths(DEFAULT_APP_DIR, DEFAULT_DATA_ROOT)`` """ if not _PATHS: from . import io io.set_paths() return _PATHS def assert_manifest_is_valid(config: dict): """ Asserts that the manifest (``manifest.json``) matches our JSON-Schema. If not a :py:exc:`jsonschema.ValidationError` will be raised. """ schema = json.loads(resource_bytes('schemes/manifest_schema.json')) jsonschema.validate(config, schema) input_types = config["application"]["input"] output_types = config["application"]["output"] err_msg = lambda x, y: "'{}'-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")) parameters = config["application"]["parameters"] if parameters is not None: for name, properties in parameters.items(): expected_type = properties["type"] enum = properties.get("enum") default_value = properties["default"] optional = properties.get("optional", False) warn_if_not_of_type(name, expected_type, enum, default_value, optional, is_default=True) def get_app_manifest() -> dict: """ Parses and returns the app manifest.json Raises: RuntimeError: If ``manifest.json`` does not exist. """ global _MANIFEST # use cache if _MANIFEST: return _MANIFEST manifest_file = get_paths()['app'] / 'manifest.json' if not manifest_file.exists(): err_msg = (f"App manifest {manifest_file} not found! " "Please provide a manifest.json in the application's root-directory.") raise RuntimeError(err_msg) try: config = json.loads(manifest_file.read_bytes()) assert_manifest_is_valid(config) # update cache _MANIFEST = config except json.JSONDecodeError: err_msg = f"App manifest {manifest_file} not a valid JSON-file - check syntax!" raise RuntimeError(err_msg) except jsonschema.ValidationError: err_msg = (f"App manifest {manifest_file} is not compatible with fastgenomics_py. " "Check the docs if your parameter- or I/O-types are supported.") raise RuntimeError(err_msg) return _MANIFEST def _update_param_value(param: Parameter, new_value: ty.Any): """helper function for updating the value of a Parameter instance""" return Parameter(name=param.name, type=param.type, value=new_value, default=param.default, optional=param.optional, enum=param.enum, description=param.description) def load_runtime_parameters() -> Parameters: """loads and returns the runtime parameters from parameters.json""" parameters_file = get_paths()['config'] / 'parameters.json' if not parameters_file.exists(): logger.info(f"No runtime parameters {parameters_file} found - using defaults.") return {} try: runtime_parameters = json.loads(parameters_file.read_bytes()) except json.JSONDecodeError: logger.error( f"Could not read {parameters_file} due to an unexpected error. " f"Please report this at https://github.com/FASTGenomics/fastgenomics-py/issues") return {} return runtime_parameters def load_parameters_from_manifest() -> ty.Dict[str, Parameter]: """returns the parameters section defined in the manifest.json as dict of Parameter """ param_section = get_app_manifest()["application"]['parameters'] return {name: Parameter(name=name, type=value['type'], value=value.get('default'), default=value.get('default'), optional=value.get('optional', False), enum=value.get('enum'), description=value['description']) for name, value in param_section.items()} def value_is_of_type(expected_type: str, enum: ty.Optional[list], value: ty.Any, optional: bool) -> bool: """tests, of a value is an instance of a given an expected type""" type_mapping = { 'float': (int, float), 'integer': int, 'bool': bool, 'list': list, 'dict': dict, 'string': str, 'enum': object, } mapped_type = type_mapping.get(expected_type) if mapped_type is None: raise ValueError(f"Unknown type to check: {expected_type}") if optional and value is None: return True if enum is not None: if expected_type != 'enum': raise ValueError(f"Enum provided but type is {expected_type}") return value in enum return isinstance(value, mapped_type) def check_parameter_types(parameters: Parameters): """checks the correct type of parameters as specified in the manifest.json""" manifest_parameters = load_parameters_from_manifest() for param_name, param in parameters.items(): manifest_param = manifest_parameters[param_name] # parameter types see manifest_schema.json # we do not throw an exception because having multi-value parameters is # common in some libraries, e.g. specify "red" or 24342 warn_if_not_of_type(name=param_name, expected_type=manifest_param.type, enum=manifest_param.enum, value=param.value, optional=manifest_param.optional) def warn_if_not_of_type(name, expected_type, enum, value, optional, is_default=False): if value_is_of_type(expected_type, enum, value, optional): return msg = f"The {'default ' if is_default else ''}parameter {name} has a different value than expected. " if enum is None: msg += f"It should be a {expected_type} but is a {type(value)}. " else: msg += f"It should be one of {enum!r} but is {value!r}. " logger.warning(msg + f"The value is accessible but beware!") PK_M=?  fastgenomics/_resources.pyimport functools import pkg_resources import jinja2 from . import __name__ as module_name j2env = jinja2.Environment(loader=jinja2.PackageLoader(module_name, 'templates')) resource_bytes = functools.partial(pkg_resources.resource_string, module_name) PK_M % fastgenomics/app_checker.py""" FASTGenomics Test-Suite ======================= Provides methods to check your app structure, ``manifest.json`` and ``input_file_mapping.json``. """ from pathlib import Path from logging import getLogger from . import _common, io logger = getLogger('fastgenomics.testing') def check_app_structure(app_dir: Path): """ Checks the structure of your app – only checks for mandatory files and directories. It only logs warnings if default parameter types are off. Args: app_dir: Root directory of the app Raises: AssertionError: If any vital files are missing ~jsonschema.exceptions.ValidationError: If the manifest isn’t valid """ # check app structure logger.info(f"Checking app-structure in {app_dir}") to_check = ['manifest.json', 'README.md', 'LICENSE', 'Dockerfile', 'requirements.txt'] warn_only = ['LICENSE', 'requirements.txt'] missing = [] for entry in to_check: entry_path = app_dir / entry if not entry_path.exists(): err_msg = f"{entry_path} is missing!" if entry in warn_only: logger.warning(err_msg) else: logger.error(err_msg) missing.append(entry_path) assert not missing, missing # check manifest.json logger.info(f"Checking manifest.json in {app_dir}") manifest = _common.get_app_manifest() # This is already done in get_app_manifest, but let’s make sure this is tested _common.assert_manifest_is_valid(manifest) fg_app = manifest["application"] # checking for sample_data logger.info(f"Checking for sample_data in {app_dir}") sample_dir = app_dir / 'sample_data' valid_sample_data_sub_dirs = ['data', 'config'] if fg_app['type'] == 'Calculation': valid_sample_data_sub_dirs += ['output', 'summary'] if not sample_dir.exists(): logger.warning("No sample_data found - please provide sample data!") else: for sub_dir in valid_sample_data_sub_dirs: assert (sample_dir / sub_dir).exists(), f"sample_data subdirectory {sub_dir} is missing!" def check_input_file_mapping(app_dir: Path): """ Checks the input_file_mapping Args: app_dir: Root directory of the app Raises: FileNotFoundError: If any mapped paths do not point to files. ~json.JSONDecodeError: If the manifest is not valid JSON KeyError: If an file in the manifest is not mapped """ sample_dir = app_dir / 'sample_data' io.set_paths(str(app_dir), str(sample_dir)) _common.get_input_file_mapping(check_mapping=True) PK_M7Q fastgenomics/app_creator.py""" FASTGenomics App Creation Suite =============================== Provides methods to create basic boilerplate for your apps. """ from pathlib import Path from logging import getLogger from ._resources import j2env from . import _common logger = getLogger('fastgenomics.testing') # registry DOCKER_REGISTRY = 'apps.fastgenomics.org' def create_docker_compose(app_dir: Path, app_name: Path, sample_dir: Path, docker_registry: str = DOCKER_REGISTRY): """ Creates an ``docker-compose.test.yml`` for testing Args: app_dir: Root directory of the app app_name: Name of the app, ``snake_case`` sample_dir: Data root dir containing ``data/**`` and ``config/input_file_mapping.json`` docker_registry: App registry """ docker_compose_file = app_dir / 'docker-compose.test.yml' if docker_compose_file.exists(): logger.warning(f"{docker_compose_file.name} already existing! Aborting.") return # get app type manifest = _common.get_app_manifest()["application"] app_type = manifest['Type'] logger.info("Loading docker-compose.test.yml template") template = j2env.get_template('docker-compose.yml.j2') logger.info(f"Writing {docker_compose_file}") with docker_compose_file.open('w') as f_out: temp = template.render( app_name=app_name, sample_dir=sample_dir.relative_to(app_dir), docker_registry=docker_registry, app_type=app_type, ) f_out.write(temp) def create_file_mapping(sample_dir: Path): """ Creates a base ``input_file_mapping.json`` Args: sample_dir: Sample data directory that will contain ``data/**`` and ``config/input_file_mapping.json`` """ sample_output_dir = sample_dir / 'data' / 'other_app_uuid' / 'output' file_mapping_file = sample_dir / 'config' / 'input_file_mapping.json' if file_mapping_file.exists(): logger.warning(f"{file_mapping_file} already existing! Aborting.") return # creating output directories sample_output_dir.mkdir(parents=True, exist_ok=True) file_mapping_file.parent.mkdir(parents=True, exist_ok=True) # create file_mappings manifest = _common.get_app_manifest()["application"] input_keys = manifest['input'].keys() file_mapping = {key: sample_output_dir / 'fix_me.txt' for key in input_keys} # write file_mappings logger.info("Loading input_file_mapping.json template") template = j2env.get_template('input_file_mapping.json.j2') logger.info(f"Writing {file_mapping_file}") with file_mapping_file.open('w') as f_out: temp = template.render(file_mapping=file_mapping) f_out.write(temp) print() print(f"Please edit {file_mapping_file} and provide the following files:") for key in input_keys: print(f" - {key}: {manifest['Input'][key]['Usage']} ({manifest['Input'][key]['Type']})") print() PK_MU%%fastgenomics/io.py""" FASTGenomics IO helpers ======================= Wraps input/output of ``input_file_mapping.json`` and parameters given by the FASTGenomics runtime. If you want to work without docker, you can set two environment variables to ease testing: ``APP_ROOT_DIR``: This path should contain manifest.json, normally this is /app. ``DATA_ROOT_DIR``: This path should contain you test data - normally, this is /fastgenomics. You can set them by environment variables or just call ``fg_io.set_paths(path_to_app, path_to_data_root)`` """ import os import typing as ty from pathlib import Path from logging import getLogger from . import _common, tools logger = getLogger('fastgenomics.io') _PARAMETERS = {} class UnknownAppTypeError(Exception): """ Exception for unsupported App types. Args: typ: FASTGenomics App type """ def __init__(self, typ: str): self.typ = typ @classmethod def check_type(cls, typ: str): if typ != 'Calculation': raise cls(typ) @property def message(self): return f'File output for “{self.typ}” applications not supported!' def __str__(self): return f'Not Supported: {self.message}' def __repr__(self): return f'{self.__class__.__name__}({self.typ!r})' def set_paths( app_dir: ty.Optional[ty.Union[Path, str]] = None, data_root: ty.Optional[ty.Union[Path, str]] = None, ): """ Sets the paths for the module to search for the ``manifest.json`` and IO/files The following strategy is used: - use variables provided to function, but warn if running within docker - else: if set, use environment-variables ``FG_APP_DIR`` and ``FG_DATA_ROOT`` - else: if running within docker use defaults ``/app`` and ``/fastgenomics`` - else: raise exception Args: app_dir: Path to root directory of the application (must contain ``manifest.json``) data_root: Path to root directory of input/output/config/summary Raises: FileNotFoundError: If any mapped paths do not point to files. """ if tools.running_within_docker() is True: if any([app_dir, data_root, os.environ.get("FG_APP_DIR"), os.environ.get("FG_DATA_ROOT")]): logger.warning("Running within docker - non-default paths may result in errors!") if app_dir is None: app_dir_path = Path(os.environ.get("FG_APP_DIR", _common.DEFAULT_APP_DIR)).absolute() else: app_dir_path = Path(app_dir).absolute() if data_root is None: data_root_path = Path(os.path.expandvars(os.environ.get("FG_DATA_ROOT", _common.DEFAULT_DATA_ROOT))).absolute() else: data_root_path = Path(data_root).absolute() logger.info(f"Using {app_dir_path} as app directory") logger.info(f"Using {data_root_path} as data root") # set paths paths = dict( app=app_dir_path, data=data_root_path / Path('data'), config=data_root_path / Path('config'), output=data_root_path / Path('output'), summary=data_root_path / Path('summary'), ) _common.check_paths(paths) # Invalidate parameter cache when changing paths if _common._PATHS != paths: _PARAMETERS.clear() _common._INPUT_FILE_MAPPING.clear() _common._MANIFEST.clear() _common._PATHS = paths def get_input_path(input_key: str) -> ty.Optional[Path]: """ Retrieves the location of a input file. Keep in mind that you have to define your input files in your ``manifest.json`` in advance! Args: input_key: Key of an input file Returns: Location of the input file or None if the file is optional and not specified in the input file mapping. Raises: KeyError: If the ``input_key`` is not defined in ``manifest.json`` FileNotFoundError: If the mapped path does not point to a file """ manifest = _common.get_app_manifest()['application']['input'] input_file_mapping = _common.get_input_file_mapping() # check for key in manifest if input_key not in manifest: err_msg = f"Input '{input_key}' not defined in manifest.json!" logger.error(err_msg) raise KeyError(err_msg) if manifest[input_key].get('optional', False): input_file = input_file_mapping.get(input_key) else: # check for key in mapping if input_key not in input_file_mapping: err_msg = f"Input '{input_key}' not defined in input_file_mapping!" logger.error(err_msg) raise KeyError(err_msg) input_file = input_file_mapping[input_key] # check existence if not (input_file is None or input_file.exists()): err_msg = f"Input-file '{input_file}' not found! Please check your input_file_mapping." logger.error(err_msg) raise FileNotFoundError(err_msg) return input_file def get_output_path(output_key: str) -> Path: """ Retrieves the location of the output file. Keep in mind that you have to define your output files in your ``manifest.json`` in advance! You can use this path-object to write your output as follows:: my_path_object = get_output_path('my_output_key') with my_path_object.open('w', encoding='utf-8') as f_out: f_out.write("something") Args: output_key: Key of an output file Returns: Location of the output file Raises: UnknownAppTypeError: If the app type is unknown KeyError: If the ``output_key`` is not defined """ manifest = _common.get_app_manifest()["application"] UnknownAppTypeError.check_type(manifest['type']) # get output_file_mapping output_file_mapping = manifest['output'] if output_key not in output_file_mapping: err_msg = f"Key '{output_key}' not defined in manifest.json!" logger.error(err_msg) raise KeyError(err_msg) output_file = _common.get_paths()['output'] / output_file_mapping[output_key]['file_name'] # check for existence if output_file.exists(): err_msg = f"Output-file '{output_file}' already exists!" logger.warning(err_msg) return output_file def get_summary_path() -> Path: """ Retrieves the location of the summary file. Please write your summary as CommonMark-compatible Markdown into this file. Returns: Location of the summary file Raises: UnknownAppTypeError: If the app type is unknown """ manifest = _common.get_app_manifest()["application"] UnknownAppTypeError.check_type(manifest['type']) output_file = _common.get_paths()['summary'] / 'summary.md' # check for existence if output_file.exists(): err_msg = f"Summary-file '{output_file}' already exists!" logger.warning(err_msg) return output_file def get_parameters() -> _common.Parameters: """ Returns a dict of all parameters along with its current value provided by ``parameters.json``, falling back to defaults defined in ``manifest.json``. Unset optional Parameters result in ``None`` values. Returns: Parameter dict with current values """ global _PARAMETERS # use cache and return dict of {param: current value} if _PARAMETERS: return _return_parameters() # else: load parameters parameters = _common.load_parameters_from_manifest() runtime_parameters = _common.load_runtime_parameters() # merge with defaults for name, current_value in runtime_parameters.items(): if name not in parameters: logger.warning(f"Ignoring runtime parameter {name}, as it is not defined in manifest.json!") continue parameters[name] = _update_param_value(parameters[name], current_value) # check types _common.check_parameter_types(parameters) # update cache and return _PARAMETERS = parameters # log so that that the chosen values are in the log info = "\n".join(f"{k}:{v.value}" for k, v in parameters.items()) logger.info(f"Parameters: \n{info}") return _return_parameters() def _return_parameters() -> _common.Parameters: """helper for structured return of parameters""" return {name: param.value for name, param in _PARAMETERS.items()} def _update_param_value(param: _common.Parameter, new_value: ty.Any): """helper function for updating the value of a Parameter instance""" return _common.Parameter( name=param.name, type=param.type, value=new_value, default=param.default, optional=param.optional, enum=param.enum, description=param.description, ) def get_parameter(param_key: str) -> _common.ParameterValue: """ Get a specific parameter's current value or the default. Provide a key defined in the ``manifest.json``. Unset optional Parameters result in ``None`` being returned. Args: param_key: The parameter's key Returns: The parameter's value. Raises: KeyError: If a parameter named ``param_key`` is not defined """ parameters = get_parameters() # check for existence and return or raise exception if param_key not in parameters: raise KeyError(f"Parameter {param_key} not defined in manifest.json!") return parameters[param_key] PK_M ifastgenomics/tools.py""" FASTGenomics tools for app development """ from pathlib import Path from logging import getLogger logger = getLogger('fastgenomics.tools') def running_within_docker() -> bool: """ detects, if module is running within docker and returns the result as bool """ if Path('/.dockerenv').exists(): logger.debug("Running within docker") return True else: logger.info("Running locally") return False PK_Mi)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"] } } } PK_M--,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 %} PK_M笌1fastgenomics/templates/input_file_mapping.json.j2{ {% for file_key, path_val in file_mapping.items() %} "{{ file_key }}": "{{ path_val }}"{{ "," if not loop.last }} {% endfor -%} } PK_MJZBB$fastgenomics-1.0.0.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!HSmPO"fastgenomics-1.0.0.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,rzd&Y)r$[)T&UrPK!HwE%fastgenomics-1.0.0.dist-info/METADATAW[o6~'@$s͚FA^$F-&A!)[3y?qrfX+-d=/U|LL%e%2M6UzLMg6kSȚl"dŃ-AUaLQhLVQ_`4kr&2^k|9:S1Zdymˆߙ.Pڐ%Z, 31BɼPSNPr a9E938,%לHNe)/[̈zIgkmx|X϶Z.l_E[E<;gnRB~n1%DYsZF^HUU/B0Lvdi\"Mc͘^kȡħ?&/Wp?KnՎY|Is6FRQBwR&Ʉ>\2ybUske؊0a5L 6ZYڣMY.V +; OK~Je s!8m9+`7^aL>9 NgqJt{K+EÞ8:e*brԅ"W(=r<0 Ց#Z~wM9okcok GeԗGT:Qs?x>3`r ׆޿iESRYtOFTxnTQ-C]^QnD&^-1ұa VyhRп]ӰmVBS?wstbuVL* AoC?z7kϽ~E9]|ɣ׾NJ wBW5d!BlZ zӂy5ee -JUW qM䂮e(kRdQ#ml~Gp@gEhZŭ p;fi+ЏakmmrrǪD݀m&sJBjT Ie@ S/CE שQ([m4=B4E6YEXօ^2 wX1%aU޽OeRvx:&q<BM.M%V_\jSA#$:r@&.yHg _kLq \ :<" ǐ)c1kX=K~#S,J(ۜJ?tS:4 -\r<~.P4I,i/_/PK!HQ#fastgenomics-1.0.0.dist-info/RECORDK:_@.-h 4HM@LUOi9s*,~_3h1%y2Benf%%g1CQ a0E4t}VbON~TO^tEꦠބPfastgenomics/app_creator.pyPK_MU%%dJfastgenomics/io.pyPK_M iRpfastgenomics/tools.pyPK_Mi)]rfastgenomics/schemes/manifest_schema.jsonPK_M--,fastgenomics/templates/docker-compose.yml.j2PK_M笌1fastgenomics/templates/input_file_mapping.json.j2PK_MJZBB$sfastgenomics-1.0.0.dist-info/LICENSEPK!HSmPO"fastgenomics-1.0.0.dist-info/WHEELPK!HwE%fastgenomics-1.0.0.dist-info/METADATAPK!HQ#fastgenomics-1.0.0.dist-info/RECORDPK>