This repository has been archived by the owner on Dec 2, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
634 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import asyncio | ||
from supabase_client import Client | ||
|
||
|
||
from dotenv import dotenv_values | ||
config = dotenv_values(".env") | ||
|
||
supabase = Client( | ||
api_url=config.get("SUPABASE_URL"), | ||
api_key=config.get("SUPABASE_KEY") | ||
) | ||
|
||
async def main(): | ||
pass | ||
|
||
if __name__ == "__main__": | ||
loop = asyncio.get_event_loop() | ||
loop.run_until_complete(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
from setuptools import setup, find_packages | ||
import codecs | ||
import os | ||
|
||
from supabase_client import __version__ | ||
|
||
here = os.path.abspath(os.path.dirname(__file__)) | ||
|
||
VERSION = __version__ | ||
DESCRIPTION = 'A supabase client for Python' | ||
|
||
# Setting up | ||
setup( | ||
name="supabase_client", | ||
version=VERSION, | ||
author="Kenneth Gabriel", | ||
author_email="[email protected]", | ||
description=DESCRIPTION, | ||
long_description="", | ||
long_description_content_type="text/markdown", | ||
packages=find_packages(), | ||
license="MIT", | ||
install_requires=[ | ||
"aiohttp >= 3.7.4", | ||
], | ||
extras_require={ | ||
"dotenv": ["python-dotenv"], | ||
}, | ||
keywords=['python', 'supabase', 'request', 'aiohttp', 'client'], | ||
classifiers=[ | ||
"Development Status :: 1 - Planning", | ||
"Intended Audience :: Developers", | ||
"Programming Language :: Python :: 3", | ||
"Operating System :: Unix", | ||
"Operating System :: MacOS :: MacOS X", | ||
"Operating System :: Microsoft :: Windows", | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# supabase_client | ||
# Author: Kenneth Gabriel ([email protected]) | ||
|
||
|
||
from .supabase_client import Client | ||
from .supebase_exceptions import ( | ||
SupabaseError, | ||
ClientConnectorError, | ||
QueryError, | ||
InvalidRangeError, | ||
UnexpectedValueTypeError | ||
) | ||
from ._utils import TableQueryBuilder | ||
|
||
__all__ = ( | ||
"Client", | ||
"SupabaseError", | ||
"ClientConnectorError", | ||
"QueryError", | ||
"InvalidRangeError", | ||
"UnexpectedValueTypeError", | ||
"TableQueryBuilder" | ||
) | ||
|
||
__version__ = "0.0.1" |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
"""Internal HTTP client module. | ||
This module provides utilities for making HTTP calls using the aiohttp library. | ||
""" | ||
|
||
import aiohttp | ||
|
||
DEFAULT_TIMEOUT_CONFIG = aiohttp.ClientTimeout(total=60, connect=None, | ||
sock_connect=None, sock_read=None) | ||
|
||
class HTTPClient: | ||
|
||
"""Base HTTP client used to make HTTP calls. | ||
HTTPClient maintains an HTTP session, and handles request authentication and retries if | ||
necessary. | ||
""" | ||
|
||
def __init__(self, url='', headers={}, | ||
timeout=DEFAULT_TIMEOUT_CONFIG, **kwargs): | ||
|
||
self.url = url | ||
self.headers = headers | ||
self.timeout = timeout | ||
self._session = None | ||
|
||
async def requests(self, method, url="", **kwargs): | ||
"""Makes an HTTP call using the Python aiohttp library. | ||
This is the sole entry point to the requests library. All other helper methods in this | ||
class call this method to send HTTP requests out. Refer to | ||
:param method: HTTP method name as a string (e.g. GET, POST). | ||
:param url: URL of the remote endpoint. | ||
:param kwargs: An additional set of keyword arguments to be passed into the requests API | ||
(e.g. json, data, headers). | ||
:return: An HTTP response object and possibly the response JSON. | ||
:raises: NotImplementedError | ||
""" | ||
|
||
method = method.upper() | ||
if method not in ["GET", "POST", "PUT", "PATCH", "DELETE"]: | ||
raise NotImplementedError(f"Method '{method}' not recognised.") | ||
|
||
if method == "GET": | ||
request_func = self._session.get | ||
elif method == "POST": | ||
request_func = self._session.post | ||
elif method == "PUT": | ||
request_func = self._session.put | ||
elif method == "PATCH": | ||
request_func = self._session.patch | ||
elif method == "DELETE": | ||
request_func = self._session.delete | ||
|
||
async with request_func(self.url, headers=self.headers, **kwargs) as response: | ||
data = await response.json() if method != "DELETE" else None | ||
return response, data | ||
|
||
return None, None | ||
|
||
async def __aenter__(self): | ||
self._session = aiohttp.ClientSession(timeout=self.timeout) | ||
return self | ||
|
||
async def __aexit__(self, *args): | ||
await self._session.close() | ||
self._session = None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
import aiohttp | ||
import urllib.parse | ||
|
||
from ._http_client import HTTPClient | ||
from ._utils import TableQueryBuilder | ||
|
||
from .supebase_exceptions import UnexpectedValueTypeError, ClientConnectorError | ||
|
||
class TableClient(TableQueryBuilder): | ||
|
||
""" | ||
This class abstracts access to the endpoint to the | ||
READ, INSERT, UPDATE, and DELETE operations on an existing table | ||
:param api_url: supabase app API_URL | ||
:type api_url: String | ||
:param table_name: an exising supabase table | ||
:type table_name: String | ||
:param headers: request headers | ||
:type headers: Dictionary | ||
Example | ||
------- | ||
>>> table_client = TableClient( | ||
... api_url = "http://app-name.supabase.co", | ||
... table_name = "posts", | ||
... headers = { | ||
... "apiKey": "SUPABASE_API_KEY", | ||
... "Authorization": "SUPABASE_API_KEY" | ||
... } | ||
... ) | ||
>>> | ||
""" | ||
|
||
|
||
def __init__(self, api_url, table_name, headers={}): | ||
self.base_url = api_url + "/" + table_name | ||
self.name = table_name | ||
self.headers = headers | ||
self.success_status_codes = [200, 201, 204] | ||
super().__init__(self.base_url) | ||
|
||
async def get(self, row): | ||
""" | ||
Lets you READ the data in the specified `row` | ||
:return: serialized JSON | ||
""" | ||
return self.select(row).query() | ||
|
||
async def update(self, target, data): | ||
""" | ||
Lets you UPDATE rows. | ||
:params column_values: (column, value) | ||
:type column_values: Tuple<String, Any> | ||
:params new_value: (column, value) | ||
:type new_value: Tuple<String, Any> | ||
:return: the replaced values. | ||
Example | ||
-------- | ||
... | ||
>>> # Provided there is an existing table called `posts` | ||
>>> supabase.table("posts").update(("id", 1), ("title", "new title")) | ||
""" | ||
|
||
headers = self.headers.copy() | ||
headers.update({ | ||
"Prefer": "return=representation" | ||
}) | ||
|
||
endpoint = f"{self.base_url}?{urllib.parse.urlencode(target)}" | ||
|
||
try: | ||
async with HTTPClient(endpoint, headers=headers) as session: | ||
response, json_data = await session.requests("PATCH", json=data) | ||
return self._error(response), json_data | ||
except aiohttp.ClientConnectorError as err: | ||
raise ClientConnectorError(str(err)) | ||
|
||
|
||
async def insert(self, data): | ||
""" | ||
Lets you INSERT into your tables. | ||
You can also insert in bulk. | ||
:param data: the data you wish to insert | ||
:type data: List<Dictionary> | ||
:return: the replaced values. | ||
Example | ||
-------- | ||
... | ||
>>> # Provided there is an existing table called `posts` | ||
>>> supabase.table("posts").insert([{"title": "Hello, world!"}]) | ||
""" | ||
|
||
if type(data) is not list: | ||
raise UnexpectedValueTypeError("Expected a list for: `value`") | ||
|
||
headers = self.headers.copy() | ||
headers.update({ | ||
"Prefer": "return=representation" | ||
}) | ||
|
||
endpoint = self.base_url | ||
|
||
try: | ||
async with HTTPClient(endpoint, headers=headers) as session: | ||
response, json_data = await session.requests("POST", json=data) | ||
return self._error(response), json_data | ||
except aiohttp.ClientConnectorError as err: | ||
raise ClientConnectorError(str(err)) | ||
|
||
async def delete(self, data): | ||
""" | ||
Lets you DELETE rows. | ||
:param column: an existing column | ||
:type column: String | ||
:param value: matching value | ||
:return: None | ||
Example | ||
-------- | ||
... | ||
>>> # Provided there is an existing table called `posts` | ||
>>> supabase.table("posts").delete("id", 3) | ||
""" | ||
|
||
endpoint = f"{self.base_url}?{urllib.parse.urlencode(data)}" | ||
|
||
try: | ||
async with HTTPClient(endpoint, headers=self.headers) as session: | ||
response, json_data = await session.requests("DELETE") | ||
return self._error(response), json_data | ||
except aiohttp.ClientConnectorError as err: | ||
raise ClientConnectorError(str(err)) | ||
|
||
|
||
async def query(self): | ||
""" | ||
Executes a sequence of queries. | ||
:return: serialized JSON | ||
""" | ||
if self._as_url: | ||
try: | ||
async with HTTPClient(self._as_url, headers=self.headers) as session: | ||
response, json_data = await session.requests("GET") | ||
return self._error(response), json_data | ||
|
||
except aiohttp.ClientConnectorError as err: | ||
raise ClientConnectorError(str(err)) | ||
|
||
def _error(self, response): | ||
return response.status not in self.success_status_codes |
Oops, something went wrong.