From 66e5c42ce152c132f98e49d947a7baed26204084 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 22:11:41 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Use=20short-circuiting=20fo?= =?UTF-8?q?r=20Qobuz=20secret=20testing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: davidjuarezdev <230496599+davidjuarezdev@users.noreply.github.com> --- .jules/bolt.md | 3 +++ streamrip/client/qobuz.py | 18 +++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index babd2024..5841ddd5 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -5,3 +5,6 @@ ## 2026-03-20 - Non-blocking I/O in Deezer client **Learning:** `streamrip/client/deezer.py` uses the synchronous `deezer-python` library. Direct calls like `client.gw.get_track()` and `client.get_track_url()` block the entire `asyncio` event loop. While the metadata fetching methods (`get_track`, `get_album`, etc.) correctly wrapped these calls in `await asyncio.to_thread(...)`, `get_downloadable` missed this, causing heavy blocking during concurrent downloads. **Action:** Ensure all synchronous third-party API calls in async methods are wrapped with `await asyncio.to_thread(...)`. +## 2025-03-20 - Non-blocking concurrent requests with asyncio.as_completed +**Learning:** In `streamrip/client/qobuz.py`, when testing a list of potential app secrets, `asyncio.gather` was used, which waits for *all* test requests to complete before returning the first valid secret. By migrating to `asyncio.as_completed`, the application can short-circuit the network I/O validation process and cancel the remaining pending requests as soon as the first valid secret is found. This saves time and avoids unnecessary network calls. +**Action:** Prefer `asyncio.as_completed` over `asyncio.gather` for concurrent requests when only the first successful response is needed, ensuring to cancel any pending tasks to free up resources. diff --git a/streamrip/client/qobuz.py b/streamrip/client/qobuz.py index 734e2b82..5fb5b4d1 100644 --- a/streamrip/client/qobuz.py +++ b/streamrip/client/qobuz.py @@ -409,14 +409,18 @@ async def _test_secret(self, secret: str) -> Optional[str]: return None async def _get_valid_secret(self, secrets: list[str]) -> str: - results = await asyncio.gather( - *[self._test_secret(secret) for secret in secrets], - ) - working_secrets = [r for r in results if r is not None] - if len(working_secrets) == 0: - raise InvalidAppSecretError(secrets) + tasks = [asyncio.create_task(self._test_secret(secret)) for secret in secrets] + try: + for future in asyncio.as_completed(tasks): + result = await future + if result is not None: + return result - return working_secrets[0] + raise InvalidAppSecretError(secrets) + finally: + for task in tasks: + if not task.done(): + task.cancel() async def _request_file_url( self,