diff --git a/Makefile b/Makefile index 2d8083f..6ea3a97 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/pyproject.toml b/pyproject.toml index 08e3d4d..d71fbd5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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"] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 2f4c80e..0000000 --- a/pytest.ini +++ /dev/null @@ -1,2 +0,0 @@ -[pytest] -asyncio_mode = auto diff --git a/supabase_functions/__init__.py b/supabase_functions/__init__.py index 7b81969..7faa952 100644 --- a/supabase_functions/__init__.py +++ b/supabase_functions/__init__.py @@ -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 diff --git a/supabase_functions/_async/functions_client.py b/supabase_functions/_async/functions_client.py index ba12dbb..439ce3e 100644 --- a/supabase_functions/_async/functions_client.py +++ b/supabase_functions/_async/functions_client.py @@ -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, @@ -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") @@ -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, diff --git a/supabase_functions/_sync/functions_client.py b/supabase_functions/_sync/functions_client.py index d8a410f..cd55fc7 100644 --- a/supabase_functions/_sync/functions_client.py +++ b/supabase_functions/_sync/functions_client.py @@ -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, @@ -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") @@ -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, diff --git a/supabase_functions/utils.py b/supabase_functions/utils.py index e7bf645..6eaf460 100644 --- a/supabase_functions/utils.py +++ b/supabase_functions/utils.py @@ -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 @@ -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() diff --git a/tests/_async/test_function_client.py b/tests/_async/test_function_client.py index e2ee513..c2d0650 100644 --- a/tests/_async/test_function_client.py +++ b/tests/_async/test_function_client.py @@ -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 @@ -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 diff --git a/tests/_sync/test_function_client.py b/tests/_sync/test_function_client.py index d11bc11..f43e11c 100644 --- a/tests/_sync/test_function_client.py +++ b/tests/_sync/test_function_client.py @@ -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 @@ -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 diff --git a/tests/test_client.py b/tests/test_client.py index 47f744c..6092909 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -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]