Repository navigation
Type the gateway's zigpy device handling against BaseDevice instead of blanket mypy suppressions #931
Description
Follow-up from the review thread on #914 (zigpy 2.2.0 → 2.3.0).
Since zigpy 2.3.0 (zigpy/zigpy#1874 split BaseDevice into ZigbeeDevice and GreenPowerDevice), ControllerApplication.devices and get_device() return BaseDevice, and the device resolver type is Callable[[BaseDevice], BaseDevice]. The ZHA gateway still annotates these as ZigbeeDevice. mypy only stays green because zha.application.gateway has blanket arg-type and return-value suppressions in pyproject.toml.
Running mypy without the per-module overrides (the run tools.regenerate_mypy_ignores does internally) on dev at 0ea5e1a shows three errors from this:
gateway.py:252:device_resolver=DEVICE_REGISTRY.resolvepasses aCallable[[ZigbeeDevice], ZigbeeDevice]whereCallable[[BaseDevice], BaseDevice] | Noneis expected.gateway.py:302:_find_coordinator_devicereturns theBaseDevicefromget_device()as aZigbeeDevice.gateway.py:309:load_devicespasses aBaseDevicefromapplication_controller.devicestoget_or_create_device, which takes aZigbeeDevice.
The same two codes also cover four unrelated gateway errors (lines 217, 285, 409 and 423), so the suppressions can't just be dropped. The count comments are stale too: arg-type is 5 (comment says 3) and return-value is 2 (comment says 1).
There is no runtime impact today. zigpy 2.3.0 defines GreenPowerDevice but never instantiates it: commissioning isn't implemented, and GP frames from unknown devices are logged and dropped. Every device the gateway sees is still a ZigbeeDevice.
This is an issue rather than a PR because the right fix depends on how ZHA ends up representing GP devices, and that work is in flight:
- Base ZGP device support #868 (draft, base ZGP device support) changes
DEVICE_REGISTRY.resolveandGateway.get_or_create_deviceto take aBaseDevice. At 60919c2, with the overrides removed, that clears the resolver andload_deviceserrors. The coordinator one stays (line 308 there), and four new ones show up that the gateway override hides: line 697 (get_devicereturns aBaseDevice | Nonefrom_devicesasDevice | None), lines 775 and 782 inasync_device_initialized, where theBaseDevicereturned byget_or_create_devicegoes into_async_device_rejoined/_async_device_joined, and line 266, where thatBaseDeviceis assigned tocoordinator_zha_device: Device | None(anassignmenterror)._async_device_rejoinedand_async_device_joinedtake a ZHADeviceand callasync_configure(), which only the Zigbee device class has. - zgp: add the Green Power manager, device, and proxy runtime zigpy#1848 (open, GP manager runtime) keeps commissioned GPDs in its own
green_powerregistry with its own events, not inapp.devicesor thedevice_initializedlistener event. So it isn't settled yet which gateway paths a GPD will reach.
Adding isinstance(..., ZigbeeDevice) skip guards on dev now would contradict #868, which creates ZHA devices for GPDs instead of skipping them.
Tasks:
-
_find_coordinator_device: narrow withassert isinstance(zigpy_coordinator, zigpy.device.ZigbeeDevice). The coordinator is always a Zigbee device (zigpy'sget_device(nwk=...)only matchesZigbeeDevices),zha/application/platforms/virtual.pyalready asserts the same (line 211), and this doesn't depend on the GP design. - Once the GP device model is settled (Base ZGP device support #868 and the zigpy side): make the gateway paths that receive zigpy devices (the resolver,
load_devices, thedevice_*listener callbacks) either acceptBaseDeviceor narrow explicitly. A GPD that reaches a Zigbee-only path (configure, re-interview, groups) should then be a mypy error, not a suppressed one. - Regenerate the overrides with
python -m tools.regenerate_mypy_ignoresafterwards. On dev today the regen also updateszha.application.platforms.sensor(arg-type12 → 11,attr-defined5 → 2,operator4 → 2), which moves it and the gateway entry in the count-sorted list. - Optional: fix the four unrelated errors as well, so
arg-typeandreturn-valuecan be removed from the gateway override entirely.
How this was checked
# dev at 0ea5e1ad, zigpy 2.3.0 (uv.lock), worktree venv
.venv/bin/python -m mypy zha/ # Success: no issues found in 63 source files
# unsuppressed run via tools.regenerate_mypy_ignores.run_mypy(), gateway arg-type/return-value only:
gateway.py:217 [return-value] tuple[type[ControllerApplication], ...] vs tuple[ControllerApplication, ...]
gateway.py:252 [arg-type] device_resolver Callable[[ZigbeeDevice], ZigbeeDevice] vs Callable[[BaseDevice], BaseDevice] | None
gateway.py:285 [arg-type] request_priority: PacketPriority | None vs int
gateway.py:302 [return-value] BaseDevice vs ZigbeeDevice
gateway.py:309 [arg-type] get_or_create_device: BaseDevice vs ZigbeeDevice
gateway.py:409 [arg-type] DeviceJoinedDeviceInfo ieee: EUI64 vs str
gateway.py:423 [arg-type] RawDeviceInitializedDeviceInfo ieee: EUI64 vs str
# same run on #868 at 60919c2 (contains #914, zigpy 2.3.0): 252 and 309 gone; 308, 697, 775, 782 present, plus 266 [assignment]
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Size
Area
Activity