Skip to content

Commit 60a4aac

Browse files
dprodgerclaude
andcommitted
backend: add shared HTTP session factory, use it for the song intro
Introduce core/http_client.py with HTTP_USER_AGENT and make_session(), a single home for the outbound User-Agent that's currently copy-pasted into ~20 files. song_intro.py now builds its session via make_session() instead of hardcoding the UA string. A follow-up PR will sweep the remaining call sites onto the factory. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent df343d7 commit 60a4aac

2 files changed

Lines changed: 38 additions & 5 deletions

File tree

backend/core/http_client.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Shared HTTP client configuration.
2+
3+
A single home for the outbound User-Agent and a `requests.Session` factory,
4+
so every crawler/integration identifies us the same way and a version bump
5+
is a one-line change rather than a sweep across ~20 files.
6+
7+
Most external services (MusicBrainz, Wikipedia/MediaWiki, Cover Art Archive,
8+
Wikimedia Commons, etc.) expect — and in some cases require — a descriptive
9+
User-Agent. Use `make_session()` to get a session that already carries it.
10+
11+
Note: this only handles identification/headers, not per-service rate
12+
limiting. Clients that must throttle (e.g. MusicBrainz) keep their own
13+
rate-limit logic on top of the session.
14+
"""
15+
16+
import requests
17+
18+
# Outbound identity sent on every API/crawl request. Bump the version here.
19+
HTTP_USER_AGENT = "ApproachNote/1.0 (+support@approachnote.com)"
20+
21+
22+
def make_session(accept_json: bool = True) -> requests.Session:
23+
"""Return a requests.Session preconfigured with our User-Agent.
24+
25+
Args:
26+
accept_json: Also set ``Accept: application/json`` (the common case
27+
for the JSON APIs we call). Pass False for HTML/binary fetches.
28+
"""
29+
session = requests.Session()
30+
headers = {'User-Agent': HTTP_USER_AGENT}
31+
if accept_json:
32+
headers['Accept'] = 'application/json'
33+
session.headers.update(headers)
34+
return session

backend/integrations/wikipedia/song_intro.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121

2222
import requests
2323

24+
from core.http_client import make_session
25+
2426
logger = logging.getLogger(__name__)
2527

26-
# MediaWiki asks API clients to send a descriptive User-Agent.
27-
USER_AGENT = "ApproachNote/1.0 (+support@approachnote.com)"
2828
DEFAULT_SENTENCES = 4
2929
REQUEST_TIMEOUT = 15
3030

@@ -58,7 +58,7 @@ def fetch_wikipedia_intro(page_title: str, api_url: str,
5858
request fails. Raises nothing for HTTP-level non-200s (logs + returns
5959
None); network exceptions propagate to the caller.
6060
"""
61-
sess = session or requests.Session()
61+
sess = session or make_session()
6262
params = {
6363
'action': 'query',
6464
'format': 'json',
@@ -69,8 +69,7 @@ def fetch_wikipedia_intro(page_title: str, api_url: str,
6969
'explaintext': 1,
7070
'exsentences': sentences,
7171
}
72-
headers = {'User-Agent': USER_AGENT, 'Accept': 'application/json'}
73-
resp = sess.get(api_url, params=params, headers=headers, timeout=REQUEST_TIMEOUT)
72+
resp = sess.get(api_url, params=params, timeout=REQUEST_TIMEOUT)
7473
if resp.status_code != 200:
7574
logger.warning("Wikipedia returned status %s for %s", resp.status_code, page_title)
7675
return None

0 commit comments

Comments
 (0)