feat(ble-extra): make Bluetooth support an optional extra (tesla-fleet-api[ble]) - #135
Draft
Bre77 wants to merge 5 commits into
Draft
feat(ble-extra): make Bluetooth support an optional extra (tesla-fleet-api[ble])#135Bre77 wants to merge 5 commits into
Bre77 wants to merge 5 commits into
Conversation
bleak and bleak-retry-connector move to an optional-dependencies "ble" extra so cloud-only consumers (Fleet API, Teslemetry, Tessie, and the read-side ObservationFunnel/router) no longer carry the local Bluetooth stack. cryptography, protobuf, and tesla-protocol stay base dependencies since the cloud signed-command path needs them too, not just Bluetooth. Vehicles.Bluetooth/VehiclesBluetooth.Bluetooth and the TeslaBluetooth/ VehicleBluetooth re-exports now resolve tesla.vehicle.bluetooth lazily instead of importing it at module load, so importing the cloud surface never requires bleak to be installed; missing-extra access raises a clear ImportError pointing at the extra. DEFAULT_KEEPALIVE_INTERVAL moves to const.py so it can stay a real default value on the bleak-free paths. tests/test_ble_optional_extra.py proves this by poisoning bleak/ bleak_retry_connector in sys.modules in a subprocess and asserting the cloud surface still imports and works.
…ard import without bleak
…s lazy bluetooth accessors
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Bre77
marked this pull request as draft
August 26, 2026 05:51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Make the Bluetooth stack an optional extra in python-tesla-fleet-api so cloud-only consumers (Fleet API, Teslemetry, Tessie) stop carrying it.
Problem: pyproject.toml declared bleak, bleak-retry-connector, protobuf, cryptography and tesla-protocol as mandatory, with no optional-dependencies section at all, so every consumer carried the local Bluetooth and protobuf stack whether or not it touched Bluetooth.
Requirements:
What I did:
from tesla_fleet_api.tesla.fleet import TeslaFleetApi), so switched those three re-exports to TYPE_CHECKING + a module-level getattr that imports bleak-backed classes on first attribute access only.import bleakraise ModuleNotFoundError exactly like an actual missing package) and asserts the cloud surface (TeslaFleetApi, Teslemetry, Tessie, Router/VehicleRouter/EnergySiteRouter, ObservationFunnel, Vehicles.createFleet/createSigned) still imports and works, and that createBluetooth()/TeslaBluetooth raise a clear ImportError pointing at the ble extra when bleak is absent.pip install tesla-fleet-api[ble]instructions, and AGENTS.md with a durable-knowledge note about the optional-dependency boundary and lazy-import pattern.Constraint: do not merge the resulting PR - open it and report only.
What Changed
pyproject.toml: movedbleak/bleak-retry-connectorout of basedependenciesinto a new[project.optional-dependencies]blegroup (pip install tesla-fleet-api[ble]);cryptography,protobuf, andtesla-protocolstay base deps since the cloud/signed-command path also needs them.uv.lockregenerated to match.tesla_fleet_api/__init__.py,tesla_fleet_api/tesla/__init__.py, andtesla_fleet_api/tesla/vehicle/__init__.pynow exposeTeslaBluetooth/VehicleBluetoothviaTYPE_CHECKING+ a module-level__getattr__that lazily imports the bleak-backed module on first access (via a newimport_ble_classhelper inutil.pythat raises a friendlyImportErrorpointing at thebleextra whenbleakis missing), instead of importing them eagerly at package-init time.tesla_fleet_api/tesla/vehicle/vehicles.py: dropped its top-levelbleak/VehicleBluetoothimport;Vehicles.BluetoothandVehiclesBluetooth.Bluetoothbecame properties that resolveVehicleBluetoothon demand instead of class-attribute defaults.DEFAULT_KEEPALIVE_INTERVALfromtesla/vehicle/bluetooth.pytoconst.pysocreateBluetooth()can keep it as a real default parameter value without importing the bluetooth module.tests/test_ble_optional_extra.py, which runs subprocess checks withbleak/bleak_retry_connectorpoisoned insys.modulesto prove the cloud surface (TeslaFleetApi,Teslemetry,Tessie,Router/VehicleRouter/EnergySiteRouter,ObservationFunnel,Vehicles.createFleet/createSigned) still imports and works withoutbleak, and thatcreateBluetooth()/TeslaBluetoothraise a clearImportErrornaming thebleextra when it's absent.README.mdanddocs/bluetooth_vehicles.mdwithpip install tesla-fleet-api[ble]install instructions, andAGENTS.mdwith the optional-dependency boundary and lazy-import pattern.Risk Assessment
✅ Low: Both prior-round findings (star-import breakage without bleak, and inconsistent ImportError messaging on the top-level lazy accessors) are now fixed correctly and consistently via a single shared
import_ble_classhelper in util.py, with no new issues introduced by the fix commits; the optional-extra split matches the stated intent (bleak/bleak-retry-connector only, base deps unchanged, ble extra, lockfile/docs updated) and the new tests exercise real subprocess import/instantiation behavior rather than source-text matching.Testing
All targeted automated tests pass and were independently corroborated by manual end-to-end reproduction in a genuinely bleak-free venv (not just the test suite's sys.modules poisoning), confirming the base install drops bleak, the cloud surface works fully without it, backward-compat ImportErrors correctly name the ble extra on every lazy accessor (including star-imports), and the ble-extra path is unaffected when bleak is installed; no issues found.
Evidence: New optional-extra pytest run (7 tests)
Evidence: Manual base-install reproduction (fresh venv, no ble extra, bleak genuinely absent)
Evidence: ble extra installed: TeslaBluetooth/createBluetooth still work as before
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed (2) ✅
tesla_fleet_api/__init__.py:40-__all__intesla_fleet_api/__init__.py,tesla_fleet_api/tesla/__init__.py, andtesla_fleet_api/tesla/vehicle/__init__.pystill listsTeslaBluetooth/VehicleBluetooth, but those names are now only real module attributes at runtime via the module-level__getattr__(they're TYPE_CHECKING-only otherwise). CPython'sfrom module import *resolves every name in__all__viagetattr(module, name), which invokes__getattr__and propagates any exception it raises — it does not skip on failure. Verified by running (in this repo's own uv env, with bleak/bleak_retry_connector poisoned to simulate an uninstalledbleextra):from tesla_fleet_api import *andfrom tesla_fleet_api.tesla import *both raiseModuleNotFoundError: ... bleakand abort the whole import, even though the caller never touches Bluetooth. Before this change, these same wildcard imports worked in a base install becausebleakwas a mandatory dependency — so this is a regression for that access pattern, and it directly undercuts the PR's core goal ('a base install that still imports bleak achieves nothing') for any cloud-only consumer usingimport *. The added test suite only exercises explicit named imports (from tesla_fleet_api import (TeslaFleetApi, ...)), so it doesn't catch this.tesla_fleet_api/tesla/vehicle/vehicles.py:117-Vehicles.Bluetooth/VehiclesBluetooth.Bluetoothchanged from a plain class attribute (Bluetooth: type[VehicleBluetooth[...]] = VehicleBluetooth) to an instance@property. Accessed through an instance (as the two internal call sitesself.Bluetooth(...)do) this works fine, but accessed at the class level — e.g.Vehicles.Bluetoothfor an isinstance check or a pre-instantiation reference, a pattern still supported by the siblingFleet/Signedattributes which remain plain class attrs — now returns the property descriptor object instead of theVehicleBluetoothtype, which would fail loudly (TypeError) rather than with the intended friendly ImportError. No code in this repo does this today, per the PR's own stated check, so this is a narrow residual risk from the one deliberate restructuring the author already called out and justified, not a required fix.🔧 Fix: fix(ble-extra): stop all from breaking wildcard import without bleak
1 warning still open:
tesla_fleet_api/__init__.py:63- The user intent's required backward-compatibility constraint states 'someone already depending on this library and using Bluetooth must be told how to keep working (install the ble extra), not silently broken,' and the author's own 'What I did' claims 'createBluetooth()/TeslaBluetooth raise a clear ImportError pointing at the ble extra when bleak is absent.' That's true forVehicles.Bluetooth/createBluetooth()(vehicles.py's_import_vehicle_bluetooth()explicitly catches ImportError and re-raises with 'Bluetooth support requires the ble extra: install withpip install tesla-fleet-api[ble]'), but it is NOT true for the top-level lazy re-exports.tesla_fleet_api/__init__.py:63(and the equivalent__getattr__intesla_fleet_api/tesla/__init__.py:53andtesla_fleet_api/tesla/vehicle/__init__.py:29) just doesfrom tesla_fleet_api.tesla.bluetooth import TeslaBluetoothwith no try/except, so a caller doingfrom tesla_fleet_api import TeslaBluetooth(ortesla_fleet_api.TeslaBluetooth) without thebleextra gets Python's rawModuleNotFoundError: import of bleak halted; None in sys.modules(verified by running the poisoned-bleak repro directly:uv run python -c "import sys; sys.modules['bleak']=None; sys.modules['bleak_retry_connector']=None; import tesla_fleet_api; tesla_fleet_api.TeslaBluetooth"), never mentioning thebleextra or install instructions. The new regression testtests/test_ble_optional_extra.py::test_tesla_bluetooth_attribute_without_bleak_raises_clear_error(line 101) asserts"ble" in str(err) or "bleak" in str(err)— since "ble" is literally a substring of the package name "bleak", this passes for any bleak-related error regardless of clarity, so it doesn't actually catch this gap. Recommend wrapping the three__getattr__bodies with the same try/except ImportError -> friendly-message pattern already used invehicles.py's_import_vehicle_bluetooth(), and tightening the test assertion to require the actual extra-install text.🔧 Fix: fix(ble-extra): unify friendly ImportError across lazy bluetooth accessors
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
uv sync --extra bleuv run pytest tests/test_ble_optional_extra.py -vManual: fresh venv viauv pip install -e .(no ble extra) — verified bleak absent frompip listManual: cloud surface import + Vehicles.createFleet/createSigned with bleak absentManual:tesla_fleet_api.TeslaBluetoothandVehicles.createBluetooth()raise friendly ble-extra ImportError with bleak absentManual:from tesla_fleet_api import *andfrom tesla_fleet_api.tesla import *succeed with bleak absentuv sync --locked --extra ble (lockfile consistency)Manual: TeslaBluetooth import and Vehicles.createBluetooth() succeed normally with the ble extra installed✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.