Skip to content

Commit 1d3ce59

Browse files
authored
feat: allow injection of httpx client (#205)
1 parent a85bed7 commit 1d3ce59

File tree

10 files changed

+163
-35
lines changed

10 files changed

+163
-35
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ build_sync:
1919
poetry run unasync supabase_functions tests
2020
sed -i '0,/SyncMock, /{s/SyncMock, //}' tests/_sync/test_function_client.py
2121
sed -i 's/SyncMock/Mock/g' tests/_sync/test_function_client.py
22+
sed -i 's/SyncClient/Client/g' supabase_functions/_sync/functions_client.py tests/_sync/test_function_client.py
2223

2324

2425
rename_project: rename_package_dir rename_package

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ coveralls = "^3.3.0"
2929
[tool.pytest.ini_options]
3030
asyncio_mode = "auto"
3131
addopts = "tests"
32+
filterwarnings = [
33+
"ignore::DeprecationWarning", # ignore deprecation warnings globally
34+
]
3235

3336
[build-system]
3437
requires = ["poetry-core>=1.0.0"]

pytest.ini

Lines changed: 0 additions & 2 deletions
This file was deleted.

supabase_functions/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
from __future__ import annotations
22

3-
from typing import Literal, Optional, Union, overload
3+
from typing import Literal, Union, overload
44

55
from ._async.functions_client import AsyncFunctionsClient
66
from ._sync.functions_client import SyncFunctionsClient
77
from .utils import FunctionRegion
88

9-
__all__ = ["create_client"]
9+
__all__ = [
10+
"create_client",
11+
"FunctionRegion",
12+
"AsyncFunctionsClient",
13+
"SyncFunctionsClient",
14+
]
1015

1116

1217
@overload

supabase_functions/_async/functions_client.py

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
from typing import Any, Dict, Literal, Optional, Union
22
from warnings import warn
33

4-
from httpx import HTTPError, Response
4+
from httpx import AsyncClient, HTTPError, Response
55

66
from ..errors import FunctionsHttpError, FunctionsRelayError
77
from ..utils import (
8-
AsyncClient,
98
FunctionRegion,
109
is_http_url,
1110
is_valid_jwt,
@@ -19,9 +18,10 @@ def __init__(
1918
self,
2019
url: str,
2120
headers: Dict,
22-
timeout: int,
23-
verify: bool = True,
21+
timeout: Optional[int] = None,
22+
verify: Optional[bool] = None,
2423
proxy: Optional[str] = None,
24+
http_client: Optional[AsyncClient] = None,
2525
):
2626
if not is_http_url(url):
2727
raise ValueError("url must be a valid HTTP URL string")
@@ -30,15 +30,43 @@ def __init__(
3030
"User-Agent": f"supabase-py/functions-py v{__version__}",
3131
**headers,
3232
}
33-
self._client = AsyncClient(
34-
base_url=self.url,
35-
headers=self.headers,
36-
verify=bool(verify),
37-
timeout=int(abs(timeout)),
38-
proxy=proxy,
39-
follow_redirects=True,
40-
http2=True,
41-
)
33+
34+
if timeout is not None:
35+
warn(
36+
"The 'timeout' parameter is deprecated. Please configure it in the http client instead.",
37+
DeprecationWarning,
38+
stacklevel=2,
39+
)
40+
if verify is not None:
41+
warn(
42+
"The 'verify' parameter is deprecated. Please configure it in the http client instead.",
43+
DeprecationWarning,
44+
stacklevel=2,
45+
)
46+
if proxy is not None:
47+
warn(
48+
"The 'proxy' parameter is deprecated. Please configure it in the http client instead.",
49+
DeprecationWarning,
50+
stacklevel=2,
51+
)
52+
53+
self.verify = bool(verify) if verify is not None else True
54+
self.timeout = int(abs(timeout)) if timeout is not None else 60
55+
56+
if http_client is not None:
57+
http_client.base_url = self.url
58+
http_client.headers.update({**self.headers})
59+
self._client = http_client
60+
else:
61+
self._client = AsyncClient(
62+
base_url=self.url,
63+
headers=self.headers,
64+
verify=self.verify,
65+
timeout=self.timeout,
66+
proxy=proxy,
67+
follow_redirects=True,
68+
http2=True,
69+
)
4270

4371
async def _request(
4472
self,

supabase_functions/_sync/functions_client.py

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
from typing import Any, Dict, Literal, Optional, Union
22
from warnings import warn
33

4-
from httpx import HTTPError, Response
4+
from httpx import Client, HTTPError, Response
55

66
from ..errors import FunctionsHttpError, FunctionsRelayError
77
from ..utils import (
88
FunctionRegion,
9-
SyncClient,
109
is_http_url,
1110
is_valid_jwt,
1211
is_valid_str_arg,
@@ -19,9 +18,10 @@ def __init__(
1918
self,
2019
url: str,
2120
headers: Dict,
22-
timeout: int,
23-
verify: bool = True,
21+
timeout: Optional[int] = None,
22+
verify: Optional[bool] = None,
2423
proxy: Optional[str] = None,
24+
http_client: Optional[Client] = None,
2525
):
2626
if not is_http_url(url):
2727
raise ValueError("url must be a valid HTTP URL string")
@@ -30,15 +30,43 @@ def __init__(
3030
"User-Agent": f"supabase-py/functions-py v{__version__}",
3131
**headers,
3232
}
33-
self._client = SyncClient(
34-
base_url=self.url,
35-
headers=self.headers,
36-
verify=bool(verify),
37-
timeout=int(abs(timeout)),
38-
proxy=proxy,
39-
follow_redirects=True,
40-
http2=True,
41-
)
33+
34+
if timeout is not None:
35+
warn(
36+
"The 'timeout' parameter is deprecated. Please configure it in the http client instead.",
37+
DeprecationWarning,
38+
stacklevel=2,
39+
)
40+
if verify is not None:
41+
warn(
42+
"The 'verify' parameter is deprecated. Please configure it in the http client instead.",
43+
DeprecationWarning,
44+
stacklevel=2,
45+
)
46+
if proxy is not None:
47+
warn(
48+
"The 'proxy' parameter is deprecated. Please configure it in the http client instead.",
49+
DeprecationWarning,
50+
stacklevel=2,
51+
)
52+
53+
self.verify = bool(verify) if verify is not None else True
54+
self.timeout = int(abs(timeout)) if timeout is not None else 60
55+
56+
if http_client is not None:
57+
http_client.base_url = self.url
58+
http_client.headers.update({**self.headers})
59+
self._client = http_client
60+
else:
61+
self._client = Client(
62+
base_url=self.url,
63+
headers=self.headers,
64+
verify=self.verify,
65+
timeout=self.timeout,
66+
proxy=proxy,
67+
follow_redirects=True,
68+
http2=True,
69+
)
4270

4371
def _request(
4472
self,

supabase_functions/utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import re
22
import sys
33
from urllib.parse import urlparse
4+
from warnings import warn
45

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

3536

3637
class SyncClient(BaseClient):
38+
def __init__(self, *args, **kwargs):
39+
warn(
40+
"The 'SyncClient' class is deprecated. Please use `Client` from the httpx package instead.",
41+
DeprecationWarning,
42+
stacklevel=2,
43+
)
44+
45+
super().__init__(*args, **kwargs)
46+
3747
def aclose(self) -> None:
48+
warn(
49+
"The 'aclose' method is deprecated. Please use `close` method from `Client` in the httpx package instead.",
50+
DeprecationWarning,
51+
stacklevel=2,
52+
)
3853
self.close()
3954

4055

tests/_async/test_function_client.py

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

33
import pytest
4-
from httpx import HTTPError, Response, Timeout
4+
from httpx import AsyncClient, HTTPError, Response, Timeout
55

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

198198
_, kwargs = mock_request.call_args
199199
assert kwargs["headers"]["Content-Type"] == "application/json"
200+
201+
202+
async def test_init_with_httpx_client():
203+
# Create a custom httpx client with specific options
204+
headers = {"x-user-agent": "my-app/0.0.1"}
205+
custom_client = AsyncClient(
206+
timeout=Timeout(30), follow_redirects=True, max_redirects=5, headers=headers
207+
)
208+
209+
# Initialize the functions client with the custom httpx client
210+
client = AsyncFunctionsClient(
211+
url="https://example.com",
212+
headers={"Authorization": "Bearer token"},
213+
timeout=10,
214+
http_client=custom_client,
215+
)
216+
217+
# Verify the custom client options are preserved
218+
assert client._client.timeout == Timeout(30)
219+
assert client._client.follow_redirects is True
220+
assert client._client.max_redirects == 5
221+
assert client._client.headers.get("x-user-agent") == "my-app/0.0.1"
222+
223+
# Verify the client is properly configured with our custom client
224+
assert client._client is custom_client

tests/_sync/test_function_client.py

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

33
import pytest
4-
from httpx import HTTPError, Response, Timeout
4+
from httpx import Client, HTTPError, Response, Timeout
55

66
# Import the class to test
77
from supabase_functions import SyncFunctionsClient
@@ -181,3 +181,28 @@ def test_invoke_with_json_body(client: SyncFunctionsClient):
181181

182182
_, kwargs = mock_request.call_args
183183
assert kwargs["headers"]["Content-Type"] == "application/json"
184+
185+
186+
def test_init_with_httpx_client():
187+
# Create a custom httpx client with specific options
188+
headers = {"x-user-agent": "my-app/0.0.1"}
189+
custom_client = Client(
190+
timeout=Timeout(30), follow_redirects=True, max_redirects=5, headers=headers
191+
)
192+
193+
# Initialize the functions client with the custom httpx client
194+
client = SyncFunctionsClient(
195+
url="https://example.com",
196+
headers={"Authorization": "Bearer token"},
197+
timeout=10,
198+
http_client=custom_client,
199+
)
200+
201+
# Verify the custom client options are preserved
202+
assert client._client.timeout == Timeout(30)
203+
assert client._client.follow_redirects is True
204+
assert client._client.max_redirects == 5
205+
assert client._client.headers.get("x-user-agent") == "my-app/0.0.1"
206+
207+
# Verify the client is properly configured with our custom client
208+
assert client._client is custom_client

tests/test_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ def test_type_hints():
4242

4343
hints = get_type_hints(create_client)
4444

45-
assert hints["url"] == str
45+
assert hints["url"] is str
4646
assert hints["headers"] == dict[str, str]
47-
assert hints["is_async"] == bool
48-
assert hints["verify"] == bool
47+
assert hints["is_async"] is bool
48+
assert hints["verify"] is bool
4949
assert hints["return"] == Union[AsyncFunctionsClient, SyncFunctionsClient]

0 commit comments

Comments
 (0)