Skip to content

fix(typing): check mypy --strict against the pinned OWNd 2.0.0b11 types - #765

Merged
GreenGrassBlueOcean merged 1 commit into
v2-phase1-architecturefrom
fix/strict-typing-ownd-b11
Oct 21, 2026
Merged

GreenGrassBlueOcean merged 1 commit into
v2-phase1-architecturefrom
fix/strict-typing-ownd-b11

Conversation

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

Follow-up to OWNd 2.0.0b11, which manifest.json on v2-phase1-architecture pins. Found while verifying #764.


1. What is the problem?

  1. The strict-typing ratchet never sees OWNd (.github/workflows/strict-typing.yml, pyproject.toml):
    • strict-typing.yml installs Home Assistant and mypy but not OWNd, and pyproject.toml lists OWNd.* under ignore_missing_imports = true. In CI every OWNd name is therefore Any, and the ratchet reports 0 error(s); baseline 0 without ever checking the MyHOME ↔ OWNd boundary.

    • OWNd ships py.typed. With the pinned OWNd==2.0.0b11 wheel visible, mypy --strict reports 14 errors in 7 modules:

      Module Errors Cause
      gateway_events.py 5 stale type: ignore[misc] on the pre-b11 OWNScenarioPlusEvent stub; push_button (int | None) and scenario (str | None) assigned to narrower locals
      services.py 2 OWNScenarioPlusCommand = None fallback assigns None to a class
      gateway_sessions.py 2 gateway properties return handler.gateway, typed Any on GatewayIdentityMixin
      cover.py 2 message.is_closed read on OWNMessage, which has no such attribute
      where_grammar.py 1 pre-b11 fallback is_apl_address / area_of_where / normalize_where signatures differ from OWNd's
      gateway_resync.py 1 OWNLightingEvent.where (str | None) passed to _normalize_ptp_where(str | int)
      cover_scope.py 1 handle_event(OWNAutomationEvent) narrows the MyHOMECover.handle_event(OWNMessage) override
  2. Dead pre-b11 compatibility code:
    • where_grammar.py, gateway_events.py and services.py still carry except ImportError fallbacks for OWNd 2.0.0b10 (local copies of the WHERE grammar, stub OWNScenarioPlusEvent / OWNDoorEntryEvent classes, and a hand-built WHO 25 frame table). With OWNd==2.0.0b11 pinned they can never run, and three of the errors above come from them.

2. How is it solved?

  1. Type-check against the pinned OWNd (strict-typing.yml, pyproject.toml):
    • strict-typing.yml reads the exact OWNd== pin from manifest.json and installs it next to Home Assistant and mypy, so the ratchet checks the types users actually install. It uses the PyPI release, not a git branch.
    • OWNd.* is removed from the ignore_missing_imports override (serial_asyncio stays), so a missing OWNd is an error instead of a silent Any.
    • The [project.optional-dependencies] floor moves from OWNd>=2.0.0b10 to OWNd>=2.0.0b11.
  2. Drop the pre-b11 fallbacks:
    • where_grammar.py: imports area_of_where, is_apl_address and normalize_where from OWNd.message directly. The fallback copies and the setattr(_ownd_msg, …) back-patching are gone.
    • gateway_events.py: imports OWNDoorEntryEvent and OWNScenarioPlusEvent directly.
    • services.py: send_scenario_plus_command always uses OWNScenarioPlusCommand. The OWNCommand.parse frame-table fallback is gone. b11's builder produces the same *25*11#0*W##, *25*12*W##, *25*13#0#5*W##, *25*14#0#5*W## and *25*15*W## frames, which test_send_scenario_plus_command_service asserts.
  3. Fix the remaining boundary types:
    • identity.py: GatewayIdentityMixin.gateway is typed OWNGateway instead of Any.
    • cover.py: reads is_closed once with getattr and stores bool(is_closed).
    • cover_scope.py: handle_event(message: OWNMessage), matching MyHOMECover.
    • gateway_events.py: pb_val: int | None. scene_val: int | str | None converts only a non-None scenario to int; the behaviour is unchanged, since int(None) previously hit the TypeError branch and kept None.
    • gateway_resync.py: _normalize_ptp_where(raw_where: str | int | None, …) states what it already receives.
  4. Tests and docs:
    • tests/test_services.py: the fallback parse-failure case becomes a typed-builder case, where OWNScenarioPlusCommand.turn_on returns a command with is_valid=False and the service raises scenario_plus_command_invalid.
    • docs/quality_scale_plan.md: the "OWNd does not ship py.typed" note now says OWNd is checked at its pin.

Overlap with #757: #757 makes the same pyproject.toml / strict-typing.yml change and fixes 11 of these errors, but it installs OWNd from a git branch and pins OWNd==2.0.0b10. If this PR lands first, #757 can shrink to its websession injection and quality_scale.yaml licence fix.


3. State Diagram

flowchart LR
    M["manifest.json\nOWNd==2.0.0b11"] --> I["strict-typing.yml\npip install OWNd==2.0.0b11"]
    I --> Y["mypy --strict\n(OWNd py.typed, no ignore override)"]
    Y --> R["scripts/typing_ratchet.py\nbaseline 0"]
    R -->|"any OWNd boundary error"| F["job fails"]
    R -->|"0 errors"| P["job passes"]
Loading

4. Where are the traces (real ones)?

No bus behaviour changes, so no new trace applies. The only runtime path touched is send_scenario_plus_command, which now always uses OWNd's builder. Its frames are pinned by tests/test_services.py::test_send_scenario_plus_command_service, and the WHO 25 traces in tests/fixtures/traces/ keep replaying unchanged in the full suite.


5. What does the Firmware Oracle / Encyclopedia say?

Not applicable: this PR changes typing, CI and dead import fallbacks, not protocol behaviour. The WHO 25 frames send_scenario_plus_command emits are byte-identical before and after.


Verification

  • mypy --strict (pyproject.toml config) with the released OWNd==2.0.0b11 wheel: Success: no issues found in 71 source files; scripts/typing_ratchet.py: 0 error(s) in 0 module(s); baseline 0. Before this PR, the same run reported 14 errors in 7 modules.

  • Full suite against the released OWNd==2.0.0b11 wheel with --cov=custom_components/myhome: 4358 passed, 5 snapshots passed, TOTAL 14456 statements, 0 missed, 100.0%

  • ruff check custom_components tests scripts, scripts/verify_ha_standards.py, scripts/sync_documentation.py --check: all pass

  • V2 Architecture: New entities implement handle_event() and do not manually subscribe via async_dispatcher_connect. (No entities are added; MyHOMEScopeCover.handle_event only changes its annotation.)

🤖 Generated with Claude Code

The strict-typing workflow never installed OWNd and pyproject.toml
ignored missing OWNd.* imports, so every OWNd name was Any in CI and
the ratchet reported 0 errors without checking the boundary. With the
pinned OWNd==2.0.0b11 wheel visible, mypy --strict found 14 errors in
7 modules.

- strict-typing.yml installs the exact OWNd== pin from manifest.json,
  and OWNd.* leaves the ignore_missing_imports override.
- Drop the pre-b11 ImportError fallbacks in where_grammar.py,
  gateway_events.py and services.py (the WHO 25 frame-table fallback
  included); b11's builders emit the same frames.
- Type GatewayIdentityMixin.gateway as OWNGateway, read is_closed via
  getattr in cover.py, widen cover_scope.handle_event to OWNMessage,
  and give push_button/scenario/where locals their Optional types.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit 3692dd1 into v2-phase1-architecture Oct 21, 2026
18 checks passed
@GreenGrassBlueOcean
GreenGrassBlueOcean deleted the fix/strict-typing-ownd-b11 branch October 21, 2026 11:26
GreenGrassBlueOcean added a commit that referenced this pull request Oct 21, 2026
…rrect the OWNd licence

OWNd.discovery.find_gateways() and get_gateway() fetch the gateway's
UPnP/SOAP description over HTTP and accept an optional aiohttp session.
The config flow called them without one, so OWNd opened its own
ClientSession per call. Pass async_get_clientsession(self.hass) and
mark inject-websession done in quality_scale.yaml.

quality_scale.yaml also described OWNd as MIT/GPL licensed; it is
Apache-2.0 (OWNd LICENSE and pyproject.toml).

Rebuilt on v2-phase1-architecture: the typing changes this branch
carried before are in #765, and the OWNd pin stays at 2.0.0b11.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GreenGrassBlueOcean added a commit that referenced this pull request Oct 21, 2026
… b11

Since #765 the mypy ratchet checks against the pinned OWNd 2.0.0b11 types,
and expose.py reported 15 errors: gateway.send() takes OWNCommand while
the frame lists were list[OWNMessage], and cmd/confirm took the type of
their first branch (OWNEnergyCommand) before being reassigned to gateway,
lighting and automation commands. Every builder returns an OWNCommand
subclass, so the lists and both variables are now typed OWNCommand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants