Skip to content
This repository has been archived by the owner on Dec 2, 2022. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
keosariel authored Aug 7, 2021
1 parent 2a0fdec commit fafa2e5
Show file tree
Hide file tree
Showing 15 changed files with 634 additions and 0 deletions.
18 changes: 18 additions & 0 deletions _test.py
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())
38 changes: 38 additions & 0 deletions setup.py
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",
]
)
25 changes: 25 additions & 0 deletions supabase_client/__init__.py
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 added supabase_client/__pycache__/__init__.cpython-37.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added supabase_client/__pycache__/_utils.cpython-37.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
70 changes: 70 additions & 0 deletions supabase_client/_http_client.py
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
162 changes: 162 additions & 0 deletions supabase_client/_supabase_clients.py
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
Loading

0 comments on commit fafa2e5

Please sign in to comment.