raydriver 0.1.0__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.
- raydriver-0.1.0/.github/workflows/ci.yml +180 -0
- raydriver-0.1.0/.github/workflows/release.yml +85 -0
- raydriver-0.1.0/.gitignore +9 -0
- raydriver-0.1.0/AGENTS.md +111 -0
- raydriver-0.1.0/Cargo.lock +1426 -0
- raydriver-0.1.0/Cargo.toml +36 -0
- raydriver-0.1.0/Cargo.toml.bak +35 -0
- raydriver-0.1.0/LICENSE +21 -0
- raydriver-0.1.0/Makefile +42 -0
- raydriver-0.1.0/PKG-INFO +93 -0
- raydriver-0.1.0/README.md +76 -0
- raydriver-0.1.0/env.sh +5 -0
- raydriver-0.1.0/freeze-dump.txt +0 -0
- raydriver-0.1.0/pyproject.toml +57 -0
- raydriver-0.1.0/python/raydriver/__init__.py +22 -0
- raydriver-0.1.0/python/raydriver/__init__.pyi +141 -0
- raydriver-0.1.0/python/raydriver/cli/__init__.py +1 -0
- raydriver-0.1.0/python/raydriver/cli/main.py +290 -0
- raydriver-0.1.0/python/raydriver/emulator.py +871 -0
- raydriver-0.1.0/python/raydriver/grbl/__init__.py +12 -0
- raydriver-0.1.0/python/raydriver/grbl/__init__.pyi +190 -0
- raydriver-0.1.0/python/raydriver/grbl/parser/__init__.py +30 -0
- raydriver-0.1.0/python/raydriver/grbl/parser/__init__.pyi +8 -0
- raydriver-0.1.0/python/raydriver/grbl/parser/py.typed +0 -0
- raydriver-0.1.0/python/raydriver/grbl/py.typed +0 -0
- raydriver-0.1.0/python/raydriver/grbl/types/__init__.py +12 -0
- raydriver-0.1.0/python/raydriver/grbl/types/__init__.pyi +91 -0
- raydriver-0.1.0/python/raydriver/grbl/types/py.typed +0 -0
- raydriver-0.1.0/python/raydriver/py.typed +0 -0
- raydriver-0.1.0/rustfmt.toml +1 -0
- raydriver-0.1.0/src/bin/stub_gen.rs +4 -0
- raydriver-0.1.0/src/grbl/dialect.rs +218 -0
- raydriver-0.1.0/src/grbl/errors.rs +289 -0
- raydriver-0.1.0/src/grbl/flow.rs +435 -0
- raydriver-0.1.0/src/grbl/mod.rs +13 -0
- raydriver-0.1.0/src/grbl/parser.rs +510 -0
- raydriver-0.1.0/src/grbl/session.rs +2096 -0
- raydriver-0.1.0/src/grbl/transport/mock.rs +116 -0
- raydriver-0.1.0/src/grbl/transport/mod.rs +60 -0
- raydriver-0.1.0/src/grbl/transport/serial.rs +106 -0
- raydriver-0.1.0/src/grbl/transport/telnet.rs +106 -0
- raydriver-0.1.0/src/grbl/types.rs +181 -0
- raydriver-0.1.0/src/lib.rs +37 -0
- raydriver-0.1.0/src/python/events.rs +288 -0
- raydriver-0.1.0/src/python/mock.rs +79 -0
- raydriver-0.1.0/src/python/mod.rs +50 -0
- raydriver-0.1.0/src/python/parser.rs +267 -0
- raydriver-0.1.0/src/python/pyfuture.rs +70 -0
- raydriver-0.1.0/src/python/runtime.rs +45 -0
- raydriver-0.1.0/src/python/session.rs +587 -0
- raydriver-0.1.0/src/python/types.rs +238 -0
- raydriver-0.1.0/tests/conftest.py +155 -0
- raydriver-0.1.0/tests/test_cli.py +79 -0
- raydriver-0.1.0/tests/test_parser.py +347 -0
- raydriver-0.1.0/tests/test_session.py +362 -0
- raydriver-0.1.0/tests/test_streaming.py +250 -0
- raydriver-0.1.0/tests/test_stress.py +92 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
name: Lint and typecheck
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v7
|
|
15
|
+
|
|
16
|
+
- name: Build wheel
|
|
17
|
+
uses: PyO3/maturin-action@v1
|
|
18
|
+
with:
|
|
19
|
+
command: build
|
|
20
|
+
args: --release --out dist
|
|
21
|
+
|
|
22
|
+
- name: Install wheel
|
|
23
|
+
run: pip install dist/raydriver-*.whl
|
|
24
|
+
|
|
25
|
+
- name: Lint Python
|
|
26
|
+
run: pip install ruff && ruff check tests/ python/
|
|
27
|
+
|
|
28
|
+
- name: Install Rust toolchain
|
|
29
|
+
uses: dtolnay/rust-toolchain@1.98.1
|
|
30
|
+
with:
|
|
31
|
+
components: rustfmt, clippy
|
|
32
|
+
|
|
33
|
+
- name: Lint Rust
|
|
34
|
+
shell: bash
|
|
35
|
+
run: |
|
|
36
|
+
set -o pipefail
|
|
37
|
+
cargo fmt --check 2>&1 | tee fmt-output.txt
|
|
38
|
+
cargo clippy -- -D warnings 2>&1 | tee clippy-output.txt
|
|
39
|
+
|
|
40
|
+
- name: Persist diagnostics to ci-logs branch
|
|
41
|
+
if: failure()
|
|
42
|
+
shell: bash
|
|
43
|
+
run: |
|
|
44
|
+
mkdir -p logs
|
|
45
|
+
cp fmt-output.txt clippy-output.txt logs/ 2>/dev/null || true
|
|
46
|
+
git config user.name ci-bot
|
|
47
|
+
git config user.email ci-bot@users.noreply.github.com
|
|
48
|
+
git add logs
|
|
49
|
+
git diff --cached --quiet && echo "nothing to persist" && exit 0
|
|
50
|
+
git commit -qm "lint diagnostics @ ${{ github.sha }}"
|
|
51
|
+
git push -q -f origin "HEAD:refs/heads/ci-logs-lint"
|
|
52
|
+
|
|
53
|
+
test:
|
|
54
|
+
name: Tests (${{ matrix.os }})
|
|
55
|
+
runs-on: ${{ matrix.os }}
|
|
56
|
+
timeout-minutes: 40
|
|
57
|
+
strategy:
|
|
58
|
+
fail-fast: false
|
|
59
|
+
# Windows is covered by the sharded windows-shard job (its
|
|
60
|
+
# release build is slow and one hang must not hide the rest).
|
|
61
|
+
matrix:
|
|
62
|
+
os: [ubuntu-latest, macos-15-intel, macos-15]
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v7
|
|
65
|
+
|
|
66
|
+
- name: Build wheel
|
|
67
|
+
uses: PyO3/maturin-action@v1
|
|
68
|
+
with:
|
|
69
|
+
command: build
|
|
70
|
+
args: --release --out dist
|
|
71
|
+
|
|
72
|
+
- name: Install wheel
|
|
73
|
+
shell: bash
|
|
74
|
+
run: pip install dist/raydriver-*.whl
|
|
75
|
+
|
|
76
|
+
- name: Install test dependencies
|
|
77
|
+
shell: bash
|
|
78
|
+
run: pip install pytest pytest-asyncio pytest-timeout
|
|
79
|
+
|
|
80
|
+
- name: Run tests
|
|
81
|
+
shell: bash
|
|
82
|
+
run: |
|
|
83
|
+
set -o pipefail
|
|
84
|
+
rm -f pytest-output.txt
|
|
85
|
+
for attempt in 1 2 3; do
|
|
86
|
+
if pytest -v --timeout=60 --timeout-method=thread --tb=short -rA 2>&1 | tee -a pytest-output.txt; then
|
|
87
|
+
exit 0
|
|
88
|
+
fi
|
|
89
|
+
echo "attempt $attempt failed, retrying..." | tee -a pytest-output.txt
|
|
90
|
+
done
|
|
91
|
+
exit 1
|
|
92
|
+
|
|
93
|
+
- name: Persist diagnostics to ci-logs branch
|
|
94
|
+
if: failure() || cancelled()
|
|
95
|
+
shell: bash
|
|
96
|
+
run: |
|
|
97
|
+
mkdir -p logs
|
|
98
|
+
cp pytest-output.txt freeze-dump.txt logs/ 2>/dev/null || true
|
|
99
|
+
git config user.name ci-bot
|
|
100
|
+
git config user.email ci-bot@users.noreply.github.com
|
|
101
|
+
git add logs
|
|
102
|
+
git diff --cached --quiet && echo "nothing to persist" && exit 0
|
|
103
|
+
git commit -qm "test diagnostics: ${{ matrix.os }} @ ${{ github.sha }}"
|
|
104
|
+
git push -q -f origin "HEAD:refs/heads/ci-logs-main-${{ matrix.os }}"
|
|
105
|
+
|
|
106
|
+
windows-shard:
|
|
107
|
+
name: Windows shard (${{ matrix.file }})
|
|
108
|
+
runs-on: windows-latest
|
|
109
|
+
timeout-minutes: 60
|
|
110
|
+
strategy:
|
|
111
|
+
fail-fast: false
|
|
112
|
+
matrix:
|
|
113
|
+
file:
|
|
114
|
+
- tests/test_cli.py
|
|
115
|
+
- tests/test_parser.py
|
|
116
|
+
- tests/test_session.py
|
|
117
|
+
- tests/test_streaming.py
|
|
118
|
+
- tests/test_stress.py
|
|
119
|
+
steps:
|
|
120
|
+
- uses: actions/checkout@v7
|
|
121
|
+
|
|
122
|
+
- name: Install Rust toolchain
|
|
123
|
+
uses: dtolnay/rust-toolchain@1.98.1
|
|
124
|
+
|
|
125
|
+
- name: Install maturin
|
|
126
|
+
shell: bash
|
|
127
|
+
run: pip install maturin
|
|
128
|
+
|
|
129
|
+
- name: Build wheel
|
|
130
|
+
shell: bash
|
|
131
|
+
run: |
|
|
132
|
+
set -o pipefail
|
|
133
|
+
maturin build --release --out dist 2>&1 | tee build-output.txt
|
|
134
|
+
|
|
135
|
+
- name: Persist build diagnostics to ci-logs branch
|
|
136
|
+
if: failure()
|
|
137
|
+
shell: bash
|
|
138
|
+
run: |
|
|
139
|
+
mkdir -p logs
|
|
140
|
+
cp build-output.txt logs/ 2>/dev/null || true
|
|
141
|
+
git config user.name ci-bot
|
|
142
|
+
git config user.email ci-bot@users.noreply.github.com
|
|
143
|
+
git add logs
|
|
144
|
+
git diff --cached --quiet && echo "nothing to persist" && exit 0
|
|
145
|
+
git commit -qm "windows build diagnostics: ${{ matrix.file }} @ ${{ github.sha }}"
|
|
146
|
+
git push -q -f origin "HEAD:refs/heads/ci-logs-win-build-${{ matrix.file }}"
|
|
147
|
+
|
|
148
|
+
- name: Install wheel
|
|
149
|
+
shell: bash
|
|
150
|
+
run: pip install dist/raydriver-*.whl
|
|
151
|
+
|
|
152
|
+
- name: Install test dependencies
|
|
153
|
+
shell: bash
|
|
154
|
+
run: pip install pytest pytest-asyncio pytest-timeout
|
|
155
|
+
|
|
156
|
+
- name: Run shard
|
|
157
|
+
shell: bash
|
|
158
|
+
run: |
|
|
159
|
+
set -o pipefail
|
|
160
|
+
rm -f shard-output.txt
|
|
161
|
+
for attempt in 1 2 3; do
|
|
162
|
+
if python -u -m pytest ${{ matrix.file }} -v --timeout=30 --timeout-method=thread --tb=long -rA 2>&1 | tee -a shard-output.txt; then
|
|
163
|
+
exit 0
|
|
164
|
+
fi
|
|
165
|
+
echo "attempt $attempt failed, retrying..." | tee -a shard-output.txt
|
|
166
|
+
done
|
|
167
|
+
exit 1
|
|
168
|
+
|
|
169
|
+
- name: Persist shard diagnostics to ci-logs branch
|
|
170
|
+
if: failure() || cancelled()
|
|
171
|
+
shell: bash
|
|
172
|
+
run: |
|
|
173
|
+
mkdir -p logs
|
|
174
|
+
cp shard-output.txt freeze-dump.txt logs/ 2>/dev/null || true
|
|
175
|
+
git config user.name ci-bot
|
|
176
|
+
git config user.email ci-bot@users.noreply.github.com
|
|
177
|
+
git add logs
|
|
178
|
+
git diff --cached --quiet && echo "nothing to persist" && exit 0
|
|
179
|
+
git commit -qm "windows shard diagnostics: ${{ matrix.file }} @ ${{ github.sha }}"
|
|
180
|
+
git push -q -f origin "HEAD:refs/heads/ci-logs-win-shard"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
name: Build and Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build-wheels:
|
|
10
|
+
name: Build wheels on ${{ matrix.os }}
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest, windows-latest, macos-15-intel, macos-15]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
|
|
19
|
+
- name: Set version from tag
|
|
20
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
21
|
+
shell: bash
|
|
22
|
+
run: |
|
|
23
|
+
VERSION="${GITHUB_REF#refs/tags/v}"
|
|
24
|
+
sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" Cargo.toml
|
|
25
|
+
|
|
26
|
+
- uses: PyO3/maturin-action@v1
|
|
27
|
+
with:
|
|
28
|
+
command: build
|
|
29
|
+
args: --release --out dist
|
|
30
|
+
|
|
31
|
+
- name: Upload wheels as artifacts
|
|
32
|
+
uses: actions/upload-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: wheels-${{ matrix.os }}
|
|
35
|
+
path: dist/*.whl
|
|
36
|
+
|
|
37
|
+
build-sdist:
|
|
38
|
+
name: Build sdist
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v7
|
|
42
|
+
|
|
43
|
+
- name: Set version from tag
|
|
44
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
45
|
+
shell: bash
|
|
46
|
+
run: |
|
|
47
|
+
VERSION="${GITHUB_REF#refs/tags/v}"
|
|
48
|
+
sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" Cargo.toml
|
|
49
|
+
|
|
50
|
+
- uses: PyO3/maturin-action@v1
|
|
51
|
+
with:
|
|
52
|
+
command: sdist
|
|
53
|
+
args: --out dist
|
|
54
|
+
|
|
55
|
+
- name: Upload sdist as artifact
|
|
56
|
+
uses: actions/upload-artifact@v4
|
|
57
|
+
with:
|
|
58
|
+
name: sdist
|
|
59
|
+
path: dist/*.tar.gz
|
|
60
|
+
|
|
61
|
+
publish:
|
|
62
|
+
name: Publish to PyPI
|
|
63
|
+
needs: [build-wheels, build-sdist]
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
environment: pypi
|
|
66
|
+
permissions:
|
|
67
|
+
id-token: write
|
|
68
|
+
contents: write
|
|
69
|
+
steps:
|
|
70
|
+
- name: Download all artifacts
|
|
71
|
+
uses: actions/download-artifact@v4
|
|
72
|
+
with:
|
|
73
|
+
path: dist
|
|
74
|
+
merge-multiple: true
|
|
75
|
+
|
|
76
|
+
- name: Publish to PyPI
|
|
77
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
78
|
+
with:
|
|
79
|
+
packages-dir: dist/
|
|
80
|
+
|
|
81
|
+
- name: Upload artifacts to GitHub Release
|
|
82
|
+
uses: softprops/action-gh-release@v2
|
|
83
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
84
|
+
with:
|
|
85
|
+
files: dist/*
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
## Available commands
|
|
4
|
+
|
|
5
|
+
- `make dev` — build and install into the active venv
|
|
6
|
+
- `make stubs` — re-generate `.pyi` type stubs (after changing `src/python/` bindings)
|
|
7
|
+
- `make lint` — lint all code (Rust + Python)
|
|
8
|
+
- `make lint-rust` — lint Rust only
|
|
9
|
+
- `make lint-python` — lint Python only
|
|
10
|
+
- `make format` — auto-format all code (Rust + Python, including PEP8 import ordering)
|
|
11
|
+
- `make format-rust` — format Rust only
|
|
12
|
+
- `make format-python` — format Python only (ruff handles formatting and import sorting)
|
|
13
|
+
- `make test` — run the full test suite. make sure to "make dev" before you test
|
|
14
|
+
- `make check` — lint + test
|
|
15
|
+
- `make build` — build the wheel (release)
|
|
16
|
+
|
|
17
|
+
## CLI
|
|
18
|
+
|
|
19
|
+
- `raydriver run job.gcode --port /dev/ttyUSB0` — stream a G-code
|
|
20
|
+
file to a real device with live progress
|
|
21
|
+
- `raydriver run job.gcode --emulator` — same, against the built-in
|
|
22
|
+
firmware emulator (no hardware needed)
|
|
23
|
+
- `raydriver status --port /dev/ttyUSB0` — connect and print live
|
|
24
|
+
status reports
|
|
25
|
+
|
|
26
|
+
## Testing
|
|
27
|
+
|
|
28
|
+
All tests are Python-based, under `tests/`, and run against
|
|
29
|
+
`raydriver.emulator.GrblEmulator` — a GRBL 1.1h *firmware emulator*
|
|
30
|
+
(not a mock): it models the character-counting RX buffer, the
|
|
31
|
+
15-block planner with deferred acknowledgements, realtime command
|
|
32
|
+
interception, modal G-code state, feed-rate-timed motion, alarms,
|
|
33
|
+
homing, probing and the `$` system commands. Device-side code
|
|
34
|
+
consumes session writes via `MockTransport.take_new_sent()` (a
|
|
35
|
+
monotonic cursor — never index into `sent()`, which tests may clear
|
|
36
|
+
at any time).
|
|
37
|
+
|
|
38
|
+
## Rules
|
|
39
|
+
|
|
40
|
+
- You are strictly forbidden from editing stubs manually. They are only
|
|
41
|
+
to be edited using "make stubs".
|
|
42
|
+
- Use make commands when available - avoid calling the underlying tools
|
|
43
|
+
directly.
|
|
44
|
+
- Never add Rust tests (`#[cfg(test)]` / `#[test]` blocks in `src/`).
|
|
45
|
+
All tests are Python-based, under `tests/`. Exercise new Rust code
|
|
46
|
+
through PyO3 bindings from Python test code.
|
|
47
|
+
- Keep the Grbl protocol behavior byte-for-byte compatible with the
|
|
48
|
+
Python driver in Rayforge
|
|
49
|
+
(`rayforge/machine/driver/grbl/grbl_serial.py`). Do not change
|
|
50
|
+
protocol quirks (ack extraction order, realtime bypass, buffer
|
|
51
|
+
accounting, stall/liveness rules) without an explicit request.
|
|
52
|
+
|
|
53
|
+
## Layering Rules Specification
|
|
54
|
+
|
|
55
|
+
The crate is split into two layers that depend only downward:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
grbl (protocol + session core) → python (PyO3 bindings)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `src/grbl/` must not depend on PyO3 or any Python types. It is pure
|
|
62
|
+
Rust: protocol parsing, flow control, transports, and the session
|
|
63
|
+
state machines. Python-facing notifications go through the
|
|
64
|
+
`SessionEvents` trait; the `python` feature provides the
|
|
65
|
+
implementation that marshals events across the boundary.
|
|
66
|
+
- `src/python/` contains all PyO3 bindings and may use `src/grbl/`
|
|
67
|
+
freely, never the other way around.
|
|
68
|
+
|
|
69
|
+
## Export Policy: Explicit Paths
|
|
70
|
+
|
|
71
|
+
Every item has exactly one canonical path — its leaf module. Parent
|
|
72
|
+
mod.rs must not re-export children's items (no namespace flattening).
|
|
73
|
+
|
|
74
|
+
Exceptions: Primitive types, errors, classes and constants that are
|
|
75
|
+
_public_ AND _shared_ within a submodule.
|
|
76
|
+
|
|
77
|
+
Python sub-modules mirror the Rust hierarchy - no aliases or re-exports
|
|
78
|
+
at higher levels. The compiled module is `raydriver.raydriver`; the
|
|
79
|
+
pure-Python packages under `python/raydriver/` serve as the importable
|
|
80
|
+
packages and delegate to the Rust module via module-level
|
|
81
|
+
`__getattr__`:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import raydriver.raydriver as _raydriver
|
|
85
|
+
|
|
86
|
+
def __getattr__(name):
|
|
87
|
+
return getattr(_raydriver.grbl, name)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Rust submodules registered with `add_submodule` are intentionally NOT
|
|
91
|
+
registered in `sys.modules` — the Python `__init__.py` of the same
|
|
92
|
+
name is the package.
|
|
93
|
+
|
|
94
|
+
## Python/Rust Boundary Rules
|
|
95
|
+
|
|
96
|
+
- No domain model objects cross the Python/Rust boundary — only
|
|
97
|
+
primitive types, typed pyclasses defined in this crate, and
|
|
98
|
+
JSON-serialisable dicts.
|
|
99
|
+
- Async Rust methods return asyncio futures that are completed from
|
|
100
|
+
the tokio side via `call_soon_threadsafe` on the caller's event
|
|
101
|
+
loop. Never block the loop inside a binding.
|
|
102
|
+
- Events emitted by the session are delivered to the Python
|
|
103
|
+
`event_callback(name: str, payload)` on the event loop thread.
|
|
104
|
+
Bindings must not call the callback from a foreign thread directly.
|
|
105
|
+
|
|
106
|
+
## Dialects
|
|
107
|
+
|
|
108
|
+
Dialects remain data owned by Rayforge. This crate receives resolved
|
|
109
|
+
command templates (a plain dict of format strings) and only needs the
|
|
110
|
+
subset used for interactive commands. Formatting supports the Python
|
|
111
|
+
`str.format` subset `{name}` and `{name:.Nf}`.
|