Skip to content

Commit b04be02

Browse files
authored
feat: allow injection of httpx client (#363)
1 parent 7842cda commit b04be02

File tree

17 files changed

+222
-75
lines changed

17 files changed

+222
-75
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
version: latest
3737

3838
- name: Start Supabase local development setup
39-
run: supabase start --workdir infra -x studio,gotrue,postgrest,inbucket,realtime,edge-runtime,logflare,vector,pgbouncer,pg_prove
39+
run: supabase start --workdir infra -x studio,gotrue,postgrest,mailpit,realtime,edge-runtime,logflare,vector,supavisor
4040

4141
- name: Run Tests
4242
run: make run_tests

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ tests_pre_commit:
1111
poetry run pre-commit run --all-files
1212

1313
run_infra:
14-
npx supabase --workdir infra start -x studio,gotrue,postgrest,inbucket,realtime,edge-runtime,logflare,vector,pgbouncer,pg_prove
14+
npx supabase --workdir infra start -x studio,gotrue,postgrest,mailpit,realtime,edge-runtime,logflare,vector,supavisor
1515

1616
stop_infra:
1717
npx supabase --workdir infra stop
@@ -27,6 +27,8 @@ build_sync:
2727
poetry run unasync storage3 tests
2828
sed -i '0,/SyncMock, /{s/SyncMock, //}' tests/_sync/test_bucket.py tests/_sync/test_client.py
2929
sed -i 's/SyncMock/Mock/g' tests/_sync/test_bucket.py tests/_sync/test_client.py
30+
sed -i 's/SyncClient/Client/g' storage3/_sync/client.py storage3/_sync/bucket.py storage3/_sync/file_api.py tests/_sync/test_bucket.py tests/_sync/test_client.py
31+
sed -i 's/self\.session\.aclose/self\.session\.close/g' storage3/_sync/client.py
3032

3133
sleep:
3234
sleep 2

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,22 @@ As it takes some effort to get the headers. We suggest that you use the storage
1010

1111

1212
```python3
13-
from storage3 import create_client
13+
from storage3 import AsyncStorageClient
1414

1515
url = "https://<your_supabase_id>.supabase.co/storage/v1"
1616
key = "<your api key>"
1717
headers = {"apiKey": key, "Authorization": f"Bearer {key}"}
1818

19-
# pass in is_async=True to create an async client
20-
storage_client = create_client(url, headers, is_async=False)
19+
storage_client = AsyncStorageClient(url, headers)
2120

22-
storage_client.list_buckets()
21+
async def get_buckets():
22+
await storage_client.list_buckets()
2323
```
2424

2525
### Uploading files
2626
When uploading files, make sure to set the correct mimetype by using the `file_options` argument:
2727
```py
28-
storage_client.from_("bucket").upload("/folder/file.png", file_object, {"content-type": "image/png"})
28+
async def file_upload():
29+
await storage_client.from_("bucket").upload("/folder/file.png", file_object, {"content-type": "image/png"})
2930
```
3031
If no mime type is given, the default `text/plain` will be used.

poetry.lock

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

pyproject.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ version = "0.11.3" # {x-release-please-version}
2525
httpx = {version = ">=0.26,<0.29", extras = ["http2"]}
2626
python = "^3.9"
2727
python-dateutil = "^2.8.2"
28+
deprecation = "^2.1.0"
2829

29-
[tool.poetry.dev-dependencies]
30+
[tool.poetry.group.dev.dependencies]
3031
black = "^25.1.0"
3132
isort = "^6.0.1"
3233
pre-commit = "^4.2.0"
@@ -38,13 +39,14 @@ Sphinx = "^7.1.2"
3839
sphinx-press-theme = "^0.9.1"
3940
unasync-cli = "^0.0.9"
4041
coveralls = "^1.8.0"
41-
42-
[tool.poetry.group.dev.dependencies]
4342
sphinx-toolbox = "^3.4.0"
4443

4544
[tool.pytest.ini_options]
4645
asyncio_mode = "auto"
4746
addopts = "tests"
47+
filterwarnings = [
48+
"ignore::DeprecationWarning", # ignore deprecation warnings globally
49+
]
4850

4951
[build-system]
5052
build-backend = "poetry.core.masonry.api"

storage3/_async/bucket.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
from typing import Any, Optional
44

5-
from httpx import HTTPStatusError, Response
5+
from httpx import AsyncClient, HTTPStatusError, Response
66

77
from ..exceptions import StorageApiError
88
from ..types import CreateOrUpdateBucketOptions, RequestMethod
9-
from ..utils import AsyncClient
109
from .file_api import AsyncBucket
1110

1211
__all__ = ["AsyncStorageBucketAPI"]

storage3/_async/client.py

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from __future__ import annotations
22

33
from typing import Optional
4+
from warnings import warn
5+
6+
from httpx import AsyncClient
47

58
from storage3.constants import DEFAULT_TIMEOUT
69

7-
from ..utils import AsyncClient
810
from ..version import __version__
911
from .bucket import AsyncStorageBucketAPI
1012
from .file_api import AsyncBucketProxy
@@ -21,15 +23,46 @@ def __init__(
2123
self,
2224
url: str,
2325
headers: dict[str, str],
24-
timeout: int = DEFAULT_TIMEOUT,
25-
verify: bool = True,
26+
timeout: Optional[int] = None,
27+
verify: Optional[bool] = None,
2628
proxy: Optional[str] = None,
29+
http_client: Optional[AsyncClient] = None,
2730
) -> None:
2831
headers = {
2932
"User-Agent": f"supabase-py/storage3 v{__version__}",
3033
**headers,
3134
}
32-
self.session = self._create_session(url, headers, timeout, verify, proxy)
35+
36+
if timeout is not None:
37+
warn(
38+
"The 'timeout' parameter is deprecated. Please configure it in the http client instead.",
39+
DeprecationWarning,
40+
stacklevel=2,
41+
)
42+
if verify is not None:
43+
warn(
44+
"The 'verify' parameter is deprecated. Please configure it in the http client instead.",
45+
DeprecationWarning,
46+
stacklevel=2,
47+
)
48+
if proxy is not None:
49+
warn(
50+
"The 'proxy' parameter is deprecated. Please configure it in the http client instead.",
51+
DeprecationWarning,
52+
stacklevel=2,
53+
)
54+
55+
self.verify = bool(verify) if verify is not None else True
56+
self.timeout = int(abs(timeout)) if timeout is not None else DEFAULT_TIMEOUT
57+
58+
self.session = self._create_session(
59+
base_url=url,
60+
headers=headers,
61+
timeout=self.timeout,
62+
verify=self.verify,
63+
proxy=proxy,
64+
http_client=http_client,
65+
)
3366
super().__init__(self.session)
3467

3568
def _create_session(
@@ -39,13 +72,19 @@ def _create_session(
3972
timeout: int,
4073
verify: bool = True,
4174
proxy: Optional[str] = None,
75+
http_client: Optional[AsyncClient] = None,
4276
) -> AsyncClient:
77+
if http_client is not None:
78+
http_client.base_url = base_url
79+
http_client.headers.update({**headers})
80+
return http_client
81+
4382
return AsyncClient(
4483
base_url=base_url,
4584
headers=headers,
4685
timeout=timeout,
4786
proxy=proxy,
48-
verify=bool(verify),
87+
verify=verify,
4988
follow_redirects=True,
5089
http2=True,
5190
)
@@ -54,9 +93,6 @@ async def __aenter__(self) -> AsyncStorageClient:
5493
return self
5594

5695
async def __aexit__(self, exc_type, exc, tb) -> None:
57-
await self.aclose()
58-
59-
async def aclose(self) -> None:
6096
await self.session.aclose()
6197

6298
def from_(self, id: str) -> AsyncBucketProxy:

storage3/_async/file_api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from pathlib import Path
99
from typing import Any, Literal, Optional, Union, cast
1010

11-
from httpx import HTTPStatusError, Response
11+
from httpx import AsyncClient, HTTPStatusError, Response
1212

1313
from ..constants import DEFAULT_FILE_OPTIONS, DEFAULT_SEARCH_OPTIONS
1414
from ..exceptions import StorageApiError
@@ -26,7 +26,7 @@
2626
UploadResponse,
2727
URLOptions,
2828
)
29-
from ..utils import AsyncClient, StorageException
29+
from ..utils import StorageException
3030

3131
__all__ = ["AsyncBucket"]
3232

storage3/_sync/bucket.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
from typing import Any, Optional
44

5-
from httpx import HTTPStatusError, Response
5+
from httpx import Client, HTTPStatusError, Response
66

77
from ..exceptions import StorageApiError
88
from ..types import CreateOrUpdateBucketOptions, RequestMethod
9-
from ..utils import SyncClient
109
from .file_api import SyncBucket
1110

1211
__all__ = ["SyncStorageBucketAPI"]
@@ -15,7 +14,7 @@
1514
class SyncStorageBucketAPI:
1615
"""This class abstracts access to the endpoint to the Get, List, Empty, and Delete operations on a bucket"""
1716

18-
def __init__(self, session: SyncClient) -> None:
17+
def __init__(self, session: Client) -> None:
1918
self._client = session
2019

2120
def _request(

storage3/_sync/client.py

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

33
from typing import Optional
4+
from warnings import warn
5+
6+
from httpx import Client
47

58
from storage3.constants import DEFAULT_TIMEOUT
69

7-
from ..utils import SyncClient
810
from ..version import __version__
911
from .bucket import SyncStorageBucketAPI
1012
from .file_api import SyncBucketProxy
@@ -21,15 +23,46 @@ def __init__(
2123
self,
2224
url: str,
2325
headers: dict[str, str],
24-
timeout: int = DEFAULT_TIMEOUT,
25-
verify: bool = True,
26+
timeout: Optional[int] = None,
27+
verify: Optional[bool] = None,
2628
proxy: Optional[str] = None,
29+
http_client: Optional[Client] = None,
2730
) -> None:
2831
headers = {
2932
"User-Agent": f"supabase-py/storage3 v{__version__}",
3033
**headers,
3134
}
32-
self.session = self._create_session(url, headers, timeout, verify, proxy)
35+
36+
if timeout is not None:
37+
warn(
38+
"The 'timeout' parameter is deprecated. Please configure it in the http client instead.",
39+
DeprecationWarning,
40+
stacklevel=2,
41+
)
42+
if verify is not None:
43+
warn(
44+
"The 'verify' parameter is deprecated. Please configure it in the http client instead.",
45+
DeprecationWarning,
46+
stacklevel=2,
47+
)
48+
if proxy is not None:
49+
warn(
50+
"The 'proxy' parameter is deprecated. Please configure it in the http client instead.",
51+
DeprecationWarning,
52+
stacklevel=2,
53+
)
54+
55+
self.verify = bool(verify) if verify is not None else True
56+
self.timeout = int(abs(timeout)) if timeout is not None else DEFAULT_TIMEOUT
57+
58+
self.session = self._create_session(
59+
base_url=url,
60+
headers=headers,
61+
timeout=self.timeout,
62+
verify=self.verify,
63+
proxy=proxy,
64+
http_client=http_client,
65+
)
3366
super().__init__(self.session)
3467

3568
def _create_session(
@@ -39,13 +72,19 @@ def _create_session(
3972
timeout: int,
4073
verify: bool = True,
4174
proxy: Optional[str] = None,
42-
) -> SyncClient:
43-
return SyncClient(
75+
http_client: Optional[Client] = None,
76+
) -> Client:
77+
if http_client is not None:
78+
http_client.base_url = base_url
79+
http_client.headers.update({**headers})
80+
return http_client
81+
82+
return Client(
4483
base_url=base_url,
4584
headers=headers,
4685
timeout=timeout,
4786
proxy=proxy,
48-
verify=bool(verify),
87+
verify=verify,
4988
follow_redirects=True,
5089
http2=True,
5190
)
@@ -54,10 +93,7 @@ def __enter__(self) -> SyncStorageClient:
5493
return self
5594

5695
def __exit__(self, exc_type, exc, tb) -> None:
57-
self.aclose()
58-
59-
def aclose(self) -> None:
60-
self.session.aclose()
96+
self.session.close()
6197

6298
def from_(self, id: str) -> SyncBucketProxy:
6399
"""Run a storage file operation.

0 commit comments

Comments
 (0)