diff --git a/src/supabase/pyproject.toml b/src/supabase/pyproject.toml index ad5c813c..ff746e5c 100644 --- a/src/supabase/pyproject.toml +++ b/src/supabase/pyproject.toml @@ -28,6 +28,7 @@ dependencies = [ "supabase_auth == 2.25.1", # x-release-please-version "postgrest == 2.25.1", # x-release-please-version "httpx >=0.26,<0.29", + "yarl>=1.22.0", ] [project.urls] diff --git a/src/supabase/src/supabase/_async/client.py b/src/supabase/src/supabase/_async/client.py index 4fa5c0b4..97b7c329 100644 --- a/src/supabase/src/supabase/_async/client.py +++ b/src/supabase/src/supabase/_async/client.py @@ -17,6 +17,7 @@ from supabase_auth import AsyncMemoryStorage from supabase_auth.types import AuthChangeEvent, Session from supabase_functions import AsyncFunctionsClient +from yarl import URL from ..lib.client_options import AsyncClientOptions as ClientOptions from ..lib.client_options import AsyncHttpxClient @@ -65,7 +66,9 @@ def __init__( if options is None: options = ClientOptions(storage=AsyncMemoryStorage()) - self.supabase_url = supabase_url + self.supabase_url = ( + URL(supabase_url) if supabase_url.endswith("/") else URL(supabase_url + "/") + ) self.supabase_key = supabase_key self.options = copy.copy(options) self.options.headers = { @@ -73,15 +76,17 @@ def __init__( **self._get_auth_headers(), } - self.rest_url = f"{supabase_url}/rest/v1" - self.realtime_url = f"{supabase_url}/realtime/v1".replace("http", "ws") - self.auth_url = f"{supabase_url}/auth/v1" - self.storage_url = f"{supabase_url}/storage/v1/" - self.functions_url = f"{supabase_url}/functions/v1" + self.rest_url = self.supabase_url.joinpath("rest", "v1") + self.realtime_url = self.supabase_url.joinpath("realtime", "v1").with_scheme( + "wss" if self.supabase_url.scheme == "https" else "ws" + ) + self.auth_url = self.supabase_url.joinpath("auth", "v1") + self.storage_url = self.supabase_url.joinpath("storage", "v1") + self.functions_url = self.supabase_url.joinpath("functions", "v1") # Instantiate clients. self.auth = self._init_supabase_auth_client( - auth_url=self.auth_url, + auth_url=str(self.auth_url), client_options=self.options, ) self.realtime = self._init_realtime_client( @@ -178,7 +183,7 @@ def rpc( def postgrest(self) -> AsyncPostgrestClient: if self._postgrest is None: self._postgrest = self._init_postgrest_client( - rest_url=self.rest_url, + rest_url=str(self.rest_url), headers=self.options.headers, schema=self.options.schema, timeout=self.options.postgrest_client_timeout, @@ -191,7 +196,7 @@ def postgrest(self) -> AsyncPostgrestClient: def storage(self) -> AsyncStorageClient: if self._storage is None: self._storage = self._init_storage_client( - storage_url=self.storage_url, + storage_url=str(self.storage_url), headers=self.options.headers, storage_client_timeout=self.options.storage_client_timeout, http_client=self.options.httpx_client, @@ -202,7 +207,7 @@ def storage(self) -> AsyncStorageClient: def functions(self) -> AsyncFunctionsClient: if self._functions is None: self._functions = AsyncFunctionsClient( - url=self.functions_url, + url=str(self.functions_url), headers=self.options.headers, timeout=( self.options.function_client_timeout @@ -233,13 +238,15 @@ async def remove_all_channels(self) -> None: @staticmethod def _init_realtime_client( - realtime_url: str, + realtime_url: URL, supabase_key: str, options: Optional[RealtimeClientOptions] = None, ) -> AsyncRealtimeClient: realtime_options = options or {} """Private method for creating an instance of the realtime-py client.""" - return AsyncRealtimeClient(realtime_url, token=supabase_key, **realtime_options) + return AsyncRealtimeClient( + str(realtime_url), token=supabase_key, **realtime_options + ) @staticmethod def _init_storage_client( diff --git a/src/supabase/src/supabase/_sync/client.py b/src/supabase/src/supabase/_sync/client.py index 702b31fa..e9dd8472 100644 --- a/src/supabase/src/supabase/_sync/client.py +++ b/src/supabase/src/supabase/_sync/client.py @@ -16,6 +16,7 @@ from supabase_auth import SyncMemoryStorage from supabase_auth.types import AuthChangeEvent, Session from supabase_functions import SyncFunctionsClient +from yarl import URL from ..lib.client_options import SyncClientOptions as ClientOptions from ..lib.client_options import SyncHttpxClient @@ -64,7 +65,9 @@ def __init__( if options is None: options = ClientOptions(storage=SyncMemoryStorage()) - self.supabase_url = supabase_url + self.supabase_url = ( + URL(supabase_url) if supabase_url.endswith("/") else URL(supabase_url + "/") + ) self.supabase_key = supabase_key self.options = copy.copy(options) self.options.headers = { @@ -72,15 +75,17 @@ def __init__( **self._get_auth_headers(), } - self.rest_url = f"{supabase_url}/rest/v1" - self.realtime_url = f"{supabase_url}/realtime/v1".replace("http", "ws") - self.auth_url = f"{supabase_url}/auth/v1" - self.storage_url = f"{supabase_url}/storage/v1/" - self.functions_url = f"{supabase_url}/functions/v1" + self.rest_url = self.supabase_url.joinpath("rest", "v1") + self.realtime_url = self.supabase_url.joinpath("realtime", "v1").with_scheme( + "wss" if self.supabase_url.scheme == "https" else "ws" + ) + self.auth_url = self.supabase_url.joinpath("auth", "v1") + self.storage_url = self.supabase_url.joinpath("storage", "v1") + self.functions_url = self.supabase_url.joinpath("functions", "v1") # Instantiate clients. self.auth = self._init_supabase_auth_client( - auth_url=self.auth_url, + auth_url=str(self.auth_url), client_options=self.options, ) self.realtime = self._init_realtime_client( @@ -177,7 +182,7 @@ def rpc( def postgrest(self) -> SyncPostgrestClient: if self._postgrest is None: self._postgrest = self._init_postgrest_client( - rest_url=self.rest_url, + rest_url=str(self.rest_url), headers=self.options.headers, schema=self.options.schema, timeout=self.options.postgrest_client_timeout, @@ -190,7 +195,7 @@ def postgrest(self) -> SyncPostgrestClient: def storage(self) -> SyncStorageClient: if self._storage is None: self._storage = self._init_storage_client( - storage_url=self.storage_url, + storage_url=str(self.storage_url), headers=self.options.headers, storage_client_timeout=self.options.storage_client_timeout, http_client=self.options.httpx_client, @@ -201,7 +206,7 @@ def storage(self) -> SyncStorageClient: def functions(self) -> SyncFunctionsClient: if self._functions is None: self._functions = SyncFunctionsClient( - url=self.functions_url, + url=str(self.functions_url), headers=self.options.headers, timeout=( self.options.function_client_timeout @@ -232,13 +237,15 @@ def remove_all_channels(self) -> None: @staticmethod def _init_realtime_client( - realtime_url: str, + realtime_url: URL, supabase_key: str, options: Optional[RealtimeClientOptions] = None, ) -> SyncRealtimeClient: realtime_options = options or {} """Private method for creating an instance of the realtime-py client.""" - return SyncRealtimeClient(realtime_url, token=supabase_key, **realtime_options) + return SyncRealtimeClient( + str(realtime_url), token=supabase_key, **realtime_options + ) @staticmethod def _init_storage_client( diff --git a/src/supabase/tests/test_function_configuration.py b/src/supabase/tests/test_function_configuration.py index 0e451d03..856ebd97 100644 --- a/src/supabase/tests/test_function_configuration.py +++ b/src/supabase/tests/test_function_configuration.py @@ -7,8 +7,8 @@ def test_functions_client_initialization() -> None: # Sample JWT Key key = "xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxxxx" sp = supabase.Client(url, key) - assert sp.functions_url == f"https://{ref}.supabase.co/functions/v1" + assert str(sp.functions_url) == f"https://{ref}.supabase.co/functions/v1" url = "https://localhost:54322" sp_local = supabase.Client(url, key) - assert sp_local.functions_url == f"{url}/functions/v1" + assert str(sp_local.functions_url) == f"{url}/functions/v1" diff --git a/src/supabase/tests/test_realtime.py b/src/supabase/tests/test_realtime.py index 74aef7cf..c377189f 100644 --- a/src/supabase/tests/test_realtime.py +++ b/src/supabase/tests/test_realtime.py @@ -7,8 +7,8 @@ def test_realtime_client_initialization() -> None: # Sample JWT Key key = "xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxxxx" sp = supabase.Client(url, key) - assert sp.realtime_url == f"wss://{ref}.supabase.co/realtime/v1" + assert str(sp.realtime_url) == f"wss://{ref}.supabase.co/realtime/v1" url = "http://localhost:54322" sp_local = supabase.Client(url, key) - assert sp_local.realtime_url == "ws://localhost:54322/realtime/v1" + assert str(sp_local.realtime_url) == "ws://localhost:54322/realtime/v1" diff --git a/uv.lock b/uv.lock index a83ccaeb..12e9e24e 100644 --- a/uv.lock +++ b/uv.lock @@ -1774,7 +1774,7 @@ wheels = [ [[package]] name = "postgrest" -version = "2.22.3" +version = "2.25.1" source = { editable = "src/postgrest" } dependencies = [ { name = "deprecation" }, @@ -2312,7 +2312,7 @@ wheels = [ [[package]] name = "realtime" -version = "2.22.3" +version = "2.25.1" source = { editable = "src/realtime" } dependencies = [ { name = "pydantic" }, @@ -2487,6 +2487,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/cd/85b422d24ee2096eaf6faa360c95ef9bdb59097d19b9624cebce4dd9bc2a/ruamel.yaml.clib-0.2.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:808c7190a0fe7ae7014c42f73897cf8e9ef14ff3aa533450e51b1e72ec5239ad", size = 725028, upload-time = "2025-09-22T19:51:19.782Z" }, { url = "https://files.pythonhosted.org/packages/4d/ac/99e6e0ea2584f84f447069d0187fe411e9b5deb7e3ddecda25001cfc7a95/ruamel.yaml.clib-0.2.14-cp39-cp39-win32.whl", hash = "sha256:6d5472f63a31b042aadf5ed28dd3ef0523da49ac17f0463e10fda9c4a2773352", size = 100915, upload-time = "2025-09-22T19:51:21.764Z" }, { url = "https://files.pythonhosted.org/packages/5d/8d/846e43369658958c99d959bb7774136fff9210f9017d91a4277818ceafbf/ruamel.yaml.clib-0.2.14-cp39-cp39-win_amd64.whl", hash = "sha256:8dd3c2cc49caa7a8d64b67146462aed6723a0495e44bf0aa0a2e94beaa8432f6", size = 118706, upload-time = "2025-09-22T19:51:20.878Z" }, + { url = "https://files.pythonhosted.org/packages/e7/cd/150fdb96b8fab27fe08d8a59fe67554568727981806e6bc2677a16081ec7/ruamel_yaml_clib-0.2.14-cp314-cp314-win32.whl", hash = "sha256:9b4104bf43ca0cd4e6f738cb86326a3b2f6eef00f417bd1e7efb7bdffe74c539", size = 102394, upload-time = "2025-11-14T21:57:36.703Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e6/a3fa40084558c7e1dc9546385f22a93949c890a8b2e445b2ba43935f51da/ruamel_yaml_clib-0.2.14-cp314-cp314-win_amd64.whl", hash = "sha256:13997d7d354a9890ea1ec5937a219817464e5cc344805b37671562a401ca3008", size = 122673, upload-time = "2025-11-14T21:57:38.177Z" }, ] [[package]] @@ -2898,7 +2900,7 @@ wheels = [ [[package]] name = "storage3" -version = "2.22.3" +version = "2.25.1" source = { editable = "src/storage" } dependencies = [ { name = "deprecation" }, @@ -2998,7 +3000,7 @@ wheels = [ [[package]] name = "supabase" -version = "2.22.3" +version = "2.25.1" source = { editable = "src/supabase" } dependencies = [ { name = "httpx" }, @@ -3007,6 +3009,7 @@ dependencies = [ { name = "storage3" }, { name = "supabase-auth" }, { name = "supabase-functions" }, + { name = "yarl" }, ] [package.dev-dependencies] @@ -3037,6 +3040,7 @@ requires-dist = [ { name = "storage3", editable = "src/storage" }, { name = "supabase-auth", editable = "src/auth" }, { name = "supabase-functions", editable = "src/functions" }, + { name = "yarl", specifier = ">=1.22.0" }, ] [package.metadata.requires-dev] @@ -3061,7 +3065,7 @@ tests = [ [[package]] name = "supabase-auth" -version = "2.22.3" +version = "2.25.1" source = { editable = "src/auth" } dependencies = [ { name = "httpx", extra = ["http2"] }, @@ -3142,7 +3146,7 @@ tests = [ [[package]] name = "supabase-functions" -version = "2.22.3" +version = "2.25.1" source = { editable = "src/functions" } dependencies = [ { name = "httpx", extra = ["http2"] },