diff --git a/src/postgrest/src/postgrest/_async/client.py b/src/postgrest/src/postgrest/_async/client.py index b85e5895..8b65d088 100644 --- a/src/postgrest/src/postgrest/_async/client.py +++ b/src/postgrest/src/postgrest/_async/client.py @@ -36,6 +36,7 @@ def __init__( verify: Optional[bool] = None, proxy: Optional[str] = None, http_client: Optional[AsyncClient] = None, + retry_enabled: bool = True, ) -> None: headers = { "X-Client-Info": ( @@ -93,7 +94,7 @@ def __init__( verify=self.verify, proxy=proxy, ) - + self.retry_enabled = retry_enabled self.session = http_client or AsyncClient( base_url=base_url, headers=self.headers, @@ -113,6 +114,7 @@ def schema(self, schema: str) -> AsyncPostgrestClient: timeout=self.timeout, verify=self.verify, proxy=self.proxy, + retry_enabled=self.retry_enabled, ) async def __aenter__(self) -> AsyncPostgrestClient: @@ -134,7 +136,11 @@ def from_(self, table: str) -> AsyncRequestBuilder: :class:`AsyncRequestBuilder` """ return AsyncRequestBuilder( - self.session, self.base_url.joinpath(table), self.headers, self.basic_auth + self.session, + self.base_url.joinpath(table), + self.headers, + self.basic_auth, + self.retry_enabled, ) def table(self, table: str) -> AsyncRequestBuilder: diff --git a/src/postgrest/src/postgrest/_async/request_builder.py b/src/postgrest/src/postgrest/_async/request_builder.py index f5c34e5f..e33096c5 100644 --- a/src/postgrest/src/postgrest/_async/request_builder.py +++ b/src/postgrest/src/postgrest/_async/request_builder.py @@ -294,12 +294,18 @@ def explain( class AsyncRequestBuilder: # def __init__( - self, session: AsyncClient, path: URL, headers: Headers, auth: BasicAuth | None + self, + session: AsyncClient, + path: URL, + headers: Headers, + auth: BasicAuth | None, + retry_enabled: bool = True, ) -> None: self.session = session self.path = path self.headers = headers self.auth = auth + self.retry_enabled = retry_enabled def select( self, @@ -321,6 +327,7 @@ def select( session=self.session, path=self.path, auth=self.auth, + retry_enabled=self.retry_enabled, params=params, http_method=method, headers=headers, @@ -362,6 +369,7 @@ def insert( session=self.session, path=self.path, auth=self.auth, + retry_enabled=self.retry_enabled, params=params, http_method=method, headers=headers, @@ -407,6 +415,7 @@ def upsert( session=self.session, path=self.path, auth=self.auth, + retry_enabled=self.retry_enabled, params=params, http_method=method, headers=headers, @@ -440,6 +449,7 @@ def update( session=self.session, path=self.path, auth=self.auth, + retry_enabled=self.retry_enabled, params=params, http_method=method, headers=headers, @@ -470,6 +480,7 @@ def delete( session=self.session, path=self.path, auth=self.auth, + retry_enabled=self.retry_enabled, params=params, http_method=method, headers=headers, diff --git a/src/postgrest/src/postgrest/_sync/client.py b/src/postgrest/src/postgrest/_sync/client.py index 58b2e8b0..3059fcf9 100644 --- a/src/postgrest/src/postgrest/_sync/client.py +++ b/src/postgrest/src/postgrest/_sync/client.py @@ -36,6 +36,7 @@ def __init__( verify: Optional[bool] = None, proxy: Optional[str] = None, http_client: Optional[Client] = None, + retry_enabled: bool = True, ) -> None: headers = { "X-Client-Info": ( @@ -94,6 +95,8 @@ def __init__( proxy=proxy, ) + self.retry_enabled = retry_enabled + self.session = http_client or Client( base_url=base_url, headers=self.headers, @@ -113,6 +116,7 @@ def schema(self, schema: str) -> SyncPostgrestClient: timeout=self.timeout, verify=self.verify, proxy=self.proxy, + retry_enabled=self.retry_enabled, ) def __enter__(self) -> SyncPostgrestClient: @@ -134,7 +138,11 @@ def from_(self, table: str) -> SyncRequestBuilder: :class:`AsyncRequestBuilder` """ return SyncRequestBuilder( - self.session, self.base_url.joinpath(table), self.headers, self.basic_auth + self.session, + self.base_url.joinpath(table), + self.headers, + self.basic_auth, + self.retry_enabled, ) def table(self, table: str) -> SyncRequestBuilder: diff --git a/src/postgrest/src/postgrest/_sync/request_builder.py b/src/postgrest/src/postgrest/_sync/request_builder.py index df502b69..634ae514 100644 --- a/src/postgrest/src/postgrest/_sync/request_builder.py +++ b/src/postgrest/src/postgrest/_sync/request_builder.py @@ -294,12 +294,18 @@ def explain( class SyncRequestBuilder: # def __init__( - self, session: Client, path: URL, headers: Headers, auth: BasicAuth | None + self, + session: Client, + path: URL, + headers: Headers, + auth: BasicAuth | None, + retry_enabled: bool = True, ) -> None: self.session = session self.path = path self.headers = headers self.auth = auth + self.retry_enabled = retry_enabled def select( self, @@ -325,6 +331,7 @@ def select( http_method=method, headers=headers, json=json, + retry_enabled=self.retry_enabled, ) return SyncSelectRequestBuilder(request) @@ -366,6 +373,7 @@ def insert( http_method=method, headers=headers, json=json, + retry_enabled=self.retry_enabled, ) return SyncQueryRequestBuilder(request) @@ -411,6 +419,7 @@ def upsert( http_method=method, headers=headers, json=json, + retry_enabled=self.retry_enabled, ) return SyncQueryRequestBuilder(request) @@ -444,6 +453,7 @@ def update( http_method=method, headers=headers, json=json, + retry_enabled=self.retry_enabled, ) return SyncFilterRequestBuilder(request) @@ -474,5 +484,6 @@ def delete( http_method=method, headers=headers, json=json, + retry_enabled=self.retry_enabled, ) return SyncFilterRequestBuilder(request) diff --git a/src/supabase/README.md b/src/supabase/README.md index f207bbbe..5af81838 100644 --- a/src/supabase/README.md +++ b/src/supabase/README.md @@ -41,7 +41,16 @@ url: str = os.environ.get("SUPABASE_URL") key: str = os.environ.get("SUPABASE_KEY") supabase: Client = create_client(url, key) ``` +### Configure PostgREST retries +PostgREST requests retry transient errors by default. To disable automatic +retries, pass `postgrest_client_retry=False` through `ClientOptions`: + +```python +from supabase import Client, ClientOptions, create_client + +options = ClientOptions(postgrest_client_retry=False) +supabase: Client = create_client(url, key, options) Use the supabase client to interface with your database. ### Sign-up diff --git a/src/supabase/src/supabase/_async/client.py b/src/supabase/src/supabase/_async/client.py index 8b81e6ed..fd7d3177 100644 --- a/src/supabase/src/supabase/_async/client.py +++ b/src/supabase/src/supabase/_async/client.py @@ -188,6 +188,7 @@ def postgrest(self) -> AsyncPostgrestClient: schema=self.options.schema, timeout=self.options.postgrest_client_timeout, http_client=self.options.httpx_client, + retry_enabled=self.options.postgrest_client_retry, ) return self._postgrest @@ -300,6 +301,7 @@ def _init_postgrest_client( verify: bool = True, proxy: Optional[str] = None, http_client: Union[AsyncHttpxClient, None] = None, + retry_enabled: bool = True, ) -> AsyncPostgrestClient: """Private helper for creating an instance of the Postgrest client.""" if http_client is not None: @@ -315,6 +317,7 @@ def _init_postgrest_client( verify=verify, proxy=proxy, http_client=None, + retry_enabled=retry_enabled, ) def _create_auth_header(self, token: str) -> str: diff --git a/src/supabase/src/supabase/_sync/client.py b/src/supabase/src/supabase/_sync/client.py index 29c0246b..e2e53d78 100644 --- a/src/supabase/src/supabase/_sync/client.py +++ b/src/supabase/src/supabase/_sync/client.py @@ -187,6 +187,7 @@ def postgrest(self) -> SyncPostgrestClient: schema=self.options.schema, timeout=self.options.postgrest_client_timeout, http_client=self.options.httpx_client, + retry_enabled=self.options.postgrest_client_retry, ) return self._postgrest @@ -255,7 +256,7 @@ def _init_storage_client( verify: bool = True, proxy: Optional[str] = None, http_client: Union[SyncHttpxClient, None] = None, - ) -> SyncStorageClient: + ) -> SyncStorageClient: if http_client is not None: # If an http client is provided, use it return SyncStorageClient( @@ -299,6 +300,7 @@ def _init_postgrest_client( verify: bool = True, proxy: Optional[str] = None, http_client: Union[SyncHttpxClient, None] = None, + retry_enabled: bool = True, ) -> SyncPostgrestClient: """Private helper for creating an instance of the Postgrest client.""" if http_client is not None: @@ -314,6 +316,7 @@ def _init_postgrest_client( verify=verify, proxy=proxy, http_client=None, + retry_enabled=retry_enabled, ) def _create_auth_header(self, token: str) -> str: diff --git a/src/supabase/src/supabase/lib/client_options.py b/src/supabase/src/supabase/lib/client_options.py index da2e9aac..287292df 100644 --- a/src/supabase/src/supabase/lib/client_options.py +++ b/src/supabase/src/supabase/lib/client_options.py @@ -56,6 +56,9 @@ class ClientOptions: ) """Timeout passed to the SyncPostgrestClient instance.""" + postgrest_client_retry: bool = True + """Whether to retry transient PostgREST errors.""" + storage_client_timeout: int = DEFAULT_STORAGE_CLIENT_TIMEOUT """Timeout passed to the SyncStorageClient instance""" @@ -86,6 +89,7 @@ def replace( postgrest_client_timeout: Union[ int, float, Timeout ] = DEFAULT_POSTGREST_CLIENT_TIMEOUT, + postgrest_client_retry: Optional[bool] = None, storage_client_timeout: int = DEFAULT_STORAGE_CLIENT_TIMEOUT, flow_type: Optional[AuthFlowType] = None, ) -> "AsyncClientOptions": @@ -107,6 +111,11 @@ def replace( client_options.postgrest_client_timeout = ( postgrest_client_timeout or self.postgrest_client_timeout ) + client_options.postgrest_client_retry = ( + postgrest_client_retry + if postgrest_client_retry is not None + else self.postgrest_client_retry + ) client_options.storage_client_timeout = ( storage_client_timeout or self.storage_client_timeout ) @@ -133,6 +142,7 @@ def replace( postgrest_client_timeout: Union[ int, float, Timeout ] = DEFAULT_POSTGREST_CLIENT_TIMEOUT, + postgrest_client_retry: Optional[bool] = None, storage_client_timeout: int = DEFAULT_STORAGE_CLIENT_TIMEOUT, flow_type: Optional[AuthFlowType] = None, ) -> "SyncClientOptions": @@ -154,6 +164,11 @@ def replace( client_options.postgrest_client_timeout = ( postgrest_client_timeout or self.postgrest_client_timeout ) + client_options.postgrest_client_retry = ( + postgrest_client_retry + if postgrest_client_retry is not None + else self.postgrest_client_retry + ) client_options.storage_client_timeout = ( storage_client_timeout or self.storage_client_timeout ) diff --git a/src/supabase/tests/_async/test_client.py b/src/supabase/tests/_async/test_client.py index f3423ee6..26a2c240 100644 --- a/src/supabase/tests/_async/test_client.py +++ b/src/supabase/tests/_async/test_client.py @@ -42,7 +42,16 @@ async def test_postgrest_client() -> None: client = await create_async_client(url, key) assert client.table("sample") assert client.postgrest.schema("new_schema") +async def test_postgrest_client_retry_option() -> None: + client = await create_async_client( + "https://example.supabase.co", + "test-key", + AsyncClientOptions(postgrest_client_retry=False), + ) + + request = client.table("sample").select("*") + assert request.request.retry_enabled is False async def test_rpc_client() -> None: url = os.environ["SUPABASE_TEST_URL"] diff --git a/src/supabase/tests/_sync/test_client.py b/src/supabase/tests/_sync/test_client.py index a490d67d..5f12ff2f 100644 --- a/src/supabase/tests/_sync/test_client.py +++ b/src/supabase/tests/_sync/test_client.py @@ -44,6 +44,18 @@ def test_postgrest_client() -> None: assert client.postgrest.schema("new_schema") +def test_postgrest_client_retry_option() -> None: + client = create_client( + "https://example.supabase.co", + "test-key", + ClientOptions(postgrest_client_retry=False), + ) + + request = client.table("sample").select("*") + + assert request.request.retry_enabled is False + + def test_rpc_client() -> None: url = os.environ["SUPABASE_TEST_URL"] key = os.environ["SUPABASE_TEST_KEY"]