Skip to content

⚡ Bolt: Make Deezer get_downloadable calls async#24

Merged
davidjuarezdev merged 2 commits intomainfrom
bolt-deezer-async-11883376257823855673
Mar 21, 2026
Merged

⚡ Bolt: Make Deezer get_downloadable calls async#24
davidjuarezdev merged 2 commits intomainfrom
bolt-deezer-async-11883376257823855673

Conversation

@davidjuarezdev
Copy link
Copy Markdown
Owner

💡 What: Wrapped synchronous deezer-python calls (client.gw.get_track and client.get_track_url) in await asyncio.to_thread(...) within DeezerClient.get_downloadable.
🎯 Why: get_downloadable was executing synchronous network I/O calls directly on the asyncio event loop. This blocks the main thread and severely degrades performance when resolving and downloading multiple tracks concurrently (e.g., from an album or playlist).
📊 Impact: Expected to significantly reduce total download time for Deezer albums/playlists by allowing proper concurrent processing of metadata fetches without blocking the event loop.
🔬 Measurement: Can be verified by downloading a large Deezer playlist and observing a reduction in overall wall-clock time and smoother concurrent progress updates compared to before. Also added a journal entry in .jules/bolt.md recording this learning.


PR created automatically by Jules for task 11883376257823855673 started by @davidjuarezdev

Wraps blocking synchronous calls from the deezer-python API inside asyncio.to_thread in streamrip/client/deezer.py's get_downloadable.

Co-authored-by: davidjuarezdev <230496599+davidjuarezdev@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 20, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 57f07acc-70b5-4443-af73-da7028a93480

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bolt-deezer-async-11883376257823855673

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@davidjuarezdev davidjuarezdev marked this pull request as ready for review March 21, 2026 01:11
Copilot AI review requested due to automatic review settings March 21, 2026 01:11
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves Deezer download resolution concurrency by preventing synchronous deezer-py network calls from blocking the asyncio event loop inside DeezerClient.get_downloadable.

Changes:

  • Wrapped client.gw.get_track and client.get_track_url calls in await asyncio.to_thread(...) within DeezerClient.get_downloadable.
  • Minor formatting alignment (double quotes) in streamrip/db.py and whitespace cleanup in streamrip/client/deezer.py.
  • Added a journal entry documenting the async I/O learning in .jules/bolt.md.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
streamrip/client/deezer.py Moves blocking Deezer API calls off the event loop to enable real concurrency during downloads.
streamrip/db.py Formatting-only tweak to align with the project’s double-quote style.
.jules/bolt.md Adds an internal note capturing the rationale and takeaway for the change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
streamrip/client/deezer.py (1)

151-151: Add explicit timeouts to network calls that can hang indefinitely.

The underlying deezer-py library (v1.3.6) uses the requests library for HTTP calls, which has no default timeout (timeout=None). This means gw.get_track and get_track_url can stall indefinitely if the connection hangs. Wrap these calls with asyncio.wait_for() to prevent hanging.

⏱️ Suggested timeout wrapper
-        track_info = await asyncio.to_thread(self.client.gw.get_track, item_id)
+        track_info = await asyncio.wait_for(
+            asyncio.to_thread(self.client.gw.get_track, item_id),
+            timeout=30,
+        )
...
-            url = await asyncio.to_thread(self.client.get_track_url, token, format_str)
+            url = await asyncio.wait_for(
+                asyncio.to_thread(self.client.get_track_url, token, format_str),
+                timeout=30,
+            )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@streamrip/client/deezer.py` at line 151, Wrap blocking Deezer network calls
in asyncio.wait_for to enforce a bounded timeout: replace uses of
asyncio.to_thread(self.client.gw.get_track, item_id) with awaiting
asyncio.wait_for(asyncio.to_thread(self.client.gw.get_track, item_id),
timeout=YOUR_TIMEOUT) and similarly wrap any call to get_track_url (e.g.,
asyncio.to_thread(self.client.get_track_url, ...)) with asyncio.wait_for(...,
timeout=YOUR_TIMEOUT). Use a configurable constant (e.g., NETWORK_TIMEOUT or
deezer_timeout) instead of a magic number, raise or handle asyncio.TimeoutError
where appropriate, and update the methods in deezer.py that call
self.client.gw.get_track and get_track_url.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@streamrip/client/deezer.py`:
- Line 151: Wrap blocking Deezer network calls in asyncio.wait_for to enforce a
bounded timeout: replace uses of asyncio.to_thread(self.client.gw.get_track,
item_id) with awaiting
asyncio.wait_for(asyncio.to_thread(self.client.gw.get_track, item_id),
timeout=YOUR_TIMEOUT) and similarly wrap any call to get_track_url (e.g.,
asyncio.to_thread(self.client.get_track_url, ...)) with asyncio.wait_for(...,
timeout=YOUR_TIMEOUT). Use a configurable constant (e.g., NETWORK_TIMEOUT or
deezer_timeout) instead of a magic number, raise or handle asyncio.TimeoutError
where appropriate, and update the methods in deezer.py that call
self.client.gw.get_track and get_track_url.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: adce9905-b000-4c31-b696-856389c82fec

📥 Commits

Reviewing files that changed from the base of the PR and between 4b61fd6 and 4660831.

📒 Files selected for processing (3)
  • .jules/bolt.md
  • streamrip/client/deezer.py
  • streamrip/db.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
streamrip/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

streamrip/**/*.py: Use Black-compatible code formatting with double quotes and spaces via ruff format
Lint Python code with ruff using rules: E4, E7, E9, F, I, ASYNC, N, RUF, ERA001
Use async/await for asynchronous operations instead of blocking I/O
Implement Windows compatibility by using WindowsSelectorEventLoopPolicy on Windows and the pick library instead of simple-term-menu

Files:

  • streamrip/db.py
  • streamrip/client/deezer.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: davidjuarezdev/streamrip_RipDL PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T03:25:38.225Z
Learning: Applies to streamrip/**/*.py : Use async/await for asynchronous operations instead of blocking I/O
📚 Learning: 2026-03-18T03:25:38.225Z
Learnt from: CR
Repo: davidjuarezdev/streamrip_RipDL PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T03:25:38.225Z
Learning: Applies to streamrip/**/*.py : Use async/await for asynchronous operations instead of blocking I/O

Applied to files:

  • .jules/bolt.md
  • streamrip/client/deezer.py
🔇 Additional comments (3)
streamrip/db.py (1)

85-85: Formatting-only update is clean and behavior-preserving.

Using "conn" here is consistent and does not alter connection lifecycle behavior.

As per coding guidelines: streamrip/**/*.py: Use Black-compatible code formatting with double quotes and spaces via ruff format.

Also applies to: 171-171

streamrip/client/deezer.py (1)

151-151: Good fix: synchronous Deezer calls are now offloaded from the event loop.

This directly addresses blocking in get_downloadable and matches the intended async behavior.

Based on learnings: Applies to streamrip/**/*.py : Use async/await for asynchronous operations instead of blocking I/O.

Also applies to: 190-190

.jules/bolt.md (1)

5-7: Great journal entry—clear root cause and concrete remediation.

This accurately documents the event-loop blocking issue and the exact to_thread fix points for future maintainers.

@davidjuarezdev davidjuarezdev merged commit 2f39c4e into main Mar 21, 2026
1 check passed
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.

2 participants