seq-quince 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. seq_quince-0.1.1/CHANGELOG.md +16 -0
  2. seq_quince-0.1.1/CONTRIBUTING.md +47 -0
  3. seq_quince-0.1.1/LICENSE +21 -0
  4. seq_quince-0.1.1/MANIFEST.in +1 -0
  5. seq_quince-0.1.1/Makefile +110 -0
  6. seq_quince-0.1.1/PKG-INFO +145 -0
  7. seq_quince-0.1.1/README.md +102 -0
  8. seq_quince-0.1.1/bash-completion/.gitkeep +0 -0
  9. seq_quince-0.1.1/bash-completion/device_runner +19 -0
  10. seq_quince-0.1.1/bash-completion/scheduler_runner +111 -0
  11. seq_quince-0.1.1/docs/device_runner.md +84 -0
  12. seq_quince-0.1.1/docs/devices/README-actuator.md +71 -0
  13. seq_quince-0.1.1/docs/devices/README-hexapod.md +125 -0
  14. seq_quince-0.1.1/docs/devices/README-mono-zero-order.md +126 -0
  15. seq_quince-0.1.1/docs/devices/README-ophyd-motor.md +45 -0
  16. seq_quince-0.1.1/docs/devices/README-scan.md +86 -0
  17. seq_quince-0.1.1/docs/devices/README-sequence.md +71 -0
  18. seq_quince-0.1.1/docs/devices/README-sim.md +44 -0
  19. seq_quince-0.1.1/docs/devices/README-toggle.md +59 -0
  20. seq_quince-0.1.1/docs/devices/README-wait-condition.md +124 -0
  21. seq_quince-0.1.1/docs/devices.md +91 -0
  22. seq_quince-0.1.1/docs/misc/symbols.md +35 -0
  23. seq_quince-0.1.1/docs/resource-lifecycle.md +3 -0
  24. seq_quince-0.1.1/examples/beamline_env.sh +11 -0
  25. seq_quince-0.1.1/pyproject.toml +136 -0
  26. seq_quince-0.1.1/setup.cfg +4 -0
  27. seq_quince-0.1.1/src/config/beamlines/.gitkeep +0 -0
  28. seq_quince-0.1.1/src/config/common/device_templates/.gitkeep +0 -0
  29. seq_quince-0.1.1/src/seq_quince/__init__.py +0 -0
  30. seq_quince-0.1.1/src/seq_quince/config_manager.py +129 -0
  31. seq_quince-0.1.1/src/seq_quince/device/__init__.py +0 -0
  32. seq_quince-0.1.1/src/seq_quince/device/device_abs.py +96 -0
  33. seq_quince-0.1.1/src/seq_quince/device/extensions.py +211 -0
  34. seq_quince-0.1.1/src/seq_quince/device/factory.py +75 -0
  35. seq_quince-0.1.1/src/seq_quince/device/lifecycle.py +417 -0
  36. seq_quince-0.1.1/src/seq_quince/device_concrete/__init__.py +0 -0
  37. seq_quince-0.1.1/src/seq_quince/device_concrete/_option_lookup.py +42 -0
  38. seq_quince-0.1.1/src/seq_quince/device_concrete/actuator.py +88 -0
  39. seq_quince-0.1.1/src/seq_quince/device_concrete/hexapod.py +210 -0
  40. seq_quince-0.1.1/src/seq_quince/device_concrete/mono_zero_order.py +528 -0
  41. seq_quince-0.1.1/src/seq_quince/device_concrete/ophyd_motor.py +85 -0
  42. seq_quince-0.1.1/src/seq_quince/device_concrete/scan.py +195 -0
  43. seq_quince-0.1.1/src/seq_quince/device_concrete/sequence.py +189 -0
  44. seq_quince-0.1.1/src/seq_quince/device_concrete/sim.py +67 -0
  45. seq_quince-0.1.1/src/seq_quince/device_concrete/toggle.py +124 -0
  46. seq_quince-0.1.1/src/seq_quince/device_concrete/wait_condition.py +536 -0
  47. seq_quince-0.1.1/src/seq_quince/device_runner.py +71 -0
  48. seq_quince-0.1.1/src/seq_quince/loader.py +52 -0
  49. seq_quince-0.1.1/src/seq_quince/runner.py +230 -0
  50. seq_quince-0.1.1/src/seq_quince/scheduler.py +314 -0
  51. seq_quince-0.1.1/src/seq_quince/scheduler_check_devices.py +71 -0
  52. seq_quince-0.1.1/src/seq_quince/scheduler_runner.py +115 -0
  53. seq_quince-0.1.1/src/seq_quince/sequence.py +101 -0
  54. seq_quince-0.1.1/src/seq_quince/snapshot.py +116 -0
  55. seq_quince-0.1.1/src/seq_quince/tools.py +69 -0
  56. seq_quince-0.1.1/src/seq_quince.egg-info/PKG-INFO +145 -0
  57. seq_quince-0.1.1/src/seq_quince.egg-info/SOURCES.txt +93 -0
  58. seq_quince-0.1.1/src/seq_quince.egg-info/dependency_links.txt +1 -0
  59. seq_quince-0.1.1/src/seq_quince.egg-info/entry_points.txt +3 -0
  60. seq_quince-0.1.1/src/seq_quince.egg-info/requires.txt +14 -0
  61. seq_quince-0.1.1/src/seq_quince.egg-info/scm_file_list.json +93 -0
  62. seq_quince-0.1.1/src/seq_quince.egg-info/scm_version.json +8 -0
  63. seq_quince-0.1.1/src/seq_quince.egg-info/top_level.txt +1 -0
  64. seq_quince-0.1.1/tests/core_units/__init__.py +1 -0
  65. seq_quince-0.1.1/tests/core_units/_support.py +51 -0
  66. seq_quince-0.1.1/tests/core_units/test_config_manager.py +291 -0
  67. seq_quince-0.1.1/tests/core_units/test_device_execution.py +412 -0
  68. seq_quince-0.1.1/tests/core_units/test_device_runner.py +103 -0
  69. seq_quince-0.1.1/tests/core_units/test_loader.py +120 -0
  70. seq_quince-0.1.1/tests/core_units/test_motion_tracking.py +108 -0
  71. seq_quince-0.1.1/tests/core_units/test_resource_cleanup.py +500 -0
  72. seq_quince-0.1.1/tests/core_units/test_runner.py +235 -0
  73. seq_quince-0.1.1/tests/core_units/test_runner_executor.py +158 -0
  74. seq_quince-0.1.1/tests/core_units/test_scheduler.py +286 -0
  75. seq_quince-0.1.1/tests/core_units/test_scheduler_check_devices.py +211 -0
  76. seq_quince-0.1.1/tests/core_units/test_scheduler_runner.py +125 -0
  77. seq_quince-0.1.1/tests/core_units/test_scheduler_safety.py +691 -0
  78. seq_quince-0.1.1/tests/core_units/test_sequence_builder.py +79 -0
  79. seq_quince-0.1.1/tests/core_units/test_snapshot.py +301 -0
  80. seq_quince-0.1.1/tests/core_units/test_snapshot_cleanup_safety.py +110 -0
  81. seq_quince-0.1.1/tests/core_units/test_tools.py +123 -0
  82. seq_quince-0.1.1/tests/device_units/__init__.py +1 -0
  83. seq_quince-0.1.1/tests/device_units/_support.py +114 -0
  84. seq_quince-0.1.1/tests/device_units/test_actuator_device.py +172 -0
  85. seq_quince-0.1.1/tests/device_units/test_async_hardware.py +388 -0
  86. seq_quince-0.1.1/tests/device_units/test_device_contract.py +106 -0
  87. seq_quince-0.1.1/tests/device_units/test_hexapod_device.py +537 -0
  88. seq_quince-0.1.1/tests/device_units/test_mono_zero_order.py +397 -0
  89. seq_quince-0.1.1/tests/device_units/test_ophyd_motor_device.py +50 -0
  90. seq_quince-0.1.1/tests/device_units/test_resource_cleanup.py +175 -0
  91. seq_quince-0.1.1/tests/device_units/test_scan_device.py +435 -0
  92. seq_quince-0.1.1/tests/device_units/test_sequence_device.py +156 -0
  93. seq_quince-0.1.1/tests/device_units/test_sim_device.py +81 -0
  94. seq_quince-0.1.1/tests/device_units/test_toggle_device.py +58 -0
  95. seq_quince-0.1.1/tests/device_units/test_wait_condition_device.py +385 -0
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ All notable changes to `seq-quince` will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-11-13
10
+
11
+ ### Added
12
+
13
+ - Initial standalone package, extracted from the `monopy` `src/scheduler` sub-package.
14
+ - Python imports use `seq_quince`.
15
+ - Console scripts: `scheduler_runner` and `device_runner`.
16
+
@@ -0,0 +1,47 @@
1
+ # Contributing to seq_quince
2
+
3
+ Seq-quince runs configurable device sequences for beamline operation using asyncio,
4
+ EPICS, and kiwi-scan. These guidelines are written for contributors and AI agents.
5
+
6
+ ## Making changes
7
+
8
+ - Read the relevant implementation, configuration, and tests before editing.
9
+ - Keep changes focused. Preserve unrelated work and avoid incidental refactoring.
10
+ - Preserve public APIs, YAML keys, defaults, and execution order unless the task
11
+ explicitly changes them. Document intentional behavior changes.
12
+ - Keep Python 3.8 compatibility. For example, use `run_in_executor()` instead of
13
+ `asyncio.to_thread()`.
14
+ - Comment on context, e.g. reasons or hardware assumptions that the code cannot explain.
15
+ - User output can use unicode symbols from doc/misc/symbols.md for better readability
16
+
17
+ ## Devices and scheduling
18
+
19
+ - Default device implementations are located in `src/seq_quince/device_concrete/`. Follow
20
+ `DeviceABC` in `src/seq_quince/device/device_abs.py` and register new device types
21
+ through `DeviceFactory` in `src/seq_quince/device/factory.py`.
22
+ - New devices are usually tested as external device, register them with DEVICE_CLASSES and
23
+ set the SEQ_QUINCE_DEVICE_PATH environment variable.
24
+ - Keep blocking hardware operations out of the asyncio event loop. Preserve
25
+ cancellation, stop, cleanup, and rollback behavior.
26
+ - Logging: lazy formatting, and restrained output in polling loops.
27
+
28
+ ## Validation
29
+
30
+ Run:
31
+
32
+ ```bash
33
+ make lint && make test
34
+ ```
35
+
36
+ ### Unit tests
37
+
38
+ Unittests are located in tests. Use unittest.TestCase (CoreTestCase or DeviceTestCase)
39
+ Do not use real CA.
40
+
41
+ ## Notes
42
+
43
+ - Code must run on Python 3.8: no `match`, no `list[int]` at runtime, no parenthesized context managers, no `str.removeprefix`.
44
+ Check both syntax and standard-library availability.
45
+ - Keep CLI layers thin. Put reusable behavior in library functions and reuse existing configuration models, readers, and formatters.
46
+ - Verify the change. Add focused tests.
47
+ - Validate configuration before the run starts.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Helmholtz-Zentrum Berlin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ exclude CLAUDE.md AGENTS.md mkvenv.sh .gitignore
@@ -0,0 +1,110 @@
1
+ SHELL := /bin/bash
2
+ VERSION ?= 0.1.0
3
+ .DEFAULT_GOAL := help
4
+
5
+ BASH_COMPLETION_DIR ?= ~/.bash_completion.d
6
+ WITH_VENV = if [ -z "$$VIRTUAL_ENV" ]; then source "$(CURDIR)/mkvenv.sh"; fi
7
+
8
+ # set COMMIT for tagging
9
+ COMMIT ?= HEAD
10
+
11
+ # Completion files expected in bash-completion/ (copy them over from monopy/bash-completion).
12
+ SCRIPTS = \
13
+ scheduler_runner \
14
+ device_runner
15
+ # ...
16
+
17
+ SCRIPT_PATHS = $(foreach script,$(SCRIPTS),$(BASH_COMPLETION_DIR)/$(script))
18
+
19
+ .PHONY: help all install_completion uninstall_completion clean cscope tag lint test build
20
+
21
+ help: ## Show available development targets
22
+ @echo "seq_quince development targets"
23
+ @echo
24
+ @awk 'BEGIN {FS = ":.*## "} /^[a-zA-Z0-9_.-]+:.*## / { printf " %-22s %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
25
+
26
+ all: install_completion ## Install bash completion scripts
27
+
28
+ install_completion: ## Install bash completion files and add source lines to ~/.bashrc
29
+ @mkdir -p $(BASH_COMPLETION_DIR)
30
+ @for script in $(SCRIPTS); do \
31
+ if [ ! -f bash-completion/$$script ]; then echo "Skipping $$script (not found in bash-completion/)"; continue; fi; \
32
+ cp bash-completion/$$script $(BASH_COMPLETION_DIR)/$$script; \
33
+ grep -q "$$script" ~/.bashrc || echo "source $(BASH_COMPLETION_DIR)/$$script" >> ~/.bashrc; \
34
+ done
35
+ @echo "Bash completion for $(SCRIPTS) installed. Reload your shell to activate."
36
+
37
+ uninstall_completion: ## Remove installed bash completion files
38
+ @for script in $(SCRIPTS); do \
39
+ rm -f $(BASH_COMPLETION_DIR)/$$script; \
40
+ done
41
+ @echo "Bash completion for $(SCRIPTS) removed. Edit your .bashrc and remove the source lines."
42
+
43
+ clean: ## Remove local virtualenv, caches, tags, and egg-info
44
+ @rm -rf .venv
45
+ @rm -rf dist build
46
+ @rm -f tags cscope.files cscope.out
47
+ @find src -maxdepth 1 -type d -name "*.egg-info" -exec rm -rf {} +
48
+ @find . -type d -name "__pycache__" -exec rm -r {} +
49
+
50
+ cscope: ## Build cscope and ctags indexes (optionally includes ../kiwi-scan)
51
+ find . \( -path ./.venv -o -path ./build -o -path ./dist \) -prune \
52
+ -o -name "*.py" -print > cscope.files
53
+ if [ -d ../kiwi-scan ]; then \
54
+ find ../kiwi-scan/ \
55
+ \( -path ../kiwi-scan/.venv \
56
+ -o -path ../kiwi-scan/build \
57
+ -o -path ../kiwi-scan/dist \) -prune \
58
+ -o -name "*.py" -print >> cscope.files; \
59
+ fi
60
+ cscope -b -i cscope.files
61
+ ctags -R --languages=Python .
62
+
63
+ tag: ## Create a version+timestamp git tag from COMMIT
64
+ @base="$(VERSION)"; \
65
+ commit="$(COMMIT)"; \
66
+ if git rev-parse "$$base" >/dev/null 2>&1; then \
67
+ echo "ERROR: release tag $$base already exists"; \
68
+ exit 1; \
69
+ fi; \
70
+ ts=$$(git show -s --format=%cd --date=format:%Y%m%d%H%M%S "$$commit"); \
71
+ tag="$$base+$$ts"; \
72
+ if git rev-parse "$$tag" >/dev/null 2>&1; then \
73
+ echo "ERROR: tag $$tag already exists"; \
74
+ exit 1; \
75
+ fi; \
76
+ echo "Creating tag $$tag"; \
77
+ git tag -a "$$tag" "$$commit" -m "Release $$tag"
78
+
79
+ build: ## Build sdist and wheel into dist/
80
+ @$(WITH_VENV); \
81
+ python -m build
82
+
83
+ lint: ## Run pylint, ruff, and pyright (uses mkvenv.sh when needed)
84
+ @echo
85
+ @echo '========================================================================'
86
+ @echo ' PYLINT'
87
+ @echo '========================================================================'
88
+ @$(WITH_VENV); \
89
+ PYTHONPATH=src pylint src/seq_quince
90
+ @echo '========================================================================'
91
+ @echo
92
+ @echo '========================================================================'
93
+ @echo ' RUFF'
94
+ @echo '========================================================================'
95
+ @$(WITH_VENV); \
96
+ ruff check src/seq_quince tests/
97
+ @echo '========================================================================'
98
+ @echo
99
+ @echo '========================================================================'
100
+ @echo ' PYRIGHT'
101
+ @echo '========================================================================'
102
+ @$(WITH_VENV); \
103
+ pyright src/seq_quince
104
+ @echo '========================================================================'
105
+ @echo
106
+ @echo 'Lint complete.'
107
+
108
+ test: ## Run the test suite (uses mkvenv.sh when needed)
109
+ @$(WITH_VENV); \
110
+ PYTHONPATH=src python -m pytest --cov=src/seq_quince --cov-report=term
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.1
2
+ Name: seq-quince
3
+ Version: 0.1.1
4
+ Summary: An asynchronous, YAML-configured device scheduler for EPICS beamlines, built on kiwi-scan.
5
+ Author: Helmholtz-Zentrum Berlin
6
+ Project-URL: Homepage, https://github.com/hz-b/seq-quince
7
+ Project-URL: Repository, https://github.com/hz-b/seq-quince
8
+ Project-URL: Issues, https://github.com/hz-b/seq-quince/issues
9
+ Keywords: epics,scientific,instrumentation,beamline,control-system,scheduler,sequence,data-acquisition,scan,automation,yaml
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering
23
+ Classifier: Topic :: Scientific/Engineering :: Physics
24
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
25
+ Classifier: Topic :: Software Development :: Libraries
26
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
+ Requires-Python: >=3.8
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: kiwi-scan>=0.6.1
31
+ Requires-Dist: PyYAML>=6.0
32
+ Requires-Dist: jinja2
33
+ Requires-Dist: pyepics>=3.5
34
+ Requires-Dist: ophyd
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest; extra == "dev"
37
+ Requires-Dist: pytest-cov; extra == "dev"
38
+ Requires-Dist: pyright; extra == "dev"
39
+ Requires-Dist: caproto[complete]; extra == "dev"
40
+ Requires-Dist: pylint; extra == "dev"
41
+ Requires-Dist: ruff; extra == "dev"
42
+ Requires-Dist: bandit; extra == "dev"
43
+
44
+ # seq-quince
45
+
46
+ `seq-quince`: An asynchronous, YAML-configured device scheduler for EPICS beamlines, built on [kiwi-scan](https://github.com/hz-b/kiwi-scan).
47
+
48
+ Devices (motors, actuators, hexapods, PV sequences, scans, wait conditions, …) are declared in a
49
+ `beamline.yaml`. Named **presets** map devices to target poses. The scheduler turns a preset into a sequence of move
50
+ steps, runs them serially or in parallel groups, and rolls completed steps back to their pre-run snapshot if a step fails.
51
+
52
+
53
+ ## Overview of Features
54
+
55
+ - **YAML + Jinja2 configuration** – `beamline.yaml` and `presets.yaml` are rendered as Jinja2 templates; both `${TOKEN}` and
56
+ `{{ TOKEN }}` placeholders are supported, as well as `{% include %}` from sibling/parent directories.
57
+ - **Device abstraction** – every device implements `DeviceABC` (`process`, `snapshot`, `busy`, `stop`, optional capabilities).
58
+ - **Device factory** – devices are created from config by their `type:` key; custom types can be registered at runtime.
59
+ - **Custom device extensions** — Load external device modules and packages via SEQ_QUINCE_DEVICE_PATH, with explicit registration through DEVICE_CLASSES.
60
+ - **Sequences** – presets are converted into `MoveStep` lists with `serial` or `parallel` grouping and barriers.
61
+ - **Async scheduler** – concurrent execution per group, optional per-step timeouts, best-effort rollback on failure.
62
+ - **Snapshots** – current device read-backs can be saved as a new preset (`presets.yaml`).
63
+ - **kiwi-scan integration** – `ScanDevice` runs any kiwi-scan scan type, `ActuatorDevice`/`HexapodDevice` use kiwi-scan actuators.
64
+
65
+ ## Installation
66
+
67
+ ```bash
68
+ pip install seq-quince
69
+ ```
70
+
71
+ For development, clone the repository and install it in editable mode:
72
+
73
+ ```bash
74
+ pip install -e .
75
+ ```
76
+
77
+
78
+ ## Command line tools
79
+
80
+ | Command | Purpose |
81
+ |---------|---------|
82
+ | `scheduler_runner` | Run a preset (`--config`, `--preset`, `--loop`, `--delay`, `--snapshot`, `--replace`) |
83
+ | `device_runner` | Run one device from a preset; see [device runner](https://github.com/hz-b/seq-quince/blob/master/docs/device_runner.md). |
84
+
85
+ Example:
86
+
87
+ ```bash
88
+ scheduler_runner --config u171dcm1 --preset branch --replace BL=U171
89
+ ```
90
+
91
+ ## Configuration
92
+
93
+ See [docs/devices.md](https://github.com/hz-b/seq-quince/blob/master/docs/devices.md) and [docs/devices](https://github.com/hz-b/seq-quince/tree/master/docs/devices) for configuration details.
94
+
95
+ ```yaml
96
+ # beamline.yaml
97
+ devices:
98
+ - name: simulation
99
+ type: SimDevice
100
+ delay: 0.1
101
+ - name: mono_energy
102
+ type: ActuatorDevice
103
+ config: { ... } # kiwi-scan ActuatorConfig
104
+
105
+ presets:
106
+ branch:
107
+ simulation: { position: 10.0 }
108
+ mono_energy: { position: 400.0 }
109
+ ```
110
+
111
+ Presets from `presets.yaml` (written by `--snapshot`) override presets of the same name in `beamline.yaml`.
112
+ Unknown preset names raise `ValueError`; an explicitly configured empty preset
113
+ (`empty: {}`) remains valid. Device names must be unique: duplicates are rejected
114
+ before any devices are constructed.
115
+
116
+ After template rendering, YAML is parsed with `yaml.safe_load()`. Python-specific
117
+ YAML tags are rejected. Normal loading does not print the rendered configuration.
118
+
119
+ Environment variables used by the code:
120
+
121
+ | Variable | Used for |
122
+ |------------------------|-----------------------------------------------------------------|
123
+ | `SCHEDULER_CONFIG_DIR` | Direct configuration directory; overrides beamline-name lookup. |
124
+ | `SEQ_QUINCE_CONFIG_DIR` | Existing base directory; looks in `<dir>/scheduler_config/beamlines/<config>` when no direct override is set. |
125
+ | `SCAN_CONFIG_DIR` | kiwi-scan config directory for `ScanDevice` |
126
+ | `SCAN_DATA_DIR` | Output directory for `ScanDevice` scans |
127
+
128
+ See [docs/devices.md](https://github.com/hz-b/seq-quince/blob/master/docs/devices.md) for the built-in device types and their configuration keys.
129
+
130
+ ## Development
131
+
132
+ ```bash
133
+ make help # list targets
134
+ make lint # pylint, ruff, pyright
135
+ make test # pytest
136
+ make build # sdist + wheel
137
+ make install_completion # install bash completion scripts
138
+ make uninstall_completion # uninstall bash completion scripts
139
+ make clean # Remove local virtualenv and python generated files
140
+ make cscope # Build cscope and ctags indexes
141
+ make tag # create git tag
142
+ ```
143
+ ## License
144
+
145
+ MIT – see [LICENSE](https://github.com/hz-b/seq-quince/blob/master/LICENSE).
@@ -0,0 +1,102 @@
1
+ # seq-quince
2
+
3
+ `seq-quince`: An asynchronous, YAML-configured device scheduler for EPICS beamlines, built on [kiwi-scan](https://github.com/hz-b/kiwi-scan).
4
+
5
+ Devices (motors, actuators, hexapods, PV sequences, scans, wait conditions, …) are declared in a
6
+ `beamline.yaml`. Named **presets** map devices to target poses. The scheduler turns a preset into a sequence of move
7
+ steps, runs them serially or in parallel groups, and rolls completed steps back to their pre-run snapshot if a step fails.
8
+
9
+
10
+ ## Overview of Features
11
+
12
+ - **YAML + Jinja2 configuration** – `beamline.yaml` and `presets.yaml` are rendered as Jinja2 templates; both `${TOKEN}` and
13
+ `{{ TOKEN }}` placeholders are supported, as well as `{% include %}` from sibling/parent directories.
14
+ - **Device abstraction** – every device implements `DeviceABC` (`process`, `snapshot`, `busy`, `stop`, optional capabilities).
15
+ - **Device factory** – devices are created from config by their `type:` key; custom types can be registered at runtime.
16
+ - **Custom device extensions** — Load external device modules and packages via SEQ_QUINCE_DEVICE_PATH, with explicit registration through DEVICE_CLASSES.
17
+ - **Sequences** – presets are converted into `MoveStep` lists with `serial` or `parallel` grouping and barriers.
18
+ - **Async scheduler** – concurrent execution per group, optional per-step timeouts, best-effort rollback on failure.
19
+ - **Snapshots** – current device read-backs can be saved as a new preset (`presets.yaml`).
20
+ - **kiwi-scan integration** – `ScanDevice` runs any kiwi-scan scan type, `ActuatorDevice`/`HexapodDevice` use kiwi-scan actuators.
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install seq-quince
26
+ ```
27
+
28
+ For development, clone the repository and install it in editable mode:
29
+
30
+ ```bash
31
+ pip install -e .
32
+ ```
33
+
34
+
35
+ ## Command line tools
36
+
37
+ | Command | Purpose |
38
+ |---------|---------|
39
+ | `scheduler_runner` | Run a preset (`--config`, `--preset`, `--loop`, `--delay`, `--snapshot`, `--replace`) |
40
+ | `device_runner` | Run one device from a preset; see [device runner](https://github.com/hz-b/seq-quince/blob/master/docs/device_runner.md). |
41
+
42
+ Example:
43
+
44
+ ```bash
45
+ scheduler_runner --config u171dcm1 --preset branch --replace BL=U171
46
+ ```
47
+
48
+ ## Configuration
49
+
50
+ See [docs/devices.md](https://github.com/hz-b/seq-quince/blob/master/docs/devices.md) and [docs/devices](https://github.com/hz-b/seq-quince/tree/master/docs/devices) for configuration details.
51
+
52
+ ```yaml
53
+ # beamline.yaml
54
+ devices:
55
+ - name: simulation
56
+ type: SimDevice
57
+ delay: 0.1
58
+ - name: mono_energy
59
+ type: ActuatorDevice
60
+ config: { ... } # kiwi-scan ActuatorConfig
61
+
62
+ presets:
63
+ branch:
64
+ simulation: { position: 10.0 }
65
+ mono_energy: { position: 400.0 }
66
+ ```
67
+
68
+ Presets from `presets.yaml` (written by `--snapshot`) override presets of the same name in `beamline.yaml`.
69
+ Unknown preset names raise `ValueError`; an explicitly configured empty preset
70
+ (`empty: {}`) remains valid. Device names must be unique: duplicates are rejected
71
+ before any devices are constructed.
72
+
73
+ After template rendering, YAML is parsed with `yaml.safe_load()`. Python-specific
74
+ YAML tags are rejected. Normal loading does not print the rendered configuration.
75
+
76
+ Environment variables used by the code:
77
+
78
+ | Variable | Used for |
79
+ |------------------------|-----------------------------------------------------------------|
80
+ | `SCHEDULER_CONFIG_DIR` | Direct configuration directory; overrides beamline-name lookup. |
81
+ | `SEQ_QUINCE_CONFIG_DIR` | Existing base directory; looks in `<dir>/scheduler_config/beamlines/<config>` when no direct override is set. |
82
+ | `SCAN_CONFIG_DIR` | kiwi-scan config directory for `ScanDevice` |
83
+ | `SCAN_DATA_DIR` | Output directory for `ScanDevice` scans |
84
+
85
+ See [docs/devices.md](https://github.com/hz-b/seq-quince/blob/master/docs/devices.md) for the built-in device types and their configuration keys.
86
+
87
+ ## Development
88
+
89
+ ```bash
90
+ make help # list targets
91
+ make lint # pylint, ruff, pyright
92
+ make test # pytest
93
+ make build # sdist + wheel
94
+ make install_completion # install bash completion scripts
95
+ make uninstall_completion # uninstall bash completion scripts
96
+ make clean # Remove local virtualenv and python generated files
97
+ make cscope # Build cscope and ctags indexes
98
+ make tag # create git tag
99
+ ```
100
+ ## License
101
+
102
+ MIT – see [LICENSE](https://github.com/hz-b/seq-quince/blob/master/LICENSE).
File without changes
@@ -0,0 +1,19 @@
1
+ # Bash completion for device_runner.
2
+ _seq_quince_device_runner()
3
+ {
4
+ local current="${COMP_WORDS[COMP_CWORD]}"
5
+ local previous="${COMP_WORDS[COMP_CWORD-1]}"
6
+ COMPREPLY=()
7
+ case "$previous" in
8
+ --log-level)
9
+ mapfile -t COMPREPLY < <(compgen -W '0 1 2 3 4 5' -- "$current")
10
+ ;;
11
+ --config|--device|--preset|--replace)
12
+ ;;
13
+ *)
14
+ mapfile -t COMPREPLY < <(compgen -W \
15
+ '--config --device --preset --replace --log-level --help' -- "$current")
16
+ ;;
17
+ esac
18
+ }
19
+ complete -F _seq_quince_device_runner device_runner
@@ -0,0 +1,111 @@
1
+ _scheduler_runner_complete()
2
+ {
3
+ local cur prev opts loop_values preset_values expecting word i
4
+ COMPREPLY=()
5
+ cur="${COMP_WORDS[COMP_CWORD]}"
6
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
7
+
8
+ opts="--help -h --replace --config --preset --snapshot --loop --delay --log-level"
9
+ loop_values="infinite inf forever 1 2 5 10 100"
10
+ preset_values="default test prod dev"
11
+
12
+ case "$prev" in
13
+ --config)
14
+ # Complete files for config
15
+ COMPREPLY=( $(compgen -f -- "$cur") )
16
+ return 0
17
+ ;;
18
+ --preset)
19
+ # Optionally, offer static completions or load dynamically
20
+ COMPREPLY=( $(compgen -W "${preset_values}" -- "$cur") )
21
+ return 0
22
+ ;;
23
+ --delay)
24
+ # Numeric value expected
25
+ COMPREPLY=()
26
+ return 0
27
+ ;;
28
+ --loop)
29
+ # Optional argument: either suggest loop values or allow another option
30
+ if [[ "$cur" == -* ]]; then
31
+ COMPREPLY=( $(compgen -W "${opts}" -- "$cur") )
32
+ else
33
+ COMPREPLY=( $(compgen -W "${loop_values}" -- "$cur") )
34
+ fi
35
+ return 0
36
+ ;;
37
+ --replace)
38
+ # User is likely entering KEY=VALUE, but allow switching to another option
39
+ if [[ "$cur" == -* ]]; then
40
+ COMPREPLY=( $(compgen -W "${opts}" -- "$cur") )
41
+ else
42
+ COMPREPLY=()
43
+ fi
44
+ return 0
45
+ ;;
46
+ esac
47
+
48
+ # Track whether we are currently collecting values for an option.
49
+ expecting=""
50
+ for ((i=1; i<COMP_CWORD; i++)); do
51
+ word="${COMP_WORDS[i]}"
52
+
53
+ if [[ -n "$expecting" ]]; then
54
+ case "$expecting" in
55
+ replace)
56
+ if [[ "$word" == -* ]]; then
57
+ expecting=""
58
+ else
59
+ continue
60
+ fi
61
+ ;;
62
+ loop)
63
+ if [[ "$word" == -* ]]; then
64
+ expecting=""
65
+ else
66
+ expecting=""
67
+ continue
68
+ fi
69
+ ;;
70
+ config|preset|delay)
71
+ expecting=""
72
+ continue
73
+ ;;
74
+ esac
75
+ fi
76
+
77
+ case "$word" in
78
+ --replace)
79
+ expecting="replace"
80
+ ;;
81
+ --config)
82
+ expecting="config"
83
+ ;;
84
+ --preset)
85
+ expecting="preset"
86
+ ;;
87
+ --delay)
88
+ expecting="delay"
89
+ ;;
90
+ --loop)
91
+ expecting="loop"
92
+ ;;
93
+ esac
94
+ done
95
+
96
+ if [[ "$expecting" == "replace" && "$cur" != -* ]]; then
97
+ COMPREPLY=()
98
+ return 0
99
+ fi
100
+
101
+ # If current word starts with '-', complete options
102
+ if [[ "$cur" == -* ]]; then
103
+ COMPREPLY=( $(compgen -W "${opts}" -- "$cur") )
104
+ return 0
105
+ fi
106
+
107
+ # Default to completing files for last positional argument (if any)
108
+ COMPREPLY=( $(compgen -f -- "$cur") )
109
+ }
110
+
111
+ complete -F _scheduler_runner_complete scheduler_runner
@@ -0,0 +1,84 @@
1
+ # Run one device preset
2
+
3
+ Run only one configured device's entry from a named preset:
4
+
5
+ ```bash
6
+ device_runner --config my_beamline --device mono --preset alignment
7
+ device_runner --config my_beamline --device mono --preset alignment --replace IOC=TEST
8
+ ```
9
+
10
+ The configuration name and directory resolution match `scheduler_runner`:
11
+ `SCHEDULER_CONFIG_DIR` overrides the directory, otherwise the usual
12
+ `SEQ_QUINCE_CONFIG_DIR` or packaged beamline directory is used. The files are
13
+ `beamline.yaml` and optional `presets.yaml`; saved presets retain their normal
14
+ precedence. `--config` takes the beamline name, as in the existing CLI.
15
+
16
+ For example, given this `beamline.yaml`:
17
+
18
+ ```yaml
19
+ devices:
20
+ - name: mono
21
+ type: SimDevice
22
+ initial_pose: {position: 1.0}
23
+ delay: 0
24
+ - name: slit
25
+ type: SimDevice
26
+ initial_pose: {position: 0.0}
27
+ delay: 0
28
+ presets:
29
+ alignment:
30
+ mono: {position: 5.0}
31
+ slit: {position: 2.0}
32
+ ```
33
+
34
+ The example command constructs `mono` and runs it with `{position: 5.0}`.
35
+ It does not construct `slit`. Selecting a composite device, such as
36
+ `SequenceDevice`, still executes that device's configured internal operations.
37
+ Empty entries (`mono: {}`) are accepted for devices that use configured defaults.
38
+
39
+ The selection is validated before the factory constructs the device. An
40
+ unknown preset or device, a duplicate device name, a missing device entry,
41
+ or an entry that is not a mapping causes an error. Selected config and pose
42
+ data are copied before use, preserving the loaded manager's configuration.
43
+
44
+ The existing scheduler executes one step and applies the device's scheduler
45
+ timeout. Device implementations retain their normal processing and error
46
+ cleanup. The scheduler rolls back completed steps; a single failing step
47
+ has no completed step to roll back. Cancelling the async operation attempts
48
+ `device.stop()` before propagating cancellation. A stop failure is logged
49
+ without hiding cancellation. Ctrl-C uses this cancellation path during
50
+ execution and exits with status 130; a repeated interrupt can abort cleanup.
51
+
52
+ | Option | Meaning |
53
+ | --- | --- |
54
+ | `--config NAME` | Required beamline configuration name |
55
+ | `--device NAME` | Required configured device name |
56
+ | `--preset NAME` | Required named preset containing that device |
57
+ | `--replace KEY=VALUE ...` | Standard configuration replacements |
58
+ | `--log-level 0-5` | Same logging control as `scheduler_runner` |
59
+
60
+ Exit codes: 0 for success, 1 for configuration or execution failure, 2 for
61
+ argument errors, and 130 for interruption. Execution runs once.
62
+
63
+ ## Python use
64
+
65
+ ```python
66
+ from seq_quince.runner import run_device
67
+
68
+ run_device("my_beamline", "mono", "alignment", replacements={"IOC": "TEST"})
69
+ ```
70
+
71
+ For an application that already owns an event loop:
72
+
73
+ ```python
74
+ from seq_quince.runner import create_config_manager, run_device_preset
75
+
76
+ async def align_mono():
77
+ manager = create_config_manager("my_beamline", {"IOC": "TEST"})
78
+ await run_device_preset(manager, "mono", "alignment")
79
+ ```
80
+
81
+ `run_device()` owns an event loop; use the async helper inside a running loop.
82
+ Device construction happens inside the execution loop, including on Python 3.8.
83
+ `create_device_step()` is also available for applications building their own
84
+ scheduler steps; call it inside the loop used to execute the device.