Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
cc943c3
Scafolded tidal source plugin:
semohr Apr 10, 2026
b55c860
Added small typing wrapper for api layer. Not enterly needed but it m…
semohr Apr 10, 2026
c0000d9
Added the *_by_id capabilities for TidalPlugin.
semohr Apr 10, 2026
70a3a7a
Renamed Album, Track, Artists to TidalAlbum, TidalTrack, TidalArtist
semohr Apr 10, 2026
4d6fc99
Added a number of unit tests for the parsing.
semohr Apr 10, 2026
b77bc1a
Fixed a few typing issues and a small bug around how responses are ha…
semohr Apr 10, 2026
a85de2d
Implemented batch lookup methods for more efficient retrieval.
semohr Apr 11, 2026
a1f07f7
Refactored slightly and move search into own methods
semohr Apr 11, 2026
1569810
Added candidate method implementations methods: Required a slight
semohr Apr 11, 2026
137662a
Added changelog entry
semohr Apr 11, 2026
dd58cc8
Added documentation.
semohr Apr 11, 2026
996d2ea
First iteration of reviews:
semohr Apr 11, 2026
90e02be
Added tests for candidates and item_candidates.
semohr Apr 11, 2026
a585df5
Added batching for max filter size.
semohr Apr 11, 2026
199d846
Added state verify step in authentication function. Not that is neede…
semohr Apr 11, 2026
ad79dc2
super requests already raises.
semohr Apr 11, 2026
615bf91
Ignore types files in coverage.
semohr Apr 11, 2026
b9949cc
A subset of review comments from @snejus:
semohr Apr 13, 2026
2c24b93
Another subset of review comments from @snejus:
semohr Apr 13, 2026
2afa774
Move auth flow to use requests-oauth.
semohr Apr 14, 2026
2908372
Added a few tests for the RateLimitAdapter
semohr Apr 14, 2026
634b128
Added tests for the id extractor.
semohr Apr 14, 2026
830c14a
Added retry logic to TidalSession and added RequestHandler abstraction
semohr Apr 17, 2026
815dce4
renamed hashmaps to *_by_id from *_lookup
semohr Apr 17, 2026
55da1cb
Use get_json where applicable and simplified iso 8601 parsing.
semohr Apr 17, 2026
7509fca
Moved beets ui import to top.
semohr Apr 17, 2026
0568cc0
Enabled strict typing.
semohr Apr 17, 2026
aa18e2e
Auth flow should open browser if possible.
semohr Apr 17, 2026
eaf1f00
warning instead of raising errors.
semohr Apr 17, 2026
aa17a50
Changelog entry moved to unrelease section
semohr Apr 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

# Specific ownerships:
/beets/metadata_plugins.py @semohr
/beetsplug/tidal/* @semohr

/beetsplug/titlecase.py @henry-oberholtzer

Expand Down
2 changes: 1 addition & 1 deletion beets/util/id_extractors.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
# - https://github.com/snejus/beetcamp. Bandcamp album URLs usually look
# like: https://nameofartist.bandcamp.com/album/nameofalbum
"bandcamp": re.compile(r"(.+)"),
"tidal": re.compile(r"([^/]+)$"),
"tidal": re.compile(r"(?:^|tidal\.com/(?:browse/)?(?:album|track)/)(\d+)"),
}


Expand Down
33 changes: 33 additions & 0 deletions beetsplug/_utils/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import atexit
import threading
import time
from contextlib import contextmanager
from functools import cached_property
from http import HTTPStatus
Expand Down Expand Up @@ -103,6 +104,38 @@ def request(self, *args, **kwargs):
return r


class RateLimitAdapter(HTTPAdapter):
Comment thread
semohr marked this conversation as resolved.
"""HTTPAdapter that enforces minimum interval between requests.

Prevents server overload and 429 errors by sleeping when requests
come too fast. Thread-safe via lock.

Attributes:
rate_limit: Minimum seconds between requests. Default 0.25 (4/sec).

Override `_wait_time()` for custom strategies (token bucket, burst, etc.).
"""

def __init__(self, rate_limit: float = 0.25, **kwargs):
super().__init__(**kwargs)
self.rate_limit = rate_limit
self._last_request_time = 0.0
self._lock = threading.Lock()

def _wait_time(self, elapsed: float) -> float:
"""Return seconds to wait. Override for custom rate limiting."""
return max(0, self.rate_limit - elapsed)

def send(self, request: requests.PreparedRequest, *args, **kwargs):
with self._lock:
elapsed = time.monotonic() - self._last_request_time
wait = self._wait_time(elapsed)
if wait > 0:
time.sleep(wait)
self._last_request_time = time.monotonic()
return super().send(request, *args, **kwargs)


class RequestHandler:
"""Manages HTTP requests with custom error handling and session management.

Expand Down
Loading
Loading