Add dependency injection for the device constructor - #20
Merged
Conversation
The overload no longer annotates self with the class type variable, which is not allowed on __init__ and produced a pyright reportInvalidTypeVarUse warning. The module type is inferred from the device_module parameter instead, so Device(transport, behavior) still resolves to Device[BehaviorModule].
The module is consulted only for the identity check on open. It does not pre-populate the register map, so an event is parsed only for a register that has been subscribed. Examples import modules rather than names, so a register carries the portion it comes from at the point of use, core.WhoAmI beside behavior.AnalogData.
glopesdev
force-pushed
the
feat-dep-injection-on-device
branch
from
August 12, 2026 18:52
9c59998 to
e405d7b
Compare
glopesdev
self-requested a review
August 12, 2026 19:16
glopesdev
approved these changes
Aug 12, 2026
The marker sat in harp/device/, which the split turned into a shared namespace directory, so a device package installing beside it would have collided on the same file. Each portion now carries its own.
The error message says which module expected the identity, since now identity moved from a class attribute onto the injected module. Tests were added to cover the two paths: a WHO_AM_I of 0 skipping the read entirely, and the module property round-tripping.
open_serial_device tries the type[D] overload first. A class carrying REGISTER_MAP and WHO_AM_I satisfies the module protocol structurally, so the module overload would otherwise win and return Device[type[Subclass]] rather than the subclass itself.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Devicetakes the device module at construction rather than through inheritance, so identity validation is driven by the module and no subclass is needed. This is the same shapeDatasetReaderalready uses.Device is generic on its module
The module type is inferred from the
device_moduleparameter, anddevice.modulereturns it, so a type checker can see what was injected. A newmoduleproperty exposes it for introspection and for passing downstream without keeping a second reference.Identity validation
WHO_AM_Iin the injected module drives the check on open. It is skipped when no module is passed, and when the module declares0, which is how an unregistered device is marked.__whoami__is removed rather than deprecated. Every downstream device package is regenerated on publication, so there is nothing in the wild to catch.open_serial_device takes a module, a subclass, or nothing
The runtime dispatch is a single
isinstance(device_or_module, type)check, since modules are instances and device subclasses are types.What the module does not do
It is consulted only for the identity check.
_registersis not pre-populated from it and remains built lazily bysubscribe, so an event is parsed only for a register that has been subscribed. The module register map is not the concern of the device.The docstrings and the
harp-deviceREADME previously said otherwise, and are corrected here.read,writeandsubscribeare unchanged, and register classes still come from the module rather than from the device instance. SubclassingDevicestill works for adding methods; the module is passed at construction.Typing markers
py.typedmoved fromharp/device/into each ofcore,clientandschema. The namespace split turnedharp/device/into a shared directory, so leaving the marker there hadharp-deviceclaiming typing for the wholeharp.devicenamespace, and a futureharp.device.behaviordistribution installing beside it would have collided on that filename. Every other package in the repository keeps its marker inside a real package.Examples
Documentation examples now import modules rather than names, following the convention that a register carries the portion it comes from at the point of use:
This also removes an error the old examples carried. A device module names only the registers its own schema declares, so
behavior.WhoAmIraisedAttributeError.Testing
Full suite passes at 359 tests, with ruff, ruff format, pyright, codespell and
mkdocs build --strictclean.New
assert_typefixtures intests/conformance.pycover all three construction forms and check thatdevice.moduleresolves correctly in each.