Skip to content

Uniformize capability mixins: single *Capability convention, single module, single base#114

Merged
dccote merged 4 commits into
masterfrom
uniformize-capability-naming
Jul 14, 2026
Merged

Uniformize capability mixins: single *Capability convention, single module, single base#114
dccote merged 4 commits into
masterfrom
uniformize-capability-naming

Conversation

@dccote

@dccote dccote commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Uniformizes the library's capability system across all three families (DAQ, laser sources, power meters), which previously used three different naming conventions and two independent Capability base classes.

The end-state rule: *Capability = a feature a device may have (a mixin); *Device = instantiable hardware.

Three focused commits:

  1. Rename DAQ mixins *Device*CapabilityAnalogInputDeviceAnalogInputCapability, TriggerableDeviceTriggerCapability, etc. *Device no longer doubles as both a mixin suffix and a hardware-driver suffix.
  2. Rename source (*Control) and power-meter (*able) mixins → *CapabilityOnOffControlOnOffCapability, WavelengthCalibratableWavelengthCalibrationCapability, etc.
  3. Consolidate into one module with one base — every mixin now lives in hardwarelibrary/capabilities.py and subclasses a single Capability(ABC). The DAQ enums (InputSource, TriggerSource, SampleClock, DAQNotification) move with their mixins. capabilities() / hasCapability() are hoisted onto PhysicalDevice, so every device — including DAQ drivers — gets capability introspection, and the duplicated copies on LaserSourceDevice/PowerMeterDevice are removed.

Breaking changes

Public methods and runtime behavior are unchanged; only class names, the module layout, and where introspection lives change. Downstream code must update mixin names and imports. The family package __init__s still re-export their own mixins, so from hardwarelibrary.daq import AnalogIOCapability (and equivalents) keep working. See CHANGELOG.md [Unreleased] for the full old → new table.

Removed

  • hardwarelibrary/sources/capabilities.py
  • hardwarelibrary/powermeters/capabilities.py
  • hardwarelibrary/daq/daqdevice.py

Docs

  • CLAUDE.md, the pyhardwarelibrary skill, and the HOPS/Millennia manuals reflect the new names.
  • Sphinx: capabilities are documented once on the Core page; dead per-family automodule sections removed.
  • Released-version CHANGELOG history ([1.3.1], [1.4.0]) left intact as the accurate record of prior names.

Testing

Full suite: 470 passed, 247 skipped, 0 failures — identical to the pre-change baseline (skips are all hardware-absent).

🤖 Generated with Claude Code

dccote and others added 4 commits July 14, 2026 11:40
Problem: The DAQ family named its interface-segregated capability mixins
with a *Device suffix (AnalogInputDevice, TriggerableDevice, ...), while
the same objects are features a device *has*, not devices themselves.
This collided conceptually with instantiable hardware drivers, which are
also named *Device (LabjackDevice, SR830Device), making it unclear from
a name whether a class is a mixin or a real device.

Solution: Rename all nine DAQ mixins to the *Capability suffix, reserving
*Device for instantiable hardware drivers. Public methods and behavior are
unchanged; only the mixin class names change.

  AnalogInputDevice        -> AnalogInputCapability
  AnalogOutputDevice       -> AnalogOutputCapability
  AnalogIODevice           -> AnalogIOCapability
  AnalogInputStreamDevice  -> AnalogInputStreamCapability
  DigitalInputDevice       -> DigitalInputCapability
  DigitalOutputDevice      -> DigitalOutputCapability
  DigitalIODevice          -> DigitalIOCapability
  PhaseLockedDetectionDevice -> PhaseLockedDetectionCapability
  TriggerableDevice        -> TriggerCapability

Updates the LabjackDevice and SR830Device base-class lists and imports,
the daq package exports, and the SR830 tests. Documents the rename in
CHANGELOG (Unreleased, marked Breaking) and the *Capability/*Device rule
in CLAUDE.md and the skill doc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem: After the DAQ rename, two families still used their own
conventions for capability mixins: laser sources used a *Control suffix
(OnOffControl, PowerControl, ...) and power meters used adjective forms
(WavelengthCalibratable, AutoScalable, ScaleAdjustable). This left three
naming styles for the same concept -- a feature a device may have -- and
made it inconsistent which classes are mixins versus instantiable
devices.

Solution: Rename these mixins to the same *Capability suffix adopted for
DAQ, completing the uniform rule: *Capability for mixins, *Device for
instantiable hardware. Public methods and behavior are unchanged.

Laser sources (sources/capabilities.py):
  OnOffControl        -> OnOffCapability
  ShutterControl      -> ShutterCapability
  PowerControl        -> PowerCapability
  InterlockControl    -> InterlockCapability
  AutostartControl    -> AutostartCapability
  WavelengthControl   -> WavelengthCapability
  DispersionControl   -> DispersionCapability

Power meters (powermeters/capabilities.py):
  WavelengthCalibratable -> WavelengthCalibrationCapability
  AutoScalable           -> AutoScaleCapability
  ScaleAdjustable        -> ScaleCapability

Updates the CoboltDevice, MillenniaEv25Device, MatisseDevice,
VerdiGDevice, FieldMasterDevice, and IntegraDevice base-class lists and
imports, the sources/powermeters package exports, the source/powermeter
tests, and the physicaldevice.py doc comment. Documents the rename in
CHANGELOG (Unreleased, folded into the Breaking capability-rename entry)
and CLAUDE.md / the skill doc, and aligns the HOPS and Millennia manuals.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y base

Problem: Capability mixins were spread across three modules with two
independent Capability base classes: sources/capabilities.py and
powermeters/capabilities.py each defined their own Capability(ABC), while
the DAQ mixins in daq/daqdevice.py inherited bare ABC with no shared base
at all. Capability introspection (capabilities() / hasCapability()) was
duplicated verbatim on LaserSourceDevice and PowerMeterDevice, each bound
to its family's local Capability, and DAQ devices had no introspection.

Solution: Move every capability mixin into a single top-level module,
hardwarelibrary/capabilities.py, all subclassing one Capability base. The
DAQ enums (InputSource, TriggerSource, SampleClock, DAQNotification) move
with their mixins. Hoist capabilities() / hasCapability() onto
PhysicalDevice, bound to the single Capability, so every device -- DAQ
included -- gets capability introspection, and the duplicated methods are
removed (LaserSourceDevice becomes a pure marker; PowerMeterDevice keeps
only its power hooks).

The per-family capabilities.py / daqdevice.py files are deleted. All
internal imports point at hardwarelibrary.capabilities; the family package
__init__s still re-export their own mixins, so existing consumers such as
`from hardwarelibrary.daq import AnalogIOCapability` keep working.

Docs updated: capabilities are documented once on the Core page; the
per-family automodule sections and their file-path references are removed.

Full suite: 470 passed, 247 skipped, no regressions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem: DAQNotification (willAcquire/didAcquire) sat as a free-floating
module-level enum in capabilities.py, unattached to the capability that
posts it.

Solution: Nest it as AnalogInputStreamCapability.Notification, inside the
acquisition capability it belongs to, so the notification is discoverable
from the capability that raises it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dccote
dccote merged commit 163e202 into master Jul 14, 2026
14 checks passed
@dccote
dccote deleted the uniformize-capability-naming branch July 14, 2026 23:48
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