Skip to content

Commit

Permalink
Merge pull request #27 from supabase/j0_add_storage_bucket
Browse files Browse the repository at this point in the history
Add Storage Bucket API
  • Loading branch information
J0 authored May 23, 2021
2 parents 873b85b + 17c1d6a commit 256f65d
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 17 deletions.
31 changes: 20 additions & 11 deletions supabase_py/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from supabase_py.lib.auth_client import SupabaseAuthClient
from supabase_py.lib.realtime_client import SupabaseRealtimeClient
from supabase_py.lib.query_builder import SupabaseQueryBuilder
from supabase_py.lib.supabase_storage_client import SupabaseStorageClient


from typing import Any, Dict

Expand All @@ -19,7 +21,10 @@ class Client:
"""Supabase client class."""

def __init__(
self, supabase_url: str, supabase_key: str, **options,
self,
supabase_url: str,
supabase_key: str,
**options,
):
"""Instantiate the client.
Expand Down Expand Up @@ -49,21 +54,29 @@ def __init__(
self.rest_url: str = f"{supabase_url}/rest/v1"
self.realtime_url: str = f"{supabase_url}/realtime/v1".replace("http", "ws")
self.auth_url: str = f"{supabase_url}/auth/v1"
self.storage_url = f"{supabase_url}/storage/v1"
self.schema: str = settings.pop("schema")
# Instantiate clients.
self.auth: SupabaseAuthClient = self._init_supabase_auth_client(
auth_url=self.auth_url, supabase_key=self.supabase_key, **settings,
auth_url=self.auth_url,
supabase_key=self.supabase_key,
**settings,
)
# TODO(fedden): Bring up to parity with JS client.
# self.realtime: SupabaseRealtimeClient = self._init_realtime_client(
# realtime_url=self.realtime_url, supabase_key=self.supabase_key,
# )
# self.realtime: SupabaseRealtimeClient = self._init_realtime_client(
# realtime_url=self.realtime_url,
# supabase_key=self.supabase_key,
# )
self.realtime = None
self.postgrest: PostgrestClient = self._init_postgrest_client(
rest_url=self.rest_url,
supabase_key=supabase_key,
)

def storage(self):
"""Create instance of the storage client"""
return SupabaseStorageClient(self.storage_url, self._get_auth_headers())

def table(self, table_name: str) -> SupabaseQueryBuilder:
"""Perform a table operation.
Expand Down Expand Up @@ -133,13 +146,9 @@ def get_subscriptions(self):
return self.realtime.channels

@staticmethod
def _init_realtime_client(
realtime_url: str, supabase_key: str
) -> SupabaseRealtimeClient:
def _init_realtime_client(realtime_url: str, supabase_key: str) -> SupabaseRealtimeClient:
"""Private method for creating an instance of the realtime-py client."""
return SupabaseRealtimeClient(
realtime_url, {"params": {"apikey": supabase_key}}
)
return SupabaseRealtimeClient(realtime_url, {"params": {"apikey": supabase_key}})

@staticmethod
def _init_supabase_auth_client(
Expand Down
6 changes: 3 additions & 3 deletions supabase_py/lib/realtime_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ def __init__(self, socket: Socket, schema: str, table_name: str):
self.subscription = socket.set_channel(topic)

def get_payload_records(self, payload: Any):
records = {"new": {}, "old": {}}
records: dict = {"new": {}, "old": {}}
if payload.type == "INSERT" or payload.type == "UPDATE":
records.new = payload.record
records["new"] = payload.record
convert_change_data(payload.columns, payload.record)
if payload.type == "UPDATE" or payload.type == "DELETE":
records.old = payload.record
records["old"] = payload.record
convert_change_data(payload.columns, payload.old_record)
return records

Expand Down
98 changes: 98 additions & 0 deletions supabase_py/lib/storage/storage_bucket_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from typing import Dict, Any

import requests
from requests import HTTPError


class StorageBucketAPI:
"""This class abstracts access to the endpoint to the Get, List, Empty, and Delete operations on a bucket"""

def __init__(self, url, headers):
self.url = url
self.headers = headers

def list_buckets(self) -> Dict[str, Any]:
"""Retrieves the details of all storage buckets within an existing product."""
try:
response = requests.get(f"{self.url}/bucket", headers=self.headers)
response.raise_for_status()
except HTTPError as http_err:
print(f"HTTP error occurred: {http_err}") # Python 3.6
except Exception as err:
print(f"Other error occurred: {err}") # Python 3.6
else:
return response.json()

def get_bucket(self, id: str) -> Dict[str, Any]:
"""Retrieves the details of an existing storage bucket.
Parameters
----------
id
The unique identifier of the bucket you would like to retrieve.
"""
try:
response = requests.get(f"{self.url}/bucket/{id}", headers=self.headers)
response.raise_for_status()
except HTTPError as http_err:
print(f"HTTP error occurred: {http_err}") # Python 3.6
except Exception as err:
print(f"Other error occurred: {err}") # Python 3.6
else:
return response.json()

def create_bucket(self, id: str) -> Dict[str, Any]:
"""Creates a new storage bucket.
Parameters
----------
id
A unique identifier for the bucket you are creating.
"""
try:
response = requests.post(f"{self.url}/bucket", data={"id": id}, headers=self.headers)
response.raise_for_status()
except HTTPError as http_err:
print(f"HTTP error occurred: {http_err}") # Python 3.6
except Exception as err:
print(f"Other error occurred: {err}") # Python 3.6
else:
return response.json()

def empty_bucket(self, id: str) -> Dict[str, Any]:
"""Removes all objects inside a single bucket.
Parameters
----------
id
The unique identifier of the bucket you would like to empty.
"""
try:
response = requests.post(f"{self.url}/bucket/{id}/empty", data={}, headers=self.headers)
response.raise_for_status()
except HTTPError as http_err:
print(f"HTTP error occurred: {http_err}") # Python 3.6
except Exception as err:
print(f"Other error occurred: {err}") # Python 3.6
else:
return response.json()

def delete_bucket(self, id: str) -> Dict[str, Any]:
"""Deletes an existing bucket. Note that you cannot delete buckets with existing objects inside. You must first
`empty()` the bucket.
Parameters
----------
id
The unique identifier of the bucket you would like to delete.
"""
try:
response = requests.delete(f"{self.url}/bucket/{id}", data={}, headers=self.headers)

response.raise_for_status()
except HTTPError as http_err:
print(f"HTTP error occurred: {http_err}") # Python 3.6
except Exception as err:
print(f"Other error occurred: {err}") # Python 3.6
else:
return response.json()
24 changes: 24 additions & 0 deletions supabase_py/lib/supabase_storage_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from supabase_py.lib.storage.storage_bucket_api import StorageBucketAPI


class SupabaseStorageClient(StorageBucketAPI):
"""
Manage the storage bucket and files
Examples
--------
>>> url = storage_file.create_signed_url("poll3o/test2.txt", 80) # signed url
>>> loop.run_until_complete(storage_file.download("poll3o/test2.txt")) #upload or download
>>> loop.run_until_complete(storage_file.upload("poll3o/test2.txt","path_file_upload"))
>>> list_buckets = storage.list_buckets()
>>> list_files = storage_file.list("pollo")
"""

def __init__(self, url, headers):
super().__init__(url, headers)

# def StorageFileApi(self, id_, replace=False):
# return StorageFileApi(self.url, self.headers, id_, replace)

def StorageBucketAPI(self):
return StorageBucketAPI(self.url, self.headers)
18 changes: 15 additions & 3 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ def _assert_authenticated_user(data: Dict[str, Any]) -> None:
assert user.get("aud") == "authenticated"


@pytest.mark.xfail(
reason="None of these values should be able to instanciate a client object"
)
@pytest.mark.xfail(reason="None of these values should be able to instanciate a client object")
@pytest.mark.parametrize("url", ["", None, "valeefgpoqwjgpj", 139, -1, {}, []])
@pytest.mark.parametrize("key", ["", None, "valeefgpoqwjgpj", 139, -1, {}, []])
def test_incorrect_values_dont_instanciate_client(url: Any, key: Any) -> None:
Expand Down Expand Up @@ -83,3 +81,17 @@ def test_client_insert(supabase: Client) -> None:
assert current_length == previous_length + 1
# Check returned result for insert was valid.
assert result.get("status_code", 400) == 201


def test_client_bucket(supabase: Client) -> None:

"""Ensure that the storage bucket operations work"""
TEST_BUCKET_NAME = "atestbucket"
# TODO[Joel] - Reinstate once permissions on test instance are updated
# storage = supabase.storage()
# storage_bucket = storage.StorageBucketAPI()
# storage_bucket.create_bucket(TEST_BUCKET_NAME)
# storage_bucket.list_buckets()
# storage_bucket.get_bucket(TEST_BUCKET_NAME)
# storage_bucket.empty_bucket(TEST_BUCKET_NAME)
# storage_bucket.delete_bucket(TEST_BUCKET_NAME)
3 changes: 3 additions & 0 deletions tests/test_dummy.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest


import supabase_py

"""
Expand All @@ -9,10 +10,12 @@
"""



def test_dummy() -> None:
# Test auth component
assert True == True



def test_client_initialziation() -> None:
client = supabase_py.Client("http://testwebsite.com", "atestapi")

0 comments on commit 256f65d

Please sign in to comment.