labcode 0.1.6__tar.gz → 0.1.8__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.
- {labcode-0.1.6 → labcode-0.1.8}/.github/workflows/ci.yml +7 -7
- {labcode-0.1.6 → labcode-0.1.8}/.github/workflows/publish.yml +5 -5
- labcode-0.1.8/PKG-INFO +258 -0
- labcode-0.1.8/README.md +214 -0
- {labcode-0.1.6 → labcode-0.1.8}/SPECIFICATIONS.md +5 -4
- {labcode-0.1.6 → labcode-0.1.8}/examples/README.md +61 -39
- {labcode-0.1.6 → labcode-0.1.8}/examples/outputs/sila2_plate_cycle.boundary.yaml +2 -2
- {labcode-0.1.6 → labcode-0.1.8}/examples/outputs/sila2_plate_cycle.observation.yaml +20 -20
- {labcode-0.1.6 → labcode-0.1.8}/examples/outputs/sila2_plate_cycle.plan.yaml +19 -19
- labcode-0.1.8/examples/outputs/sila2_plate_cycle.svg +2 -0
- {labcode-0.1.6 → labcode-0.1.8}/examples/render_sila2_plate_cycle.py +1 -1
- {labcode-0.1.6 → labcode-0.1.8}/examples/run_sila2_plate_cycle.py +1 -1
- {labcode-0.1.6 → labcode-0.1.8}/examples/run_sila2_seal.py +1 -1
- {labcode-0.1.6 → labcode-0.1.8}/examples/sila2_plate_cycle.boundary.yaml +1 -1
- {labcode-0.1.6 → labcode-0.1.8}/examples/sila2_plate_cycle.wrapped.env.yaml +108 -72
- {labcode-0.1.6 → labcode-0.1.8}/examples/sila2_seal.boundary.yaml +1 -1
- {labcode-0.1.6 → labcode-0.1.8}/examples/sila2_seal.env.yaml +40 -26
- {labcode-0.1.6 → labcode-0.1.8}/examples/sila2_seal.wrapped.env.yaml +31 -17
- {labcode-0.1.6 → labcode-0.1.8}/labcode/_child.py +14 -1
- {labcode-0.1.6 → labcode-0.1.8}/labcode/backend.py +220 -19
- {labcode-0.1.6 → labcode-0.1.8}/labcode/cli.py +1 -1
- labcode-0.1.8/labcode/otel.py +322 -0
- labcode-0.1.8/labcode/otel_sila2.py +182 -0
- labcode-0.1.8/labcode/record.py +298 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode/run_cli.py +102 -11
- {labcode-0.1.6 → labcode-0.1.8}/labcode/runner.py +62 -5
- labcode-0.1.8/labcode/sila2_instrument.py +585 -0
- labcode-0.1.8/labcode.egg-info/PKG-INFO +258 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode.egg-info/SOURCES.txt +11 -0
- labcode-0.1.8/labcode.egg-info/requires.txt +24 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode.egg-info/scm_file_list.json +11 -0
- labcode-0.1.8/labcode.egg-info/scm_version.json +8 -0
- {labcode-0.1.6 → labcode-0.1.8}/pyproject.toml +45 -4
- {labcode-0.1.6 → labcode-0.1.8}/tests/test_backend.py +2 -2
- labcode-0.1.8/tests/test_otel.py +230 -0
- labcode-0.1.8/tests/test_otel_child.py +209 -0
- labcode-0.1.8/tests/test_otel_grpc.py +153 -0
- labcode-0.1.8/tests/test_otel_sila2.py +165 -0
- labcode-0.1.8/tests/test_record.py +164 -0
- labcode-0.1.8/tests/test_recording.py +327 -0
- {labcode-0.1.6 → labcode-0.1.8}/tests/test_run_cli.py +150 -0
- labcode-0.1.8/tests/test_sila2_instrument.py +542 -0
- labcode-0.1.6/PKG-INFO +0 -104
- labcode-0.1.6/README.md +0 -69
- labcode-0.1.6/examples/outputs/sila2_plate_cycle.svg +0 -2
- labcode-0.1.6/labcode.egg-info/PKG-INFO +0 -104
- labcode-0.1.6/labcode.egg-info/requires.txt +0 -14
- labcode-0.1.6/labcode.egg-info/scm_version.json +0 -8
- {labcode-0.1.6 → labcode-0.1.8}/.gitignore +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/LICENSE +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/MANIFEST.in +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/examples/outputs/plate_line.boundary.yaml +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/examples/outputs/plate_line.observation.yaml +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/examples/outputs/plate_line.plan.yaml +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/examples/outputs/plate_line.svg +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/examples/plate_line.boundary.yaml +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/examples/plate_line.env.yaml +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/examples/plate_line.workflow.yaml +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/examples/render_plate_line.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/examples/run_all_sila2_examples.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/examples/sila2_plate_cycle.workflow.yaml +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/examples/sila2_seal.workflow.yaml +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode/__init__.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode/__main__.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode/dialect.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode/extension.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode/idgen.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode/objectid.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode/probe.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode/py.typed +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode/sila2.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode/sila2_commands.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode.egg-info/dependency_links.txt +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode.egg-info/entry_points.txt +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/labcode.egg-info/top_level.txt +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/setup.cfg +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/tests/fixtures/device_script.env.yaml +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/tests/fixtures/device_script.workflow.yaml +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/tests/fixtures/reroute_device.env.yaml +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/tests/fixtures/reroute_transporter.env.yaml +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/tests/fixtures/transport.env.yaml +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/tests/fixtures/transport.workflow.yaml +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/tests/test_cli.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/tests/test_dialect.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/tests/test_objectid.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/tests/test_probe.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/tests/test_sila2.py +0 -0
- {labcode-0.1.6 → labcode-0.1.8}/tests/test_sila2_commands.py +0 -0
|
@@ -14,8 +14,8 @@ jobs:
|
|
|
14
14
|
matrix:
|
|
15
15
|
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
16
16
|
steps:
|
|
17
|
-
- uses: actions/checkout@
|
|
18
|
-
- uses: actions/setup-python@
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
- uses: actions/setup-python@v7
|
|
19
19
|
with:
|
|
20
20
|
python-version: ${{ matrix.python-version }}
|
|
21
21
|
cache: pip
|
|
@@ -25,8 +25,8 @@ jobs:
|
|
|
25
25
|
lint:
|
|
26
26
|
runs-on: ubuntu-latest
|
|
27
27
|
steps:
|
|
28
|
-
- uses: actions/checkout@
|
|
29
|
-
- uses: actions/setup-python@
|
|
28
|
+
- uses: actions/checkout@v7
|
|
29
|
+
- uses: actions/setup-python@v7
|
|
30
30
|
with:
|
|
31
31
|
python-version: "3.12"
|
|
32
32
|
cache: pip
|
|
@@ -38,8 +38,8 @@ jobs:
|
|
|
38
38
|
typecheck:
|
|
39
39
|
runs-on: ubuntu-latest
|
|
40
40
|
steps:
|
|
41
|
-
- uses: actions/checkout@
|
|
42
|
-
- uses: actions/setup-python@
|
|
41
|
+
- uses: actions/checkout@v7
|
|
42
|
+
- uses: actions/setup-python@v7
|
|
43
43
|
with:
|
|
44
44
|
python-version: "3.12"
|
|
45
45
|
cache: pip
|
|
@@ -56,6 +56,6 @@ jobs:
|
|
|
56
56
|
lock:
|
|
57
57
|
runs-on: ubuntu-latest
|
|
58
58
|
steps:
|
|
59
|
-
- uses: actions/checkout@
|
|
59
|
+
- uses: actions/checkout@v7
|
|
60
60
|
- uses: astral-sh/setup-uv@v7
|
|
61
61
|
- run: uv lock --check
|
|
@@ -29,11 +29,11 @@ jobs:
|
|
|
29
29
|
build:
|
|
30
30
|
runs-on: ubuntu-latest
|
|
31
31
|
steps:
|
|
32
|
-
- uses: actions/checkout@
|
|
32
|
+
- uses: actions/checkout@v7
|
|
33
33
|
with:
|
|
34
34
|
# setuptools-scm derives the version from tags + history.
|
|
35
35
|
fetch-depth: 0
|
|
36
|
-
- uses: actions/setup-python@
|
|
36
|
+
- uses: actions/setup-python@v7
|
|
37
37
|
with:
|
|
38
38
|
python-version: "3.12"
|
|
39
39
|
- run: python -m pip install --upgrade build twine
|
|
@@ -54,7 +54,7 @@ jobs:
|
|
|
54
54
|
echo "::error::built version $built != tag $tag (ambiguous or misplaced tag?)"
|
|
55
55
|
exit 1
|
|
56
56
|
fi
|
|
57
|
-
- uses: actions/upload-artifact@
|
|
57
|
+
- uses: actions/upload-artifact@v7
|
|
58
58
|
with:
|
|
59
59
|
name: dist
|
|
60
60
|
path: dist/
|
|
@@ -69,7 +69,7 @@ jobs:
|
|
|
69
69
|
permissions:
|
|
70
70
|
id-token: write
|
|
71
71
|
steps:
|
|
72
|
-
- uses: actions/download-artifact@
|
|
72
|
+
- uses: actions/download-artifact@v8
|
|
73
73
|
with:
|
|
74
74
|
name: dist
|
|
75
75
|
path: dist/
|
|
@@ -87,7 +87,7 @@ jobs:
|
|
|
87
87
|
permissions:
|
|
88
88
|
id-token: write
|
|
89
89
|
steps:
|
|
90
|
-
- uses: actions/download-artifact@
|
|
90
|
+
- uses: actions/download-artifact@v8
|
|
91
91
|
with:
|
|
92
92
|
name: dist
|
|
93
93
|
path: dist/
|
labcode-0.1.8/PKG-INFO
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: labcode
|
|
3
|
+
Version: 0.1.8
|
|
4
|
+
Summary: labcode -- a dialect wrapper over the Object-Flow Programming Language toolchain
|
|
5
|
+
Author-email: Kazunari Kaizu <kwaizu@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ofplang/labcode
|
|
8
|
+
Project-URL: Repository, https://github.com/ofplang/labcode
|
|
9
|
+
Keywords: ofplang,labcode,dataflow,workflow,cli
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: ofplang-validate>=0.1.3
|
|
24
|
+
Requires-Dist: ofplang-schedule>=0.1.3
|
|
25
|
+
Requires-Dist: ofplang-run>=0.1.13
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
28
|
+
Requires-Dist: opentelemetry-sdk>=1.20; extra == "test"
|
|
29
|
+
Requires-Dist: opentelemetry-instrumentation-grpc[instruments]>=0.41b0; extra == "test"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff>=0.16; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
34
|
+
Requires-Dist: opentelemetry-sdk>=1.20; extra == "dev"
|
|
35
|
+
Requires-Dist: opentelemetry-instrumentation-grpc[instruments]>=0.41b0; extra == "dev"
|
|
36
|
+
Provides-Extra: sila2
|
|
37
|
+
Requires-Dist: sila2>=0.14; extra == "sila2"
|
|
38
|
+
Provides-Extra: otel
|
|
39
|
+
Requires-Dist: opentelemetry-api>=1.20; extra == "otel"
|
|
40
|
+
Requires-Dist: opentelemetry-sdk>=1.20; extra == "otel"
|
|
41
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.20; extra == "otel"
|
|
42
|
+
Requires-Dist: opentelemetry-instrumentation-grpc>=0.41b0; extra == "otel"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
# labcode
|
|
46
|
+
|
|
47
|
+
[](https://github.com/ofplang/labcode/actions/workflows/ci.yml)
|
|
48
|
+
[](https://pypi.org/project/labcode/)
|
|
49
|
+
|
|
50
|
+
The **`lc`** command-line interface for the **labcode** dialect of the
|
|
51
|
+
**Object-flow Programming Language**. Installing this one package pulls in the
|
|
52
|
+
ofplang toolchain and exposes it under a single command:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
lc validate ... # check a workflow is well-formed portable v0
|
|
56
|
+
lc schedule ... # compute a schedule for a workflow
|
|
57
|
+
lc run ... # execute a workflow on the labcode backend
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
labcode is where a site-specific dialect and a custom runner (real lab hardware)
|
|
61
|
+
are developed on top of the ofplang toolchain. `lc validate` and `lc schedule`
|
|
62
|
+
forward to the ofplang siblings unchanged; **`lc run` is the labcode dialect's own
|
|
63
|
+
runner**: it drives the workflow on the labcode backend, running each device
|
|
64
|
+
operation's script — supplied in the environment as an `x-labcode.script` extension
|
|
65
|
+
on a process mode — out-of-process on a wall clock, so a long-running real operation
|
|
66
|
+
never blocks the replan loop. See [`SPECIFICATIONS.md`](SPECIFICATIONS.md) for the
|
|
67
|
+
`x-labcode` extension.
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
# in the execution environment: how a (process, mode) is carried out
|
|
71
|
+
processes:
|
|
72
|
+
measure_od:
|
|
73
|
+
modes:
|
|
74
|
+
- id: v0
|
|
75
|
+
duration: 45
|
|
76
|
+
x-labcode:
|
|
77
|
+
script:
|
|
78
|
+
language: python
|
|
79
|
+
code: |
|
|
80
|
+
return {"od": read_plate(plate)}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Install
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
pip install labcode
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Requires Python 3.10+. `lc validate` and `lc schedule` are dispatched to the ofplang
|
|
90
|
+
sibling packages unchanged; `lc run` is this package's own runner, built on them:
|
|
91
|
+
|
|
92
|
+
- [`ofplang-validate`](https://github.com/ofplang/validate) — the validator
|
|
93
|
+
- [`ofplang-schedule`](https://github.com/ofplang/schedule) — the scheduler
|
|
94
|
+
- [`ofplang-run`](https://github.com/ofplang/run) — the runner / simulator
|
|
95
|
+
|
|
96
|
+
The language is defined in the [ofplang/spec](https://github.com/ofplang/spec)
|
|
97
|
+
repository, and what labcode adds to it in [`SPECIFICATIONS.md`](SPECIFICATIONS.md).
|
|
98
|
+
|
|
99
|
+
What `lc run` brings of its own, beyond dispatching:
|
|
100
|
+
|
|
101
|
+
- **the labcode backend** — each device operation's `x-labcode.script` runs
|
|
102
|
+
out-of-process on a wall clock (§1.2, §1.3), so a real operation that takes minutes
|
|
103
|
+
does not block the replan loop, and an operation that never returns is stopped by
|
|
104
|
+
**`op_timeout`** (§1.8; `--op-timeout` / `--no-op-timeout`).
|
|
105
|
+
- **the dialect front door** — the environment's `x-labcode` extension is validated
|
|
106
|
+
before anything runs, on top of the portable-v0 check `lc validate` performs (§1, §2).
|
|
107
|
+
- **availability probing** — each machine is checked as often as its `probe` policy says,
|
|
108
|
+
and one that cannot be reached is taken out of the environment the scheduler plans
|
|
109
|
+
against, so the run routes around it (§1.5; `--no-probe`).
|
|
110
|
+
- **object identity** — the reserved `_id` view key is declared on Object types and minted
|
|
111
|
+
per object, so a physical thing can be followed through a run (§4).
|
|
112
|
+
- **`flavor: sila2`** — a script that speaks SiLA2 gets its clients opened around it
|
|
113
|
+
(§1.6). The client library itself is the `sila2` extra: `pip install labcode[sila2]`,
|
|
114
|
+
installed into whichever interpreter runs the scripts.
|
|
115
|
+
- **recording a run** — with `--trace`, what the run did is recorded as OpenTelemetry
|
|
116
|
+
traces: one trace per run, a span per operation, and — measured inside the process that
|
|
117
|
+
issued them — a span per SiLA2 connection, per command, and per gRPC call underneath
|
|
118
|
+
either. Off by default; the extra is `pip install labcode[otel]`.
|
|
119
|
+
|
|
120
|
+
## Usage
|
|
121
|
+
|
|
122
|
+
Each subcommand keeps its own options, exit codes, and `--help`:
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
lc --help # top-level help
|
|
126
|
+
lc <command> --help # command-specific options
|
|
127
|
+
lc --version
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`lc` can also be run as a module: `python -m labcode <command> ...`.
|
|
131
|
+
|
|
132
|
+
`lc validate` and `lc schedule` are the ofplang siblings' own CLIs unchanged, so their
|
|
133
|
+
options are documented in those repositories. `lc run` is this package's own, and is
|
|
134
|
+
described below.
|
|
135
|
+
|
|
136
|
+
### `lc run`
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
lc run <workflow> --env <env>
|
|
140
|
+
[--boundary DOC] [-o OUT] [--boundary-out FILE] [--observation-out FILE]
|
|
141
|
+
[--seconds-per-tick S] [--op-timeout S | --no-op-timeout] [--no-probe]
|
|
142
|
+
[--trace] [--mission-id ID] [--object-ids seeded|real]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- `<workflow>` — the portable v0 workflow: *what* happens.
|
|
146
|
+
- `--env` (required) — the labcode environment: the execution environment (spec §5) plus
|
|
147
|
+
the `x-labcode` extension saying *how* each operation is carried out.
|
|
148
|
+
- `--boundary DOC` — the whole-workflow I/O as one document: a `boundary:` mapping with a
|
|
149
|
+
`{spot, view}` descriptor per entry input / final output port. `spot` says where a
|
|
150
|
+
boundary Object sits; `view` supplies an input's value. A workflow with Object-bearing
|
|
151
|
+
entry inputs needs one, since each must be placed on a spot and only the operator knows
|
|
152
|
+
where the labware is.
|
|
153
|
+
- `-o OUT` — write the final execution status (spec §6/§7) here; the default is stdout.
|
|
154
|
+
- `--boundary-out FILE` — write the result boundary: the same schema as `--boundary`, with
|
|
155
|
+
each produced output's `view` filled in, including the `_id` its Object was minted with
|
|
156
|
+
(§4) — which is how one checks that the plate that came back is the plate that went in.
|
|
157
|
+
- `--observation-out FILE` — stream the observation document: each *completed* activity's
|
|
158
|
+
concrete input / output view values, appended as it finishes. What the instruments
|
|
159
|
+
reported, as against the status document's timings.
|
|
160
|
+
- `--seconds-per-tick S` — real seconds per environment time tick (default 20). Durations
|
|
161
|
+
in the environment are counted in ticks, and this is what maps them onto the wall clock.
|
|
162
|
+
The default is deliberately coarse, so that a real operation's dispatch → running →
|
|
163
|
+
completed reads as discrete, observable steps; a demo against a fast mock wants a small
|
|
164
|
+
value.
|
|
165
|
+
- `--op-timeout S` / `--no-op-timeout` — how long one operation may run before it is
|
|
166
|
+
stopped and failed (§1.8). The default is the environment root's `x-labcode.op_timeout`,
|
|
167
|
+
else 7200 real seconds. The two forms exclude each other.
|
|
168
|
+
- `--no-probe` — ignore the environment's `x-labcode.probe` policies and treat every
|
|
169
|
+
machine as reachable (§1.5). The documents are still validated.
|
|
170
|
+
- `--trace` — record what the run did (see below). Off by default.
|
|
171
|
+
- `--mission-id ID` — the campaign this run belongs to. Recorded with the run and given no
|
|
172
|
+
meaning by labcode: several runs may share one, and nothing here reads it back.
|
|
173
|
+
- `--object-ids seeded|real` — how Object `_id`s are minted: `seeded` is reproducible (the
|
|
174
|
+
same workflow yields the same ids every run, which is what keeps the examples' recorded
|
|
175
|
+
output stable), `real` is unique per run. Unset, it follows `--trace`.
|
|
176
|
+
|
|
177
|
+
Exit codes: `0` the run completed, `1` it failed (an activity failed, a contract was
|
|
178
|
+
violated, an operation timed out, or a replan became infeasible), `2` a usage or input
|
|
179
|
+
error — including a workflow or an `x-labcode` extension the front doors reject.
|
|
180
|
+
|
|
181
|
+
A complete invocation, against this repository's `examples/` (`--seconds-per-tick` small
|
|
182
|
+
because that example's scripts return instantly):
|
|
183
|
+
|
|
184
|
+
```sh
|
|
185
|
+
lc run examples/plate_line.workflow.yaml --env examples/plate_line.env.yaml \
|
|
186
|
+
--boundary examples/plate_line.boundary.yaml --seconds-per-tick 0.2
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The remaining options tune the replan loop rather than describe the run —
|
|
190
|
+
`--poll-interval`, `--margin`, `--seed`, `--speed`, `--max-ticks`, `--no-validate` — and
|
|
191
|
+
are covered by `lc run --help`.
|
|
192
|
+
|
|
193
|
+
### Recording a run
|
|
194
|
+
|
|
195
|
+
`--trace` records what the run did as OpenTelemetry traces. It needs the extra, in the
|
|
196
|
+
interpreter that drives the run — which is also the one that runs the scripts, since
|
|
197
|
+
labcode launches each with `sys.executable`:
|
|
198
|
+
|
|
199
|
+
```sh
|
|
200
|
+
pip install 'labcode[otel]'
|
|
201
|
+
lc run <workflow> --env <env> --trace --mission-id M-2026-001
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
One run is one trace, and the id it can be found by is printed to stderr as the run
|
|
205
|
+
starts (`lc run: recording this run as trace …`). What it holds:
|
|
206
|
+
|
|
207
|
+
```text
|
|
208
|
+
run mission.id, and the failure if it stopped on one
|
|
209
|
+
├─ process Seal which node, process and mode; the plan's interval;
|
|
210
|
+
│ │ which Objects it handled
|
|
211
|
+
│ ├─ sila2.connect the address, measured in the process that connected
|
|
212
|
+
│ │ └─ /…/SiLAService/GetFeatureDefinition one per feature, × however many
|
|
213
|
+
│ └─ sila2 SealerControl.Seal the command, from its start to its real completion
|
|
214
|
+
│ └─ /…/SealerControl/Seal the round trip that started it
|
|
215
|
+
└─ transport the route and the transporter
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
An operation's span is opened when it is dispatched and closed when a poll finds it
|
|
219
|
+
finished, so its end is late by up to one poll period; what an instrument actually spent is
|
|
220
|
+
in the command spans, which are measured where the commands are issued. `ofp.object.id`
|
|
221
|
+
lists the `_id`s an operation handled, including one it created — which is what makes
|
|
222
|
+
"everything that happened to this plate" a single query.
|
|
223
|
+
|
|
224
|
+
The innermost layer is the gRPC calls themselves, each under the connection or the command
|
|
225
|
+
that issued it — so what a connection spends is broken down into the feature definitions it
|
|
226
|
+
had to fetch, and a command's span separates its round trip from the time the instrument
|
|
227
|
+
then took. It needs `grpcio`, which arrives with the `sila2` extra; without it the record is
|
|
228
|
+
the same minus that layer. Two things it does not do: an observable command's
|
|
229
|
+
execution-info subscription is **not** recorded (it is read on a thread of `sila2`'s own,
|
|
230
|
+
where it would land in a trace of its own, and its duration is the command's anyway), and
|
|
231
|
+
each recorded call **sends the trace context to the instrument** in its gRPC metadata, which
|
|
232
|
+
a SiLA2 server ignores as it does any key that is not SiLA Client Metadata.
|
|
233
|
+
|
|
234
|
+
Where the record goes is configured by the **standard `OTEL_*` environment variables**
|
|
235
|
+
(`OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_RESOURCE_ATTRIBUTES`, `OTEL_SERVICE_NAME`, …), so
|
|
236
|
+
labcode adds no settings of its own; `service.name` falls back to `labcode` if nothing sets
|
|
237
|
+
it. `LC_TRACE_FILE=path` additionally writes the spans to a file as JSON lines, one file per
|
|
238
|
+
process (the name gets the process id), which is how a record can be read without standing
|
|
239
|
+
up a collector.
|
|
240
|
+
|
|
241
|
+
Two things to know before pointing a run at a collector. **`--trace` makes Object `_id`s
|
|
242
|
+
real rather than reproducible** unless `--object-ids` says otherwise, since a reproducible id
|
|
243
|
+
is the same on every run and would collapse several runs' plates into one. And **a collector
|
|
244
|
+
that is configured but does not answer delays each operation**, by as long as
|
|
245
|
+
OpenTelemetry's own export timeout allows: labcode sets no timeout of its own, so
|
|
246
|
+
`OTEL_EXPORTER_OTLP_TIMEOUT` is the knob, and it belongs to whoever pointed the run there.
|
|
247
|
+
|
|
248
|
+
## Examples
|
|
249
|
+
|
|
250
|
+
[`examples/`](examples/README.md) holds three worked runs: `plate_line`, an
|
|
251
|
+
Object-bearing line driven entirely by environment scripts and runnable with no
|
|
252
|
+
hardware, and `sila2_seal` and `sila2_plate_cycle`, which drive the reference lab's
|
|
253
|
+
SiLA2 servers for real. `sila2_seal` also walks through what a run does when a
|
|
254
|
+
machine stops answering — before an operation, and in the middle of one.
|
|
255
|
+
|
|
256
|
+
## License
|
|
257
|
+
|
|
258
|
+
MIT
|
labcode-0.1.8/README.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# labcode
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ofplang/labcode/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/labcode/)
|
|
5
|
+
|
|
6
|
+
The **`lc`** command-line interface for the **labcode** dialect of the
|
|
7
|
+
**Object-flow Programming Language**. Installing this one package pulls in the
|
|
8
|
+
ofplang toolchain and exposes it under a single command:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
lc validate ... # check a workflow is well-formed portable v0
|
|
12
|
+
lc schedule ... # compute a schedule for a workflow
|
|
13
|
+
lc run ... # execute a workflow on the labcode backend
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
labcode is where a site-specific dialect and a custom runner (real lab hardware)
|
|
17
|
+
are developed on top of the ofplang toolchain. `lc validate` and `lc schedule`
|
|
18
|
+
forward to the ofplang siblings unchanged; **`lc run` is the labcode dialect's own
|
|
19
|
+
runner**: it drives the workflow on the labcode backend, running each device
|
|
20
|
+
operation's script — supplied in the environment as an `x-labcode.script` extension
|
|
21
|
+
on a process mode — out-of-process on a wall clock, so a long-running real operation
|
|
22
|
+
never blocks the replan loop. See [`SPECIFICATIONS.md`](SPECIFICATIONS.md) for the
|
|
23
|
+
`x-labcode` extension.
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
# in the execution environment: how a (process, mode) is carried out
|
|
27
|
+
processes:
|
|
28
|
+
measure_od:
|
|
29
|
+
modes:
|
|
30
|
+
- id: v0
|
|
31
|
+
duration: 45
|
|
32
|
+
x-labcode:
|
|
33
|
+
script:
|
|
34
|
+
language: python
|
|
35
|
+
code: |
|
|
36
|
+
return {"od": read_plate(plate)}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
pip install labcode
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Requires Python 3.10+. `lc validate` and `lc schedule` are dispatched to the ofplang
|
|
46
|
+
sibling packages unchanged; `lc run` is this package's own runner, built on them:
|
|
47
|
+
|
|
48
|
+
- [`ofplang-validate`](https://github.com/ofplang/validate) — the validator
|
|
49
|
+
- [`ofplang-schedule`](https://github.com/ofplang/schedule) — the scheduler
|
|
50
|
+
- [`ofplang-run`](https://github.com/ofplang/run) — the runner / simulator
|
|
51
|
+
|
|
52
|
+
The language is defined in the [ofplang/spec](https://github.com/ofplang/spec)
|
|
53
|
+
repository, and what labcode adds to it in [`SPECIFICATIONS.md`](SPECIFICATIONS.md).
|
|
54
|
+
|
|
55
|
+
What `lc run` brings of its own, beyond dispatching:
|
|
56
|
+
|
|
57
|
+
- **the labcode backend** — each device operation's `x-labcode.script` runs
|
|
58
|
+
out-of-process on a wall clock (§1.2, §1.3), so a real operation that takes minutes
|
|
59
|
+
does not block the replan loop, and an operation that never returns is stopped by
|
|
60
|
+
**`op_timeout`** (§1.8; `--op-timeout` / `--no-op-timeout`).
|
|
61
|
+
- **the dialect front door** — the environment's `x-labcode` extension is validated
|
|
62
|
+
before anything runs, on top of the portable-v0 check `lc validate` performs (§1, §2).
|
|
63
|
+
- **availability probing** — each machine is checked as often as its `probe` policy says,
|
|
64
|
+
and one that cannot be reached is taken out of the environment the scheduler plans
|
|
65
|
+
against, so the run routes around it (§1.5; `--no-probe`).
|
|
66
|
+
- **object identity** — the reserved `_id` view key is declared on Object types and minted
|
|
67
|
+
per object, so a physical thing can be followed through a run (§4).
|
|
68
|
+
- **`flavor: sila2`** — a script that speaks SiLA2 gets its clients opened around it
|
|
69
|
+
(§1.6). The client library itself is the `sila2` extra: `pip install labcode[sila2]`,
|
|
70
|
+
installed into whichever interpreter runs the scripts.
|
|
71
|
+
- **recording a run** — with `--trace`, what the run did is recorded as OpenTelemetry
|
|
72
|
+
traces: one trace per run, a span per operation, and — measured inside the process that
|
|
73
|
+
issued them — a span per SiLA2 connection, per command, and per gRPC call underneath
|
|
74
|
+
either. Off by default; the extra is `pip install labcode[otel]`.
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
Each subcommand keeps its own options, exit codes, and `--help`:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
lc --help # top-level help
|
|
82
|
+
lc <command> --help # command-specific options
|
|
83
|
+
lc --version
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`lc` can also be run as a module: `python -m labcode <command> ...`.
|
|
87
|
+
|
|
88
|
+
`lc validate` and `lc schedule` are the ofplang siblings' own CLIs unchanged, so their
|
|
89
|
+
options are documented in those repositories. `lc run` is this package's own, and is
|
|
90
|
+
described below.
|
|
91
|
+
|
|
92
|
+
### `lc run`
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
lc run <workflow> --env <env>
|
|
96
|
+
[--boundary DOC] [-o OUT] [--boundary-out FILE] [--observation-out FILE]
|
|
97
|
+
[--seconds-per-tick S] [--op-timeout S | --no-op-timeout] [--no-probe]
|
|
98
|
+
[--trace] [--mission-id ID] [--object-ids seeded|real]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- `<workflow>` — the portable v0 workflow: *what* happens.
|
|
102
|
+
- `--env` (required) — the labcode environment: the execution environment (spec §5) plus
|
|
103
|
+
the `x-labcode` extension saying *how* each operation is carried out.
|
|
104
|
+
- `--boundary DOC` — the whole-workflow I/O as one document: a `boundary:` mapping with a
|
|
105
|
+
`{spot, view}` descriptor per entry input / final output port. `spot` says where a
|
|
106
|
+
boundary Object sits; `view` supplies an input's value. A workflow with Object-bearing
|
|
107
|
+
entry inputs needs one, since each must be placed on a spot and only the operator knows
|
|
108
|
+
where the labware is.
|
|
109
|
+
- `-o OUT` — write the final execution status (spec §6/§7) here; the default is stdout.
|
|
110
|
+
- `--boundary-out FILE` — write the result boundary: the same schema as `--boundary`, with
|
|
111
|
+
each produced output's `view` filled in, including the `_id` its Object was minted with
|
|
112
|
+
(§4) — which is how one checks that the plate that came back is the plate that went in.
|
|
113
|
+
- `--observation-out FILE` — stream the observation document: each *completed* activity's
|
|
114
|
+
concrete input / output view values, appended as it finishes. What the instruments
|
|
115
|
+
reported, as against the status document's timings.
|
|
116
|
+
- `--seconds-per-tick S` — real seconds per environment time tick (default 20). Durations
|
|
117
|
+
in the environment are counted in ticks, and this is what maps them onto the wall clock.
|
|
118
|
+
The default is deliberately coarse, so that a real operation's dispatch → running →
|
|
119
|
+
completed reads as discrete, observable steps; a demo against a fast mock wants a small
|
|
120
|
+
value.
|
|
121
|
+
- `--op-timeout S` / `--no-op-timeout` — how long one operation may run before it is
|
|
122
|
+
stopped and failed (§1.8). The default is the environment root's `x-labcode.op_timeout`,
|
|
123
|
+
else 7200 real seconds. The two forms exclude each other.
|
|
124
|
+
- `--no-probe` — ignore the environment's `x-labcode.probe` policies and treat every
|
|
125
|
+
machine as reachable (§1.5). The documents are still validated.
|
|
126
|
+
- `--trace` — record what the run did (see below). Off by default.
|
|
127
|
+
- `--mission-id ID` — the campaign this run belongs to. Recorded with the run and given no
|
|
128
|
+
meaning by labcode: several runs may share one, and nothing here reads it back.
|
|
129
|
+
- `--object-ids seeded|real` — how Object `_id`s are minted: `seeded` is reproducible (the
|
|
130
|
+
same workflow yields the same ids every run, which is what keeps the examples' recorded
|
|
131
|
+
output stable), `real` is unique per run. Unset, it follows `--trace`.
|
|
132
|
+
|
|
133
|
+
Exit codes: `0` the run completed, `1` it failed (an activity failed, a contract was
|
|
134
|
+
violated, an operation timed out, or a replan became infeasible), `2` a usage or input
|
|
135
|
+
error — including a workflow or an `x-labcode` extension the front doors reject.
|
|
136
|
+
|
|
137
|
+
A complete invocation, against this repository's `examples/` (`--seconds-per-tick` small
|
|
138
|
+
because that example's scripts return instantly):
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
lc run examples/plate_line.workflow.yaml --env examples/plate_line.env.yaml \
|
|
142
|
+
--boundary examples/plate_line.boundary.yaml --seconds-per-tick 0.2
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The remaining options tune the replan loop rather than describe the run —
|
|
146
|
+
`--poll-interval`, `--margin`, `--seed`, `--speed`, `--max-ticks`, `--no-validate` — and
|
|
147
|
+
are covered by `lc run --help`.
|
|
148
|
+
|
|
149
|
+
### Recording a run
|
|
150
|
+
|
|
151
|
+
`--trace` records what the run did as OpenTelemetry traces. It needs the extra, in the
|
|
152
|
+
interpreter that drives the run — which is also the one that runs the scripts, since
|
|
153
|
+
labcode launches each with `sys.executable`:
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
pip install 'labcode[otel]'
|
|
157
|
+
lc run <workflow> --env <env> --trace --mission-id M-2026-001
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
One run is one trace, and the id it can be found by is printed to stderr as the run
|
|
161
|
+
starts (`lc run: recording this run as trace …`). What it holds:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
run mission.id, and the failure if it stopped on one
|
|
165
|
+
├─ process Seal which node, process and mode; the plan's interval;
|
|
166
|
+
│ │ which Objects it handled
|
|
167
|
+
│ ├─ sila2.connect the address, measured in the process that connected
|
|
168
|
+
│ │ └─ /…/SiLAService/GetFeatureDefinition one per feature, × however many
|
|
169
|
+
│ └─ sila2 SealerControl.Seal the command, from its start to its real completion
|
|
170
|
+
│ └─ /…/SealerControl/Seal the round trip that started it
|
|
171
|
+
└─ transport the route and the transporter
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
An operation's span is opened when it is dispatched and closed when a poll finds it
|
|
175
|
+
finished, so its end is late by up to one poll period; what an instrument actually spent is
|
|
176
|
+
in the command spans, which are measured where the commands are issued. `ofp.object.id`
|
|
177
|
+
lists the `_id`s an operation handled, including one it created — which is what makes
|
|
178
|
+
"everything that happened to this plate" a single query.
|
|
179
|
+
|
|
180
|
+
The innermost layer is the gRPC calls themselves, each under the connection or the command
|
|
181
|
+
that issued it — so what a connection spends is broken down into the feature definitions it
|
|
182
|
+
had to fetch, and a command's span separates its round trip from the time the instrument
|
|
183
|
+
then took. It needs `grpcio`, which arrives with the `sila2` extra; without it the record is
|
|
184
|
+
the same minus that layer. Two things it does not do: an observable command's
|
|
185
|
+
execution-info subscription is **not** recorded (it is read on a thread of `sila2`'s own,
|
|
186
|
+
where it would land in a trace of its own, and its duration is the command's anyway), and
|
|
187
|
+
each recorded call **sends the trace context to the instrument** in its gRPC metadata, which
|
|
188
|
+
a SiLA2 server ignores as it does any key that is not SiLA Client Metadata.
|
|
189
|
+
|
|
190
|
+
Where the record goes is configured by the **standard `OTEL_*` environment variables**
|
|
191
|
+
(`OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_RESOURCE_ATTRIBUTES`, `OTEL_SERVICE_NAME`, …), so
|
|
192
|
+
labcode adds no settings of its own; `service.name` falls back to `labcode` if nothing sets
|
|
193
|
+
it. `LC_TRACE_FILE=path` additionally writes the spans to a file as JSON lines, one file per
|
|
194
|
+
process (the name gets the process id), which is how a record can be read without standing
|
|
195
|
+
up a collector.
|
|
196
|
+
|
|
197
|
+
Two things to know before pointing a run at a collector. **`--trace` makes Object `_id`s
|
|
198
|
+
real rather than reproducible** unless `--object-ids` says otherwise, since a reproducible id
|
|
199
|
+
is the same on every run and would collapse several runs' plates into one. And **a collector
|
|
200
|
+
that is configured but does not answer delays each operation**, by as long as
|
|
201
|
+
OpenTelemetry's own export timeout allows: labcode sets no timeout of its own, so
|
|
202
|
+
`OTEL_EXPORTER_OTLP_TIMEOUT` is the knob, and it belongs to whoever pointed the run there.
|
|
203
|
+
|
|
204
|
+
## Examples
|
|
205
|
+
|
|
206
|
+
[`examples/`](examples/README.md) holds three worked runs: `plate_line`, an
|
|
207
|
+
Object-bearing line driven entirely by environment scripts and runnable with no
|
|
208
|
+
hardware, and `sila2_seal` and `sila2_plate_cycle`, which drive the reference lab's
|
|
209
|
+
SiLA2 servers for real. `sila2_seal` also walks through what a run does when a
|
|
210
|
+
machine stops answering — before an operation, and in the middle of one.
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT
|
|
@@ -306,7 +306,7 @@ x-labcode:
|
|
|
306
306
|
- transporter: arm
|
|
307
307
|
from: plateloc.stage
|
|
308
308
|
to: thermal_cycler.block
|
|
309
|
-
duration:
|
|
309
|
+
duration: 43
|
|
310
310
|
x-labcode:
|
|
311
311
|
script:
|
|
312
312
|
language: python
|
|
@@ -317,9 +317,10 @@ x-labcode:
|
|
|
317
317
|
|
|
318
318
|
cycler = sila2_clients["thermal_cycler"].AutomatedThermalCyclerController
|
|
319
319
|
settle(cycler.OpenLid(), "OpenLid")
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
320
|
+
# The transporter is still the first client. Its own names for the places it
|
|
321
|
+
# serves are stations, not `device.spot`, so a route writes them out.
|
|
322
|
+
labware = sila2_client.LabwareService
|
|
323
|
+
settle(labware.Transfer(SourceStation="Base4", DestinationStation="Base6"), "Transfer")
|
|
323
324
|
```
|
|
324
325
|
- **Connections last one operation**, opened before the code runs and closed after it — on
|
|
325
326
|
any exit, including a `return` or an exception, and including a *later* connection
|