Skip to content

Rename Command attributes around four conceptual operations - #86

Merged
dccote merged 1 commit into
masterfrom
commands-rename
May 22, 2026
Merged

dccote merged 1 commit into
masterfrom
commands-rename

Conversation

@dccote

@dccote dccote commented May 19, 2026

Copy link
Copy Markdown
Collaborator

First step of the redesign discussed during review of PR #85.

What changes

Every `Command` subclass — `TextCommand`, `MultilineTextCommand`, `DataCommand` — now carries the same four attribute names for the same four conceptual operations:

Role Attribute What TextCommand holds What DataCommand holds
Encode outgoing request `requestEncoder` format string `"g r{register}\n"` `DataEncoder('<clllc', fields, defaults)`
Decode incoming request (mock) `requestDecoder` regex `r'g r(?P...)'` `DataDecoder('<xlllx', fields, prefix=b'M')`
Encode outgoing reply (mock) `replyEncoder` format string `"v {value}\r"` `DataEncoder('<lllc', fields)`
Decode incoming reply `replyDecoder` regex `r'v\s(-?\d+)'` `DataDecoder('<c', length=1)`

Two new frozen dataclasses (`DataEncoder`, `DataDecoder`) collapse the 12 previous attribute slots on `DataCommand` (`sendFormat`/`sendFields`/`sendDefaults`/`requestFormat`/`requestFields`/`prefix`/`responseFormat`/`responseFields`/`unpackingMask`/`replyDataLength`/`replyHexRegex`/`_prefix`) into 4 grouped slots.

Deleted attributes

  • `alternatePattern` (TextCommand + MultilineTextCommand) — plumbed through `readMatchingGroups` but never set by any Command in the codebase. If a future caller needs alternation, write it into the `replyDecoder` regex.
  • `replyHexRegex` (DataCommand) — only code path raised `NotImplementedError`. Dead.

Migration scope

All five device families' `commands = {...}` dicts migrated:

Device Commands New form's shape
`SutterDevice` 4 (binary, MP-285 protocol) `DataEncoder` + `DataDecoder` triples
`IntellidriveDevice` 3 (text, named groups) rename only
`IntegraDevice` 5 (text + multiline) rename only
`EchoDevice` 3 (mixed) rename + one `DataDecoder(length=...)`
`CoboltDevice` 11 (text) rename only

Test fixtures in `testCommandRecognition.py` (38 tests), `testTableDrivenDebugPort.py` (3 fixtures + 12 tests), and `testCommunicationPorts.py` (5 tests) all rewritten for the new constructor signatures.

Before / after — SutterDevice MOVE

```python

Before

"MOVE": DataCommand(name="MOVE", prefix=b'M',
sendFormat='<clllc',
sendFields=('header', 'x', 'y', 'z', 'terminator'),
sendDefaults={'header': b'M', 'terminator': b'\r'},
requestFormat='<xlllx', requestFields=('x', 'y', 'z'),
replyDataLength=1, unpackingMask='<c'),

After

"MOVE": DataCommand(name="MOVE",
requestEncoder=DataEncoder('<clllc',
('header', 'x', 'y', 'z', 'terminator'),
{'header': b'M', 'terminator': b'\r'}),
requestDecoder=DataDecoder('<xlllx', ('x', 'y', 'z'), prefix=b'M'),
replyDecoder=DataDecoder('<c', length=1),
),
```

Internal renames

  • `TextCommand.effectiveMatchPattern` (property) → `effectiveDecoder` (property)
  • `TextCommand._autoMatchPattern` (was a property, never set state) → `_autoMatchPattern()` (now a method)

Both are private-ish; the only tests that touched them were updated.

Test plan

  • Full suite: 261 passed, 203 skipped, 0 failed. Skips are hardware-gated and were also skipping before this commit.
  • `testCommandRecognition.py` (36 unit tests): all pass with new attribute names.
  • `testTableDrivenDebugPort.py` (12 dispatch tests): all pass with new fixture syntax.
  • Mock-side device tests for Sutter / Intellidrive / Echo / Integra / Cobolt: all pass.
  • Verify against a live Sutter / spectrometer when convenient.

What this PR does not address (intentional, deferred)

From the review of PR #85, the redesign concerns were:

  1. ✅ TextCommand and DataCommand don't share a shape — done in this PR.
  2. ⏭ Schema vs instance conflation — `Command` still holds runtime state (`reply`, `matchGroups`, `isSent`) on the same object as the immutable schema. Separating these is the next step.
  3. ⏭ Mock hook contract weakness — `process_command(name, params, endPointIndex)` still receives `name` as a string and `params` as either tuple-or-dict. Tightening this is downstream.
  4. ⏭ Error model inconsistency — TextCommand swallows + stores in `.exceptions`; DataCommand re-raises. Unchanged here.
  5. ⏭ `endPoints` on Command — kept as-is per your call.

Relationship to PR #85

`unify-sendcommand` (PR #85) is unaffected by this PR — they touch different concerns. After this PR merges, PR #85 will need a rebase that picks up the new attribute names.

🤖 Generated with Claude Code

@dccote
dccote marked this pull request as draft May 19, 2026 19:50
Every Command (TextCommand, MultilineTextCommand, DataCommand) carries
the same four conceptual operations:

    requestEncoder   encode an outgoing request payload   (device -> wire)
    requestDecoder   decode an incoming request payload   (wire -> mock)
    replyEncoder     encode an outgoing reply payload     (mock -> wire)
    replyDecoder     decode an incoming reply payload     (wire -> device)

Before this commit, those operations were named ad-hoc per subclass --
TextCommand used text_format/matchPattern/replyPattern/responseTemplate
while DataCommand used sendFormat+sendFields+sendDefaults / requestFormat+
requestFields+prefix / unpackingMask+replyDataLength / responseFormat+
responseFields. Twenty-two attribute slots describing four conceptual
roles, no two sharing a name across subclasses.

This commit gives every Command subclass the same four attribute names
for the same four operations. For TextCommand they hold strings (format
strings for encoders, regexes for decoders). For DataCommand they hold
small frozen dataclasses (DataEncoder bundles struct format + fields +
defaults; DataDecoder bundles struct format + fields + length + prefix).

Deleted attributes:

- alternatePattern: TextCommand + MultilineTextCommand kwarg that was
  plumbed through readMatchingGroups but never actually set by any
  Command in the codebase. Fold into replyDecoder regex if needed.
- replyHexRegex: DataCommand kwarg whose only code path raised
  NotImplementedError. Dead.

Migration of every Command in the codebase to the new constructor:

- SutterDevice (4 commands): each DataCommand now wraps the struct
  format + fields + defaults inside DataEncoder/DataDecoder. The MOVE
  command goes from 7 keyword args + a prefix=b'M' to one tidy
  requestEncoder/requestDecoder/replyDecoder triple.
- IntellidriveDevice (3 text commands), IntegraDevice (5 text/multiline
  commands), EchoDevice (3 mixed commands), CoboltDevice (11 text
  commands): same pattern, attribute names renamed.
- testCommandRecognition.py (38 tests), testTableDrivenDebugPort.py
  (3 fixtures + 12 tests), testCommunicationPorts.py (5 tests): all
  rewritten to use the new constructor signatures.

Internal property renames:

- TextCommand.effectiveMatchPattern -> effectiveDecoder
- TextCommand._autoMatchPattern (was a property) -> _autoMatchPattern()
  (now a method, since it's called rather than read)

These are private/dunder-ish; the only tests that referenced them were
updated.

Verified:

- 261 passed, 203 skipped, 0 failures on the full test suite. The
  skipped tests are all hardware-gated (no spectrometer / no Sutter /
  no LabJack attached) and were also skipping before this commit.

Closes the first item of the redesign discussed in PR #85's review:
"TextCommand and DataCommand don't share a shape". The remaining items
(separate Command schema from per-call instance, unify error model,
mock hook contract) are left for follow-up work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dccote
dccote marked this pull request as ready for review May 22, 2026 14:19
@dccote
dccote force-pushed the commands-rename branch from a591f75 to 02005af Compare May 22, 2026 14:19
@dccote
dccote merged commit 1d8f628 into master May 22, 2026
@dccote
dccote deleted the commands-rename branch May 22, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant