Skip to content

Commit ee2247d

Browse files
committed
fix: do not mutate functions URL
1 parent 304ccbb commit ee2247d

File tree

7 files changed

+43
-46
lines changed

7 files changed

+43
-46
lines changed

src/functions/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ requires-python = ">=3.9"
1515
dependencies = [
1616
"httpx[http2] >=0.26,<0.29",
1717
"strenum >=0.4.15",
18+
"yarl>=1.20.1",
1819
]
1920

2021

src/functions/src/supabase_functions/_async/functions_client.py

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from warnings import warn
33

44
from httpx import AsyncClient, HTTPError, Response, QueryParams
5+
from yarl import URL
56

67
from ..errors import FunctionsHttpError, FunctionsRelayError
78
from ..utils import (
@@ -24,7 +25,7 @@ def __init__(
2425
):
2526
if not is_http_url(url):
2627
raise ValueError("url must be a valid HTTP URL string")
27-
self.url = url
28+
self.url = URL(url)
2829
self.headers = {
2930
"User-Agent": f"supabase-py/functions-py v{__version__}",
3031
**headers,
@@ -51,37 +52,32 @@ def __init__(
5152

5253
self.verify = bool(verify) if verify is not None else True
5354
self.timeout = int(abs(timeout)) if timeout is not None else 60
54-
55-
if http_client is not None:
56-
http_client.base_url = self.url
57-
http_client.headers.update({**self.headers})
58-
self._client = http_client
59-
else:
60-
self._client = AsyncClient(
61-
base_url=self.url,
62-
headers=self.headers,
63-
verify=self.verify,
64-
timeout=self.timeout,
65-
proxy=proxy,
66-
follow_redirects=True,
67-
http2=True,
68-
)
55+
self._client = http_client or AsyncClient(
56+
verify=self.verify,
57+
timeout=self.timeout,
58+
proxy=proxy,
59+
follow_redirects=True,
60+
http2=True,
61+
)
6962

7063
async def _request(
7164
self,
7265
method: Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"],
73-
url: str,
66+
path: list[str],
7467
headers: Optional[Dict[str, str]] = None,
7568
json: Optional[Dict[Any, Any]] = None,
7669
params: Optional[QueryParams] = None,
7770
) -> Response:
71+
url = self.url.joinpath(*path)
72+
headers = headers or dict()
73+
headers.update(self.headers)
7874
response = (
7975
await self._client.request(
80-
method, url, data=json, headers=headers, params=params
76+
method, str(url), data=json, headers=headers, params=params
8177
)
8278
if isinstance(json, str)
8379
else await self._client.request(
84-
method, url, json=json, headers=headers, params=params
80+
method, str(url), json=json, headers=headers, params=params
8581
)
8682
)
8783
try:
@@ -129,7 +125,6 @@ async def invoke(
129125
params = QueryParams()
130126
body = None
131127
response_type = "text/plain"
132-
url = f"{self.url}/{function_name}"
133128

134129
if invoke_options is not None:
135130
headers.update(invoke_options.get("headers", {}))
@@ -153,7 +148,7 @@ async def invoke(
153148
headers["Content-Type"] = "application/json"
154149

155150
response = await self._request(
156-
"POST", url, headers=headers, json=body, params=params
151+
"POST", [function_name], headers=headers, json=body, params=params
157152
)
158153
is_relay_error = response.headers.get("x-relay-header")
159154

src/functions/src/supabase_functions/_sync/functions_client.py

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from warnings import warn
33

44
from httpx import Client, HTTPError, Response, QueryParams
5+
from yarl import URL
56

67
from ..errors import FunctionsHttpError, FunctionsRelayError
78
from ..utils import (
@@ -24,7 +25,7 @@ def __init__(
2425
):
2526
if not is_http_url(url):
2627
raise ValueError("url must be a valid HTTP URL string")
27-
self.url = url
28+
self.url = URL(url)
2829
self.headers = {
2930
"User-Agent": f"supabase-py/functions-py v{__version__}",
3031
**headers,
@@ -51,35 +52,32 @@ def __init__(
5152

5253
self.verify = bool(verify) if verify is not None else True
5354
self.timeout = int(abs(timeout)) if timeout is not None else 60
54-
55-
if http_client is not None:
56-
http_client.base_url = self.url
57-
http_client.headers.update({**self.headers})
58-
self._client = http_client
59-
else:
60-
self._client = Client(
61-
base_url=self.url,
62-
headers=self.headers,
63-
verify=self.verify,
64-
timeout=self.timeout,
65-
proxy=proxy,
66-
follow_redirects=True,
67-
http2=True,
68-
)
55+
self._client = http_client or Client(
56+
verify=self.verify,
57+
timeout=self.timeout,
58+
proxy=proxy,
59+
follow_redirects=True,
60+
http2=True,
61+
)
6962

7063
def _request(
7164
self,
7265
method: Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"],
73-
url: str,
66+
path: list[str],
7467
headers: Optional[Dict[str, str]] = None,
7568
json: Optional[Dict[Any, Any]] = None,
7669
params: Optional[QueryParams] = None,
7770
) -> Response:
71+
url = self.url.joinpath(*path)
72+
headers = headers or dict()
73+
headers.update(self.headers)
7874
response = (
79-
self._client.request(method, url, data=json, headers=headers, params=params)
75+
self._client.request(
76+
method, str(url), data=json, headers=headers, params=params
77+
)
8078
if isinstance(json, str)
8179
else self._client.request(
82-
method, url, json=json, headers=headers, params=params
80+
method, str(url), json=json, headers=headers, params=params
8381
)
8482
)
8583
try:
@@ -127,7 +125,6 @@ def invoke(
127125
params = QueryParams()
128126
body = None
129127
response_type = "text/plain"
130-
url = f"{self.url}/{function_name}"
131128

132129
if invoke_options is not None:
133130
headers.update(invoke_options.get("headers", {}))
@@ -150,7 +147,9 @@ def invoke(
150147
elif isinstance(body, dict):
151148
headers["Content-Type"] = "application/json"
152149

153-
response = self._request("POST", url, headers=headers, json=body, params=params)
150+
response = self._request(
151+
"POST", [function_name], headers=headers, json=body, params=params
152+
)
154153
is_relay_error = response.headers.get("x-relay-header")
155154

156155
if is_relay_error and is_relay_error == "true":

src/functions/tests/_async/test_function_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ async def test_init_with_valid_params(valid_url, default_headers):
3131
client = AsyncFunctionsClient(
3232
url=valid_url, headers=default_headers, timeout=10, verify=True
3333
)
34-
assert client.url == valid_url
34+
assert str(client.url) == valid_url
3535
assert "User-Agent" in client.headers
3636
assert client.headers["User-Agent"] == f"supabase-py/functions-py v{__version__}"
3737
assert client._client.timeout == Timeout(10)

src/functions/tests/_sync/test_function_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def test_init_with_valid_params(valid_url, default_headers):
3131
client = SyncFunctionsClient(
3232
url=valid_url, headers=default_headers, timeout=10, verify=True
3333
)
34-
assert client.url == valid_url
34+
assert str(client.url) == valid_url
3535
assert "User-Agent" in client.headers
3636
assert client.headers["User-Agent"] == f"supabase-py/functions-py v{__version__}"
3737
assert client._client.timeout == Timeout(10)

src/functions/tests/test_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def test_create_async_client(valid_url, valid_headers):
2222
)
2323

2424
assert isinstance(client, AsyncFunctionsClient)
25-
assert client.url == valid_url
25+
assert str(client.url) == valid_url
2626
assert all(client.headers[key] == value for key, value in valid_headers.items())
2727

2828

@@ -33,7 +33,7 @@ def test_create_sync_client(valid_url, valid_headers):
3333
)
3434

3535
assert isinstance(client, SyncFunctionsClient)
36-
assert client.url == valid_url
36+
assert str(client.url) == valid_url
3737
assert all(client.headers[key] == value for key, value in valid_headers.items())
3838

3939

uv.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)