From 5acb0aaf0529e5efb32c950e0d77a830a40fc06e Mon Sep 17 00:00:00 2001 From: tangkong Date: Fri, 10 Jan 2025 16:05:08 -0800 Subject: [PATCH 1/2] PERF: delay preset sync until tab completion is accessed --- pcdsdevices/interface.py | 29 +++++++++++++++++++---------- pcdsdevices/tests/test_lens.py | 3 +++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/pcdsdevices/interface.py b/pcdsdevices/interface.py index a139fdd4669..e50459aff67 100644 --- a/pcdsdevices/interface.py +++ b/pcdsdevices/interface.py @@ -227,8 +227,13 @@ def __init_subclass__(cls, **kwargs): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._tab = self._class_tab.new_instance(self) + self._tab_initialized = False def __dir__(self): + if not self._tab_initialized and hasattr(self, 'presets'): + self._tab_initialized = True + self.presets.sync() + return self._tab.get_dir() def __repr__(self): @@ -1085,16 +1090,19 @@ def sync(self): self._remove_methods() self._cache = {} logger.debug('filling %s cache', self.name) - for preset_type in self._paths.keys(): - path = self._path(preset_type) - if path.exists(): - try: - self._cache[preset_type] = self._read(preset_type) - except BlockingIOError: - self._log_flock_error() - else: - logger.debug('No %s preset file for %s', - preset_type, self._device.name) + + # only consult files if tab-completion has been attempted + if self._device._tab_initialized: + for preset_type in self._paths.keys(): + path = self._path(preset_type) + if path.exists(): + try: + self._cache[preset_type] = self._read(preset_type) + except BlockingIOError: + self._log_flock_error() + else: + logger.debug('No %s preset file for %s', + preset_type, self._device.name) self._create_methods() def _log_flock_error(self): @@ -1140,6 +1148,7 @@ def _register_method(self, obj, method_name, method): self._methods.append((obj, method_name)) setattr(obj, method_name, MethodType(method, obj)) if hasattr(obj, '_tab'): + # obj._tab: TabCompletionHelperInstance obj._tab.add(method_name) def _make_add(self, preset_type): diff --git a/pcdsdevices/tests/test_lens.py b/pcdsdevices/tests/test_lens.py index 39bea9e5040..050a4612eef 100644 --- a/pcdsdevices/tests/test_lens.py +++ b/pcdsdevices/tests/test_lens.py @@ -116,6 +116,9 @@ def fake_lensstack(fake_att): E=sample_E, z_offset=.01, z_dir=1, att_obj=fake_att, lcls_obj=.01, mono_obj=.01) + # "activate" tab completion + for attr in ['x', 'y', 'z']: + getattr(fake_lensstack, attr)._tab_initialized = True return fake_lensstack From 61fe0197c3ab6fa2ac9f0746923d690f89e03db7 Mon Sep 17 00:00:00 2001 From: tangkong Date: Fri, 10 Jan 2025 16:54:48 -0800 Subject: [PATCH 2/2] MNT: make deferred loading of presets optional --- pcdsdevices/interface.py | 18 +++++++++++------- pcdsdevices/tests/test_lens.py | 3 --- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pcdsdevices/interface.py b/pcdsdevices/interface.py index e50459aff67..f8d7f3962b8 100644 --- a/pcdsdevices/interface.py +++ b/pcdsdevices/interface.py @@ -904,7 +904,7 @@ def set_position(self, position): self.set_current_position(position) -def setup_preset_paths(**paths): +def setup_preset_paths(**kwargs): """ Prepare the :class:`Presets` class. @@ -912,17 +912,21 @@ def setup_preset_paths(**paths): Parameters ---------- - **paths : str keyword args + **kwargs: str keyword args A mapping from type of preset to destination path. These will be directories that contain the yaml files that define the preset positions. - """ + (Optional) "defer_loading": bool, whether or not to defer the loading + of preset files until the first tab completion + """ + defer_loading = kwargs.pop("defer_loading", False) + paths = kwargs Presets._paths = {} for k, v in paths.items(): Presets._paths[k] = Path(v) for preset in Presets._registry: - preset.sync() + preset.sync(defer_loading=defer_loading) class Presets: @@ -1084,15 +1088,15 @@ def _update(self, preset_type, name, value=None, comment=None, except BlockingIOError: self._log_flock_error() - def sync(self): + def sync(self, defer_loading: bool = False): """Synchronize the presets with the database.""" logger.debug('call %s presets.sync()', self._device.name) self._remove_methods() self._cache = {} logger.debug('filling %s cache', self.name) - # only consult files if tab-completion has been attempted - if self._device._tab_initialized: + # only consult files if requested + if not defer_loading: for preset_type in self._paths.keys(): path = self._path(preset_type) if path.exists(): diff --git a/pcdsdevices/tests/test_lens.py b/pcdsdevices/tests/test_lens.py index 050a4612eef..39bea9e5040 100644 --- a/pcdsdevices/tests/test_lens.py +++ b/pcdsdevices/tests/test_lens.py @@ -116,9 +116,6 @@ def fake_lensstack(fake_att): E=sample_E, z_offset=.01, z_dir=1, att_obj=fake_att, lcls_obj=.01, mono_obj=.01) - # "activate" tab completion - for attr in ['x', 'y', 'z']: - getattr(fake_lensstack, attr)._tab_initialized = True return fake_lensstack