supabase-py icon indicating copy to clipboard operation
supabase-py copied to clipboard

Async Support

Open Pixeled99 opened this issue 1 year ago • 2 comments

Is your feature request related to a problem? Please describe. There are a good amount of python libraries that require async to use. for me it's mostly discord.py. so giving async support would open many new opportunities for this library.

Describe the solution you'd like an async client

Describe alternatives you've considered not using a wrapper or making your own custom one

Additional context none

Pixeled99 avatar Aug 02 '22 22:08 Pixeled99

What specific features of supabase do you need to use? We already have async support on auth, database access and storage; we just haven't yet written an async client here. So for example if you only wanted to use the database, you could directly use postgrest-py which already has sync/async support.

anand2312 avatar Aug 03 '22 00:08 anand2312

A minimal working Async Supabase-Client to run queries on the database with proper type hints could look like this:

# pip install supabase
# pip install postgrest-py

import os
from typing import Any, Coroutine, Dict

from postgrest._async.client import AsyncPostgrestClient
from postgrest._async.request_builder import AsyncFilterRequestBuilder, AsyncRequestBuilder
from supabase.lib.client_options import ClientOptions
from supabase import Client as SyncClient


class Client(SyncClient):

    def __init__(
        self,
        supabase_url: str,
        supabase_key: str,
        options: ClientOptions = ClientOptions(),
    ):
        super().__init__(supabase_url=supabase_url, supabase_key=supabase_key, options=options)
        self.postgrest: AsyncPostgrestClient = self._init_postgrest_client(
            rest_url=self.rest_url,
            supabase_key=self.supabase_key,
            headers=options.headers,
            schema=options.schema,
        )

    def table(self, table_name: str) -> AsyncRequestBuilder:
        return self.from_(table_name)

    def from_(self, table_name: str) -> AsyncRequestBuilder:
        return self.postgrest.from_(table_name)

    def rpc(self, fn: str, params: Dict[Any, Any]) -> Coroutine[None, None, AsyncFilterRequestBuilder]:
        return self.postgrest.rpc(fn, params)

    @staticmethod
    def _init_postgrest_client(
        rest_url: str, supabase_key: str, headers: Dict[str, str], schema: str
    ) -> AsyncPostgrestClient:
        """Private helper for creating an instance of the Postgrest client."""
        client = AsyncPostgrestClient(rest_url, headers=headers, schema=schema)
        client.auth(token=supabase_key)
        return client

def create_client(
    supabase_url: str,
    supabase_key: str,
    options: ClientOptions = ClientOptions(),
) -> Client:
    return Client(supabase_url=supabase_url, supabase_key=supabase_key, options=options)

# instantiate like this
url: str = os.getenv('SUPABASEURL')
key: str = os.getenv('SUPABASEKEY')
supabase: Client = create_client(url, key)

async def main():
    from postgrest.base_request_builder import APIResponse
    # now inside async functions you can do async queries like the following
    response: APIResponse = await supabase.table("credit_data").select("*").limit(10).execute()
    for row in response.data:
        print(row)
        
    # terminate session and http connection at the end of your program, unsure if required
    await supabase.postgrest.aclose()

Of course this needs to be adapted accordingly if supabase-py or postgrest-py change/update.

With async working, should subscriptions also be working inside the client? @anand2312

BurnySc2 avatar Sep 20 '22 06:09 BurnySc2

What specific features of supabase do you need to use? We already have async support on auth, database access and storage; we just haven't yet written an async client here.

@anand2312 do you have any sample code for async support for auth?

levity avatar Jul 12 '23 22:07 levity

Is there any downside to just wrapping all sync calls in loop.run_in_executor like this, while we wait for the release of async support?

import asyncio
import concurrent.futures

async def asyncify(op):
    loop = asyncio.get_running_loop()
    with concurrent.futures.ThreadPoolExecutor() as pool:
        result = await loop.run_in_executor(pool, op)
    return result

supabase = await asyncify(lambda: create_client(SUPABASE_URL, SUPABASE_KEY))
session = await asyncify(lambda: supabase.auth.set_session(
    access_token=access_token,
    refresh_token=refresh_token
))

levity avatar Jul 12 '23 23:07 levity

@levity You'd import from the gotrue-py lib directly, there are some examples re: initializing the gotrue client in the project readme (the auth methods here at supabase-py come from there)

Wrapping the sync calls with run_in_executor will work fine (although you won't need to asyncify create_client)

anand2312 avatar Jul 13 '23 16:07 anand2312

@Atticuszz

I think it is better to contribute to this repository instead of maintaining your own fork just for wrapping async libs underneath

cyucelen avatar Nov 09 '23 11:11 cyucelen

Functionality was added in v2.1.0.

silentworks avatar Nov 24 '23 10:11 silentworks

excuse me bros,and do u how can i contribute the rep with our ideas?i find it's dened as create a async branch@J0 @silentworks ,i wanna contribute my package(supabase-py-async) which is especially prepareed for fastapi😝

Atticuszz avatar Nov 30 '23 06:11 Atticuszz

The library shoudl now be fully async in the latest version 2.2.0 of supabase-py. You can use the async version in your code by doing:

from supabase._async.client import AsyncClient as Client, create_client

async def create_supabase() -> Client:
    return await create_client(url, key)

silentworks avatar Dec 01 '23 08:12 silentworks

excuse me bros,and do u how can i contribute the rep with our ideas?i find it's dened as create a async branch@J0 @silentworks ,i wanna contribute my package(supabase-py-async) which is especially prepareed for fastapi😝

@Atticuszz thank you for creating that package while this library wasn't fully async, I looked at the code and it had quite a few breaking changes which we wouldn't have been able to do in the library itself. It also leaves the handling of passing around the token/auth_client to the user which is something we are aiming to avoid. I think the solution we came up with for version 2.2.0 should work for everyone quite well. I look forward to your future contributions to this project.

silentworks avatar Dec 01 '23 08:12 silentworks