Skip to content

v0.79.0 — capability-driven platform refactor

Choose a tag to compare

@dzerik dzerik released this 28 May 11:45
· 24 commits to main since this release
6a4fb0a

Pure-refactor release. No user-facing behavior change.

What changed

The integration now operates as a brand-agnostic platform: HA entity factories and BLE mixins no longer test client.brand.brand_slug == "X" or import directly from brands.<vendor>. All brand-specific behavior flows through the BrandProfile Protocol contract and per-family MachineCapabilities flags.

Adding a new coffee-machine brand (Krups SubLime, Severin, hypothetical 3rd Eugster-OEM) is now a self-contained operation:

  1. Implement the BrandProfile Protocol
  2. Create per-family modules with EXPORTS dicts
  3. Register in brands/__init__.py

No edits to shared layers (sensor.py, button.py, select.py, number.py, _ble_commands.py, _ble_recipes.py) required.

Refactored

  • BrandProfile contract extended with 6 new methods: temp_recipe_type_register (class attr), temp_recipe_register, fluid_write_scale, mycoffee_layout, mycoffee_register, is_chilled_selector. NivonaProfile already implemented these as @staticmethod; MelittaProfile gets stubs returning None / 1 / False. Shared mixins (_ble_commands, _ble_recipes) and sensor.py no longer from .brands.nivona import ….
  • 3 new MachineCapabilities feature flags: supports_factory_reset, supports_brew_overrides, uses_legacy_total_cups_sensor. Set per-family in _family_*.py and in MelittaProfile. Entity factories use these flags instead of brand_slug == "X". Hardcoded _FACTORY_RESET_FAMILIES = {"600", "700", …} frozenset deleted.
  • Redundant gates dropped: button.py:55 / select.py:146 HE-selector brew entities now key off "HC" not in supported_extensions alone (brand_slug half was redundant); number.py:101 / select.py:130-132 settings-entity gates rely on caps.settings being empty for Melitta.

Added

  • docs/BRAND_PROFILE_SPEC.md — contract specification documenting:
    • Required BrandProfile Protocol surface (identity, crypto, family resolution, parse_status, recipe write-path methods)
    • Required MachineCapabilities fields + feature flag conventions
    • Step-by-step guide for adding a new brand
    • Explicit anti-patterns banned in shared layers (brand_slug == checks, direct brands.<vendor> imports, hardcoded family sets, isinstance checks, mutating frozen capabilities)
    • Forward-looking notes on what's NOT in the contract yet (plugin loader, Protocol versioning, capability-coverage tests)

Test infrastructure

Test fixtures across tests/test_button.py, tests/test_init.py, tests/test_sensor.py switched from client.brand = MagicMock() to real MelittaProfile() / NivonaProfile() instances + capabilities_for(family_key). Real BrandProfile gives capability-driven gates actual values; previous MagicMock-truthy false positives caused setup-time TypeErrors. Tests mutating caps.my_coffee_slots directly now use dataclasses.replace().

Full suite: 992 passed (unchanged from v0.78.1).

Full changelog · PR #28 · Contract spec