def start(self): """ Start the plugin life-cycle. This method is called by the framework at the appropriate time. It should never be called by user code. """ super(PulsesManagerPlugin, self).start() core = self.workbench.get_plugin('enaml.workbench.core') core.invoke_command('ecpy.app.errors.enter_error_gathering') state = core.invoke_command('ecpy.app.states.get', {'state_id': 'ecpy.app.directory'}) p_dir = os.path.join(state.app_directory, 'pulses') # Create pulses subfolder if it does not exist. if not os.path.isdir(p_dir): os.mkdir(p_dir) temp_dir = os.path.join(p_dir, 'templates') # Create templates subfolder if it does not exist. if not os.path.isdir(temp_dir): os.mkdir(temp_dir) self.templates_folders = [temp_dir] # Start the Declarators and Extensions collectors to collect # all elements of the plugin that are declared through enaml # declarations self._filters = ExtensionsCollector(workbench=self.workbench, point=FILTERS_POINT, ext_class=SequenceFilter) self._configs = DeclaratorsCollector(workbench=self.workbench, point=CONFIGS_POINT, ext_class=(SequenceConfig, SequenceConfigs)) self._sequences = DeclaratorsCollector(workbench=self.workbench, point=SEQUENCES_POINT, ext_class=(Sequences, Sequence)) self._contexts = DeclaratorsCollector(workbench=self.workbench, point=CONTEXTS_POINT, ext_class=(Contexts, Context)) self._shapes = DeclaratorsCollector(workbench=self.workbench, point=SHAPES_POINT, ext_class=(Shapes, Shape)) # Bind the observers before starting the collectors so that they will # update the lists of known seq, configs, filters, contexts... self._bind_observers() self._sequences.start() self._configs.start() self._filters.start() self._contexts.start() self._shapes.start() # Populate the Pulse Info Object self._pulse_infos = PulseInfos() self._pulse_infos.cls = Pulse self._pulse_infos.view = PulseView core.invoke_command('ecpy.app.errors.exit_error_gathering')
class PulsesManagerPlugin(HasPreferencesPlugin): """Plugin responsible for managing pulses. """ #: Folders containings templates which should be loaded. templates_folders = List() # .tag(pref=True) # TODO harcoded currently #: List of all known sequences and template-sequences. sequences = List(Unicode()) #: List of all known contexts contexts = List(Unicode()) #: List of all known shape. shapes = List(Unicode()) #: List of all known filters: filters = List() #: Reference to the workspace or None if the workspace is not active. workspace = ForwardTyped(workspace) #: Reference to the workspace state. workspace_state = ForwardTyped(workspace_state) def start(self): """ Start the plugin life-cycle. This method is called by the framework at the appropriate time. It should never be called by user code. """ super(PulsesManagerPlugin, self).start() core = self.workbench.get_plugin('enaml.workbench.core') core.invoke_command('ecpy.app.errors.enter_error_gathering') state = core.invoke_command('ecpy.app.states.get', {'state_id': 'ecpy.app.directory'}) p_dir = os.path.join(state.app_directory, 'pulses') # Create pulses subfolder if it does not exist. if not os.path.isdir(p_dir): os.mkdir(p_dir) temp_dir = os.path.join(p_dir, 'templates') # Create templates subfolder if it does not exist. if not os.path.isdir(temp_dir): os.mkdir(temp_dir) self.templates_folders = [temp_dir] # Start the Declarators and Extensions collectors to collect # all elements of the plugin that are declared through enaml # declarations self._filters = ExtensionsCollector(workbench=self.workbench, point=FILTERS_POINT, ext_class=SequenceFilter) self._configs = DeclaratorsCollector(workbench=self.workbench, point=CONFIGS_POINT, ext_class=(SequenceConfig, SequenceConfigs)) self._sequences = DeclaratorsCollector(workbench=self.workbench, point=SEQUENCES_POINT, ext_class=(Sequences, Sequence)) self._contexts = DeclaratorsCollector(workbench=self.workbench, point=CONTEXTS_POINT, ext_class=(Contexts, Context)) self._shapes = DeclaratorsCollector(workbench=self.workbench, point=SHAPES_POINT, ext_class=(Shapes, Shape)) # Bind the observers before starting the collectors so that they will # update the lists of known seq, configs, filters, contexts... self._bind_observers() self._sequences.start() self._configs.start() self._filters.start() self._contexts.start() self._shapes.start() # Populate the Pulse Info Object self._pulse_infos = PulseInfos() self._pulse_infos.cls = Pulse self._pulse_infos.view = PulseView core.invoke_command('ecpy.app.errors.exit_error_gathering') def stop(self): """ Stop the plugin life-cycle. This method is called by the framework at the appropriate time. It should never be called by user code. """ super(PulsesManagerPlugin, self).stop() self._unbind_observers() self._template_sequences_data.clear() self._template_sequences_infos.clear() # Stop all Extension/DeclaratorCollectors self._filters.stop() self._configs.stop() self._sequences.stop() self._contexts.stop() self._shapes.stop() def get_item_infos(self, item_id): """Give access to an item infos. NB : an item can be a sequence or a pulse. Parameters ---------- item_id : unicode The id of the requested item. Returns ------- item_infos : ItemInfos or None The required item infos or None if it was not found. """ if item_id == "ecpy_pulses.Pulse": return self._pulse_infos if item_id in self._sequences.contributions: return self._sequences.contributions[item_id] elif item_id in self._template_sequences_infos: t_info = self._template_sequences_infos[item_id] if not t_info.metadata['loaded']: config, doc = load_sequence_prefs(t_info.metadata['path']) t_info.metadata['template_config'] = config t_info.metadata['template_doc'] = doc t_info.metadata['loaded'] = True return t_info elif item_id == "ecpy_pulses.__template__": infos = SequenceInfos() infos.cls = TemplateSequence infos.view = TemplateSequenceView return infos else: return None def get_item(self, item_id, view=False): """Access a given item class. Parameters ---------- item_id : unicode Id of the item for which to return the actual class. view : bool, optional Whether or not to return the view assoicated with the item. Returns ------- item_cls : type or None Class associated to the requested item or None if the item was not found. item_view : EnamlDefMeta or None, optional Associated view if requested. """ infos = self.get_item_infos(item_id) if not infos: return None if not view else (None, None) else: return infos.cls if not view else (infos.cls, infos.view) def get_items(self, item_ids): """Access the classes associated to a set of items. Parameters ---------- item_ids : list(unicode) Ids of the item for which to return the actual class. Returns ------- items_cls : dict Dictionary mapping the requested items to the actual classes. missing : list List of items that were not found. """ items_cls = {} missing = [] for t in item_ids: res = self.get_item(t) if res: items_cls[t] = res else: missing.append(t) return items_cls, missing def get_context_infos(self, context_id): """Give access to a context infos. Parameters ---------- context_id : unicode Id of the requested context. Returns ------- context_infos : ContextInfos or None Infos for the requested context or None if the context was not found. """ return self._contexts.contributions.get(context_id) def get_context(self, context_id, view=False): """Access the class associated with a context. Parameters ---------- context_id : unicode Id of the context for which to return the class view : bool, optional Whether or not to return the view associated with context. Returns ------- context_cls : type or None Class associated to the requested context or None if the context was not found. item_view : EnamlDefMeta or None, optional Associated view if requested. """ infos = self.get_context_infos(context_id) if not infos: return None if not view else (None, None) else: return infos.cls if not view else (infos.cls, infos.view) def get_shape_infos(self, shape_id): """ Give access to a shape infos. Parameters ---------- shape : unicode Id of the requested shapes. view : bool When false, the view is not returned alongside the class. Returns ------- shape_infos : ShapeInfos or None The required shape infos or None if the shape was not found. """ return self._shapes.contributions.get(shape_id) def get_shape(self, shape_id, view=False): """Access the class associated with a shape. Parameters ---------- shape_id : unicode Id of the shape for which to return the class view : bool, optional Whether or not to return the view associated with context. Returns ------- context_cls : type or None Class associated to the requested shape or None if the shape was not found. item_view : EnamlDefMeta or None, optional Associated view if requested. """ infos = self.get_shape_infos(shape_id) if not infos: return None if not view else (None, None) else: return infos.cls if not view else (infos.cls, infos.view) # TODO for future easiness of extension # Note that the pulse view should be updated too def get_modulation_infos(self, modulation_id): """ """ raise NotImplementedError() def get_modulation(self, modulation_id, view=False): """Get the modulation class. """ if modulation_id == 'ecpy_pulses.Modulation': return Modulation else: return None def get_config(self, sequence_id): """ Access the proper config for a sequence. Parameters ---------- sequence_id : str Id of the sequence for which a config is required Returns ------- config : tuple Tuple containing the config object requested, and its visualisation Notes ----- It is the responsability of the user to properly set the root attribute of the returned config object. """ templates = self._template_sequences_data if sequence_id in templates: config_infos = self._configs.contributions['__template__'] conf_cls = config_infos.cls conf_view = config_infos.view t_config, t_doc = load_sequence_prefs(templates[sequence_id]) conf = conf_cls(manager=self, template_config=t_config, template_doc=t_doc, root=self.workspace.state.sequence) view = conf_view(model=conf) return conf, view elif sequence_id in self._sequences.contributions: configs = self._configs.contributions # Look up the hierarchy of the selected sequence to get the # appropriate SequenceConfig sequence_class = self._sequences.contributions[sequence_id].cls for i_class in type.mro(sequence_class): if i_class in configs: conf_cls = configs[i_class].cls conf_view = configs[i_class].view conf = conf_cls(manager=self, sequence_class=sequence_class) view = conf_view(model=conf) return conf, view return None, None def list_sequences(self, filter_name='All'): """ Filter the known sequences using the specified filter. Parameters ---------- filter_name : str Name of the filter to use Returns ------- sequences : list(str) or None Sequences selected by the filter, or None if the filter does not exist. """ s_filter = self._filters.contributions.get(filter_name) if s_filter: # Remove items that should not be shown in the list sequences = self._sequences.contributions.copy() template_sequences_data = self._template_sequences_data.copy() try: sequences.pop('ecpy_pulses.RootSequence') except KeyError: # pragma: no cover pass return s_filter.filter_sequences(sequences, template_sequences_data) else: logger = logging.getLogger(__name__) logger.warn("Did not find the filter " + str(filter_name) + " and returned zero elements.") return [] # --- Private API --------------------------------------------------------- #: Sequences implemented in Python _sequences = Typed(DeclaratorsCollector) #: Template sequences (store full path to .ini) _template_sequences_data = Dict(Unicode(), Unicode()) #: Template sequences infos _template_sequences_infos = Dict(Unicode(), SequenceInfos) #: Info Object for Pulse _pulse_infos = Typed(PulseInfos) #: Sequence contexts. _contexts = Typed(DeclaratorsCollector) #: Task config dict for python tasks (task_class: (config, view)) _shapes = Typed(DeclaratorsCollector) #: Contributed task filters. _filters = Typed(ExtensionsCollector) #: Configuration object used to insert new sequences in existing ones. _configs = Typed(DeclaratorsCollector) # Watchdog observer _observer = Typed(Observer, ()) def _refresh_known_template_sequences(self): """Refresh the known template sequences. """ templates = {} for path in self.templates_folders: if os.path.isdir(path): filenames = [f for f in os.listdir(path) if (os.path.isfile(os.path.join(path, f)) and f.endswith('.temp_pulse.ini'))] filenames.sort() for filename in filenames: template_name = filename[:-len('.temp_pulse.ini')] template_path = os.path.join(path, filename) # Beware redundant names are overwrited templates[template_name] = template_path else: logger = logging.getLogger(__name__) logger.warn('{} is not a valid directory'.format(path)) self._template_sequences_data = templates aux = (list(self._sequences.contributions) + list(templates)) self.sequences = aux self._refresh_template_sequences_infos() def _refresh_template_sequences_infos(self): """ Refresh the known template sequence infos. """ # TODO Should be more proper in case of update templates = self._template_sequences_data templates_infos = {} for template_name, template_path in templates.items(): metadata = {'is_template': True, 'path': template_path, 'loaded': False} infos = SequenceInfos(metadata=metadata) infos.cls = TemplateSequence infos.view = TemplateSequenceView templates_infos[template_name] = infos self._template_sequences_infos = templates_infos def _update_filters(self, change): """ Update the list of known filters. """ self.filters = list(self._filters.contributions.keys()) def _update_known_contexts(self, change): """ Update the list of known contexts. """ self.contexts = list(self._contexts.contributions.keys()) def _update_known_sequences(self, change): """ Update the list of known sequences. """ self.sequences = list(self._sequences.contributions.keys()) #: Always refresh the list of known templates after refreshing #: sequences, as we could have just added a template. self._refresh_known_template_sequences() def _update_known_shapes(self, change): """ Update the list of known shapes. """ self.shapes = list(self._shapes.contributions.keys()) def _bind_observers(self): """ Setup the observers for the plugin. """ for folder in self.templates_folders: handler = SystematicFileUpdater( self._refresh_known_template_sequences) self._observer.schedule(handler, folder, recursive=True) self._observer.start() self._contexts.observe('contributions', self._update_known_contexts) self._shapes.observe('contributions', self._update_known_shapes) self._sequences.observe('contributions', self._update_known_sequences) self._filters.observe('contributions', self._update_filters) self.observe('templates_folders', self._update_templates) def _unbind_observers(self): """ Remove the observers for the plugin. """ self.unobserve('templates_folders', self._update_templates) self._filters.unobserve('contributions', self._update_filters) self._observer.unschedule_all() self._observer.stop() self._observer.join() def _update_templates(self, change): """Observer ensuring that we observe the right template folders. """ self._observer.unschedule_all() for folder in self.templates_folders: if not os.path.isdir(folder): continue handler = SystematicFileUpdater( self._refresh_known_template_sequences) self._observer.schedule(handler, folder, recursive=True) self._refresh_known_template_sequences()
class PulsesManagerPlugin(HasPreferencesPlugin): """Plugin responsible for managing pulses. """ #: Folders containings templates which should be loaded. templates_folders = List().tag(pref=True) #: List of all known sequences and template-sequences. sequences = List(Unicode()) #: List of all known contexts contexts = List(Unicode()) #: List of all known shape. shapes = List(Unicode()) #: List of all known filters: filters = List() #: Reference to the workspace or None if the workspace is not active. workspace = ForwardTyped(workspace) #: Reference to the workspace state. workspace_state = ForwardTyped(workspace_state) def start(self): """ Start the plugin life-cycle. This method is called by the framework at the appropriate time. It should never be called by user code. """ super(PulsesManagerPlugin, self).start() core = self.workbench.get_plugin('enaml.workbench.core') core.invoke_command('ecpy.app.errors.enter_error_gathering') state = core.invoke_command('ecpy.app.states.get', {'state_id': 'ecpy.app.directory'}) t_dir = os.path.join(state.app_directory, 'pulses') # Create tasks subfolder if it does not exist. if not os.path.isdir(t_dir): os.mkdir(t_dir) temp_dir = os.path.join(t_dir, 'templates') # Create profiles subfolder if it does not exist. if not os.path.isdir(temp_dir): os.mkdir(temp_dir) self.templates_folders = [temp_dir] #: Start the Declarators and Extensions collectors to collect #: all elements of the plugin that are declared through enaml #: declarations self._filters = ExtensionsCollector(workbench=self.workbench, point=FILTERS_POINT, ext_class=ItemFilter) self._configs = DeclaratorsCollector(workbench=self.workbench, point=CONFIGS_POINT, ext_class=(SequenceConfig, SequenceConfigs)) self._sequences = DeclaratorsCollector(workbench=self.workbench, point=SEQUENCES_POINT, ext_class=(Sequences, Sequence)) self._contexts = DeclaratorsCollector(workbench=self.workbench, point=CONTEXTS_POINT, ext_class=(Contexts, Context)) self._shapes = DeclaratorsCollector(workbench=self.workbench, point=SHAPES_POINT, ext_class=(Shapes, Shape)) #: Bind the observers before strating the collectors so that they will #: update thelists of known seq,configs, filters, contexts... self._bind_observers() self._sequences.start() self._configs.start() self._filters.start() self._contexts.start() self._shapes.start() #: Populate the Pulse Info Object self._pulse_info = PulseInfos() self._pulse_info.cls = Pulse self._pulse_info.view = PulseView core.invoke_command('ecpy.app.errors.exit_error_gathering') def stop(self): """ Stop the plugin life-cycle. This method is called by the framework at the appropriate time. It should never be called by user code. """ super(PulsesManagerPlugin, self).stop() self._unbind_observers() self._template_sequences_data.clear() self._template_sequences_infos.clear() #: Stop all Extension/DeclaratorCollectors self._filters.stop() self._configs.stop() self._sequences.stop() self._contexts.stop() self._shapes.stop() def get_sequences_infos(self, sequences): """ Give access to sequence infos. Parameters ---------- sequences : list(str) The names of the requested sequences. views : bool When false views are not returned alongside the class. Returns ------- sequences : dict The required sequences infos as a dict. For Python sequences the entry will contain the class and the view ({name: (class, view)}). For templates the entry will contain the path, the data as a ConfigObj object and the doc ({name : (path, data, doc)}) missings : list List of the sequences which were not found. """ answer = {} missing_py = set([name for name in sequences if name not in self._sequences.contributions.keys()]) missing_temp = set([name for name in sequences if name not in self._template_sequences_infos.keys()]) missing = list(set.intersection(missing_py, missing_temp)) answer.update({key: val for key, val in self._sequences.contributions.items() if key in sequences}) templ = {key: val for key, val in self._template_sequences_infos.items() if key in sequences} for t_name, t_info in templ.items(): #: Load metadata if it was not alredy loaded if not t_info.metadata['loaded']: config, doc = load_sequence_prefs(t_info.metadata['path']) t_info.metadata['config'] = config t_info.metadata['doc'] = doc answer.update(templ) return answer, missing def get_sequence_infos(self, sequence): """Access a given sequence infos. Parameters ---------- sequence : unicode Id of the sequence class for which to return the actual class. Returns ------- infos : SequenceInfos or None Object containing all the infos about the requested sequence. This object should never be manipulated directly by user code. """ sequences = [sequence] _answer, _ = self.get_sequences_infos(sequences) try: answer = _answer[sequence] missing = None except KeyError: answer = None missing = [sequence] return answer, missing def get_sequences(self, sequences): """Access a given sequence class. Parameters ---------- sequence : unicode Id of the sequence class for which to return the actual class. view : bool, optional Whether or not to return the view assoicated with the sequence. Returns ------- task_cls : type or None Class associated to the requested sequence or None if the sequence was not found. task_view : EnamlDefMeta or None, optional Associated view if requested. """ _answer, _missing = self.get_sequences_infos(sequences) answer = {key: (val.cls, val.view) for key, val in _answer} for key, val in _answer: answer = {key, val} return answer, _missing def get_sequence(self, sequence): """Access a given sequence class. Parameters ---------- sequence : unicode Id of the sequence class for which to return the actual class. view : bool, optional Whether or not to return the view assoicated with the sequence. Returns ------- task_cls : type or None Class associated to the requested sequence or None if the sequence was not found. task_view : EnamlDefMeta or None, optional Associated view if requested. """ _answer, _ = self.get_sequence_infos(sequence) if _answer is None: return (None, None) return (_answer.cls, _answer.view) def get_items_infos(self, items): """TODO """ _answer, _missing = self.get_sequences_infos(items) additional_items = {} missing = [] for el in _missing: if el == "ecpy_pulses.Pulse": additional_items[el] = self._pulse_info elif el == "ecpy_pulses.__template__": infos = SequenceInfos() infos.cls = TemplateSequence infos.view = TemplateSequenceView additional_items[el] = infos else: missing.append(el) _answer.update(additional_items) return _answer, missing def get_item_infos(self, item): """TODO """ _answer, _ = self.get_items_infos([item]) try: answer = _answer[item] missing = None except KeyError: answer = None missing = [item] return answer, missing def get_contexts_infos(self, contexts): """ Give access to context infos. Parameters ---------- contexts : list(str) The names of the requested contexts. Returns ------- contexts : dict The required contexts infos as a dict {name: (class, view)}. """ if not isinstance(contexts, list): raise ValueError("plugin.py:get_contexts_infos" + " - contexts should be a list") answer = {} a = {val.cls.__name__: val for val in self._contexts.contributions.values()} missing = [name for name in contexts if name not in self._contexts.contributions.keys()] answer.update({key: val for key, val in self._contexts.contributions.items() if key in contexts}) return answer, missing def get_context_infos(self, context): """ Give access to context infos. Parameters ---------- contexts : list(str) The names of the requested contexts. Returns ------- contexts : dict The required contexts infos as a dict {name: (class, view)}. """ contexts = [context] _answer, _ = self.get_contexts_infos(contexts) try: answer = _answer[context] missing = None except KeyError: answer = None missing = context return answer, missing def get_shapes_infos(self, shapes): """ Give access to shape infos. Parameters ---------- shapes : list(str) The names of the requested shapes. views : bool When flase views are not returned alongside the class. Returns ------- shapes : dict The required shapes infos as a dict {name: (class, view)}. """ answer = {} missing = [name for name in shapes if name not in self._shapes.contributions.keys()] answer.update({key: val for key, val in self._shapes.contributions.items() if key in shapes}) return answer, missing def get_shape_infos(self, shape): """ Give access to a single shape infos. Parameters ---------- shapes : list(str) The names of the requested shapes. views : bool When flase views are not returned alongside the class. Returns ------- shapes : ShapeInfo The required shapes infos . """ shapes = [shape] _answer, _ = self.get_shapes_infos(shapes) try: answer = _answer[shape] missing = None except KeyError: answer = None missing = shape return answer, missing def get_modulation_class(self): return Modulation def get_config(self, sequence_id): """ Access the proper config for a sequence. Parameters ---------- sequence : str Name of the sequnce for which a config is required Returns ------- config : tuple Tuple containing the config object requested, and its visualisation """ templates = self._template_sequences_data if sequence_id in templates: config_infos = self._configs.contributions['__template__'] conf_cls = config_infos.cls conf_view = config_infos.view t_config, t_doc = load_sequence_prefs(templates[sequence_id]) conf = conf_cls(manager=self, template_config=t_config, template_doc=t_doc, root=self.workspace.state.sequence) view = conf_view(model=conf) return conf, view elif sequence_id in self._sequences.contributions: configs = self._configs.contributions # Look up the hierarchy of the selected sequence to get the # appropriate SequenceConfig sequence_class = self._sequences.contributions[sequence_id].cls for i_class in type.mro(sequence_class): if i_class in configs: conf_cls = configs[i_class].cls conf_view = configs[i_class].view conf = conf_cls(manager=self, sequence_class=sequence_class, root=self.workspace.state.sequence) view = conf_view(model=conf) return conf, view return None, None def list_sequences(self, filter_name='All'): """ Filter the known sequences using the specified filter. Parameters ---------- filter_name : str Name of the filter to use Returns ------- sequences : list(str) or None Sequences selected by the filter, or None if the filter does not exist. """ s_filter = self._filters.contributions.get(filter_name) if s_filter: # Remove items that should not be shown in the list sequences = self._sequences.contributions.copy() template_sequences_data = self._template_sequences_data.copy() #TODO To contribution try: template_sequences_data.pop('Pulse') template_sequences_data.pop('ecpy_pulses.RootSequence') except KeyError: pass return s_filter.filter_items(sequences, template_sequences_data) else: logger = logging.getLogger(__name__) logger.warn("Did not find the filter " + str(filter_name) + " and returned zero elements.") return [] # --- Private API --------------------------------------------------------- #: Sequences implemented in Python _sequences = Typed(DeclaratorsCollector) #: Template sequences (store full path to .ini) _template_sequences_data = Dict(Unicode(), Unicode()) #: Template sequences infos _template_sequences_infos = Dict(Unicode(), SequenceInfos) #: Info Object for Pulse _pulse_info = Typed(PulseInfos) #: Sequence contexts. _contexts = Typed(DeclaratorsCollector) #: Task config dict for python tasks (task_class: (config, view)) _shapes = Typed(DeclaratorsCollector) #: Contributed task filters. _filters = Typed(ExtensionsCollector) #: Configuration object used to insert new sequences in existing ones. _configs = Typed(DeclaratorsCollector) # Watchdog observer _observer = Typed(Observer, ()) def _refresh_known_template_sequences(self): """ Refresh the known template sequences. """ templates = {} for path in self.templates_folders: if os.path.isdir(path): filenames = sorted(f for f in os.listdir(path) if (os.path.isfile(os.path.join(path, f)) and f.endswith('.ini'))) for filename in filenames: template_name = self._normalise_name(filename) template_path = os.path.join(path, filename) # Beware redundant names are overwrited templates[template_name] = template_path else: logger = logging.getLogger(__name__) logger.warn('{} is not a valid directory'.format(path)) self._template_sequences_data = templates aux = (list(self._sequences.contributions.keys()) + list(templates.keys())) self.sequences = aux self._refresh_template_sequences_infos() def _refresh_template_sequences_infos(self): """ Refresh the known template sequence infos. """ # TODO Should be more proper in case of update templates = self._template_sequences_data templates_infos = {} for template_name, template_path in templates.items(): metadata = {'is_template': True, 'path': template_path, 'loaded': False} infos = SequenceInfos(metadata=metadata) infos.cls = TemplateSequence infos.view = TemplateSequenceView templates_infos[template_name] = infos self._template_sequences_infos = templates_infos def _update_filters(self, change): """ Update the list of known filters. """ self.filters = list(self._filters.contributions.keys()) def _update_known_contexts(self, change): """ Update the list of known contexts. """ self.contexts = list(self._contexts.contributions.keys()) def _update_known_sequences(self, change): """ Update the list of known sequences. """ self.sequences = list(self._sequences.contributions.keys()) #: Always refresh the list of known templates after refreshing #: sequences, as we could have just added a template. self._refresh_known_template_sequences() def _update_known_shapes(self, change): """ Update the list of known shapes. """ self.shapes = list(self._shapes.contributions.keys()) def _bind_observers(self): """ Setup the observers for the plugin. """ for folder in self.templates_folders: handler = SystematicFileUpdater( self._refresh_known_template_sequences) self._observer.schedule(handler, folder, recursive=True) self._observer.start() self._contexts.observe('contributions', self._update_known_contexts) self._shapes.observe('contributions', self._update_known_shapes) self._sequences.observe('contributions', self._update_known_sequences) self._filters.observe('contributions', self._update_filters) self.observe('templates_folders', self._update_templates) def _unbind_observers(self): """ Remove the observers for the plugin. """ self.unobserve('templates_folders', self._update_templates) self._filters.unobserve('contributions', self._update_filters) self._observer.unschedule_all() self._observer.stop() self._observer.join() def _update_templates(self, change): """ Observer ensuring that we observe the right template folders. """ self._observer.unschedule_all() for folder in self.templates_folders: handler = SystematicFileUpdater(self._refresh_template_tasks) self._observer.schedule(handler, folder, recursive=True) self._refresh_known_template_sequences() @staticmethod def _normalise_name(name): """Normalize names by replacing '_' by spaces, removing the extension, and adding spaces between 'aA' sequences. """ if name.endswith('.ini'): name = name[:-4] + '\0' elif name.endswith('Shape'): name = name[:-5] + '\0' else: name += '\0' aux = '' for i, char in enumerate(name): if char == '_': aux += ' ' continue if char != '\0': if char.isupper() and i != 0: if name[i - 1].islower(): if name[i + 1].islower(): aux += ' ' + char.lower() else: aux += ' ' + char else: if name[i + 1].islower(): aux += ' ' + char.lower() else: aux += char else: if i == 0: aux += char.upper() else: aux += char return aux
class PulsesManagerPlugin(HasPreferencesPlugin): """Plugin responsible for managing pulses. """ #: Folders containings templates which should be loaded. templates_folders = List() # .tag(pref=True) # TODO harcoded currently #: List of all known sequences and template-sequences. sequences = List(Unicode()) #: List of all known contexts contexts = List(Unicode()) #: List of all known shape. shapes = List(Unicode()) #: List of all known filters: filters = List() #: Reference to the workspace or None if the workspace is not active. workspace = ForwardTyped(workspace) #: Reference to the workspace state. workspace_state = ForwardTyped(workspace_state) def start(self): """ Start the plugin life-cycle. This method is called by the framework at the appropriate time. It should never be called by user code. """ super(PulsesManagerPlugin, self).start() core = self.workbench.get_plugin('enaml.workbench.core') core.invoke_command('ecpy.app.errors.enter_error_gathering') state = core.invoke_command('ecpy.app.states.get', {'state_id': 'ecpy.app.directory'}) p_dir = os.path.join(state.app_directory, 'pulses') # Create pulses subfolder if it does not exist. if not os.path.isdir(p_dir): os.mkdir(p_dir) temp_dir = os.path.join(p_dir, 'templates') # Create templates subfolder if it does not exist. if not os.path.isdir(temp_dir): os.mkdir(temp_dir) self.templates_folders = [temp_dir] # Start the Declarators and Extensions collectors to collect # all elements of the plugin that are declared through enaml # declarations self._filters = ExtensionsCollector(workbench=self.workbench, point=FILTERS_POINT, ext_class=SequenceFilter) self._configs = DeclaratorsCollector(workbench=self.workbench, point=CONFIGS_POINT, ext_class=(SequenceConfig, SequenceConfigs)) self._sequences = DeclaratorsCollector(workbench=self.workbench, point=SEQUENCES_POINT, ext_class=(Sequences, Sequence)) self._contexts = DeclaratorsCollector(workbench=self.workbench, point=CONTEXTS_POINT, ext_class=(Contexts, Context)) self._shapes = DeclaratorsCollector(workbench=self.workbench, point=SHAPES_POINT, ext_class=(Shapes, Shape)) # Bind the observers before starting the collectors so that they will # update the lists of known seq, configs, filters, contexts... self._bind_observers() self._sequences.start() self._configs.start() self._filters.start() self._contexts.start() self._shapes.start() # Populate the Pulse Info Object self._pulse_infos = PulseInfos() self._pulse_infos.cls = Pulse self._pulse_infos.view = PulseView core.invoke_command('ecpy.app.errors.exit_error_gathering') def stop(self): """ Stop the plugin life-cycle. This method is called by the framework at the appropriate time. It should never be called by user code. """ super(PulsesManagerPlugin, self).stop() self._unbind_observers() self._template_sequences_data.clear() self._template_sequences_infos.clear() # Stop all Extension/DeclaratorCollectors self._filters.stop() self._configs.stop() self._sequences.stop() self._contexts.stop() self._shapes.stop() def get_item_infos(self, item_id): """Give access to an item infos. NB : an item can be a sequence or a pulse. Parameters ---------- item_id : unicode The id of the requested item. Returns ------- item_infos : ItemInfos or None The required item infos or None if it was not found. """ if item_id == "ecpy_pulses.Pulse": return self._pulse_infos if item_id in self._sequences.contributions: return self._sequences.contributions[item_id] elif item_id in self._template_sequences_infos: t_info = self._template_sequences_infos[item_id] if not t_info.metadata['loaded']: config, doc = load_sequence_prefs(t_info.metadata['path']) t_info.metadata['template_config'] = config t_info.metadata['template_doc'] = doc t_info.metadata['loaded'] = True return t_info elif item_id == "ecpy_pulses.__template__": infos = SequenceInfos() infos.cls = TemplateSequence infos.view = TemplateSequenceView return infos else: return None def get_item(self, item_id, view=False): """Access a given item class. Parameters ---------- item_id : unicode Id of the item for which to return the actual class. view : bool, optional Whether or not to return the view assoicated with the item. Returns ------- item_cls : type or None Class associated to the requested item or None if the item was not found. item_view : EnamlDefMeta or None, optional Associated view if requested. """ infos = self.get_item_infos(item_id) if not infos: return None if not view else (None, None) else: return infos.cls if not view else (infos.cls, infos.view) def get_items(self, item_ids): """Access the classes associated to a set of items. Parameters ---------- item_ids : list(unicode) Ids of the item for which to return the actual class. Returns ------- items_cls : dict Dictionary mapping the requested items to the actual classes. missing : list List of items that were not found. """ items_cls = {} missing = [] for t in item_ids: res = self.get_item(t) if res: items_cls[t] = res else: missing.append(t) return items_cls, missing def get_context_infos(self, context_id): """Give access to a context infos. Parameters ---------- context_id : unicode Id of the requested context. Returns ------- context_infos : ContextInfos or None Infos for the requested context or None if the context was not found. """ return self._contexts.contributions.get(context_id) def get_context(self, context_id, view=False): """Access the class associated with a context. Parameters ---------- context_id : unicode Id of the context for which to return the class view : bool, optional Whether or not to return the view associated with context. Returns ------- context_cls : type or None Class associated to the requested context or None if the context was not found. item_view : EnamlDefMeta or None, optional Associated view if requested. """ infos = self.get_context_infos(context_id) if not infos: return None if not view else (None, None) else: return infos.cls if not view else (infos.cls, infos.view) def get_shape_infos(self, shape_id): """ Give access to a shape infos. Parameters ---------- shape : unicode Id of the requested shapes. view : bool When false, the view is not returned alongside the class. Returns ------- shape_infos : ShapeInfos or None The required shape infos or None if the shape was not found. """ return self._shapes.contributions.get(shape_id) def get_shape(self, shape_id, view=False): """Access the class associated with a shape. Parameters ---------- shape_id : unicode Id of the shape for which to return the class view : bool, optional Whether or not to return the view associated with context. Returns ------- context_cls : type or None Class associated to the requested shape or None if the shape was not found. item_view : EnamlDefMeta or None, optional Associated view if requested. """ infos = self.get_shape_infos(shape_id) if not infos: return None if not view else (None, None) else: return infos.cls if not view else (infos.cls, infos.view) # TODO for future easiness of extension # Note that the pulse view should be updated too def get_modulation_infos(self, modulation_id): """ """ raise NotImplementedError() def get_modulation(self, modulation_id, view=False): """Get the modulation class. """ if modulation_id == 'ecpy_pulses.Modulation': return Modulation else: return None def get_config(self, sequence_id): """ Access the proper config for a sequence. Parameters ---------- sequence_id : str Id of the sequence for which a config is required Returns ------- config : tuple Tuple containing the config object requested, and its visualisation Notes ----- It is the responsability of the user to properly set the root attribute of the returned config object. """ templates = self._template_sequences_data if sequence_id in templates: config_infos = self._configs.contributions['__template__'] conf_cls = config_infos.cls conf_view = config_infos.view t_config, t_doc = load_sequence_prefs(templates[sequence_id]) conf = conf_cls(manager=self, template_config=t_config, template_doc=t_doc, root=self.workspace.state.sequence) view = conf_view(model=conf) return conf, view elif sequence_id in self._sequences.contributions: configs = self._configs.contributions # Look up the hierarchy of the selected sequence to get the # appropriate SequenceConfig sequence_class = self._sequences.contributions[sequence_id].cls for i_class in type.mro(sequence_class): if i_class in configs: conf_cls = configs[i_class].cls conf_view = configs[i_class].view conf = conf_cls(manager=self, sequence_class=sequence_class) view = conf_view(model=conf) return conf, view return None, None def list_sequences(self, filter_name='All'): """ Filter the known sequences using the specified filter. Parameters ---------- filter_name : str Name of the filter to use Returns ------- sequences : list(str) or None Sequences selected by the filter, or None if the filter does not exist. """ s_filter = self._filters.contributions.get(filter_name) if s_filter: # Remove items that should not be shown in the list sequences = self._sequences.contributions.copy() template_sequences_data = self._template_sequences_data.copy() try: sequences.pop('ecpy_pulses.RootSequence') except KeyError: # pragma: no cover pass return s_filter.filter_sequences(sequences, template_sequences_data) else: logger = logging.getLogger(__name__) logger.warn("Did not find the filter " + str(filter_name) + " and returned zero elements.") return [] # --- Private API --------------------------------------------------------- #: Sequences implemented in Python _sequences = Typed(DeclaratorsCollector) #: Template sequences (store full path to .ini) _template_sequences_data = Dict(Unicode(), Unicode()) #: Template sequences infos _template_sequences_infos = Dict(Unicode(), SequenceInfos) #: Info Object for Pulse _pulse_infos = Typed(PulseInfos) #: Sequence contexts. _contexts = Typed(DeclaratorsCollector) #: Task config dict for python tasks (task_class: (config, view)) _shapes = Typed(DeclaratorsCollector) #: Contributed task filters. _filters = Typed(ExtensionsCollector) #: Configuration object used to insert new sequences in existing ones. _configs = Typed(DeclaratorsCollector) # Watchdog observer _observer = Typed(Observer, ()) def _refresh_known_template_sequences(self): """Refresh the known template sequences. """ templates = {} for path in self.templates_folders: if os.path.isdir(path): filenames = [ f for f in os.listdir(path) if (os.path.isfile(os.path.join(path, f)) and f.endswith('.temp_pulse.ini')) ] filenames.sort() for filename in filenames: template_name = filename[:-len('.temp_pulse.ini')] template_path = os.path.join(path, filename) # Beware redundant names are overwrited templates[template_name] = template_path else: logger = logging.getLogger(__name__) logger.warn('{} is not a valid directory'.format(path)) self._template_sequences_data = templates aux = (list(self._sequences.contributions) + list(templates)) self.sequences = aux self._refresh_template_sequences_infos() def _refresh_template_sequences_infos(self): """ Refresh the known template sequence infos. """ # TODO Should be more proper in case of update templates = self._template_sequences_data templates_infos = {} for template_name, template_path in templates.items(): metadata = { 'is_template': True, 'path': template_path, 'loaded': False } infos = SequenceInfos(metadata=metadata) infos.cls = TemplateSequence infos.view = TemplateSequenceView templates_infos[template_name] = infos self._template_sequences_infos = templates_infos def _update_filters(self, change): """ Update the list of known filters. """ self.filters = list(self._filters.contributions.keys()) def _update_known_contexts(self, change): """ Update the list of known contexts. """ self.contexts = list(self._contexts.contributions.keys()) def _update_known_sequences(self, change): """ Update the list of known sequences. """ self.sequences = list(self._sequences.contributions.keys()) #: Always refresh the list of known templates after refreshing #: sequences, as we could have just added a template. self._refresh_known_template_sequences() def _update_known_shapes(self, change): """ Update the list of known shapes. """ self.shapes = list(self._shapes.contributions.keys()) def _bind_observers(self): """ Setup the observers for the plugin. """ for folder in self.templates_folders: handler = SystematicFileUpdater( self._refresh_known_template_sequences) self._observer.schedule(handler, folder, recursive=True) self._observer.start() self._contexts.observe('contributions', self._update_known_contexts) self._shapes.observe('contributions', self._update_known_shapes) self._sequences.observe('contributions', self._update_known_sequences) self._filters.observe('contributions', self._update_filters) self.observe('templates_folders', self._update_templates) def _unbind_observers(self): """ Remove the observers for the plugin. """ self.unobserve('templates_folders', self._update_templates) self._filters.unobserve('contributions', self._update_filters) self._observer.unschedule_all() self._observer.stop() self._observer.join() def _update_templates(self, change): """Observer ensuring that we observe the right template folders. """ self._observer.unschedule_all() for folder in self.templates_folders: if not os.path.isdir(folder): continue handler = SystematicFileUpdater( self._refresh_known_template_sequences) self._observer.schedule(handler, folder, recursive=True) self._refresh_known_template_sequences()