From 16eedb5b3d1b88978a1321d93c16162293334b33 Mon Sep 17 00:00:00 2001 From: galactikvoyager Date: Fri, 25 May 2018 11:18:19 +0200 Subject: [PATCH] [WIP] Environment Variables Started to work on - [ ] env_node.py - [ ] environment.py - [ ] base_envvar.py --- .../environment/editors/base_editor.py | 0 .../environment/editors/std_editor.py | 0 exopy/measurement/environment/env_node.py | 176 ++++++++++++++++++ exopy/measurement/environment/environment.py | 47 +++++ .../environment_variable/base_envvar.py | 154 +++++++++++++++ exopy/measurement/environment/infos.py | 25 +++ exopy/measurement/environment/manifest.enaml | 0 exopy/measurement/environment/plugin.py | 0 8 files changed, 402 insertions(+) create mode 100644 exopy/measurement/environment/editors/base_editor.py create mode 100644 exopy/measurement/environment/editors/std_editor.py create mode 100644 exopy/measurement/environment/env_node.py create mode 100644 exopy/measurement/environment/environment.py create mode 100644 exopy/measurement/environment/environment_variable/base_envvar.py create mode 100644 exopy/measurement/environment/infos.py create mode 100644 exopy/measurement/environment/manifest.enaml create mode 100644 exopy/measurement/environment/plugin.py diff --git a/exopy/measurement/environment/editors/base_editor.py b/exopy/measurement/environment/editors/base_editor.py new file mode 100644 index 00000000..e69de29b diff --git a/exopy/measurement/environment/editors/std_editor.py b/exopy/measurement/environment/editors/std_editor.py new file mode 100644 index 00000000..e69de29b diff --git a/exopy/measurement/environment/env_node.py b/exopy/measurement/environment/env_node.py new file mode 100644 index 00000000..4a158f89 --- /dev/null +++ b/exopy/measurement/environment/env_node.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2015-2018 by Exopy Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +from collections import Iterable + +from atom.api import (Atom, List, Signal, Bool, Section, Typed) + +from ...utils.atom_util import (tagged_members, member_to_pref, + members_from_preferences) +from ...utils.container_change import ContainerChange + +#: Id used to identify dependencies type. +DEP_TYPE = 'exopy.envvar' + + +class EnvNode(Atom): + """Environment variable composed of several sub env_var. + + """ + #: List of all the children of the env_var. The list should not be + #: manipulated directly by user code. + #: The tag 'child' is used to mark that a member can contain child env_var + #: and is used to gather children for operation which must occur on all of + #: them. + children = List().tag(child=100) + + #: Signal emitted when the list of children change, the payload will be a + #: ContainerChange instance. + #: The tag 'child_notifier' is used to mark that a member emmit + #: notifications about modification of another 'child' member. This allow + #: editors to correctly track all of those. + children_changed = Signal().tag(child_notifier='children') + + def add_child_envvar(self, index, child): + """Add a child envvar at the given index. + + Parameters + ---------- + index : int + Index at which to insert the new child env_var. + + child : BaseEnvVar + Env_var to insert in the list of children env_var. + + """ + self.children.insert(index, child) + + # In the absence of a root envvar do nothing else than inserting the + # child. + if self.has_root: + child.depth = self.depth + 1 + child.path = self._child_path() + + # Give him its root so that it can proceed to any child + # registration it needs to. + child.parent = self + child.root = self.root + + change = ContainerChange(obj=self, name='children', + added=[(index, child)]) + self.children_changed(change) + + def move_child_envvar(self, old, new): + """Move a child env_var. + + Parameters + ---------- + old : int + Index at which the child to move is currently located. + + new : BaseEnvVar + Index at which to insert the child env_var. + + """ + child = self.children.pop(old) + self.children.insert(new, child) + + # In the absence of a root env_var do nothing else than moving the + # child. + if self.has_root: + + change = ContainerChange(obj=self, name='children', + moved=[(old, new, child)]) + self.children_changed(change) + + def remove_child_envvar(self, index): + """Remove a child env_var from the children list. + + Parameters + ---------- + index : int + Index at which the child to remove is located. + + """ + child = self.children.pop(index) + + # Cleanup database, update preferences + child.root = None + child.parent = None + + change = ContainerChange(obj=self, name='children', + removed=[(index, child)]) + self.children_changed(change) + + def preferences_from_members(self): + """ + + """ + prefs = super().preferences_from_members() + for i, child in enumerate(self.children): + prefs["child%i" % i] = child.preferences_from_members() + + return prefs + + @classmethod + def build_from_config(cls, config, dependencies): + """Create a new instance using the provided infos for initialisation. + + Parameters + ---------- + config : dict(str) + Dictionary holding the new values to give to the members in string + format, or dictionnary like for instance with prefs. + + dependencies : dict + Dictionary holding the necessary classes needed when rebuilding. + This is assembled by the EnvVarManager. + + Returns + ------- + envvar : BaseEnvVar + Newly created and initiliazed env_var. + + Notes + ----- + This method is fairly powerful and can handle a lot of cases so + don't override it without checking that it works. + + """ + envvar = cls() + members_from_preferences(envvar, config) + for name, member in tagged_members(envvar, 'child').items(): + + if isinstance(member, List): + i = 0 + pref = name + '_{}' + validated = [] + while True: + child_name = pref.format(i) + if child_name not in config: + break + child_config = config[child_name] + child_class_name = child_config.pop('envvar_id') + child_cls = dependencies[DEP_TYPE][child_class_name] + child = child_cls.build_from_config(child_config, + dependencies) + validated.append(child) + i += 1 + + else: + if name not in config: + continue + child_config = config[name] + child_class_name = child_config.pop('envvar_id') + child_class = dependencies[DEP_TYPE][child_class_name] + validated = child_class.build_from_config(child_config, + dependencies) + + setattr(envvar, name, validated) + + return envvar diff --git a/exopy/measurement/environment/environment.py b/exopy/measurement/environment/environment.py new file mode 100644 index 00000000..312963e3 --- /dev/null +++ b/exopy/measurement/environment/environment.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2015-2018 by Exopy Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- + +from atom.api import (Unicode, ForwardTyped) +from configobj import ConfigObj + +from ..utils.atom_util import HasPrefAtom + + +def environment_plugin(): + """Delayed to avoid circular references. + + """ + from .plugin import EnvironmentPlugin + return EnvironmentPlugin + + +class Environment(HasPrefAtom): + + #: Name of the environment. + name = Unicode().tag(pref=True) + + #: Reference to the environment plugin managing this environment. + plugin = ForwardTyped(environment_plugin) + + def __init__(self, **kwargs): + + super(Environment, self).__init__(**kwargs) + + def save(self, path=None): + """Save the environment as a ConfigObj object. + + Parameters + ---------- + path : unicode + Path of the file to which save the environment. + + """ + config = ConfigObj(indent_type=' ', encoding='utf-8') + config.update(self.node.preferences_from_members()) + diff --git a/exopy/measurement/environment/environment_variable/base_envvar.py b/exopy/measurement/environment/environment_variable/base_envvar.py new file mode 100644 index 00000000..7d6bb0fb --- /dev/null +++ b/exopy/measurement/environment/environment_variable/base_envvar.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2015-2018 by Exopy Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- + +from atom.api import (Atom, Constant, Str, Dict, Value, + ForwardTyped, Property) + +from enaml.core.api import d_ + +from .infos import EnvVarInfos + +from ..utils.declarator import Declarator, import_and_get + + +DEP_TYPE = 'exopy.envvar' + + +class BaseEnvVar(Atom): + """Base class defining common members of all Environment Variables. + + This class basically defines the minimal skeleton of an Environment + variable in term of members and methods. + + """ + #: Identifier for the build dependency collector + dep_type = Constant(DEP_TYPE).tag(pref=True) + + #: Name of the class, used for persistence. + envvar_id = Str().tag(pref=True) + + #: Id of the editor. + editor_id = Str().tag(pref=True) + + #: Name of the env_var. This should be unique in hierarchy. + name = Str().tag(pref=True) + + #: Reference to the root env_var in the hierarchy. + root = ForwardTyped(lambda: RootEnvVar) + + #: Refrence to the parent env_var. + parent = ForwardTyped(lambda: BaseEnvVar) + + value = Value().tag(pref=True) + + metadata = Dict().tag(pref=True) + + def preferences_from_members(self): + """Update the entries in the preference object. + + """ + raise NotImplementedError() + + @classmethod + def build_from_config(cls, config, dependencies): + """Create a new instance using the provided infos for initialisation. + + Parameters + ---------- + config : dict(str) + Dictionary holding the new values to give to the members in string + format, or dictionnary like for instance with prefs. + + dependencies : dict + Dictionary holding the necessary classes needed when rebuilding.. + + """ + raise NotImplementedError() + + +class EnvVar(Declarator): + """Declarator used to contribute a env_var. + + """ + #: Path to the env_var object. Path should be dot separated and the class + #: name preceded by ':'. + envvar = d_(Str()) + + #: Path to the view object associated with the env_var. + #: The path of any parent GroupDeclarator object will be prepended to it. + view = d_(Str()) + + #: Metadata associated to the env_var. + metadata = d_(Dict()) + + #: Id of the env_var computed from the top-level package and the env_var + #: name + id = Property(cached=True) + + def register(self, collector, traceback): + """Collect env_var and view and add infos to the DeclaratorCollector + contributions member. + + The group declared by a parent if any is taken into account. All + Interface children are also registered. + + """ + # Build the env_var id by assembling the package name and the class + # name + envvar_id = self.id + + # If the env_var only specifies a name update the matching infos. + if ':' not in self.EnvVar: + if self.envvar not in collector.contributions: + collector._delayed.append(self) + return + + infos = collector.contributions[envvar_id] + infos.metadata.update(self.metadata) + + self.is_registered = True + return + + # Determine the path to the env_var. + path = self.get_path() + try: + e_path, envvar = (path + '.' + self.envvar + if path else self.envvar).split(':') + + except ValueError: + msg = 'Incorrect %s (%s), path must be of the form a.b.c:Class' + err_id = e_path.split('.', 1)[0] + '.' + envvar + + traceback[err_id] = msg + return + + # Check that the env_var does not already exist. + if envvar_id in collector.contributions or envvar_id in traceback: + i = 1 + while True: + err_id = '%s_duplicate%d' % (envvar_id, i) + if err_id not in traceback: + break + + msg = 'Duplicate definition of {}, found in {}' + traceback[err_id] = msg.format(envvar, e_path) + return + + infos = EnvVarInfos(metadata=self.metadata) + + # Get the env_var class. + t_cls = import_and_get(e_path, envvar, traceback, envvar_id) + if t_cls is None: + return + + # Add group and add to collector + infos.metadata['group'] = self.get_group() + collector.contributions[envvar_id] = infos + + self.is_registered = True diff --git a/exopy/measurement/environment/infos.py b/exopy/measurement/environment/infos.py new file mode 100644 index 00000000..9474ef28 --- /dev/null +++ b/exopy/measurement/environment/infos.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2015-2018 by Exopy Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Objects used to store tasks, interfaces and configs in the manager. + +""" +from atom.api import (Atom, Subclass, Dict) + +from .environment_variable.base_envvar import BaseEnvVar + + +class EnvVarInfos(Atom): + """An object used to store informations about a env_var. + """ + #: Class representing this env_var. + cls = Subclass(BaseEnvVar) + + #: Metadata associated with this task such as group, looping capabilities, + #: etc + metadata = Dict() diff --git a/exopy/measurement/environment/manifest.enaml b/exopy/measurement/environment/manifest.enaml new file mode 100644 index 00000000..e69de29b diff --git a/exopy/measurement/environment/plugin.py b/exopy/measurement/environment/plugin.py new file mode 100644 index 00000000..e69de29b