Skip to content

feat: allow injection of httpx client #1117

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 6 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ build_sync:
sed -i 's/asynch/synch/g' supabase/_sync/auth_client.py
sed -i 's/Async/Sync/g' supabase/_sync/auth_client.py
sed -i 's/Async/Sync/g' supabase/_sync/client.py
sed -i 's/create_async_client/create_client/g' tests/_sync/test_client.py
sed -i 's/SyncClient/Client/gi' tests/_sync/test_client.py
sed -i 's/SyncHTTPTransport/HTTPTransport/g' tests/_sync/test_client.py
sed -i 's/SyncMock/Mock/g' tests/_sync/test_client.py
4 changes: 4 additions & 0 deletions supabase/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@
from ._async.client import AsyncClient as AClient
from ._async.client import AsyncStorageClient as ASupabaseStorageClient
from ._async.client import SupabaseException as ASupabaseException
from ._async.client import SupabaseException as AsyncSupabaseException
from ._async.client import create_client as acreate_client
from ._async.client import create_client as create_async_client

# Sync Client
from ._sync.auth_client import SyncSupabaseAuthClient as SupabaseAuthClient
from ._sync.client import SupabaseException
from ._sync.client import SupabaseException as SyncSupabaseException
from ._sync.client import SyncClient as Client
from ._sync.client import SyncStorageClient as SupabaseStorageClient
from ._sync.client import create_client
Expand Down Expand Up @@ -71,4 +73,6 @@
"NotConnectedError",
"SupabaseException",
"ASupabaseException",
"AsyncSupabaseException",
"SyncSupabaseException",
)
47 changes: 40 additions & 7 deletions supabase/_async/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from supafunc import AsyncFunctionsClient

from ..lib.client_options import AsyncClientOptions as ClientOptions
from ..lib.client_options import AsyncHttpxClient
from .auth_client import AsyncSupabaseAuthClient


Expand Down Expand Up @@ -174,6 +175,7 @@ def postgrest(self):
headers=self.options.headers,
schema=self.options.schema,
timeout=self.options.postgrest_client_timeout,
http_client=self.options.httpx_client,
)

return self._postgrest
Expand All @@ -185,16 +187,22 @@ def storage(self):
storage_url=self.storage_url,
headers=self.options.headers,
storage_client_timeout=self.options.storage_client_timeout,
http_client=self.options.httpx_client,
)
return self._storage

@property
def functions(self):
if self._functions is None:
self._functions = AsyncFunctionsClient(
self.functions_url,
self.options.headers,
self.options.function_client_timeout,
url=self.functions_url,
headers=self.options.headers,
timeout=(
self.options.function_client_timeout
if self.options.httpx_client is None
else None
),
http_client=self.options.httpx_client,
)
return self._functions

Expand Down Expand Up @@ -232,9 +240,23 @@ def _init_storage_client(
storage_client_timeout: int = DEFAULT_STORAGE_CLIENT_TIMEOUT,
verify: bool = True,
proxy: Optional[str] = None,
http_client: Union[AsyncHttpxClient, None] = None,
) -> AsyncStorageClient:
if http_client is not None:
# If an http client is provided, use it
kwargs = {"http_client": http_client}
else:
kwargs = {
"timeout": storage_client_timeout,
"verify": verify,
"proxy": proxy,
"http_client": None,
}

return AsyncStorageClient(
storage_url, headers, storage_client_timeout, verify, proxy
url=storage_url,
headers=headers,
**kwargs,
)

@staticmethod
Expand All @@ -254,6 +276,7 @@ def _init_supabase_auth_client(
flow_type=client_options.flow_type,
verify=verify,
proxy=proxy,
http_client=client_options.httpx_client,
)

@staticmethod
Expand All @@ -264,15 +287,25 @@ def _init_postgrest_client(
timeout: Union[int, float, Timeout] = DEFAULT_POSTGREST_CLIENT_TIMEOUT,
verify: bool = True,
proxy: Optional[str] = None,
http_client: Union[AsyncHttpxClient, None] = None,
) -> AsyncPostgrestClient:
"""Private helper for creating an instance of the Postgrest client."""
if http_client is not None:
# If an http client is provided, use it
kwargs = {"http_client": http_client}
else:
kwargs = {
"timeout": timeout,
"verify": verify,
"proxy": proxy,
"http_client": None,
}

return AsyncPostgrestClient(
rest_url,
headers=headers,
schema=schema,
timeout=timeout,
verify=verify,
proxy=proxy,
**kwargs,
)

def _create_auth_header(self, token: str):
Expand Down
47 changes: 40 additions & 7 deletions supabase/_sync/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from supafunc import SyncFunctionsClient

from ..lib.client_options import SyncClientOptions as ClientOptions
from ..lib.client_options import SyncHttpxClient
from .auth_client import SyncSupabaseAuthClient


Expand Down Expand Up @@ -173,6 +174,7 @@ def postgrest(self):
headers=self.options.headers,
schema=self.options.schema,
timeout=self.options.postgrest_client_timeout,
http_client=self.options.httpx_client,
)

return self._postgrest
Expand All @@ -184,16 +186,22 @@ def storage(self):
storage_url=self.storage_url,
headers=self.options.headers,
storage_client_timeout=self.options.storage_client_timeout,
http_client=self.options.httpx_client,
)
return self._storage

@property
def functions(self):
if self._functions is None:
self._functions = SyncFunctionsClient(
self.functions_url,
self.options.headers,
self.options.function_client_timeout,
url=self.functions_url,
headers=self.options.headers,
timeout=(
self.options.function_client_timeout
if self.options.httpx_client is None
else None
),
http_client=self.options.httpx_client,
)
return self._functions

Expand Down Expand Up @@ -231,9 +239,23 @@ def _init_storage_client(
storage_client_timeout: int = DEFAULT_STORAGE_CLIENT_TIMEOUT,
verify: bool = True,
proxy: Optional[str] = None,
http_client: Union[SyncHttpxClient, None] = None,
) -> SyncStorageClient:
if http_client is not None:
# If an http client is provided, use it
kwargs = {"http_client": http_client}
else:
kwargs = {
"timeout": storage_client_timeout,
"verify": verify,
"proxy": proxy,
"http_client": None,
}

return SyncStorageClient(
storage_url, headers, storage_client_timeout, verify, proxy
url=storage_url,
headers=headers,
**kwargs,
)

@staticmethod
Expand All @@ -253,6 +275,7 @@ def _init_supabase_auth_client(
flow_type=client_options.flow_type,
verify=verify,
proxy=proxy,
http_client=client_options.httpx_client,
)

@staticmethod
Expand All @@ -263,15 +286,25 @@ def _init_postgrest_client(
timeout: Union[int, float, Timeout] = DEFAULT_POSTGREST_CLIENT_TIMEOUT,
verify: bool = True,
proxy: Optional[str] = None,
http_client: Union[SyncHttpxClient, None] = None,
) -> SyncPostgrestClient:
"""Private helper for creating an instance of the Postgrest client."""
if http_client is not None:
# If an http client is provided, use it
kwargs = {"http_client": http_client}
else:
kwargs = {
"timeout": timeout,
"verify": verify,
"proxy": proxy,
"http_client": None,
}

return SyncPostgrestClient(
rest_url,
headers=headers,
schema=schema,
timeout=timeout,
verify=verify,
proxy=proxy,
**kwargs,
)

def _create_auth_header(self, token: str):
Expand Down
14 changes: 14 additions & 0 deletions supabase/lib/client_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
SyncMemoryStorage,
SyncSupportedStorage,
)
from httpx import AsyncClient as AsyncHttpxClient
from httpx import Client as SyncHttpxClient
from httpx import Timeout
from postgrest.constants import DEFAULT_POSTGREST_CLIENT_TIMEOUT
from storage3.constants import DEFAULT_TIMEOUT as DEFAULT_STORAGE_CLIENT_TIMEOUT
Expand Down Expand Up @@ -43,6 +45,9 @@ class ClientOptions:
realtime: Optional[RealtimeClientOptions] = None
"""Options passed to the realtime-py instance"""

httpx_client: Optional[SyncHttpxClient] = None
"""httpx client instance to be used by the PostgREST, functions, auth and storage clients."""

postgrest_client_timeout: Union[int, float, Timeout] = (
DEFAULT_POSTGREST_CLIENT_TIMEOUT
)
Expand All @@ -67,6 +72,7 @@ def replace(
persist_session: Optional[bool] = None,
storage: Optional[SyncSupportedStorage] = None,
realtime: Optional[RealtimeClientOptions] = None,
httpx_client: Optional[SyncHttpxClient] = None,
postgrest_client_timeout: Union[
int, float, Timeout
] = DEFAULT_POSTGREST_CLIENT_TIMEOUT,
Expand All @@ -85,6 +91,7 @@ def replace(
client_options.persist_session = persist_session or self.persist_session
client_options.storage = storage or self.storage
client_options.realtime = realtime or self.realtime
client_options.httpx_client = httpx_client or self.httpx_client
client_options.postgrest_client_timeout = (
postgrest_client_timeout or self.postgrest_client_timeout
)
Expand All @@ -100,6 +107,9 @@ class AsyncClientOptions(ClientOptions):
storage: AsyncSupportedStorage = field(default_factory=AsyncMemoryStorage)
"""A storage provider. Used to store the logged in session."""

httpx_client: Optional[AsyncHttpxClient] = None
"""httpx client instance to be used by the PostgREST, functions, auth and storage clients."""

def replace(
self,
schema: Optional[str] = None,
Expand All @@ -108,6 +118,7 @@ def replace(
persist_session: Optional[bool] = None,
storage: Optional[AsyncSupportedStorage] = None,
realtime: Optional[RealtimeClientOptions] = None,
httpx_client: Optional[AsyncHttpxClient] = None,
postgrest_client_timeout: Union[
int, float, Timeout
] = DEFAULT_POSTGREST_CLIENT_TIMEOUT,
Expand All @@ -126,6 +137,7 @@ def replace(
client_options.persist_session = persist_session or self.persist_session
client_options.storage = storage or self.storage
client_options.realtime = realtime or self.realtime
client_options.httpx_client = httpx_client or self.httpx_client
client_options.postgrest_client_timeout = (
postgrest_client_timeout or self.postgrest_client_timeout
)
Expand All @@ -146,6 +158,7 @@ def replace(
persist_session: Optional[bool] = None,
storage: Optional[SyncSupportedStorage] = None,
realtime: Optional[RealtimeClientOptions] = None,
httpx_client: Optional[SyncHttpxClient] = None,
postgrest_client_timeout: Union[
int, float, Timeout
] = DEFAULT_POSTGREST_CLIENT_TIMEOUT,
Expand All @@ -164,6 +177,7 @@ def replace(
client_options.persist_session = persist_session or self.persist_session
client_options.storage = storage or self.storage
client_options.realtime = realtime or self.realtime
client_options.httpx_client = httpx_client or self.httpx_client
client_options.postgrest_client_timeout = (
postgrest_client_timeout or self.postgrest_client_timeout
)
Expand Down
Empty file added tests/_async/__init__.py
Empty file.
Loading