Skip to content

Commit

Permalink
#320 added support for dynamic "default" option, depending on other p…
Browse files Browse the repository at this point in the history
…arameters
  • Loading branch information
bugy committed Mar 16, 2023
1 parent 001c824 commit a4c38f7
Show file tree
Hide file tree
Showing 9 changed files with 337 additions and 149 deletions.
4 changes: 3 additions & 1 deletion samples/configs/parameterized.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@
"name": "Default Text",
"required": true,
"param": "--def_text",
"default": "some_text",
"default": {
"script": "echo ${Required List}"
},
"description": "Text with default value and required"
},
{
Expand Down
83 changes: 56 additions & 27 deletions src/model/parameter_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from model import model_helper
from model.model_helper import resolve_env_vars, replace_auth_vars, is_empty, SECURE_MASK, \
normalize_extension, read_bool_from_config, InvalidValueException, read_str_from_config, read_int_from_config
from model.template_property import TemplateProperty
from react.properties import ObservableDict, observable_fields
from utils import file_utils, string_utils
from utils.file_utils import FileMatcher
Expand Down Expand Up @@ -46,7 +47,8 @@
'file_recursive',
'ui_width_weight')
class ParameterModel(object):
def __init__(self, parameter_config, username, audit_name, other_params_supplier,
def __init__(self, parameter_config, username, audit_name,
other_params_supplier,
process_invoker: ProcessInvoker,
other_param_values: ObservableDict = None,
working_dir=None):
Expand All @@ -61,14 +63,14 @@ def __init__(self, parameter_config, username, audit_name, other_params_supplier
self._original_config = parameter_config
self._parameter_values = other_param_values

self._reload()
self._setup()

if (other_param_values is not None) \
and (self._values_provider is not None) \
and self._values_provider.get_required_parameters():
other_param_values.subscribe(self._param_values_observer)

def _reload(self):
def _setup(self):
config = self._original_config

self.param = config.get('param')
Expand All @@ -89,12 +91,14 @@ def _reload(self):
default='single_argument',
allowed_values=['single_argument', 'argument_per_value', 'repeat_param_value'])
self.type = self._read_type(config)
self.default = _resolve_default(
self._set_default_value(
config.get('default'),
self._username,
self._audit_name,
self._working_dir,
self.type,
self._parameters_supplier(),
self._parameter_values,
self._process_invoker)
self.file_dir = _resolve_file_dir(config, 'file_dir')
self._list_files_dir = _resolve_list_files_dir(self.file_dir, self._working_dir)
Expand Down Expand Up @@ -450,36 +454,61 @@ def _validate_recursive_path(self, path, intermediate):
def _build_list_file_path(self, child_path):
return os.path.normpath(os.path.join(self._list_files_dir, *child_path))

def _set_default_value(
self,
default_config,
username,
audit_name,
working_dir,
type,
parameters,
parameter_values,
process_invoker: ProcessInvoker):
if is_empty(default_config):
self.default = default_config
return

def _resolve_default(default, username, audit_name, working_dir, type, process_invoker: ProcessInvoker):
if not default:
return default
script = False
if isinstance(default_config, dict) and 'script' in default_config:
string_value = default_config['script']
script = True
elif isinstance(default_config, str):
string_value = default_config
else:
self.default = default_config
return

script = False
if isinstance(default, dict) and 'script' in default:
string_value = default['script']
script = True
elif isinstance(default, str):
string_value = default
else:
return default
resolved_string_value = resolve_env_vars(string_value, full_match=True)
if resolved_string_value == string_value:
resolved_string_value = replace_auth_vars(string_value, username, audit_name)

resolved_string_value = resolve_env_vars(string_value, full_match=True)
if resolved_string_value == string_value:
resolved_string_value = replace_auth_vars(string_value, username, audit_name)
if not script:
self.default = resolved_string_value
return

template_property = TemplateProperty(resolved_string_value, parameters, parameter_values)
shell = read_bool_from_config('shell', default_config, default=is_empty(template_property.required_parameters))

if script:
has_variables = string_value != resolved_string_value
shell = read_bool_from_config('shell', default, default=not has_variables)
output = process_invoker.invoke(resolved_string_value, working_dir, shell=shell)
stripped_output = output.strip()
def get_script_output(script):
output = process_invoker.invoke(script, working_dir, shell=shell)
stripped_output = output.strip()

if type == PARAM_TYPE_MULTISELECT and '\n' in stripped_output:
return [line.strip() for line in stripped_output.split('\n') if not is_empty(line)]
if type == PARAM_TYPE_MULTISELECT and '\n' in stripped_output:
return [line.strip() for line in stripped_output.split('\n') if not is_empty(line)]

return stripped_output
return stripped_output

return resolved_string_value
if not template_property.required_parameters:
self.default = get_script_output(resolved_string_value)
else:
def update_default(_, new):
if new is None:
self.default = None
else:
self.default = get_script_output(new)

template_property.subscribe(update_default)
update_default(None, template_property.value)


def _resolve_file_dir(config, key):
Expand Down
97 changes: 6 additions & 91 deletions src/model/script_config.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import logging
import logging
import os
import re
from collections import OrderedDict
from dataclasses import dataclass, field
from typing import List

from auth.authorization import ANY_USER
from config.exceptions import InvalidConfigException
from model import parameter_config
from model.model_helper import is_empty, fill_parameter_values, read_bool_from_config, InvalidValueException, \
from model.model_helper import is_empty, read_bool_from_config, InvalidValueException, \
read_str_from_config, replace_auth_vars, read_list
from model.parameter_config import ParameterModel
from model.server_conf import LoggingConfig
from react.properties import ObservableList, ObservableDict, observable_fields, Property
from model.template_property import TemplateProperty
from react.properties import ObservableList, ObservableDict, observable_fields
from utils import file_utils, custom_json
from utils.object_utils import merge_dicts
from utils.process_utils import ProcessInvoker
Expand Down Expand Up @@ -82,9 +82,9 @@ def __init__(self,
self.parameter_values = ObservableDict()

self._original_config = config_object
self._included_config_paths = _TemplateProperty(read_list(config_object, 'include'),
parameters=self.parameters,
values=self.parameter_values)
self._included_config_paths = TemplateProperty(read_list(config_object, 'include'),
parameters=self.parameters,
values=self.parameter_values)
self._included_config_prop.bind(self._included_config_paths, self._read_and_merge_included_paths)

self._reload_config()
Expand Down Expand Up @@ -389,91 +389,6 @@ def __init__(self, param_name) -> None:
self.param_name = param_name


class _TemplateProperty:
def __init__(self, template_config, parameters: ObservableList, values: ObservableDict, empty=None) -> None:
self._value_property = Property(None)
self._template_config = template_config
self._values = values
self._empty = empty
self._parameters = parameters

pattern = re.compile('\${([^}]+)\}')

search_start = 0
script_template = ''
required_parameters = set()

templates = template_config if isinstance(template_config, list) else [template_config]

for template in templates:
if template:
while search_start < len(template):
match = pattern.search(template, search_start)
if not match:
script_template += template[search_start:]
break
param_start = match.start()
if param_start > search_start:
script_template += template[search_start:param_start]

param_name = match.group(1)
required_parameters.add(param_name)

search_start = match.end() + 1

self.required_parameters = tuple(required_parameters)

self._reload()

if self.required_parameters:
values.subscribe(self._value_changed)
parameters.subscribe(self)

def _value_changed(self, parameter, old, new):
if parameter in self.required_parameters:
self._reload()

def on_add(self, parameter, index):
if parameter.name in self.required_parameters:
self._reload()

def on_remove(self, parameter):
if parameter.name in self.required_parameters:
self._reload()

def _reload(self):
values_filled = True
for param_name in self.required_parameters:
value = self._values.get(param_name)
if is_empty(value):
values_filled = False
break

if self._template_config is None:
self.value = None
elif values_filled:
if isinstance(self._template_config, list):
values = []
for single_template in self._template_config:
values.append(fill_parameter_values(self._parameters, single_template, self._values))
self.value = values
else:
self.value = fill_parameter_values(self._parameters, self._template_config, self._values)
else:
self.value = self._empty

self._value_property.set(self.value)

def subscribe(self, observer):
self._value_property.subscribe(observer)

def unsubscribe(self, observer):
self._value_property.unsubscribe(observer)

def get(self):
return self._value_property.get()


def get_sorted_config(config):
key_order = ['name', 'script_path',
'working_directory',
Expand Down
89 changes: 89 additions & 0 deletions src/model/template_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import re

from model.model_helper import is_empty, fill_parameter_values
from react.properties import ObservableList, ObservableDict, Property


class TemplateProperty:
def __init__(self, template_config, parameters: ObservableList, values: ObservableDict, empty=None) -> None:
self._value_property = Property(None)
self._template_config = template_config
self._values = values
self._empty = empty
self._parameters = parameters

pattern = re.compile('\${([^}]+)\}')

search_start = 0
script_template = ''
required_parameters = set()

templates = template_config if isinstance(template_config, list) else [template_config]

for template in templates:
if template:
while search_start < len(template):
match = pattern.search(template, search_start)
if not match:
script_template += template[search_start:]
break
param_start = match.start()
if param_start > search_start:
script_template += template[search_start:param_start]

param_name = match.group(1)
required_parameters.add(param_name)

search_start = match.end() + 1

self.required_parameters = tuple(required_parameters)

self._reload()

if self.required_parameters:
values.subscribe(self._value_changed)
parameters.subscribe(self)

def _value_changed(self, parameter, old, new):
if parameter in self.required_parameters:
self._reload()

def on_add(self, parameter, index):
if parameter.name in self.required_parameters:
self._reload()

def on_remove(self, parameter):
if parameter.name in self.required_parameters:
self._reload()

def _reload(self):
values_filled = True
for param_name in self.required_parameters:
value = self._values.get(param_name)
if is_empty(value):
values_filled = False
break

if self._template_config is None:
self.value = None
elif values_filled:
if isinstance(self._template_config, list):
values = []
for single_template in self._template_config:
values.append(fill_parameter_values(self._parameters, single_template, self._values))
self.value = values
else:
self.value = fill_parameter_values(self._parameters, self._template_config, self._values)
else:
self.value = self._empty

self._value_property.set(self.value)

def subscribe(self, observer):
self._value_property.subscribe(observer)

def unsubscribe(self, observer):
self._value_property.unsubscribe(observer)

def get(self):
return self._value_property.get()
Loading

0 comments on commit a4c38f7

Please sign in to comment.