Skip to content

Commit e70b553

Browse files
Support custom client headers in transport configuration
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent b4d8d5b commit e70b553

6 files changed

Lines changed: 79 additions & 17 deletions

File tree

hyperbrowser/client/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __init__(
3636
raise HyperbrowserError("API key must be provided")
3737

3838
self.config = config
39-
self.transport = transport(config.api_key)
39+
self.transport = transport(config.api_key, headers=config.headers)
4040

4141
def _build_url(self, path: str) -> str:
4242
return f"{self.config.base_url}/api{path}"

hyperbrowser/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from dataclasses import dataclass
2+
from typing import Dict, Optional
23
import os
34

45
from .exceptions import HyperbrowserError
@@ -10,6 +11,7 @@ class ClientConfig:
1011

1112
api_key: str
1213
base_url: str = "https://api.hyperbrowser.ai"
14+
headers: Optional[Dict[str, str]] = None
1315

1416
@classmethod
1517
def from_env(cls) -> "ClientConfig":

hyperbrowser/transport/async_transport.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
class AsyncTransport(AsyncTransportStrategy):
1111
"""Asynchronous transport implementation using httpx"""
1212

13-
def __init__(self, api_key: str):
14-
self.client = httpx.AsyncClient(
15-
headers={
16-
"x-api-key": api_key,
17-
"User-Agent": f"hyperbrowser-python-sdk/{__version__}",
18-
}
19-
)
13+
def __init__(self, api_key: str, headers: Optional[dict] = None):
14+
merged_headers = {
15+
"x-api-key": api_key,
16+
"User-Agent": f"hyperbrowser-python-sdk/{__version__}",
17+
}
18+
if headers:
19+
merged_headers.update(headers)
20+
self.client = httpx.AsyncClient(headers=merged_headers)
2021
self._closed = False
2122

2223
async def close(self) -> None:

hyperbrowser/transport/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class SyncTransportStrategy(ABC):
3737
"""Abstract base class for synchronous transport implementations"""
3838

3939
@abstractmethod
40-
def __init__(self, api_key: str):
40+
def __init__(self, api_key: str, headers: Optional[dict] = None):
4141
...
4242

4343
@abstractmethod
@@ -69,7 +69,7 @@ class AsyncTransportStrategy(ABC):
6969
"""Abstract base class for asynchronous transport implementations"""
7070

7171
@abstractmethod
72-
def __init__(self, api_key: str):
72+
def __init__(self, api_key: str, headers: Optional[dict] = None):
7373
...
7474

7575
@abstractmethod

hyperbrowser/transport/sync.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
class SyncTransport(SyncTransportStrategy):
1010
"""Synchronous transport implementation using httpx"""
1111

12-
def __init__(self, api_key: str):
13-
self.client = httpx.Client(
14-
headers={
15-
"x-api-key": api_key,
16-
"User-Agent": f"hyperbrowser-python-sdk/{__version__}",
17-
}
18-
)
12+
def __init__(self, api_key: str, headers: Optional[dict] = None):
13+
merged_headers = {
14+
"x-api-key": api_key,
15+
"User-Agent": f"hyperbrowser-python-sdk/{__version__}",
16+
}
17+
if headers:
18+
merged_headers.update(headers)
19+
self.client = httpx.Client(headers=merged_headers)
1920

2021
def _handle_response(self, response: httpx.Response) -> APIResponse:
2122
try:

tests/test_custom_headers.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import asyncio
2+
3+
from hyperbrowser import AsyncHyperbrowser, Hyperbrowser
4+
from hyperbrowser.config import ClientConfig
5+
from hyperbrowser.transport.async_transport import AsyncTransport
6+
from hyperbrowser.transport.sync import SyncTransport
7+
8+
9+
def test_sync_transport_accepts_custom_headers():
10+
transport = SyncTransport(
11+
api_key="test-key",
12+
headers={"X-Correlation-Id": "abc123", "User-Agent": "custom-agent"},
13+
)
14+
try:
15+
assert transport.client.headers["x-api-key"] == "test-key"
16+
assert transport.client.headers["X-Correlation-Id"] == "abc123"
17+
assert transport.client.headers["User-Agent"] == "custom-agent"
18+
finally:
19+
transport.close()
20+
21+
22+
def test_async_transport_accepts_custom_headers():
23+
async def run() -> None:
24+
transport = AsyncTransport(
25+
api_key="test-key",
26+
headers={"X-Correlation-Id": "abc123", "User-Agent": "custom-agent"},
27+
)
28+
try:
29+
assert transport.client.headers["x-api-key"] == "test-key"
30+
assert transport.client.headers["X-Correlation-Id"] == "abc123"
31+
assert transport.client.headers["User-Agent"] == "custom-agent"
32+
finally:
33+
await transport.close()
34+
35+
asyncio.run(run())
36+
37+
38+
def test_sync_client_config_headers_are_applied_to_transport():
39+
client = Hyperbrowser(
40+
config=ClientConfig(api_key="test-key", headers={"X-Team-Trace": "team-1"})
41+
)
42+
try:
43+
assert client.transport.client.headers["X-Team-Trace"] == "team-1"
44+
finally:
45+
client.close()
46+
47+
48+
def test_async_client_config_headers_are_applied_to_transport():
49+
async def run() -> None:
50+
client = AsyncHyperbrowser(
51+
config=ClientConfig(api_key="test-key", headers={"X-Team-Trace": "team-1"})
52+
)
53+
try:
54+
assert client.transport.client.headers["X-Team-Trace"] == "team-1"
55+
finally:
56+
await client.close()
57+
58+
asyncio.run(run())

0 commit comments

Comments
 (0)