Skip to content

fix(python-sdk): pass CommandFactory (not invoked coroutine) to observables#918

Open
akeildev wants to merge 1 commit into
gopro:mainfrom
akeildev:fix/observable-command-factory
Open

fix(python-sdk): pass CommandFactory (not invoked coroutine) to observables#918
akeildev wants to merge 1 commit into
gopro:mainfrom
akeildev:fix/observable-command-factory

Conversation

@akeildev

Copy link
Copy Markdown

Description

GoproObserverDistinctInitial.start() invokes the registration command as a factory:

# domain/gopro_observable.py:24
CommandFactory = Callable[[], Coroutine[Any, Any, GoProResp[Any]]]
# domain/gopro_observable.py:89
initial_response = await self._register_command_factory()

Several call sites pass an already-invoked coroutine instead of a factory, so start() ends up calling a coroutine object:

TypeError: 'coroutine' object is not callable
  File ".../features/access_point_feature.py", line 88, in connect
    if is_successful(response := await self.scan_wifi_networks()):
  File ".../features/access_point_feature.py", line 59, in scan_wifi_networks
    async with GoproObserverDistinctInitial[...](
  File ".../domain/gopro_observable.py", line 64, in __aenter__
    await self.start()
  File ".../domain/gopro_observable.py", line 89, in start
    initial_response = await self._register_command_factory()
TypeError: 'coroutine' object is not callable

This makes AccessPointFeature.connect() fail deterministically, which in turn breaks COHN provisioning (it calls access_point.connect()), and also affects LiveStreamController's status observable.

Affected call sites (passing coro() where a Callable[[], coro] is expected)

  • features/access_point_feature.py:61register_command=self._gopro.ble_command.scan_wifi_networks()
  • features/access_point_feature.py:104register_command=command (where command is an invoked coroutine)
  • features/streaming/livestream.py:82,85register_command / unregister_command

The correct pattern (a lambda: factory) is already used elsewhere, e.g. features/cohn_feature.py:69 and api/builders.py.

Fix

Wrap these in factories:

  • scan_wifi_networks()lambda: ...scan_wifi_networks()
  • the wifi-connect command → functools.partial(...)
  • livestream register/unregister → lambda: ...

No behavior change beyond deferring coroutine creation to call time, as the type contract intends.

Testing

Verified against a HERO12 Black. Before: gopro.access_point.connect(ssid, pw) raised 'coroutine' object is not callable. After: it completes the WiFi scan and returns a normal Result (e.g. a clean <Failure: Could not find SSID: ...> for a non-existent SSID, and provisioning proceeds for a real one). py_compile passes on both files.

Related

May explain user-reported COHN provisioning / scan_wifi_networks failures discussed in #744.

Checklist

  • References an issue / describes the bug above
  • No new files requiring copyright headers
  • Pre-merge checks (will run on PR)

🤖 Generated with Claude Code
https://claude.ai/code/session_017S8amPPZwqFt7hqdNk3yJh

…vables

`GoproObserverDistinctInitial.start()` calls `self._register_command_factory()`,
and the type is `CommandFactory = Callable[[], Coroutine[...]]` — i.e. a factory
that *returns* a coroutine. Several call sites instead passed an already-invoked
coroutine, so `start()` tried to call a coroutine object:

    TypeError: 'coroutine' object is not callable

This breaks `AccessPointFeature.connect()` (and therefore COHN provisioning,
which depends on it) deterministically, as well as `LiveStreamController`'s
status observable.

Fix the call sites to pass factories, matching the correct pattern already used
elsewhere (e.g. cohn_feature.py, api/builders.py):
- access_point_feature.py: wrap `scan_wifi_networks()` in a lambda; build the
  wifi-connect command with functools.partial.
- livestream.py: wrap register/unregister status commands in lambdas.

Tested against a HERO12 Black: `access_point.connect()` now completes the wifi
scan and returns a normal Result instead of raising.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017S8amPPZwqFt7hqdNk3yJh
@akeildev akeildev requested a review from tcamise-gpsw as a code owner June 26, 2026 23:45
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