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)
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): """
class Tree(HasTraits): value = Unicode() leaves = List(This())
class Bar(Foo): t = This()
class Foo(HasTraits): t = This()
class B(A): tt = This() ttt = This()
class A(HasTraits): t = This() tt = This()
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(' ', ' ') 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 = {}