diff --git a/src/nextflowspawner/__about__.py b/src/nextflowspawner/__about__.py index 1f04780..ecc0ae4 100644 --- a/src/nextflowspawner/__about__.py +++ b/src/nextflowspawner/__about__.py @@ -1 +1 @@ -__version__ = '0.9.2' +__version__ = '0.9.3' diff --git a/src/nextflowspawner/__init__.py b/src/nextflowspawner/__init__.py index 31d7847..39c45f6 100644 --- a/src/nextflowspawner/__init__.py +++ b/src/nextflowspawner/__init__.py @@ -12,6 +12,10 @@ def ignite(): + """ + Launch a Nextflow pipeline instance via jupyter-server-proxy. + """ + cmd = ['nextflow', 'run', os.environ['NXF_USER_WORKFLOW'], '--PORT={port}', '-resume'] if 'NXF_USER_REVISION' in os.environ: @@ -28,6 +32,9 @@ def ignite(): } class NextflowSpawner(LocalProcessSpawner): + """ + A Spawner for Nextflow pipelines. + """ default_url = Unicode('/nextflow', help="The entrypoint for the server proxy") @@ -91,16 +98,17 @@ def make_preexec_fn(self, name): return set_user_setuid(name, chdir=False) def _get_params_from_schema(self, schema, key=None): - params = {} + params_dict = {} groups = schema['$defs'] if '$defs' in schema else schema['defs'] - for group in groups.values(): - for param, value in group.get('properties').items(): - if value.get('type') != 'object': - params[param] = value if key is None else value.get(key) + for group, defs in groups.items(): + params_dict[group] = {} + for param, properties in defs.get('properties').items(): + if properties.get('type') != 'object': + params_dict[group][param] = properties if key is None else properties.get(key) else: # recurse nested parameters - params[param] = self._get_params_from_schema({'$defs': {param: {**value}}}, key) - return params + params_dict[group] |= self._get_params_from_schema({'$defs': {param: {**properties}}}, key) + return params_dict def _construct_form_field(self, name, param): html = [] @@ -133,11 +141,7 @@ def _construct_form_field(self, name, param): for p, v in param.items(): nested += self._construct_form_field(p, v) if nested: - html += "
" - html += f"
{name} options
" - html += "
" html += nested - html += "
" return html def _write_params_file(self, config): @@ -153,10 +157,15 @@ def _write_params_file(self, config): return f'{self.nxf_home}/nextflowspawner_{json_sha}.json' def _options_form_default(self): - params = self._get_params_from_schema(self.schema) form = [] - for k, v in params.items(): - form += (self._construct_form_field(k, v)) + for group, params in self._get_params_from_schema(self.schema).items(): + if category := self._construct_form_field(group, params): + # this only renders card if category contains atleast one non-hidden parameter + form += "
" + form += f"
{group} options
" + form += "
" + form += category + form += "
" return "".join(form) def options_from_form(self, formdata): @@ -171,27 +180,33 @@ def _cast_schema_type(ptype, param): case _: return str(param) - # get types and defaults from schema - types, defaults = self._get_params_from_schema(self.schema, 'type'), self._get_params_from_schema(self.schema, 'default') - - # get user-defined parameters from form and cast types - params = { k: _cast_schema_type(types.get(k), v.pop()) for k, v in formdata.items() } - - # check if provided paths exist and permissions suffice - for param, fmt in self._get_params_from_schema(self.schema, 'format').items(): - if (pattern := params.get(param)) and fmt == 'path': - if not (paths := glob.glob(pattern)): - msg = f"{pattern} does not exist." - raise FileNotFoundError(msg) - if not os.access(os.path.dirname(pattern), os.R_OK): - msg = "Parent directory is not readable." - raise PermissionError(msg) - if (not_readable := [path for path in paths if not os.access(path, os.R_OK)]): - msg = f"{not_readable} are not readable." - raise PermissionError(msg) - - # update defaults with user-defined parameters from form - options = defaults | params + def _apply_form_params(params, formdata): + params_dict = {} + for param, properties in params.items(): + if not 'type' in properties: + # recurse nested parameters + return {param: _apply_form_params(properties, formdata)} + + value = _cast_schema_type(properties.get('type'), formdata.get(param, [properties.get('default')]).pop(0)) + + # check if file(s) exists and permissions suffice + if 'exists' in properties: + if not glob.glob(value): + msg = f"{value} does not exist." + raise FileNotFoundError(msg) + if not os.access(os.path.dirname(value), os.R_OK): + msg = "{value} is not readable." + raise PermissionError(msg) + + params_dict[param] = value + + return params_dict + + options = {} + + # apply user-defined parameters from form, use defaults if not provided + for _, param in self._get_params_from_schema(self.schema).items(): + options |= _apply_form_params(param, formdata) # add email address for notifications (if provided via config) if 'NXF_USER_EMAIL' in self.environment: