Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[run]
branch = True

[report]
fail_under = 98
39 changes: 39 additions & 0 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Healthy Pull Request
on:
pull_request:
types: [opened, reopened, ready_for_review, synchronize]
jobs:
test-python:
timeout-minutes: 10
name: Test / OS ${{ matrix.os }} / Python ${{ matrix.python-version }}
strategy:
matrix:
os: [ubuntu-latest]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
runs-on: ${{ matrix.os }}

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Set up Poetry
uses: abatilo/actions-poetry@v3
with:
poetry-version: 1.8.4

- name: Install dependencies
run: poetry install

- name: Lint
run: poetry run ruff check

- name: Check formatting
run: poetry run ruff format --check

- name: Test
run: poetry run pytest --cov=./
97 changes: 96 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ supabase = "^2.10.0"
pytest = "^8.3.4"
ruff = "^0.8.2"
pytest-asyncio = "^0.24.0"
pytest-cov = "^6.0.0"

[build-system]
requires = ["poetry-core"]
Expand Down
34 changes: 32 additions & 2 deletions tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import Any
import pytest
from unittest.mock import patch

from tws import ClientException, create_client, create_async_client

from tws import AsyncClient, Client, ClientException, create_client, create_async_client

GOOD_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTczMzc3MDg4OCwiZXhwIjoyMDQ5MzAzNjg4fQ.geVaN_7Yg1tTj2UjibuSpV1_qTzyEjoBXoVR01X0s_M"
BAD_KEY = "not a valid JWT"
Expand All @@ -15,6 +15,7 @@
[
[None, GOOD_KEY, GOOD_URL, "Public key is required"],
[GOOD_KEY, None, GOOD_URL, "Secret key is required"],
[GOOD_KEY, GOOD_KEY, None, "API URL is required"],
[BAD_KEY, GOOD_KEY, GOOD_URL, "Malformed public key"],
[GOOD_KEY, BAD_KEY, GOOD_URL, "Malformed secret key"],
[GOOD_KEY, GOOD_KEY, BAD_URL, "Malformed API URL"],
Expand All @@ -28,12 +29,21 @@ def test_client_instantiation_exceptions(
assert exception_message in str(exc_info.value)


@patch("tws._sync.client.create_supabase_client")
def test_client_unknown_exception(supabase_client):
supabase_client.side_effect = Exception("Unknown error")
with pytest.raises(ClientException) as exc_info:
_ = create_client(GOOD_KEY, GOOD_KEY, GOOD_URL)
assert "Unable to create API client" in str(exc_info.value)


@pytest.mark.asyncio
@pytest.mark.parametrize(
"public_key,secret_key,api_url,exception_message",
[
[None, GOOD_KEY, GOOD_URL, "Public key is required"],
[GOOD_KEY, None, GOOD_URL, "Secret key is required"],
[GOOD_KEY, GOOD_KEY, None, "API URL is required"],
[BAD_KEY, GOOD_KEY, GOOD_URL, "Malformed public key"],
[GOOD_KEY, BAD_KEY, GOOD_URL, "Malformed secret key"],
[GOOD_KEY, GOOD_KEY, BAD_URL, "Malformed API URL"],
Expand All @@ -45,3 +55,23 @@ async def test_async_client_instantiation_exceptions(
with pytest.raises(ClientException) as exc_info:
_ = await create_async_client(public_key, secret_key, api_url)
assert exception_message in str(exc_info.value)


@pytest.mark.asyncio
@patch("tws._async.client.create_async_supabase_client")
async def test_async_client_unknown_exception(supabase_client):
supabase_client.side_effect = Exception("Unknown error")
with pytest.raises(ClientException) as exc_info:
_ = await create_async_client(GOOD_KEY, GOOD_KEY, GOOD_URL)
assert "Unable to create API client" in str(exc_info.value)


def test_client_instantiation():
client = create_client(GOOD_KEY, GOOD_KEY, GOOD_URL)
assert isinstance(client, Client)


@pytest.mark.asyncio
async def test_async_client_instantiation():
client = await create_async_client(GOOD_KEY, GOOD_KEY, GOOD_URL)
assert isinstance(client, AsyncClient)
5 changes: 2 additions & 3 deletions tws/_async/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ async def create(cls, public_key: str, secret_key: str, api_url: str):
raise ClientException("Malformed public key")
if "Invalid URL" in str(e):
raise ClientException("Malformed API URL")
raise ClientException("Unable to create API client")

return self


async def create_client(
public_key: str, secret_key: str, api_url="https://api.tuneni.ai"
):
async def create_client(public_key: str, secret_key: str, api_url: str):
return await AsyncClient.create(public_key, secret_key, api_url)
18 changes: 8 additions & 10 deletions tws/_sync/client.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
from supabase import create_client as create_supabase_client
from supabase import ClientOptions

from tws.base.client import TWSClient, ClientException


class SyncClient(TWSClient):
def __init__(
self,
public_key: str,
secret_key: str,
api_url: str,
):
super().__init__(public_key, secret_key, api_url)
@classmethod
def create(cls, public_key: str, secret_key: str, api_url: str):
self = cls(public_key, secret_key, api_url)
try:
self.api_client = create_supabase_client(
api_url, public_key, self.api_client_options
Expand All @@ -21,7 +16,10 @@ def __init__(
raise ClientException("Malformed public key")
if "Invalid URL" in str(e):
raise ClientException("Malformed API URL")
raise ClientException("Unable to create API client")

return self

def create_client(public_key: str, secret_key: str, api_url="https://api.tuneni.ai"):
return SyncClient(public_key, secret_key, api_url)

def create_client(public_key: str, secret_key: str, api_url: str):
return SyncClient.create(public_key, secret_key, api_url)
15 changes: 0 additions & 15 deletions tws/client.py

This file was deleted.

1 change: 0 additions & 1 deletion tws/version.py

This file was deleted.

Loading