Skip to content

Commit 0aa01c1

Browse files
committed
use dataclass
1 parent b5e59d5 commit 0aa01c1

File tree

4 files changed

+40
-15
lines changed

4 files changed

+40
-15
lines changed

openlibrary/core/fulltext.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from openlibrary.core.lending import get_availability
99
from openlibrary.plugins.openlibrary.home import format_book_data
1010
from openlibrary.utils import async_utils
11-
from openlibrary.utils.async_utils import async_bridge, x_forwarded_for
11+
from openlibrary.utils.async_utils import async_bridge, req_context
1212

1313
logger = logging.getLogger("openlibrary.inside")
1414

@@ -24,7 +24,7 @@ async def fulltext_search_api(params):
2424
search_endpoint = config.plugin_inside['search_endpoint']
2525
search_select = search_endpoint + '?' + urlencode(params, 'utf-8')
2626
headers = {
27-
"x-preferred-client-id": x_forwarded_for.get(),
27+
"x-preferred-client-id": req_context.get().x_forwarded_for or "ol-internal",
2828
"x-application-id": "openlibrary",
2929
}
3030
if config_fts_context is not None:

openlibrary/core/lending.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from openlibrary.core import cache, stats
2222
from openlibrary.plugins.upstream.utils import urlencode
2323
from openlibrary.utils import dateutil, uniq
24-
from openlibrary.utils.async_utils import user_agent, x_forwarded_for
24+
from openlibrary.utils.async_utils import req_context
2525

2626
from . import helpers as h
2727
from . import ia
@@ -298,7 +298,7 @@ def get_available(
298298
# Internet Archive Elastic Search (which powers some of our
299299
# carousel queries) needs Open Library to forward user IPs so
300300
# we can attribute requests to end-users
301-
client_ip = x_forwarded_for.get()
301+
client_ip = req_context.get().x_forwarded_for or "ol-internal"
302302
headers = {
303303
"x-client-id": client_ip,
304304
"x-preferred-client-id": client_ip,
@@ -440,8 +440,8 @@ def key_func(_id: str) -> str:
440440

441441
try:
442442
headers = {
443-
"x-preferred-client-id": x_forwarded_for.get(),
444-
"x-preferred-client-useragent": user_agent.get(),
443+
"x-preferred-client-id": req_context.get().x_forwarded_for or "ol-internal",
444+
"x-preferred-client-useragent": req_context.get().user_agent or "",
445445
"x-application-id": "openlibrary",
446446
"user-agent": "Open Library Site",
447447
}

openlibrary/tests/core/test_lending.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from unittest.mock import Mock, patch
22

33
from openlibrary.core import lending
4-
from openlibrary.utils.async_utils import user_agent, x_forwarded_for
4+
from openlibrary.utils.async_utils import RequestContextVars, req_context
55

66

77
class TestAddAvailability:
@@ -44,8 +44,12 @@ def mock_get_availability(id_type, ocaids):
4444
class TestGetAvailability:
4545
def test_cache(self):
4646
# Set needed context variables
47-
x_forwarded_for.set("ol-internal")
48-
user_agent.set("test-user-agent")
47+
req_context.set(
48+
RequestContextVars(
49+
x_forwarded_for="ol-internal",
50+
user_agent="test-user-agent",
51+
)
52+
)
4953

5054
with patch("openlibrary.core.ia.session.get") as mock_get:
5155
mock_get.return_value = Mock()

openlibrary/utils/async_utils.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import threading
33
from collections.abc import Callable, Coroutine
44
from contextvars import ContextVar
5+
from dataclasses import dataclass
56
from typing import Any, ParamSpec, TypeVar
67

78
import web
@@ -48,8 +49,20 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
4849
# These are scoped to a specific request
4950
# Defining them here for now to keep things close together
5051

51-
x_forwarded_for: ContextVar[str] = ContextVar("x_forwarded_for")
52-
user_agent: ContextVar[str] = ContextVar("user_agent")
52+
53+
@dataclass(frozen=True)
54+
class RequestContextVars:
55+
"""
56+
These are scoped to a specific request and should be derived directly from the request.
57+
Use sparingly.
58+
"""
59+
60+
x_forwarded_for: str | None
61+
user_agent: str | None
62+
63+
64+
req_context: ContextVar[RequestContextVars] = ContextVar("req_context")
65+
5366
# TODO: Create an async and stateless version of site so we don't have to do this
5467
site: ContextVar[Site] = ContextVar("site")
5568

@@ -62,8 +75,12 @@ def set_context_for_sync_request():
6275
That version should be usable without instantiating a site
6376
"""
6477
site.set(create_site())
65-
user_agent.set(web.ctx.env.get("HTTP_USER_AGENT", ""))
66-
x_forwarded_for.set(web.ctx.env.get("HTTP_X_FORWARDED_FOR", "ol-internal"))
78+
req_context.set(
79+
RequestContextVars(
80+
x_forwarded_for=web.ctx.env.get("HTTP_X_FORWARDED_FOR"),
81+
user_agent=web.ctx.env.get("HTTP_USER_AGENT"),
82+
)
83+
)
6784

6885

6986
def set_context_for_async_request(request: Request):
@@ -76,5 +93,9 @@ def set_context_for_async_request(request: Request):
7693
This is preferable for testable and maintainable code.
7794
"""
7895
site.set(create_site())
79-
user_agent.set(request.headers.get("User-Agent", ""))
80-
x_forwarded_for.set(request.headers.get("X-Forwarded-For", "ol-internal"))
96+
req_context.set(
97+
RequestContextVars(
98+
x_forwarded_for=request.headers.get("X-Forwarded-For"),
99+
user_agent=request.headers.get("User-Agent"),
100+
)
101+
)

0 commit comments

Comments
 (0)