Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
36 changes: 36 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,41 @@ def request(self, *args, **kwargs):
return r


class RateLimitAdapter(HTTPAdapter):
"""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,
):
super().__init__()
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