Skip to content

Commit d80f982

Browse files
Custom exception class, typo fixes.
1 parent 5f860ac commit d80f982

File tree

4 files changed

+85
-83
lines changed

4 files changed

+85
-83
lines changed

Diff for: supabase/client.py

+34-20
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,21 @@
1111
from .lib.storage_client import SupabaseStorageClient
1212

1313

14+
# Create an exception class when user does not provide a valid url or key.
15+
class SupabaseException(Exception):
16+
def __init__(self, message: str):
17+
self.message = message
18+
super().__init__(self.message)
19+
20+
1421
class Client:
1522
"""Supabase client class."""
1623

1724
def __init__(
18-
self,
19-
supabase_url: str,
20-
supabase_key: str,
21-
options: ClientOptions = ClientOptions(),
25+
self,
26+
supabase_url: str,
27+
supabase_key: str,
28+
options: ClientOptions = ClientOptions(),
2229
):
2330
"""Instantiate the client.
2431
@@ -34,9 +41,18 @@ def __init__(
3441
"""
3542

3643
if not supabase_url:
37-
raise Exception("supabase_url is required")
44+
raise SupabaseException("supabase_url is required")
3845
if not supabase_key:
39-
raise Exception("supabase_key is required")
46+
raise SupabaseException("supabase_key is required")
47+
48+
# Check if the url and key are valid
49+
if not re.match(r"^(https?)://.+", supabase_url):
50+
raise SupabaseException("Invalid URL")
51+
52+
# Check if the key is a valid JWT
53+
if not re.match(r"^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$", supabase_key):
54+
raise SupabaseException("Invalid API key")
55+
4056
self.supabase_url = supabase_url
4157
self.supabase_key = supabase_key
4258
options.headers.update(self._get_auth_headers())
@@ -58,10 +74,9 @@ def __init__(
5874
# Instantiate clients.
5975
self.auth = self._init_supabase_auth_client(
6076
auth_url=self.auth_url,
61-
supabase_key=self.supabase_key,
6277
client_options=options,
6378
)
64-
# TODO(fedden): Bring up to parity with JS client.
79+
# TODO: Bring up to parity with JS client.
6580
# self.realtime: SupabaseRealtimeClient = self._init_realtime_client(
6681
# realtime_url=self.realtime_url,
6782
# supabase_key=self.supabase_key,
@@ -85,7 +100,7 @@ def table(self, table_name: str) -> SyncRequestBuilder:
85100
"""Perform a table operation.
86101
87102
Note that the supabase client uses the `from` method, but in Python,
88-
this is a reserved keyword so we have elected to use the name `table`.
103+
this is a reserved keyword, so we have elected to use the name `table`.
89104
Alternatively you can use the `.from_()` method.
90105
"""
91106
return self.from_(table_name)
@@ -153,9 +168,8 @@ def rpc(self, fn: str, params: Dict[Any, Any]) -> SyncFilterRequestBuilder:
153168

154169
@staticmethod
155170
def _init_supabase_auth_client(
156-
auth_url: str,
157-
supabase_key: str,
158-
client_options: ClientOptions,
171+
auth_url: str,
172+
client_options: ClientOptions,
159173
) -> SupabaseAuthClient:
160174
"""Creates a wrapped instance of the GoTrue Client."""
161175
return SupabaseAuthClient(
@@ -168,11 +182,11 @@ def _init_supabase_auth_client(
168182

169183
@staticmethod
170184
def _init_postgrest_client(
171-
rest_url: str,
172-
supabase_key: str,
173-
headers: Dict[str, str],
174-
schema: str,
175-
timeout: Union[int, float, Timeout] = DEFAULT_POSTGREST_CLIENT_TIMEOUT,
185+
rest_url: str,
186+
supabase_key: str,
187+
headers: Dict[str, str],
188+
schema: str,
189+
timeout: Union[int, float, Timeout] = DEFAULT_POSTGREST_CLIENT_TIMEOUT,
176190
) -> SyncPostgrestClient:
177191
"""Private helper for creating an instance of the Postgrest client."""
178192
client = SyncPostgrestClient(
@@ -191,9 +205,9 @@ def _get_auth_headers(self) -> Dict[str, str]:
191205

192206

193207
def create_client(
194-
supabase_url: str,
195-
supabase_key: str,
196-
options: ClientOptions = ClientOptions(),
208+
supabase_url: str,
209+
supabase_key: str,
210+
options: ClientOptions = ClientOptions(),
197211
) -> Client:
198212
"""Create client function to instantiate supabase client like JS runtime.
199213

Diff for: supabase/lib/auth_client.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def __init__(
2525
api: Optional[SyncGoTrueAPI] = None,
2626
replace_default_headers: bool = False,
2727
):
28-
"""Instanciate SupabaseAuthClient instance."""
28+
"""Instantiate SupabaseAuthClient instance."""
2929
SyncGoTrueClient.__init__(
3030
self,
3131
url=url,

Diff for: supabase/lib/client_options.py

+48-61
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,52 @@
1-
from dataclasses import dataclass, field
2-
from typing import Any, Callable, Dict, Optional, Union
1+
from typing import Any, Callable
32

4-
from gotrue import SyncMemoryStorage, SyncSupportedStorage
5-
from httpx import Timeout
6-
from postgrest.constants import DEFAULT_POSTGREST_CLIENT_TIMEOUT
3+
from realtime.connection import Socket
4+
from realtime.transformers import convert_change_data
75

8-
from supabase import __version__
96

10-
DEFAULT_HEADERS = {"X-Client-Info": f"supabase-py/{__version__}"}
11-
12-
13-
@dataclass
14-
class ClientOptions:
15-
schema: str = "public"
16-
"""
17-
The Postgres schema which your tables belong to.
18-
Must be on the list of exposed schemas in Supabase. Defaults to 'public'.
19-
"""
20-
21-
headers: Dict[str, str] = field(default_factory=DEFAULT_HEADERS.copy)
22-
"""Optional headers for initializing the client."""
23-
24-
auto_refresh_token: bool = True
25-
"""Automatically refreshes the token for logged in users."""
26-
27-
persist_session: bool = True
28-
"""Whether to persist a logged in session to storage."""
29-
30-
local_storage: SyncSupportedStorage = field(default_factory=SyncMemoryStorage)
31-
"""A storage provider. Used to store the logged in session."""
32-
33-
realtime: Optional[Dict[str, Any]] = None
34-
"""Options passed to the realtime-py instance"""
35-
36-
fetch: Optional[Callable] = None
37-
"""A custom `fetch` implementation."""
38-
39-
timeout: Union[int, float, Timeout] = DEFAULT_POSTGREST_CLIENT_TIMEOUT
40-
"""Timeout passed to the SyncPostgrestClient instance."""
41-
42-
def replace(
43-
self,
44-
schema: Optional[str] = None,
45-
headers: Optional[Dict[str, str]] = None,
46-
auto_refresh_token: Optional[bool] = None,
47-
persist_session: Optional[bool] = None,
48-
local_storage: Optional[SyncSupportedStorage] = None,
49-
realtime: Optional[Dict[str, Any]] = None,
50-
fetch: Optional[Callable] = None,
51-
timeout: Union[int, float, Timeout] = DEFAULT_POSTGREST_CLIENT_TIMEOUT,
52-
) -> "ClientOptions":
53-
"""Create a new SupabaseClientOptions with changes"""
54-
client_options = ClientOptions()
55-
client_options.schema = schema or self.schema
56-
client_options.headers = headers or self.headers
57-
client_options.auto_refresh_token = (
58-
auto_refresh_token or self.auto_refresh_token
7+
class SupabaseRealtimeClient:
8+
def __init__(self, socket: Socket, schema: str, table_name: str):
9+
topic = (
10+
f"realtime:{schema}"
11+
if table_name == "*"
12+
else f"realtime:{schema}:{table_name}"
13+
)
14+
self.subscription = socket.set_channel(topic)
15+
16+
@staticmethod
17+
def get_payload_records(payload: Any):
18+
records: dict = {"new": {}, "old": {}}
19+
if payload.type in ["INSERT", "UPDATE"]:
20+
records["new"] = payload.record
21+
convert_change_data(payload.columns, payload.record)
22+
if payload.type in ["UPDATE", "DELETE"]:
23+
records["old"] = payload.record
24+
convert_change_data(payload.columns, payload.old_record)
25+
return records
26+
27+
def on(self, event, callback: Callable[..., Any]):
28+
def cb(payload):
29+
enriched_payload = {
30+
"schema": payload.schema,
31+
"table": payload.table,
32+
"commit_timestamp": payload.commit_timestamp,
33+
"event_type": payload.type,
34+
"new": {},
35+
"old": {},
36+
}
37+
enriched_payload = {**enriched_payload, **self.get_payload_records(payload)}
38+
callback(enriched_payload)
39+
40+
self.subscription.join().on(event, cb)
41+
return self
42+
43+
def subscribe(self, callback: Callable[..., Any]):
44+
# TODO: Handle state change callbacks for error and close
45+
self.subscription.join().on("ok", callback("SUBSCRIBED"))
46+
self.subscription.join().on(
47+
"error", lambda x: callback("SUBSCRIPTION_ERROR", x)
48+
)
49+
self.subscription.join().on(
50+
"timeout", lambda: callback("RETRYING_AFTER_TIMEOUT")
5951
)
60-
client_options.persist_session = persist_session or self.persist_session
61-
client_options.local_storage = local_storage or self.local_storage
62-
client_options.realtime = realtime or self.realtime
63-
client_options.fetch = fetch or self.fetch
64-
client_options.timeout = timeout or self.timeout
65-
return client_options
52+
return self.subscription

Diff for: supabase/lib/realtime_client.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ def __init__(self, socket: Socket, schema: str, table_name: str):
1313
)
1414
self.subscription = socket.set_channel(topic)
1515

16-
def get_payload_records(self, payload: Any):
16+
@staticmethod
17+
def get_payload_records(payload: Any):
1718
records: dict = {"new": {}, "old": {}}
1819
if payload.type in ["INSERT", "UPDATE"]:
1920
records["new"] = payload.record

0 commit comments

Comments
 (0)