Skip to content

Commit cd109a0

Browse files
FEAT: Implement get_templates and enhance codebase
- Add SirenClient.get_templates method with unit tests. - Update example script for API key handling via .env. - Add python-dotenv and update Pyright configuration. - Refine comments and formatting for clarity.
1 parent 8e041e2 commit cd109a0

File tree

6 files changed

+244
-49
lines changed

6 files changed

+244
-49
lines changed

coverage.xml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?xml version="1.0" ?>
2+
<coverage version="7.8.2" timestamp="1749098591003" lines-valid="32" lines-covered="27" line-rate="0.8438" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
3+
<!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.8.2 -->
4+
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
5+
<sources>
6+
<source>/home/jithu/projects/siren-ai/siren</source>
7+
</sources>
8+
<packages>
9+
<package name="." line-rate="0.8438" branch-rate="0" complexity="0">
10+
<classes>
11+
<class name="__init__.py" filename="__init__.py" complexity="0" line-rate="1" branch-rate="0">
12+
<methods/>
13+
<lines>
14+
<line number="12" hits="1"/>
15+
</lines>
16+
</class>
17+
<class name="client.py" filename="client.py" complexity="0" line-rate="0.8387" branch-rate="0">
18+
<methods/>
19+
<lines>
20+
<line number="3" hits="1"/>
21+
<line number="5" hits="1"/>
22+
<line number="10" hits="1"/>
23+
<line number="14" hits="1"/>
24+
<line number="16" hits="1"/>
25+
<line number="22" hits="1"/>
26+
<line number="26" hits="1"/>
27+
<line number="46" hits="1"/>
28+
<line number="47" hits="1"/>
29+
<line number="51" hits="1"/>
30+
<line number="52" hits="1"/>
31+
<line number="53" hits="0"/>
32+
<line number="54" hits="1"/>
33+
<line number="55" hits="0"/>
34+
<line number="56" hits="1"/>
35+
<line number="57" hits="0"/>
36+
<line number="58" hits="1"/>
37+
<line number="59" hits="1"/>
38+
<line number="60" hits="1"/>
39+
<line number="61" hits="1"/>
40+
<line number="63" hits="1"/>
41+
<line number="64" hits="1"/>
42+
<line number="67" hits="1"/>
43+
<line number="68" hits="1"/>
44+
<line number="69" hits="1"/>
45+
<line number="73" hits="1"/>
46+
<line number="75" hits="1"/>
47+
<line number="76" hits="0"/>
48+
<line number="78" hits="0"/>
49+
<line number="79" hits="1"/>
50+
<line number="82" hits="1"/>
51+
</lines>
52+
</class>
53+
<class name="formatter.py" filename="formatter.py" complexity="0" line-rate="1" branch-rate="0">
54+
<methods/>
55+
<lines/>
56+
</class>
57+
<class name="templates.py" filename="templates.py" complexity="0" line-rate="1" branch-rate="0">
58+
<methods/>
59+
<lines/>
60+
</class>
61+
<class name="utils.py" filename="utils.py" complexity="0" line-rate="1" branch-rate="0">
62+
<methods/>
63+
<lines/>
64+
</class>
65+
<class name="webhooks.py" filename="webhooks.py" complexity="0" line-rate="1" branch-rate="0">
66+
<methods/>
67+
<lines/>
68+
</class>
69+
<class name="workflows.py" filename="workflows.py" complexity="0" line-rate="1" branch-rate="0">
70+
<methods/>
71+
<lines/>
72+
</class>
73+
</classes>
74+
</package>
75+
</packages>
76+
</coverage>

examples/basic_usage.py

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,38 @@
11
"""Basic usage examples for the Siren SDK."""
22

3-
# examples/basic_usage.py
4-
5-
# This file will demonstrate basic usage of the SirenClient.
6-
7-
# from siren import SirenClient # This will work once the package is installed
8-
93
# For local development, you might need to adjust sys.path:
104
import os
115
import sys
126

7+
from dotenv import load_dotenv
8+
139
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
1410

1511
from siren.client import SirenClient
1612

1713
if __name__ == "__main__":
18-
print("Running Siren SDK basic usage example...")
14+
load_dotenv() # Load environment variables from .env file
1915

20-
# Replace 'YOUR_API_KEY' with a real or test API key
21-
api_key = "YOUR_API_KEY"
22-
if api_key == "YOUR_API_KEY":
23-
print("Please replace 'YOUR_API_KEY' with an actual API key to test.")
24-
# exit(1)
16+
api_key = os.getenv("SIREN_API_KEY")
17+
18+
if not api_key:
19+
print("Error: SIREN_API_KEY not found in .env file or environment variables.")
20+
print(
21+
"Please create a .env file in the project root with SIREN_API_KEY='your_key'"
22+
)
23+
sys.exit(1)
2524

2625
client = SirenClient(api_key=api_key)
2726

28-
# Example: Send a message (this will be implemented later)
29-
# try:
30-
# response = client.send_message({
31-
# "to": "user@example.com",
32-
# "template": "ai_task_completed",
33-
# "data": {
34-
# "task_name": "Data Cleanup",
35-
# "result": "Success"
36-
# }
37-
# })
38-
# print(f"Message sent successfully: {response}")
39-
# except Exception as e:
40-
# print(f"Error sending message: {e}")
41-
42-
print("Basic usage example finished.")
27+
try:
28+
templates_response = client.get_templates(
29+
page=0, size=5
30+
) # Get first 5 templates
31+
print("Successfully fetched templates:")
32+
import json # For pretty printing
33+
34+
print(json.dumps(templates_response, indent=2))
35+
except Exception as e:
36+
print(f"Error fetching templates: {e}")
37+
38+
print("\nBasic usage example finished.")

pyproject.toml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ dev = [
4949
"pyright", # Static type checker
5050
"pre-commit", # For managing pre-commit hooks
5151
"uv", # Explicitly list if desired, often installed globally
52+
"python-dotenv", # For loading .env files
5253
# Consider adding 'build' and 'twine' for release management
5354
# "build",
5455
# "twine",
@@ -114,10 +115,16 @@ skip-magic-trailing-comma = false
114115
line-ending = "auto"
115116

116117
# Pyright configuration (static type checker)
117-
# Often works well with defaults. Can also be in 'pyrightconfig.json'.
118118
[tool.pyright]
119-
# include = ["siren"] # Files/directories to analyze
120-
# exclude = ["**/__pycache__", "tests", "examples", ".venv"] # Files/directories to ignore
121-
# reportMissingImports = true
122-
# pythonVersion = "3.8"
123-
# pythonPlatform = "Linux"
119+
include = ["siren", "tests", "examples"]
120+
exclude = [
121+
"**/__pycache__",
122+
".venv",
123+
".git",
124+
".ruff_cache",
125+
# Add other typical cache/build directories if needed, e.g., "dist", "build"
126+
]
127+
reportMissingImports = true
128+
# reportUnusedImport = true # Optional: to report unused imports
129+
# pythonVersion = "3.8" # Usually inferred or can be set if specific behavior is needed
130+
# pythonPlatform = "Linux" # Usually inferred

siren/client.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,73 @@
11
"""Siren API client implementation."""
22

3-
# siren/client.py
3+
from typing import Any, Dict, Optional
4+
5+
import requests
46

57

68
class SirenClient:
79
"""Client for interacting with the Siren API."""
810

11+
# TODO: Implement logic to select API URL based on API key type (dev/prod) or environment variable
12+
BASE_API_URL = "https://api.dev.trysiren.io/api/v1/public"
13+
914
def __init__(self, api_key: str):
1015
"""Initialize the SirenClient.
1116
1217
Args:
1318
api_key: The API key for authentication.
1419
"""
1520
self.api_key = api_key
16-
# We will add base_url and other configurations later
17-
print(f"SirenClient initialized with API key: {api_key[:5]}...")
1821

19-
# MVP methods will be added here
22+
def get_templates(
23+
self,
24+
tag_names: Optional[str] = None,
25+
search: Optional[str] = None,
26+
sort: Optional[str] = None,
27+
page: Optional[int] = None,
28+
size: Optional[int] = None,
29+
) -> Dict[str, Any]:
30+
"""Fetch templates.
31+
32+
Args:
33+
tag_names: Filter by tag names.
34+
search: Search by field.
35+
sort: Sort by field.
36+
page: Page number.
37+
size: Page size.
38+
39+
Returns:
40+
A dictionary containing the API response.
41+
"""
42+
endpoint = f"{self.BASE_API_URL}/template"
43+
headers = {
44+
"Authorization": f"Bearer {self.api_key}",
45+
"Accept": "application/json",
46+
}
47+
params: Dict[str, Any] = {}
48+
if tag_names is not None:
49+
params["tagNames"] = tag_names
50+
if search is not None:
51+
params["search"] = search
52+
if sort is not None:
53+
params["sort"] = sort
54+
if page is not None:
55+
params["page"] = page
56+
if size is not None:
57+
params["size"] = size
58+
59+
try:
60+
response = requests.get(
61+
endpoint, headers=headers, params=params, timeout=10
62+
)
63+
response.raise_for_status() # Raises HTTPError for bad responses (4XX or 5XX)
64+
return response.json()
65+
except requests.exceptions.HTTPError as http_err:
66+
# Attempt to return JSON error from response, otherwise re-raise HTTPError
67+
try:
68+
return http_err.response.json()
69+
except requests.exceptions.JSONDecodeError:
70+
raise http_err
71+
except requests.exceptions.RequestException as req_err:
72+
# For non-HTTP request issues (e.g., network, timeout)
73+
raise req_err

tests/test_client.py

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
"""Tests for the Siren API client."""
22

3-
# tests/test_client.py
4-
53
import os
64

7-
# from siren.client import SirenClient # Adjust import based on package structure
85
# For local development, you might need to adjust sys.path:
96
import sys
107

118
import pytest
9+
import requests
1210

1311
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
1412

@@ -24,14 +22,51 @@ def client():
2422
def test_siren_client_initialization(client):
2523
"""Test that the SirenClient initializes correctly."""
2624
assert client.api_key == "test_api_key", "API key should be set on initialization"
27-
print("SirenClient initialized successfully for testing.")
2825

2926

30-
# We will add more tests here as we implement features.
31-
# For example:
32-
# def test_send_message_success(client, mocker):
33-
# mock_response = mocker.Mock(status_code=200, json=lambda: {"id": "msg_123", "status": "sent"})
34-
# mocker.patch("requests.post", return_value=mock_response)
35-
# # Simulate API call
36-
# response = client.send_message({"to": "test@example.com", "message": "Hello"})
37-
# assert response["id"] == "msg_123"
27+
def test_get_templates_success(client, requests_mock):
28+
"""Test successful retrieval of templates."""
29+
mock_response_data = {
30+
"data": {
31+
"content": [
32+
{"id": "tpl_1", "name": "Test Template 1"},
33+
{"id": "tpl_2", "name": "Test Template 2"},
34+
],
35+
"totalElements": 2,
36+
}
37+
}
38+
requests_mock.get(
39+
f"{client.BASE_API_URL}/template",
40+
json=mock_response_data,
41+
status_code=200,
42+
)
43+
44+
response = client.get_templates(page=0, size=10)
45+
assert response == mock_response_data
46+
assert len(response["data"]["content"]) == 2
47+
assert response["data"]["content"][0]["name"] == "Test Template 1"
48+
49+
50+
def test_get_templates_http_error(client, requests_mock):
51+
"""Test handling of HTTP error when getting templates."""
52+
error_response_data = {
53+
"error": {"errorCode": "UNAUTHORISED", "message": "Invalid API Key"}
54+
}
55+
requests_mock.get(
56+
f"{client.BASE_API_URL}/template",
57+
json=error_response_data,
58+
status_code=401,
59+
)
60+
61+
response = client.get_templates()
62+
assert response == error_response_data
63+
64+
65+
def test_get_templates_network_error(client, requests_mock):
66+
"""Test handling of a network error when getting templates."""
67+
requests_mock.get(
68+
f"{client.BASE_API_URL}/template", exc=requests.exceptions.ConnectTimeout
69+
)
70+
71+
with pytest.raises(requests.exceptions.ConnectTimeout):
72+
client.get_templates()

uv.lock

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)