Skip to content

feat: allow injection of httpx client #205

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 16, 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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ build_sync:
poetry run unasync supabase_functions tests
sed -i '0,/SyncMock, /{s/SyncMock, //}' tests/_sync/test_function_client.py
sed -i 's/SyncMock/Mock/g' tests/_sync/test_function_client.py
sed -i 's/SyncClient/Client/g' supabase_functions/_sync/functions_client.py tests/_sync/test_function_client.py


rename_project: rename_package_dir rename_package
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ coveralls = "^3.3.0"
[tool.pytest.ini_options]
asyncio_mode = "auto"
addopts = "tests"
filterwarnings = [
"ignore::DeprecationWarning", # ignore deprecation warnings globally
]

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
2 changes: 0 additions & 2 deletions pytest.ini

This file was deleted.

9 changes: 7 additions & 2 deletions supabase_functions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
from __future__ import annotations

from typing import Literal, Optional, Union, overload
from typing import Literal, Union, overload

from ._async.functions_client import AsyncFunctionsClient
from ._sync.functions_client import SyncFunctionsClient
from .utils import FunctionRegion

__all__ = ["create_client"]
__all__ = [
"create_client",
"FunctionRegion",
"AsyncFunctionsClient",
"SyncFunctionsClient",
]


@overload
Expand Down
54 changes: 41 additions & 13 deletions supabase_functions/_async/functions_client.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from typing import Any, Dict, Literal, Optional, Union
from warnings import warn

from httpx import HTTPError, Response
from httpx import AsyncClient, HTTPError, Response

from ..errors import FunctionsHttpError, FunctionsRelayError
from ..utils import (
AsyncClient,
FunctionRegion,
is_http_url,
is_valid_jwt,
Expand All @@ -19,9 +18,10 @@ def __init__(
self,
url: str,
headers: Dict,
timeout: int,
verify: bool = True,
timeout: Optional[int] = None,
verify: Optional[bool] = None,
proxy: Optional[str] = None,
http_client: Optional[AsyncClient] = None,
):
if not is_http_url(url):
raise ValueError("url must be a valid HTTP URL string")
Expand All @@ -30,15 +30,43 @@ def __init__(
"User-Agent": f"supabase-py/functions-py v{__version__}",
**headers,
}
self._client = AsyncClient(
base_url=self.url,
headers=self.headers,
verify=bool(verify),
timeout=int(abs(timeout)),
proxy=proxy,
follow_redirects=True,
http2=True,
)

if timeout is not None:
warn(
"The 'timeout' parameter is deprecated. Please configure it in the http client instead.",
DeprecationWarning,
stacklevel=2,
)
if verify is not None:
warn(
"The 'verify' parameter is deprecated. Please configure it in the http client instead.",
DeprecationWarning,
stacklevel=2,
)
if proxy is not None:
warn(
"The 'proxy' parameter is deprecated. Please configure it in the http client instead.",
DeprecationWarning,
stacklevel=2,
)

self.verify = bool(verify) if verify is not None else True
self.timeout = int(abs(timeout)) if timeout is not None else 60

if http_client is not None:
http_client.base_url = self.url
http_client.headers.update({**self.headers})
self._client = http_client
else:
self._client = AsyncClient(
base_url=self.url,
headers=self.headers,
verify=self.verify,
timeout=self.timeout,
proxy=proxy,
follow_redirects=True,
http2=True,
)

async def _request(
self,
Expand Down
54 changes: 41 additions & 13 deletions supabase_functions/_sync/functions_client.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from typing import Any, Dict, Literal, Optional, Union
from warnings import warn

from httpx import HTTPError, Response
from httpx import Client, HTTPError, Response

from ..errors import FunctionsHttpError, FunctionsRelayError
from ..utils import (
FunctionRegion,
SyncClient,
is_http_url,
is_valid_jwt,
is_valid_str_arg,
Expand All @@ -19,9 +18,10 @@ def __init__(
self,
url: str,
headers: Dict,
timeout: int,
verify: bool = True,
timeout: Optional[int] = None,
verify: Optional[bool] = None,
proxy: Optional[str] = None,
http_client: Optional[Client] = None,
):
if not is_http_url(url):
raise ValueError("url must be a valid HTTP URL string")
Expand All @@ -30,15 +30,43 @@ def __init__(
"User-Agent": f"supabase-py/functions-py v{__version__}",
**headers,
}
self._client = SyncClient(
base_url=self.url,
headers=self.headers,
verify=bool(verify),
timeout=int(abs(timeout)),
proxy=proxy,
follow_redirects=True,
http2=True,
)

if timeout is not None:
warn(
"The 'timeout' parameter is deprecated. Please configure it in the http client instead.",
DeprecationWarning,
stacklevel=2,
)
if verify is not None:
warn(
"The 'verify' parameter is deprecated. Please configure it in the http client instead.",
DeprecationWarning,
stacklevel=2,
)
if proxy is not None:
warn(
"The 'proxy' parameter is deprecated. Please configure it in the http client instead.",
DeprecationWarning,
stacklevel=2,
)

self.verify = bool(verify) if verify is not None else True
self.timeout = int(abs(timeout)) if timeout is not None else 60

if http_client is not None:
http_client.base_url = self.url
http_client.headers.update({**self.headers})
self._client = http_client
else:
self._client = Client(
base_url=self.url,
headers=self.headers,
verify=self.verify,
timeout=self.timeout,
proxy=proxy,
follow_redirects=True,
http2=True,
)

def _request(
self,
Expand Down
15 changes: 15 additions & 0 deletions supabase_functions/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import re
import sys
from urllib.parse import urlparse
from warnings import warn

from httpx import AsyncClient as AsyncClient # noqa: F401
from httpx import Client as BaseClient
Expand Down Expand Up @@ -34,7 +35,21 @@ class FunctionRegion(StrEnum):


class SyncClient(BaseClient):
def __init__(self, *args, **kwargs):
warn(
"The 'SyncClient' class is deprecated. Please use `Client` from the httpx package instead.",
DeprecationWarning,
stacklevel=2,
)

super().__init__(*args, **kwargs)

def aclose(self) -> None:
warn(
"The 'aclose' method is deprecated. Please use `close` method from `Client` in the httpx package instead.",
DeprecationWarning,
stacklevel=2,
)
self.close()


Expand Down
27 changes: 26 additions & 1 deletion tests/_async/test_function_client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from unittest.mock import AsyncMock, Mock, patch

import pytest
from httpx import HTTPError, Response, Timeout
from httpx import AsyncClient, HTTPError, Response, Timeout

# Import the class to test
from supabase_functions import AsyncFunctionsClient
Expand Down Expand Up @@ -197,3 +197,28 @@ async def test_invoke_with_json_body(client: AsyncFunctionsClient):

_, kwargs = mock_request.call_args
assert kwargs["headers"]["Content-Type"] == "application/json"


async def test_init_with_httpx_client():
# Create a custom httpx client with specific options
headers = {"x-user-agent": "my-app/0.0.1"}
custom_client = AsyncClient(
timeout=Timeout(30), follow_redirects=True, max_redirects=5, headers=headers
)

# Initialize the functions client with the custom httpx client
client = AsyncFunctionsClient(
url="https://example.com",
headers={"Authorization": "Bearer token"},
timeout=10,
http_client=custom_client,
)

# Verify the custom client options are preserved
assert client._client.timeout == Timeout(30)
assert client._client.follow_redirects is True
assert client._client.max_redirects == 5
assert client._client.headers.get("x-user-agent") == "my-app/0.0.1"

# Verify the client is properly configured with our custom client
assert client._client is custom_client
27 changes: 26 additions & 1 deletion tests/_sync/test_function_client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from unittest.mock import Mock, patch

import pytest
from httpx import HTTPError, Response, Timeout
from httpx import Client, HTTPError, Response, Timeout

# Import the class to test
from supabase_functions import SyncFunctionsClient
Expand Down Expand Up @@ -181,3 +181,28 @@ def test_invoke_with_json_body(client: SyncFunctionsClient):

_, kwargs = mock_request.call_args
assert kwargs["headers"]["Content-Type"] == "application/json"


def test_init_with_httpx_client():
# Create a custom httpx client with specific options
headers = {"x-user-agent": "my-app/0.0.1"}
custom_client = Client(
timeout=Timeout(30), follow_redirects=True, max_redirects=5, headers=headers
)

# Initialize the functions client with the custom httpx client
client = SyncFunctionsClient(
url="https://example.com",
headers={"Authorization": "Bearer token"},
timeout=10,
http_client=custom_client,
)

# Verify the custom client options are preserved
assert client._client.timeout == Timeout(30)
assert client._client.follow_redirects is True
assert client._client.max_redirects == 5
assert client._client.headers.get("x-user-agent") == "my-app/0.0.1"

# Verify the client is properly configured with our custom client
assert client._client is custom_client
6 changes: 3 additions & 3 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ def test_type_hints():

hints = get_type_hints(create_client)

assert hints["url"] == str
assert hints["url"] is str
assert hints["headers"] == dict[str, str]
assert hints["is_async"] == bool
assert hints["verify"] == bool
assert hints["is_async"] is bool
assert hints["verify"] is bool
assert hints["return"] == Union[AsyncFunctionsClient, SyncFunctionsClient]