Skip to content

fix(deps): make aioesphomeapi an optional [esphome] extra (fixes #38) - #47

Open
chriguschneider wants to merge 1 commit into
jens62:mainfrom
chriguschneider:fix/aioesphomeapi-optional-dep
Open

chriguschneider wants to merge 1 commit into
jens62:mainfrom
chriguschneider:fix/aioesphomeapi-optional-dep

Conversation

@chriguschneider

Copy link
Copy Markdown

Problem

Declaring aioesphomeapi as a hard runtime dependency in pyproject.toml breaks every Noise-encrypted ESPHome device on a Home Assistant instance that has this integration installed (issue #38).

Root cause

  • HA Core pins aioesphomeapi exactly via its esphome / bleak_esphome integrations (e.g. ==45.6.1).
  • This package requires aioesphomeapi without a version constraint and is installed from a git URL. So on ha core rebuild (and on every Core update that recreates the container), pip installs the newest PyPI release over Core's pin.
  • On interpreters without prebuilt wheels (currently cp314 / Python 3.14), that unpinned install is built from source, producing ABI-incompatible Cython artifacts. The Noise frame-helper import then crashes:
    ValueError: aioesphomeapi.connection.APIConnection size changed, may indicate binary incompatibility. Expected 240 from C header, got 232 from PyObject
    ... UnhandledAPIConnectionError: Error while finishing connection: '__reduce_cython__'
    
    Because the failing path is the Noise handshake, all Noise-encrypted ESPHome API devices go unavailable while the devices themselves stay online. A ha core rebuild only recovers it until the next Core update reinstalls the unpinned version.

As noted in #38, pinning alone is insufficient (the git-URL install still rebuilds from source into a second binary of the same version). Not declaring the dependency for the HA path avoids both the version override and the source rebuild.

Fix

aioesphomeapi is not needed inside HA: HA provides it, and in HA the ESP32-Bluetooth-Proxy transport (ESPHomeAPIClient) is not used — HA has its own habluetooth stack. So this moves it to an optional esphome extra:

  • HA manifest install (geberit-aquaclean @ git+..., no extra) → no longer overrides Core's pinned aioesphomeapi.
  • All aioesphomeapi imports in the codebase are already lazy (function-level), so the package imports and runs fine without the extra when the ESPHome-proxy transport is not used.

Standalone bridge is preserved

Standalone / non-HA users who use the ESP32-Bluetooth-Proxy transport still get aioesphomeapioperation_support/install.sh, operation_support/update.sh, and the README now install geberit-aquaclean[esphome].

Changes

  • pyproject.toml: move aioesphomeapi from dependencies to optional-dependencies.esphome (with explanatory comment).
  • operation_support/install.sh, operation_support/update.sh: install with [esphome] extra.
  • README.md: manual install uses [esphome] extra.

Verified

  • pyproject.toml parses; aioesphomeapi absent from base deps, present under optional-dependencies.esphome.
  • Confirmed all aioesphomeapi imports are function-level (grep), so no import-time breakage without the extra.

Independent reproduction environment: HA OS 18.2 / RPi 5 (aarch64) / Core 2026.8.3 / Python 3.14.6, aquaclean 3.1.2 — full trace posted in #38.

…62#38)

Declaring `aioesphomeapi` as a hard runtime dependency breaks every
Noise-encrypted ESPHome device on a Home Assistant instance that installs
this integration.

Root cause: HA Core pins aioesphomeapi (via the `esphome` / `bleak_esphome`
integrations, e.g. ==45.6.1). Because this package requires it without a
version constraint AND is installed from a git URL, pip installs the newest
PyPI release over Core's pin and, on interpreters without prebuilt wheels
(e.g. cp314 / Python 3.14), rebuilds it from source. The resulting
ABI-incompatible Cython artifacts crash the Noise frame-helper import
("APIConnection size changed ... Expected 240 ... got 232" followed by
"'__reduce_cython__'"), so all Noise-encrypted ESPHome devices go
unavailable. A `ha core rebuild` only recovers it until the next Core
update recreates the container and reinstalls the unpinned version.

Fix: aioesphomeapi is not needed inside HA (HA provides it and uses its own
habluetooth stack, not this package's ESP32-Bluetooth-Proxy transport).
Move it to an optional `esphome` extra so the HA manifest install
(`geberit-aquaclean @ git+...`, no extra) no longer overrides Core's pin.
All aioesphomeapi imports are already lazy (function-level), so the package
imports and runs fine without the extra when the ESPHome transport is unused.

Standalone bridge users keep the transport: install/update scripts and the
README now install `geberit-aquaclean[esphome]`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chriguschneider

Copy link
Copy Markdown
Author

Hi @jens62 👋 — friendly nudge on this one.

It's a small change (moves aioesphomeapi into an optional [esphome] extra), but it fully resolves #38: on Home Assistant installs the unpinned dependency overrides Core's aioesphomeapi==45.6.1 pin with the newest PyPI release (45.12.0), and on Python builds without prebuilt wheels (cp314) that forces a source rebuild with ABI-incompatible Cython artifacts — which takes down every Noise-encrypted ESPHome device on the instance.

The PR is CI-green and shows as mergeable, and all aioesphomeapi imports in the code are already lazy, so nothing breaks for standalone users (they keep the transport via geberit-aquaclean[esphome]). Would you be open to reviewing/merging when you have a moment? Happy to adjust anything you'd like. Thanks for the integration! 🙏

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.

1 participant