Esempio n. 1
0
class HasTree(HasTraits):
    tree = Instance(klass=RegulusTree, allow_none=True)
    _ref = Instance(klass=RegulusTree, allow_none=True)
    _owner = This(allow_none=True)

    def __init__(self, tree=None):
        super().__init__()
        self.ref = tree

    @property
    def ref(self):
        return self._ref

    @ref.setter
    def ref(self, tree):
        if self._owner is not None:
            self._owner.unobserve(self.tree_changed, names='tree')
        if isinstance(tree, RegulusTree):
            self._ref = tree
            self._owner = None
        elif isinstance(tree, HasTree):
            self._owner = tree
            self._ref = self._owner.tree
            self._owner.observe(self.tree_changed, names='tree')
        self.ref_changed(None)

    def ref_changed(self, change):
        if change is not None:
            self._ref = change['new']
        self.update(None)

    def tree_changed(self, change):
        pass

    def update(self, change):
        self.tree = self._ref
class Registry(HasTraits):
    """
    Stores which Clip-Type is responsible for wrapping
    specific applications.
    """

    clip_types: Dictionary = Dict(value_trait=Class(klass=Clip),
                                  key_trait=Class())
    sub_registries: Listing['Registry'] = List(This())

    @default("clip_types")
    def _init_cliptypes(self) -> Dictionary[Type[Clip], Type[T]]:
        return {}

    @default("sub_registries")
    def _init_subregistries(self) -> Listing['Registry']:
        return []

    def all_types(self) -> Iterator[Type]:
        """
        A generator that returns all supported types.
        """
        yield from self.clip_types.keys()
        for registry in self.sub_registries:
            yield from registry.all_types()

    def add_subregistry(self, registry: 'Registry') -> None:
        """
        Adds a subregistry to the registry.

        These registries can be removed at any time.

        :param registry:  The registry to add
        """
        self.sub_registries.append(registry)

    def remove_subregistry(self, registry: 'Registry') -> None:
        """
        Removes a subregistry from the registry.

        :param registry: The registry to remove.
        """
        self.sub_registries.remove(registry)

    def register(
            self,
            base: Type[Clip],
            type: Type[T] = None
    ) -> Optional[Callable[[Type[Clip]], Type[Clip]]]:
        """
        Registers a new clip type for the given clip.

        :param base:  The clip-type
        :param type:  The type the clip is wrapping. (If omitted it will represent a decorator)
        :return: A decorator or none.
        """

        # Decorator Syntax
        if type is None:

            def _decorator(type: Type[T]) -> Type[T]:
                self.register(type, base)
                return type

            return _decorator

        # Normal handling
        self.clip_types[type] = base

    def get_clip_type_for(self, item: T) -> Optional[Type[Clip]]:
        """
        Returns the clip type for the given object.

        :param item:  The clip to convert.
        :return:  Type clip-type responsible for wrapping the object
        """
        own_result = self._find_own(item)
        if own_result is not None:
            return own_result

        # Then find in foreign
        for registry in self.sub_registries:
            result = registry.get_clip_type_for(item)
            if result is not None:
                return result

        return None

    def _find_own(self, item: T) -> Optional[Type[Clip]]:
        # Find in own first
        for cls in type(item).mro():
            if cls in self.clip_types:
                return self.clip_types[cls]

        for cls in self.clip_types:
            if isinstance(item, cls):
                return self.clip_types[cls]

        return None

    def wrap(self, item: T) -> Clip:
        """
        Returns a wrapper for the clip type.

        :param item: The item to convert.
        :return: The clip wrapping the item.
        """
        clip_type = self.get_clip_type_for(item)
        if clip_type is None:
            raise ValueError(f"Unsupported type {type(item)!r}")
        return clip_type(item)
Esempio n. 3
0
class BaseObject(HasTraits):
    """
    Base class for all objects in VaiTk.
    """

    # The parent of this object
    parent = This(allow_none=True)

    # The list of its children
    children = List(Instance(This))

    # The list of installed event filters.
    event_filters = List(Instance(This))

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent=parent, **kwargs)
        self._event_filters = []
        if self.parent is not None:
            parent.add_child(self)

    @default('children')
    def children_default(self):
        return []

    def add_child(self, child):
        """
        Adds a child to the list of children of this object.
        If the child is already in the list, do nothing.
        You don't need to call this method explicitly, as the
        hierarchy is set up by the parent relationship.

        Args:
            child: BaseObject
                the child to add

        Returns: None

        """
        if child not in self.children:
            self.children.append(child)

    def remove_child(self, child):
        """
        Removes a previously added child to the list of children.
        You don't need to call this method explicitly, as the
        hierarchy is set up by the parent relationship.

        Args:
            child:

        Returns: None

        Raises:
            ValueError: if the child is not present

        """
        self.children.remove(child)

    def install_event_filter(self, event_filter):
        """
        Installs an event filter.
        If the event filter is already present, do nothing.

        Args:
            event_filter: BaseObject
                The object that will receive notifications.

        Returns: None

        """
        if event_filter not in self.event_filters:
            self.event_filters.append(event_filter)

    def event_filter(self, watched, event):
        """
        Method that is called when this object has been registered
        as an event filter.

        By default, the implementation does nothing. Classes that want
        to act must reimplement this method

        Args:
            watched: BaseObject
                The original object that received the event
            event: Event
                The event that is being dispatched.

        Returns: bool
            True if the event has been handled. False otherwise.

        """
        return False

    def event(self, event):
        """
        Receives a dispatched event.

        Args:
            event: Event
                The event that is being dispatched

        Returns: bool
            True if the event was recognised and handled. False otherwise.
        """
        if isinstance(event, TimerEvent):
            self.timer_event(event)
            return True
        return False

    def timer_event(self, event):
        """
Esempio n. 4
0
 class Tree(HasTraits):
     value = Unicode()
     leaves = List(This())
Esempio n. 5
0
 class Bar(Foo):
     t = This()
Esempio n. 6
0
 class Foo(HasTraits):
     t = This()
Esempio n. 7
0
 class B(A):
     tt = This()
     ttt = This()
Esempio n. 8
0
 class A(HasTraits):
     t = This()
     tt = This()
Esempio n. 9
0
class Project(AbstractProject, NDIO):
    _id = Unicode()
    _name = Unicode(allow_none=True)

    _parent = This()
    _projects = Dict(This)
    _datasets = Dict(Instance(NDDataset))
    _scripts = Dict(Instance(Script))
    _others = Dict()
    _meta = Instance(Meta)

    _filename = Instance(pathlib.Path, allow_none=True)
    _directory = Instance(pathlib.Path, allow_none=True)

    # ..................................................................................................................
    def __init__(self, *args, argnames=None, name=None, **meta):
        """
        A manager for projects, datasets and scripts.

        It can handle multiple datsets, sub-projects, and scripts in a main project.

        Parameters
        ----------
        *args : Series of objects, optional
            Argument type will be interpreted correctly if they are of type
            |NDDataset|, |Project|, or other objects such as |Script|.
            This is optional, as they can be added later.
        argnames : list, optional
            If not None, this list gives the names associated to each
            objects passed as args. It MUST be the same length that the
            number of args, or an error wil be raised.
            If None, the internal name of each object will be used instead.
        name : str, optional
            The name of the project.  If the name is not provided, it will be
            generated automatically.
        **meta : dict
            Any other attributes to described the project.

        See Also
        --------
        NDDataset : The main object containing arrays.
        Script : Executables scripts container.

        Examples
        --------
        >>> import spectrochempy as scp
        >>> myproj = scp.Project(name='project_1')
        >>> ds = scp.NDDataset([1., 2., 3.], name='dataset_1')
        >>> myproj.add_dataset(ds)
        >>> print(myproj)
        Project project_1:
            ⤷ dataset_1 (dataset)
        """
        super().__init__()

        self.parent = None
        self.name = name

        if meta:
            self.meta.update(meta)

        for i, obj in enumerate(args):
            name = None
            if argnames:
                name = argnames[i]
            self._set_from_type(obj, name)

    # ------------------------------------------------------------------------------------------------------------------
    # Private methods
    # ------------------------------------------------------------------------------------------------------------------

    # ..................................................................................................................
    def _set_from_type(self, obj, name=None):

        if isinstance(obj, NDDataset):
            # add it to the _datasets dictionary
            self.add_dataset(obj, name)

        elif isinstance(obj, type(self)):  # can not use Project here!
            self.add_project(obj, name)

        elif isinstance(obj, Script):
            self.add_script(obj, name)

        elif hasattr(obj, 'name'):
            self._others[obj.name] = obj

        else:
            raise ValueError('objects of type {} has no name and so '
                             'cannot be appended to the project '.format(
                                 type(obj).__name__))

    # ..................................................................................................................
    def _get_from_type(self, name):
        pass  # TODO: ???

    # ..................................................................................................................
    def _repr_html_(self):

        h = self.__str__()
        h = h.replace('\n', '<br/>\n')
        h = h.replace(' ', '&nbsp;')

        return h

    # ------------------------------------------------------------------------------------------------------------------
    # Special methods
    # ------------------------------------------------------------------------------------------------------------------

    # ..................................................................................................................
    def __getitem__(self, key):

        if not isinstance(key, str):
            raise KeyError('The key must be a string.')

        if key == 'No project':
            return

        if '/' in key:
            # Case of composed name (we assume not more than one level subproject
            parent = key.split('/')[0]
            if parent in self.projects_names:
                if key in self._projects[parent].datasets_names:
                    return self._projects[parent]._datasets[key]
                elif key in self._projects[parent].scripts_names:
                    return self._projects[parent]._scripts[key]
        if key in self.datasets_names:
            return self._datasets[key]
        elif key in self.projects_names:
            return self._projects[key]
        elif key in self.scripts_names:
            return self._scripts[key]
        else:
            raise KeyError(
                f"{key}: This object name does not exist in this project.")

    # ..................................................................................................................
    def __setitem__(self, key, value):

        if not isinstance(key, str):
            raise KeyError('The key must be a string.')

        if key in self.allnames and not isinstance(value, type(self[key])):
            raise ValueError('the key exists but for a different type '
                             'of object: {}'.format(type(self[key]).__name__))

        if key in self.datasets_names:
            value.parent = self
            self._datasets[key] = value
        elif key in self.projects_names:
            value.parent = self
            self._projects[key] = value
        elif key in self.scripts_names:
            value.parent = self
            self._scripts[key] = value
        else:
            # the key does not exists
            self._set_from_type(value, name=key)

    # ..................................................................................................................
    def __getattr__(self, item):

        if "_validate" in item or "_changed" in item:
            # this avoid infinite recursion due to the traits management
            return super().__getattribute__(item)

        elif item in self.allnames:
            # allows to access project, dataset or script by attribute
            return self[item]

        elif item in self.meta.keys():
            # return the attribute
            return self.meta[item]

        else:
            raise AttributeError("`%s` has no attribute `%s`" %
                                 (type(self).__name__, item))

    # ..................................................................................................................
    def __iter__(self):
        for items in self.allitems:
            yield items

    # ..................................................................................................................
    def __str__(self):

        s = "Project {}:\n".format(self.name)

        lens = len(s)

        def _listproj(s, project, ns):
            ns += 1
            sep = "   " * ns

            for k, v in project._projects.items():
                s += "{} ⤷ {} (sub-project)\n".format(sep, k)
                s = _listproj(s, v, ns)  # recursive call

            for k, v in project._datasets.items():
                s += "{} ⤷ {} (dataset)\n".format(sep, k)

            for k, v in project._scripts.items():
                s += "{} ⤷ {} (script)\n".format(sep, k)

            if len(s) == lens:
                # nothing has been found in the project
                s += "{} (empty project)\n".format(sep)

            return s.strip('\n')

        return _listproj(s, self, 0)

    def __dir__(self):
        return [
            'name',
            'meta',
            'parent',
            'datasets',
            'projects',
            'scripts',
        ]

    def __copy__(self):
        new = Project()
        # new.name = self.name + '*'
        for item in self.__dir__():
            # if item == 'name':
            #     continue
            item = "_" + item
            data = getattr(self, item)
            # if isinstance(data, (Project,NDDataset, Script)):
            #     setattr(new, item, data.copy())
            # elif item in ['_datasets', '_projects', '_scripts']:
            #
            # else:
            setattr(new, item, cpy(data))
        return new

    # ------------------------------------------------------------------------------------------------------------------
    # properties
    # ------------------------------------------------------------------------------------------------------------------

    # ..................................................................................................................
    @default('_id')
    def _id_default(self):
        # a unique id
        return f"{type(self).__name__}_{str(uuid.uuid1()).split('-')[0]}"

    # ..................................................................................................................
    @property
    def id(self):
        """
        str - Readonly object identifier.
        """
        return self._id

    # ..................................................................................................................
    @property
    def name(self):
        """
        str - An user friendly name for the project. The default is
        automatically generated.
        """
        return self._name

    # ..................................................................................................................
    @name.setter
    def name(self, name):
        # property.setter for name
        if name is not None:
            self._name = name
        else:
            self.name = "Project-" + self.id.split('-')[0]

    # ..................................................................................................................
    @property
    def parent(self):
        """
        project - instance of the Project which is the parent (if any) of the
        current project.
        """
        return self._parent

    # ..................................................................................................................
    @parent.setter
    def parent(self, value):
        if self._parent is not None:
            # A parent project already exists for this sub-project but the
            # entered values gives a different parent. This is not allowed,
            # as it can produce impredictable results. We will fisrt remove it
            # from the current project.
            self._parent.remove_project(self.name)
        self._parent = value

    # ..................................................................................................................
    @default('_parent')
    def _get_parent(self):
        return None

    # ..................................................................................................................
    @default('_meta')
    def _meta_default(self):
        return Meta()

    # ..................................................................................................................
    @property
    def meta(self):
        """
        meta - metadata for the project

        meta contains all attribute except the name,
        id and parent of the current project.
        """
        return self._meta

    # ..................................................................................................................
    @property
    def datasets_names(self):
        """
        list - names of all dataset included in this project.
        (does not return those located in sub-folders).
        """
        lst = list(self._datasets.keys())
        return lst

    @property
    def directory(self):
        return self._directory

    # ..................................................................................................................
    @property
    def datasets(self):
        """
        list - datasets included in this project excluding those
        located in subprojects.
        """
        d = []
        for name in self.datasets_names:
            d.append(self._datasets[name])
        return d

    @datasets.setter
    def datasets(self, datasets):

        self.add_datasets(*datasets)

    # ..................................................................................................................
    @property
    def projects_names(self):
        """
        list - names of all subprojects included in this project.
        """
        lst = list(self._projects.keys())
        return lst

    # ..................................................................................................................
    @property
    def projects(self):
        """
        list - subprojects included in this project.
        """
        p = []
        for name in self.projects_names:
            p.append(self._projects[name])
        return p

    @projects.setter
    def projects(self, projects):

        self.add_projects(*projects)

    # ..................................................................................................................
    @property
    def scripts_names(self):
        """
        list - names of all scripts included in this project.
        """
        lst = list(self._scripts.keys())
        return lst

    # ..................................................................................................................
    @property
    def scripts(self):
        """
        list - scripts included in this project.
        """
        s = []
        for name in self.scripts_names:
            s.append(self._scripts[name])
        return s

    @scripts.setter
    def scripts(self, scripts):

        self.add_scripts(*scripts)

    @property
    def allnames(self):
        """
        list - names of all objects contained in this project
        """
        return self.datasets_names + self.projects_names + self.scripts_names

    @property
    def allitems(self):
        """
        list - all items contained in this project
        """
        return list(self._datasets.items()) + list(
            self._projects.items()) + list(self._scripts.items())

    # ------------------------------------------------------------------------------------------------------------------
    # Public methods
    # ------------------------------------------------------------------------------------------------------------------

    # ..................................................................................................................
    def implements(self, name=None):
        """
        Utility to check if the current object implement `Project`.

        Rather than isinstance(obj, Project) use object.implements('Project').
        This is useful to check type without importing the module.
        """
        if name is None:
            return 'Project'
        else:
            return name == 'Project'

    def copy(self):
        """
        Make an exact copy of the current project.
        """
        return self.__copy__()

    # ------------------------------------------------------------------------------------------------------------------
    # dataset items
    # ------------------------------------------------------------------------------------------------------------------

    # ..................................................................................................................
    def add_datasets(self, *datasets):
        """
        Add datasets to the current project.

        Parameters
        ----------
        datasets : series of |NDDataset|
            Datasets to add to the current project.
            The name of the entries in the project will be identical to the
            names of the datasets.

        Examples
        --------
        Assuming that ds1, ds2 and ds3 are already defined datasets :

        >>> proj = Project()
        >>> proj.add_datasets(ds1, ds2, ds3) # doctest: +SKIP
        """
        for ds in datasets:
            self.add_dataset(ds)

    # ..................................................................................................................
    def add_dataset(self, dataset, name=None):
        """
        Add datasets to the current project.

        Parameters
        ----------
        dataset : |NDDataset|
            Datasets to add.
            The name of the entry will be the name of the dataset, except
            if parameter `name` is defined.
        name : str, optional
            If provided the name will be used to name the entry in the project.

        Examples
        --------
        Assuming that ds1 is an already defined dataset :

        >>> proj = Project()
        >>> proj.add_dataset(ds1, name='Toto') # doctest: +SKIP
        """

        dataset.parent = self
        if name is None:
            name = dataset.name

        n = 1
        while name in self.allnames:
            # this name already exists
            name = f'{dataset.name}-{n}'
            n += 1

        dataset.name = name
        self._datasets[name] = dataset

    # ..................................................................................................................
    def remove_dataset(self, name):
        """
        Remove a dataset from the project.

        Parameters
        ----------
        name : str
            Name of the dataset to remove.
        """
        self._datasets[name]._parent = None  # remove the parent info
        del self._datasets[name]  # remove the object from the list of datasets

    # ..................................................................................................................
    def remove_all_dataset(self):
        """
        Remove all dataset from the project.
        """
        for v in self._datasets.values():
            v._parent = None
        self._datasets = {}

    # ------------------------------------------------------------------------------------------------------------------
    # project items
    # ------------------------------------------------------------------------------------------------------------------

    # ..................................................................................................................
    def add_projects(self, *projects):
        """
        Add one or a series of projects to the current project.

        Parameters
        ----------
        projects : project instances
            The projects to add to the current ones.
        """
        for proj in projects:
            self.add_project(proj)

    # ..................................................................................................................
    def add_project(self, proj, name=None):
        """
        Add one project to the current project.

        Parameters
        ----------
        proj : a project instance
            A project to add to the current one
        """
        proj.parent = self
        if name is None:
            name = proj.name
        else:
            proj.name = name
        self._projects[name] = proj

    # ..................................................................................................................
    def remove_project(self, name):
        """
        remove one project from the current project.

        Parameters
        ----------
        name : str
            Name of the project to remove
        """
        self._projects[name]._parent = None
        del self._projects[name]

    # ..................................................................................................................
    def remove_all_project(self):
        """
        remove all projects from the current project.
        """
        for v in self._projects.values():
            v._parent = None
        self._projects = {}

    # ------------------------------------------------------------------------------------------------------------------
    # script items
    # ------------------------------------------------------------------------------------------------------------------

    # ..................................................................................................................
    def add_scripts(self, *scripts):
        """
         Add one or a series of scripts to the current project.

         Parameters
         ----------
         scripts : |Script| instances
         """
        for sc in scripts:
            self.add_script(sc)

    # ..................................................................................................................
    def add_script(self, script, name=None):
        """
        Add one script to the current project.

        Parameters
        ----------
        script : a |Script| instance
        name : str
        """
        script.parent = self
        if name is None:
            name = script.name
        else:
            script.name = name
        self._scripts[name] = script

    # ..................................................................................................................
    def remove_script(self, name):
        self._scripts[name]._parent = None
        del self._scripts[name]

    # ..................................................................................................................
    def remove_all_script(self):
        for v in self._scripts.values():
            v._parent = None
        self._scripts = {}