Adds the terminal-native picker: typing the trigger and pressing Tab pops
IPython's own completion menu (same widget, works in a real terminal or a
notebook -- ipykernel's completion goes through the same IPCompleter
matcher pipeline) instead of the Qt modal. Two triggers:
"..." component mode: Recent, Recommended (FrequencyCounts-ranked),
then a live substring-filterable walk of the namespace tree.
"...h" command mode (mnemonic: history): Recent/Recommended prior
*commands* (eco.logs.recent_commands/frequent_commands) -- no
namespace section, nothing to browse there.
Selecting an entry only ever inserts text at the cursor -- it never
executes, in either mode -- so the rest of the line can still be composed/
edited before submitting. This composes with the existing Qt-modal `...`
mechanism rather than conflicting with it: accepting a completion replaces
`...` in the buffer before the line is ever submitted, so if you don't use
Tab and just hit Enter, the AST-transform fallback still applies to
whatever literal `...` is left.
Real bug found while building this and wiring it through a live
IPCompleter (not just unit tests): eco.widgets.component_selector's
ComponentNode.name (and so pick_component_modal's return value) is
relative to `root` (confirmed against Alias.get_full_name(base=...), which
stops at base) -- not a standalone, globally-evaluable expression the way
this module had assumed everywhere. Both the Qt-modal path (_EllipsisPicker)
and this new completer were splicing that relative fragment in directly,
silently dropping the root's own name from the printed/inserted code.
Fixed with _full_path(root, relative_path), which reattaches the name
`root` is actually bound to (its own `.name`, matching this codebase's
`import eco.bernina as bernina` convention) -- verified against a real
IPCompleter.complete() call, not just the mocked unit tests, which is
exactly what surfaced the bug in the first place (a hand-fed "already
correct" path in the earlier Qt smoke test had been masking it).
Also fixed the smoke-test script that had encoded the same wrong
assumption, for anyone re-running it later.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
eco — Experiment Control
___ _______
/ -_) __/ _ \
Experiment Control \__/\__/\___/
eco is a Python-based control environment for experiments, developed and used at SwissFEL, PSI. It is used both as:
- a library of experimental devices for higher-level Python applications or GUIs, and
- an interactive command-line interface, e.g. from an IPython/Jupyter shell or notebook.
eco follows an object-oriented approach: every device is represented as a Python object with a small, predictable interface, so devices can be freely combined in generic control/acquisition routines and analysed with the scientific Python ecosystem. For a general introduction to object-oriented Python, see e.g. this short introduction.
Documentation
The full documentation — installation, core concepts, and worked examples (listening monitors, archiver data and strip charts, pipeline offload, motor configuration) — lives in docs/ and is built with Sphinx, configured to build on Read the Docs via .readthedocs.yaml.
Build it locally:
pip install -r docs/requirements.txt
sphinx-build -b html docs docs/_build/html
Installation
conda install -c paulscherrerinstitute eco
or, for development, in editable mode from a checkout:
git clone https://github.com/paulscherrerinstitute/eco.git
cd eco
pip install -e .
See Installation for beamline-specific setup (the
eco launcher, .ecorc defaults) and the full dependency picture.
Creating a new device
New devices are implemented as a subclass of Assembly, which provides
naming, aliasing, and shell representation:
from eco.elements.assembly import Assembly
class MyDevice(Assembly):
def __init__(self, name=None):
super().__init__(name=name)
self._append(MySubObject, name="my_sub_object", is_setting=True, is_status=True)
is_setting=True marks the child as a setting of the assembly (shown by
.settings() and captured when settings are saved); is_status=True marks it
as contributing to the assembly's .status(). See
Representing real devices — the Assembly in the full docs
for the rest of the model (Adjustable, Detector, Namespace) and a
from-scratch, runnable example of each.