From f2a44ec128aa30ab44c8a067ef049d07130ef997 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 24 Mar 2022 14:43:01 +0100 Subject: [PATCH] Allow Variables to reference each other --- lumen/dashboard.py | 3 +- lumen/state.py | 6 +++- lumen/ui/dashboard.py | 2 +- lumen/variables/base.py | 61 ++++++++++++++++++++++++++++------------- 4 files changed, 49 insertions(+), 23 deletions(-) diff --git a/lumen/dashboard.py b/lumen/dashboard.py index 93fa4712f..fca6d0b08 100644 --- a/lumen/dashboard.py +++ b/lumen/dashboard.py @@ -224,8 +224,7 @@ def __init__(self, specification=None, **params): self.auth = Auth.from_spec(state.spec.get('auth', {})) self.config = Config.from_spec(state.spec.get('config', {})) self.defaults = Defaults.from_spec(state.spec.get('defaults', {})) - vars = Variables.from_spec(state.spec.get('variables', {})) - self.variables = state._variables[pn.state.curdoc] = vars + self.variables = Variables.from_spec(state.spec.get('variables', {})) self.defaults.apply() # Load and populate template diff --git a/lumen/state.py b/lumen/state.py index 15516d1a2..11a8f82d5 100644 --- a/lumen/state.py +++ b/lumen/state.py @@ -166,10 +166,14 @@ def _resolve_source_ref(self, refs): def resolve_reference(self, reference, variables=None): if not is_ref(reference): raise ValueError('References should be prefixed by $ symbol.') + from .variables import Variable refs = reference[1:].split('.') vars = variables or self.variables if refs[0] == 'variables': - return vars[refs[1]] + value = vars[refs[1]] + if isinstance(value, Variable): + value = value.value + return value return self._resolve_source_ref(refs) diff --git a/lumen/ui/dashboard.py b/lumen/ui/dashboard.py index b6179d2f0..f05446753 100644 --- a/lumen/ui/dashboard.py +++ b/lumen/ui/dashboard.py @@ -140,7 +140,7 @@ def _yaml_upload(self, event): return def _create_new(self, event): - self.spec = {'config': {}, 'sources': {}, 'targets': []} + self.spec = {'config': {}, 'sources': {}, 'targets': [], 'variables': {}} def _selected(self, event): self.spec = event.obj.spec diff --git a/lumen/variables/base.py b/lumen/variables/base.py index 126525887..c5657f18a 100644 --- a/lumen/variables/base.py +++ b/lumen/variables/base.py @@ -20,34 +20,48 @@ class Variables(param.Parameterized): + """ + The Variables component stores a number Variable types and mirrors + their values onto dynamically created parameters allowing other + components to easily watch changes in a variable. + """ - def __init__(self, **vars): - super().__init__() - for p, var in vars.items(): - vtype = type(var.value) - ptype = _PARAM_MAP.get(vtype, param.Parameter) - self.param.add_parameter(p, ptype()) - var.param.watch(partial(self._update_value, p), 'value') - var.param.trigger('value') - self._vars = vars - - def _update_value(self, p, event): - self.param.update({p: event.new}) - - def __getitem__(self, key): - return getattr(self, key) + def __init__(self, **params): + super().__init__(**params) + self._vars = {} @classmethod def from_spec(cls, spec): - vars = {} + variables = cls() + if pn.state.curdoc: + state._variables[pn.state.curdoc] = variables for name, var_spec in spec.items(): if not isinstance(var_spec, dict): var_spec = { 'type': 'constant', 'default': var_spec } - vars[name] = Variable.from_spec(dict(var_spec, name=name), vars) - return cls(**vars) + var = Variable.from_spec(dict(var_spec, name=name), variables) + variables.add_variable(var) + return variables + + def add_variable(self, var): + """ + Adds a new variable to the Variables instance and sets up + a parameter that can be watched. + """ + self._vars[var.name] = var + self.param.add_parameter(var.name, param.Parameter(default=var.value)) + var.param.watch(partial(self._update_value, var.name), 'value') + + def _update_value(self, name, event): + self.param.update({name: event.new}) + + def __getitem__(self, key): + if key in self.param: + return getattr(self, key) + else: + raise KeyError(f'No variable named {key!r} has been defined.') @property def panel(self): @@ -60,6 +74,14 @@ def panel(self): class Variable(Component): + """ + A Variable may declare a static or dynamic value that can be + referenced from other components. The source of the Variable value + can be anything from an environment variable to a widget or URL + parameter. Variable components allow a concise way to configure + other components and make it possible to orchestrate actions across + multiple components. + """ default = param.Parameter(doc=""" Default value to use if no other value is defined""") @@ -74,7 +96,8 @@ class Variable(Component): secure = param.Boolean(default=False, constant=True, doc=""" Whether the variable should be treated as secure.""") - value = param.Parameter() + value = param.Parameter(doc=""" + The materialized value of the variable.""") variable_type = None