Skip to content

Commit 78fbe77

Browse files
authored
Merge branch 'develop' into j0_add_storage_file_api
2 parents 41682ad + 5f7b3bb commit 78fbe77

16 files changed

+272
-65
lines changed

.github/workflows/ci-python.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,4 @@ jobs:
2828
pip install poetry
2929
poetry install
3030
- name: Test with Pytest
31-
run: |
32-
SUPABASE_TEST_URL="https://tfsatoopsijgjhrqplra.supabase.co" SUPABASE_TEST_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMjYwOTMyMiwiZXhwIjoxOTI4MTg1MzIyfQ.XL9W5I_VRQ4iyQHVQmjG0BkwRfx6eVyYB3uAKcesukg" poetry run pytest
31+
run: ./test.sh
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: pre-commit
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
9+
steps:
10+
- name: checkout
11+
uses: actions/checkout@v2
12+
13+
- name: setup python
14+
uses: actions/setup-python@v2
15+
with:
16+
python-version: 3.7
17+
18+
- name: install pre-commit
19+
run: |
20+
python -m pip install --upgrade pip
21+
pip install pre-commit
22+
23+
- name: run static analysis
24+
run: |
25+
pre-commit run --all-files

.pre-commit-config.yaml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
repos:
2+
3+
- repo: https://github.com/pre-commit/pre-commit-hooks
4+
rev: v4.0.1
5+
hooks:
6+
- id: trailing-whitespace
7+
- id: check-added-large-files
8+
- id: mixed-line-ending
9+
args: ['--fix=lf']
10+
11+
- repo: https://github.com/pre-commit/mirrors-isort
12+
rev: v5.8.0
13+
hooks:
14+
- id: isort
15+
args: ['--multi-line=3', '--trailing-comma', '--force-grid-wrap=0', '--use-parentheses', '--line-width=88']
16+
17+
- repo: https://github.com/humitos/mirrors-autoflake.git
18+
rev: v1.1
19+
hooks:
20+
- id: autoflake
21+
args: ['--in-place', '--remove-all-unused-imports']
22+
23+
- repo: https://github.com/ambv/black
24+
rev: 21.5b1
25+
hooks:
26+
- id: black
27+
language_version: python3.7

README.md

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
# supabase-py
22

3-
[![Documentation Status](https://readthedocs.org/projects/gotrue-py/badge/?version=latest)](https://gotrue-py.readthedocs.io/en/latest/?badge=latest)
3+
[![Tests](https://github.com/supabase/supabase-py/actions/workflows/ci-python.yml/badge.svg)](https://github.com/supabase/supabase-py/actions)
4+
[![PYPI Version](https://badge.fury.io/py/supabase-py.svg)](https://badge.fury.io/py/supabase-py)
5+
[![Documentation Status](https://readthedocs.org/projects/supabase-py/badge/?version=latest)](https://gotrue-py.readthedocs.io/en/latest/?badge=latest)
46

57
Supabase client for Python. This mirrors the design of [supabase-js](https://github.com/supabase/supabase-js/blob/master/README.md)
68

79
## Status
10+
811
- [x] Alpha: We are testing Supabase with a closed set of customers
912
- [x] Public Alpha: Anyone can sign up over at [app.supabase.io](https://app.supabase.io). But go easy on us, there are a few kinks.
1013
- [ ] Public Beta: Stable enough for most non-enterprise use-cases
@@ -20,7 +23,7 @@ We are currently in Public Alpha. Watch "releases" of this repo to get notified
2023

2124
#### PyPi installation
2225

23-
Now install the package.
26+
Now install the package. (for > Python 3.7)
2427

2528
```bash
2629
pip install supabase-py
@@ -64,12 +67,10 @@ Currently the test suites are in a state of flux. We are expanding our clients t
6467
<img width="720" height="481" src="https://i.ibb.co/Bq7Kdty/db.png">
6568
</p>
6669

67-
The above test database is a blank supabase instance that has populated the `countries` table with the built in countries script that can be found in the supabase UI. You can launch the test scripts and point to the above test database with the
70+
The above test database is a blank supabase instance that has populated the `countries` table with the built in countries script that can be found in the supabase UI. You can launch the test scripts and point to the above test database by running
6871

6972
```bash
70-
SUPABASE_TEST_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMjYwOTMyMiwiZXhwIjoxOTI4MTg1MzIyfQ.XL9W5I_VRQ4iyQHVQmjG0BkwRfx6eVyYB3uAKcesukg" \
71-
SUPABASE_TEST_URL="https://tfsatoopsijgjhrqplra.supabase.co" \
72-
pytest -x
73+
./test.sh
7374
```
7475

7576
### See issues for what to work on
@@ -115,17 +116,19 @@ user = supabase.auth.sign_in(email=random_email, password=random_password)
115116
## Managing Data
116117

117118
#### Insertion of Data
119+
118120
```python
119121
from supabase_py import create_client, Client
120122

121123
url: str = os.environ.get("SUPABASE_TEST_URL")
122124
key: str = os.environ.get("SUPABASE_TEST_KEY")
123125
supabase: Client = create_client(url, key)
124-
data = supabase.table("countries").select("*").execute()
126+
data = supabase.table("countries").insert({"name":"Germany"}).execute()
125127
assert len(data.get("data", [])) > 0
126128
```
127129

128130
#### Selection of Data
131+
129132
```python
130133
from supabase_py import create_client, Client
131134

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pytest = "^6"
1414
requests = "2.25.1"
1515

1616
[tool.poetry.dev-dependencies]
17+
pre_commit = "^2.1.0"
1718

1819
[build-system]
1920
requires = [

supabase_py/__init__.py

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
# Retain module level imports for structured imports in tests etc.
2-
from . import lib
3-
from . import client
4-
5-
# Open up the client and function as an easy import.
6-
from .client import Client, create_client
7-
1+
from supabase_py import client, lib
2+
from supabase_py.client import Client, create_client
83

94
__version__ = "0.0.2"
5+
6+
__all__ = ["client", "lib", "Client", "create_client"]

supabase_py/client.py

+21-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
from typing import Any, Dict
2+
13
from postgrest_py import PostgrestClient
4+
25
from supabase_py.lib.auth_client import SupabaseAuthClient
3-
from supabase_py.lib.realtime_client import SupabaseRealtimeClient
46
from supabase_py.lib.query_builder import SupabaseQueryBuilder
5-
6-
from typing import Any, Dict
7-
7+
from supabase_py.lib.realtime_client import SupabaseRealtimeClient
8+
from supabase_py.lib.supabase_storage_client import SupabaseStorageClient
89

910
DEFAULT_OPTIONS = {
1011
"schema": "public",
@@ -19,7 +20,10 @@ class Client:
1920
"""Supabase client class."""
2021

2122
def __init__(
22-
self, supabase_url: str, supabase_key: str, **options,
23+
self,
24+
supabase_url: str,
25+
supabase_key: str,
26+
**options,
2327
):
2428
"""Instantiate the client.
2529
@@ -49,21 +53,29 @@ def __init__(
4953
self.rest_url: str = f"{supabase_url}/rest/v1"
5054
self.realtime_url: str = f"{supabase_url}/realtime/v1".replace("http", "ws")
5155
self.auth_url: str = f"{supabase_url}/auth/v1"
56+
self.storage_url = f"{supabase_url}/storage/v1"
5257
self.schema: str = settings.pop("schema")
5358
# Instantiate clients.
5459
self.auth: SupabaseAuthClient = self._init_supabase_auth_client(
55-
auth_url=self.auth_url, supabase_key=self.supabase_key, **settings,
60+
auth_url=self.auth_url,
61+
supabase_key=self.supabase_key,
62+
**settings,
5663
)
5764
# TODO(fedden): Bring up to parity with JS client.
58-
# self.realtime: SupabaseRealtimeClient = self._init_realtime_client(
59-
# realtime_url=self.realtime_url, supabase_key=self.supabase_key,
60-
# )
65+
# self.realtime: SupabaseRealtimeClient = self._init_realtime_client(
66+
# realtime_url=self.realtime_url,
67+
# supabase_key=self.supabase_key,
68+
# )
6169
self.realtime = None
6270
self.postgrest: PostgrestClient = self._init_postgrest_client(
6371
rest_url=self.rest_url,
6472
supabase_key=supabase_key,
6573
)
6674

75+
def storage(self):
76+
"""Create instance of the storage client"""
77+
return SupabaseStorageClient(self.storage_url, self._get_auth_headers())
78+
6779
def table(self, table_name: str) -> SupabaseQueryBuilder:
6880
"""Perform a table operation.
6981

supabase_py/lib/__init__.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from . import auth_client
2-
from . import query_builder
3-
from . import realtime_client
4-
from . import storage
1+
from supabase_py.lib import auth_client, query_builder, realtime_client, storage
2+
3+
__all__ = ["auth_client", "query_builder", "realtime_client"]

supabase_py/lib/query_builder.py

-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
from postgrest_py.client import PostgrestClient
66
from postgrest_py.request_builder import QueryRequestBuilder
77

8-
from .realtime_client import SupabaseRealtimeClient
9-
108

119
def _execute_monkey_patch(self) -> Dict[str, Any]:
1210
"""Temporary method to enable syncronous client code."""

supabase_py/lib/realtime_client.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ def __init__(self, socket: Socket, schema: str, table_name: str):
1414
self.subscription = socket.set_channel(topic)
1515

1616
def get_payload_records(self, payload: Any):
17-
records = {"new": {}, "old": {}}
17+
records: dict = {"new": {}, "old": {}}
1818
if payload.type == "INSERT" or payload.type == "UPDATE":
19-
records.new = payload.record
19+
records["new"] = payload.record
2020
convert_change_data(payload.columns, payload.record)
2121
if payload.type == "UPDATE" or payload.type == "DELETE":
22-
records.old = payload.record
22+
records["old"] = payload.record
2323
convert_change_data(payload.columns, payload.old_record)
2424
return records
2525

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
from typing import Any, Dict
2+
3+
import requests
4+
from requests import HTTPError
5+
6+
7+
class StorageBucketAPI:
8+
"""This class abstracts access to the endpoint to the Get, List, Empty, and Delete operations on a bucket"""
9+
10+
def __init__(self, url, headers):
11+
self.url = url
12+
self.headers = headers
13+
14+
def list_buckets(self) -> Dict[str, Any]:
15+
"""Retrieves the details of all storage buckets within an existing product."""
16+
try:
17+
response = requests.get(f"{self.url}/bucket", headers=self.headers)
18+
response.raise_for_status()
19+
except HTTPError as http_err:
20+
print(f"HTTP error occurred: {http_err}") # Python 3.6
21+
except Exception as err:
22+
print(f"Other error occurred: {err}") # Python 3.6
23+
else:
24+
return response.json()
25+
26+
def get_bucket(self, id: str) -> Dict[str, Any]:
27+
"""Retrieves the details of an existing storage bucket.
28+
29+
Parameters
30+
----------
31+
id
32+
The unique identifier of the bucket you would like to retrieve.
33+
"""
34+
try:
35+
response = requests.get(f"{self.url}/bucket/{id}", headers=self.headers)
36+
response.raise_for_status()
37+
except HTTPError as http_err:
38+
print(f"HTTP error occurred: {http_err}") # Python 3.6
39+
except Exception as err:
40+
print(f"Other error occurred: {err}") # Python 3.6
41+
else:
42+
return response.json()
43+
44+
def create_bucket(self, id: str) -> Dict[str, Any]:
45+
"""Creates a new storage bucket.
46+
47+
Parameters
48+
----------
49+
id
50+
A unique identifier for the bucket you are creating.
51+
"""
52+
try:
53+
response = requests.post(
54+
f"{self.url}/bucket", data={"id": id}, headers=self.headers
55+
)
56+
response.raise_for_status()
57+
except HTTPError as http_err:
58+
print(f"HTTP error occurred: {http_err}") # Python 3.6
59+
except Exception as err:
60+
print(f"Other error occurred: {err}") # Python 3.6
61+
else:
62+
return response.json()
63+
64+
def empty_bucket(self, id: str) -> Dict[str, Any]:
65+
"""Removes all objects inside a single bucket.
66+
67+
Parameters
68+
----------
69+
id
70+
The unique identifier of the bucket you would like to empty.
71+
"""
72+
try:
73+
response = requests.post(
74+
f"{self.url}/bucket/{id}/empty", data={}, headers=self.headers
75+
)
76+
response.raise_for_status()
77+
except HTTPError as http_err:
78+
print(f"HTTP error occurred: {http_err}") # Python 3.6
79+
except Exception as err:
80+
print(f"Other error occurred: {err}") # Python 3.6
81+
else:
82+
return response.json()
83+
84+
def delete_bucket(self, id: str) -> Dict[str, Any]:
85+
"""Deletes an existing bucket. Note that you cannot delete buckets with existing objects inside. You must first
86+
`empty()` the bucket.
87+
88+
Parameters
89+
----------
90+
id
91+
The unique identifier of the bucket you would like to delete.
92+
"""
93+
try:
94+
response = requests.delete(
95+
f"{self.url}/bucket/{id}", data={}, headers=self.headers
96+
)
97+
98+
response.raise_for_status()
99+
except HTTPError as http_err:
100+
print(f"HTTP error occurred: {http_err}") # Python 3.6
101+
except Exception as err:
102+
print(f"Other error occurred: {err}") # Python 3.6
103+
else:
104+
return response.json()
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from supabase_py.lib.storage.storage_bucket_api import StorageBucketAPI
2+
3+
4+
class SupabaseStorageClient(StorageBucketAPI):
5+
"""
6+
Manage the storage bucket and files
7+
8+
Examples
9+
--------
10+
>>> url = storage_file.create_signed_url("poll3o/test2.txt", 80) # signed url
11+
>>> loop.run_until_complete(storage_file.download("poll3o/test2.txt")) #upload or download
12+
>>> loop.run_until_complete(storage_file.upload("poll3o/test2.txt","path_file_upload"))
13+
>>> list_buckets = storage.list_buckets()
14+
>>> list_files = storage_file.list("pollo")
15+
"""
16+
17+
def __init__(self, url, headers):
18+
super().__init__(url, headers)
19+
20+
# def StorageFileApi(self, id_, replace=False):
21+
# return StorageFileApi(self.url, self.headers, id_, replace)
22+
23+
def StorageBucketAPI(self):
24+
return StorageBucketAPI(self.url, self.headers)

test.sh

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/sh
2+
SUPABASE_TEST_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMjYwOTMyMiwiZXhwIjoxOTI4MTg1MzIyfQ.XL9W5I_VRQ4iyQHVQmjG0BkwRfx6eVyYB3uAKcesukg" \
3+
SUPABASE_TEST_URL="https://tfsatoopsijgjhrqplra.supabase.co" \
4+
poetry run pytest

tests/conftest.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from __future__ import annotations
2+
3+
import os
4+
5+
import pytest
6+
7+
from supabase_py import Client, create_client
8+
9+
10+
@pytest.fixture(scope="session")
11+
def supabase() -> Client:
12+
url: str = os.environ.get("SUPABASE_TEST_URL")
13+
key: str = os.environ.get("SUPABASE_TEST_KEY")
14+
supabase: Client = create_client(url, key)
15+
return supabase

0 commit comments

Comments
 (0)