Add retries for connection errors

This commit is contained in:
Šarūnas Nejus 2025-12-19 20:36:19 +00:00
parent 8de7efa6c6
commit 3cbc3f0b2b
No known key found for this signature in database
2 changed files with 25 additions and 11 deletions

View file

@ -8,6 +8,8 @@ from http import HTTPStatus
from typing import TYPE_CHECKING, Any, ClassVar, Generic, Protocol, TypeVar
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from beets import __version__
@ -60,7 +62,7 @@ class SingletonMeta(type, Generic[C]):
return SingletonMeta._instances[cls]
class TimeoutSession(requests.Session, metaclass=SingletonMeta):
class TimeoutAndRetrySession(requests.Session, metaclass=SingletonMeta):
"""HTTP session with automatic timeout and status checking.
Extends requests.Session to provide sensible defaults for beets HTTP
@ -72,6 +74,11 @@ class TimeoutSession(requests.Session, metaclass=SingletonMeta):
super().__init__(*args, **kwargs)
self.headers["User-Agent"] = f"beets/{__version__} https://beets.io/"
retry = Retry(connect=2, backoff_factor=1, allowed_methods=None)
adapter = HTTPAdapter(max_retries=retry)
self.mount("https://", adapter)
self.mount("http://", adapter)
def request(self, *args, **kwargs):
"""Execute HTTP request with automatic timeout and status validation.
@ -106,15 +113,21 @@ class RequestHandler:
Feel free to define common methods that are used in multiple plugins.
"""
session_type: ClassVar[type[TimeoutSession]] = TimeoutSession
explicit_http_errors: ClassVar[list[type[BeetsHTTPError]]] = [
HTTPNotFoundError
]
def create_session(self) -> TimeoutAndRetrySession:
"""Create a new HTTP session instance.
Can be overridden by subclasses to provide custom session types.
"""
return TimeoutAndRetrySession()
@cached_property
def session(self) -> Any:
def session(self) -> TimeoutAndRetrySession:
"""Lazily initialize and cache the HTTP session."""
return self.session_type()
return self.create_session()
def status_to_error(
self, code: int

View file

@ -35,7 +35,11 @@ from beets.metadata_plugins import MetadataSourcePlugin
from beets.util.deprecation import deprecate_for_user
from beets.util.id_extractors import extract_release_id
from ._utils.requests import HTTPNotFoundError, RequestHandler, TimeoutSession
from ._utils.requests import (
HTTPNotFoundError,
RequestHandler,
TimeoutAndRetrySession,
)
if TYPE_CHECKING:
from collections.abc import Iterable, Sequence
@ -99,20 +103,17 @@ BROWSE_CHUNKSIZE = 100
BROWSE_MAXTRACKS = 500
class LimiterTimeoutSession(LimiterMixin, TimeoutSession):
class LimiterTimeoutSession(LimiterMixin, TimeoutAndRetrySession):
pass
@dataclass
class MusicBrainzAPI(RequestHandler):
session_type = LimiterTimeoutSession
api_host: str
rate_limit: float
@cached_property
def session(self) -> LimiterTimeoutSession:
return self.session_type(per_second=self.rate_limit)
def create_session(self) -> LimiterTimeoutSession:
return LimiterTimeoutSession(per_second=self.rate_limit)
def get_entity(self, entity: str, **kwargs) -> JSONDict:
return self._group_relations(