Skip to content

Type the gateway's zigpy device handling against BaseDevice instead of blanket mypy suppressions #931

Open

Description

@zigpy-review-bot

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.resolve passes a Callable[[ZigbeeDevice], ZigbeeDevice] where Callable[[BaseDevice], BaseDevice] | None is expected.
  • gateway.py:302: _find_coordinator_device returns the BaseDevice from get_device() as a ZigbeeDevice.
  • gateway.py:309: load_devices passes a BaseDevice from application_controller.devices to get_or_create_device, which takes a ZigbeeDevice.

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.resolve and Gateway.get_or_create_device to take a BaseDevice. At 60919c2, with the overrides removed, that clears the resolver and load_devices errors. The coordinator one stays (line 308 there), and four new ones show up that the gateway override hides: line 697 (get_device returns a BaseDevice | None from _devices as Device | None), lines 775 and 782 in async_device_initialized, where the BaseDevice returned by get_or_create_device goes into _async_device_rejoined / _async_device_joined, and line 266, where that BaseDevice is assigned to coordinator_zha_device: Device | None (an assignment error). _async_device_rejoined and _async_device_joined take a ZHA Device and call async_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_power registry with its own events, not in app.devices or the device_initialized listener 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 with assert isinstance(zigpy_coordinator, zigpy.device.ZigbeeDevice). The coordinator is always a Zigbee device (zigpy's get_device(nwk=...) only matches ZigbeeDevices), zha/application/platforms/virtual.py already 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, the device_* listener callbacks) either accept BaseDevice or 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_ignores afterwards. On dev today the regen also updates zha.application.platforms.sensor (arg-type 12 → 11, attr-defined 5 → 2, operator 4 → 2), which moves it and the gateway entry in the count-sorted list.
  • Optional: fix the four unrelated errors as well, so arg-type and return-value can 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]

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    Priority

    None yet

    Size

    None yet

    Area

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions