Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Environment Variables #145

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
Empty file.
176 changes: 176 additions & 0 deletions exopy/measurement/environment/env_node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright 2015-2018 by Exopy Authors, see AUTHORS for more details.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copyright should not predate the creation of the file ideally.

#
# 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tag is not needed in that case, since we ruled out the need for multiple child location.

#: 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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has_root does not exist and was removed from ComplexTask as being an horror, if you need to add a root element (not sure) the check should simply check if it None or not.

child.depth = self.depth + 1
child.path = self._child_path()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those attributes need to be defined.


# Give him its root so that it can proceed to any child
# registration it needs to.
child.parent = self
child.root = self.root
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here those need to be defined.


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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For homogeneity a _ between the name and number would be nice.


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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not apply here.


"""
envvar = cls()
members_from_preferences(envvar, config)
for name, member in tagged_members(envvar, 'child').items():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be heavily simplified !


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
47 changes: 47 additions & 0 deletions exopy/measurement/environment/environment.py
Original file line number Diff line number Diff line change
@@ -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())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node needs to be defined.


154 changes: 154 additions & 0 deletions exopy/measurement/environment/environment_variable/base_envvar.py
Original file line number Diff line number Diff line change
@@ -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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should inherit from HasPrefsAtom

"""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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will need doc.


metadata = Dict().tag(pref=True)

def preferences_from_members(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in HasPrefsAtom

"""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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be implemented using update_members_from_preferences from HasPrefsAtom



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
Loading