Centralize requests setup with requests.Session

Improve requests performance with requests.Session which uses connection
pooling for repeated requests to the same host.

Additionally, this centralizes request configuration, making sure that
we use the same timeout and provide beets user agent for all requests.
This commit is contained in:
Šarūnas Nejus 2024-09-04 04:15:46 +01:00
parent c40db1034a
commit 06eac79c0d
No known key found for this signature in database
GPG key ID: DD28F6704DBE3435
2 changed files with 28 additions and 44 deletions

View file

@ -16,6 +16,7 @@
from __future__ import annotations from __future__ import annotations
import atexit
import errno import errno
import itertools import itertools
import json import json
@ -24,13 +25,12 @@ import os.path
import re import re
import struct import struct
import unicodedata import unicodedata
import warnings
from contextlib import suppress from contextlib import suppress
from dataclasses import dataclass from dataclasses import dataclass
from functools import cached_property, partial, total_ordering from functools import cached_property, partial, total_ordering
from http import HTTPStatus from http import HTTPStatus
from typing import TYPE_CHECKING, ClassVar, Iterable, Iterator from typing import TYPE_CHECKING, ClassVar, Iterable, Iterator
from urllib.parse import quote, urlencode, urlparse from urllib.parse import quote, urlparse
import requests import requests
from typing_extensions import TypedDict from typing_extensions import TypedDict
@ -106,6 +106,22 @@ class NotFoundError(requests.exceptions.HTTPError):
pass pass
class TimeoutSession(requests.Session):
def request(self, *args, **kwargs):
kwargs.setdefault("timeout", 10)
return super().request(*args, **kwargs)
r_session = TimeoutSession()
r_session.headers.update({"User-Agent": USER_AGENT})
@atexit.register
def close_session():
"""Close the requests session on shut down."""
r_session.close()
# Utilities. # Utilities.
@ -246,21 +262,7 @@ class Backend:
is unreachable. is unreachable.
""" """
try: try:
# Disable the InsecureRequestWarning that comes from using r = r_session.get(url)
# `verify=false`.
# https://github.com/kennethreitz/requests/issues/2214
# We're not overly worried about the NSA MITMing our lyrics scraper
with warnings.catch_warnings():
warnings.simplefilter("ignore")
r = requests.get(
url,
verify=False,
headers={
"User-Agent": USER_AGENT,
},
timeout=10,
**kwargs,
)
except requests.RequestException as exc: except requests.RequestException as exc:
self._log.debug("lyrics request failed: {0}", exc) self._log.debug("lyrics request failed: {0}", exc)
return return
@ -368,9 +370,7 @@ class LRCLib(Backend):
def fetch_json(self, *args, **kwargs): def fetch_json(self, *args, **kwargs):
"""Wrap the request method to raise an exception on HTTP errors.""" """Wrap the request method to raise an exception on HTTP errors."""
kwargs.setdefault("timeout", 10) r = r_session.get(*args, **kwargs)
kwargs.setdefault("headers", {"User-Agent": USER_AGENT})
r = requests.get(*args, **kwargs)
if r.status_code == HTTPStatus.NOT_FOUND: if r.status_code == HTTPStatus.NOT_FOUND:
raise NotFoundError("HTTP Error: Not Found", response=r) raise NotFoundError("HTTP Error: Not Found", response=r)
r.raise_for_status() r.raise_for_status()
@ -535,10 +535,7 @@ class Genius(SearchBackend):
def __init__(self, config, log): def __init__(self, config, log):
super().__init__(config, log) super().__init__(config, log)
self.api_key = config["genius_api_key"].as_str() self.api_key = config["genius_api_key"].as_str()
self.headers = { self.headers = {"Authorization": f"Bearer {self.api_key}"}
"Authorization": "Bearer %s" % self.api_key,
"User-Agent": USER_AGENT,
}
def fetch(self, artist: str, title: str, *_) -> str | None: def fetch(self, artist: str, title: str, *_) -> str | None:
"""Fetch lyrics from genius.com """Fetch lyrics from genius.com
@ -573,18 +570,13 @@ class Genius(SearchBackend):
search_url = self.base_url + "/search" search_url = self.base_url + "/search"
data = {"q": title + " " + artist.lower()} data = {"q": title + " " + artist.lower()}
try: try:
response = requests.get( r = r_session.get(search_url, params=data, headers=self.headers)
search_url,
params=data,
headers=self.headers,
timeout=10,
)
except requests.RequestException as exc: except requests.RequestException as exc:
self._log.debug("Genius API request failed: {0}", exc) self._log.debug("Genius API request failed: {0}", exc)
return None return None
try: try:
return response.json() return r.json()
except ValueError: except ValueError:
return None return None
@ -979,13 +971,7 @@ class LyricsPlugin(plugins.BeetsPlugin):
} }
oauth_url = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13" oauth_url = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13"
oauth_token = json.loads( oauth_token = r_session.post(oauth_url, params=params).json()
requests.post(
oauth_url,
data=urlencode(params),
timeout=10,
).content
)
if "access_token" in oauth_token: if "access_token" in oauth_token:
return "Bearer " + oauth_token["access_token"] return "Bearer " + oauth_token["access_token"]
else: else:
@ -1202,10 +1188,8 @@ class LyricsPlugin(plugins.BeetsPlugin):
"https://api.microsofttranslator.com/v2/Http.svc/" "https://api.microsofttranslator.com/v2/Http.svc/"
"Translate?text=%s&to=%s" % ("|".join(text_lines), to_lang) "Translate?text=%s&to=%s" % ("|".join(text_lines), to_lang)
) )
r = requests.get( r = r_session.get(
url, url, headers={"Authorization": self.bing_auth_token}
headers={"Authorization ": self.bing_auth_token},
timeout=10,
) )
if r.status_code != 200: if r.status_code != 200:
self._log.debug( self._log.debug(

View file

@ -21,8 +21,8 @@ omit = beets/test/*
precision = 2 precision = 2
skip_empty = true skip_empty = true
show_missing = true show_missing = true
exclude_lines = exclude_also =
pragma: no cover @atexit.register
if TYPE_CHECKING if TYPE_CHECKING
if typing.TYPE_CHECKING if typing.TYPE_CHECKING
raise AssertionError raise AssertionError