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""
- 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""
+ 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: