From e5d34f2cb3e359aadd611374507426a642f17294 Mon Sep 17 00:00:00 2001 From: Peter Chng Date: Sat, 30 May 2026 09:18:48 -0700 Subject: [PATCH] Allow passing `headers` from `ElevenLabs`, `AsyncElevenLabs` to the parent/base classes --- src/elevenlabs/client.py | 16 ++++++ tests/test_client_headers.py | 106 +++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 tests/test_client_headers.py diff --git a/src/elevenlabs/client.py b/src/elevenlabs/client.py index e907e986..02b14ec4 100644 --- a/src/elevenlabs/client.py +++ b/src/elevenlabs/client.py @@ -33,8 +33,12 @@ class ElevenLabs(BaseElevenLabs): - api_key: typing.Optional[str]. + - headers: typing.Optional[typing.Dict[str, str]]. Additional headers to send with every request. + - timeout: typing.Optional[float]. The timeout to be used, in seconds, for requests by default the timeout is 240 seconds. + - follow_redirects: typing.Optional[bool]. Whether the default httpx client should follow redirects. Defaults to True. Ignored when a custom `httpx_client` is supplied. + - httpx_client: typing.Optional[httpx.Client]. The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. --- from elevenlabs.client import ElevenLabs @@ -49,14 +53,18 @@ def __init__( base_url: typing.Optional[str] = None, environment: ElevenLabsEnvironment = ElevenLabsEnvironment.PRODUCTION, api_key: typing.Optional[str] = os.getenv("ELEVENLABS_API_KEY"), + headers: typing.Optional[typing.Dict[str, str]] = None, timeout: typing.Optional[float] = 240, + follow_redirects: typing.Optional[bool] = True, httpx_client: typing.Optional[httpx.Client] = None ): super().__init__( base_url=base_url, environment=environment, api_key=api_key, + headers=headers, timeout=timeout, + follow_redirects=follow_redirects, httpx_client=httpx_client ) self._text_to_speech = RealtimeTextToSpeechClient(client_wrapper=self._client_wrapper) @@ -83,8 +91,12 @@ class AsyncElevenLabs(AsyncBaseElevenLabs): - api_key: typing.Optional[str]. + - headers: typing.Optional[typing.Dict[str, str]]. Additional headers to send with every request. + - timeout: typing.Optional[float]. The timeout to be used, in seconds, for requests by default the timeout is 240 seconds. + - follow_redirects: typing.Optional[bool]. Whether the default httpx client should follow redirects. Defaults to True. Ignored when a custom `httpx_client` is supplied. + - httpx_client: typing.Optional[httpx.AsyncClient]. The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. --- from elevenlabs.client import AsyncElevenLabs @@ -100,14 +112,18 @@ def __init__( base_url: typing.Optional[str] = None, environment: ElevenLabsEnvironment = ElevenLabsEnvironment.PRODUCTION, api_key: typing.Optional[str] = os.getenv("ELEVENLABS_API_KEY"), + headers: typing.Optional[typing.Dict[str, str]] = None, timeout: typing.Optional[float] = 240, + follow_redirects: typing.Optional[bool] = True, httpx_client: typing.Optional[httpx.AsyncClient] = None ): super().__init__( base_url=base_url, environment=environment, api_key=api_key, + headers=headers, timeout=timeout, + follow_redirects=follow_redirects, httpx_client=httpx_client ) self._webhooks = AsyncWebhooksClient(client_wrapper=self._client_wrapper) diff --git a/tests/test_client_headers.py b/tests/test_client_headers.py new file mode 100644 index 00000000..3d127787 --- /dev/null +++ b/tests/test_client_headers.py @@ -0,0 +1,106 @@ +"""Tests that the user-facing ElevenLabs / AsyncElevenLabs classes forward +the ``headers`` and ``follow_redirects`` kwargs through to the underlying +client wrapper / httpx client. + +Regression coverage for the case where ``ElevenLabs(headers={...})`` and +``ElevenLabs(follow_redirects=False)`` raised TypeError because the +subclass's hand-rolled ``__init__`` did not accept or forward kwargs that +``BaseElevenLabs`` supports. +""" + +import pytest + +from elevenlabs.client import AsyncElevenLabs, ElevenLabs + + +def test_sync_client_accepts_headers_kwarg(): + """ElevenLabs(headers=...) does not raise and stores the headers.""" + custom = {"x-trace-id": "abc-123", "x-tenant": "acme"} + client = ElevenLabs(api_key="sk-test", headers=custom) + assert client._client_wrapper.get_custom_headers() == custom + + +def test_sync_client_headers_default_is_none(): + """Omitting headers leaves the wrapper's custom headers unset (backward-compatible).""" + client = ElevenLabs(api_key="sk-test") + assert client._client_wrapper.get_custom_headers() is None + + +def test_sync_client_custom_headers_appear_in_merged_headers(): + """Headers from the constructor are merged into get_headers() output.""" + client = ElevenLabs(api_key="sk-test", headers={"x-trace-id": "abc-123"}) + merged = client._client_wrapper.get_headers() + assert merged.get("x-trace-id") == "abc-123" + + +async def test_async_client_accepts_headers_kwarg(): + """AsyncElevenLabs(headers=...) does not raise and stores the headers.""" + custom = {"x-trace-id": "abc-123"} + client = AsyncElevenLabs(api_key="sk-test", headers=custom) + assert client._client_wrapper.get_custom_headers() == custom + + +async def test_async_client_headers_default_is_none(): + """Omitting headers on AsyncElevenLabs leaves custom headers unset.""" + client = AsyncElevenLabs(api_key="sk-test") + assert client._client_wrapper.get_custom_headers() is None + + +def test_sync_constructor_signature_advertises_headers(): + """`headers` is a documented keyword-only parameter on ElevenLabs.__init__.""" + import inspect + + sig = inspect.signature(ElevenLabs.__init__) + assert "headers" in sig.parameters + assert sig.parameters["headers"].kind == inspect.Parameter.KEYWORD_ONLY + + +def test_async_constructor_signature_advertises_headers(): + """`headers` is a documented keyword-only parameter on AsyncElevenLabs.__init__.""" + import inspect + + sig = inspect.signature(AsyncElevenLabs.__init__) + assert "headers" in sig.parameters + assert sig.parameters["headers"].kind == inspect.Parameter.KEYWORD_ONLY + + +def test_sync_client_accepts_follow_redirects_false(): + """ElevenLabs(follow_redirects=False) propagates to the default httpx client.""" + client = ElevenLabs(api_key="sk-test", follow_redirects=False) + assert client._client_wrapper.httpx_client.httpx_client.follow_redirects is False + + +def test_sync_client_follow_redirects_default_is_true(): + """Omitting follow_redirects keeps the parent's default of True.""" + client = ElevenLabs(api_key="sk-test") + assert client._client_wrapper.httpx_client.httpx_client.follow_redirects is True + + +def test_async_client_accepts_follow_redirects_false(): + """AsyncElevenLabs(follow_redirects=False) propagates to the default httpx client.""" + client = AsyncElevenLabs(api_key="sk-test", follow_redirects=False) + assert client._client_wrapper.httpx_client.httpx_client.follow_redirects is False + + +def test_async_client_follow_redirects_default_is_true(): + """Omitting follow_redirects on AsyncElevenLabs keeps the parent's default of True.""" + client = AsyncElevenLabs(api_key="sk-test") + assert client._client_wrapper.httpx_client.httpx_client.follow_redirects is True + + +def test_sync_constructor_signature_advertises_follow_redirects(): + """`follow_redirects` is a documented keyword-only parameter on ElevenLabs.__init__.""" + import inspect + + sig = inspect.signature(ElevenLabs.__init__) + assert "follow_redirects" in sig.parameters + assert sig.parameters["follow_redirects"].kind == inspect.Parameter.KEYWORD_ONLY + + +def test_async_constructor_signature_advertises_follow_redirects(): + """`follow_redirects` is a documented keyword-only parameter on AsyncElevenLabs.__init__.""" + import inspect + + sig = inspect.signature(AsyncElevenLabs.__init__) + assert "follow_redirects" in sig.parameters + assert sig.parameters["follow_redirects"].kind == inspect.Parameter.KEYWORD_ONLY