mirror of
https://github.com/beetbox/beets.git
synced 2025-12-30 04:22:40 +01:00
Add retries for connection errors
This commit is contained in:
parent
8de7efa6c6
commit
3cbc3f0b2b
2 changed files with 25 additions and 11 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue