raredaysai 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,80 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # env files (can opt-in for committing if needed)
34
+ .env*
35
+ !.env.example
36
+
37
+ # sst
38
+ .sst/
39
+ sst-env.d.ts
40
+ sst.pyi
41
+
42
+ # opennext
43
+ .open-next/
44
+
45
+ # uv (python reranker)
46
+ uv.lock
47
+
48
+ # pdf data
49
+ /pdfs/
50
+
51
+ # evaluation results
52
+ /eval-results/
53
+
54
+ # vercel
55
+ .vercel
56
+
57
+ # typescript
58
+ *.tsbuildinfo
59
+ next-env.d.ts
60
+ pdfs-full/
61
+ .deploy-config.json
62
+
63
+ # transcript usefulness identifier
64
+ gen_report.output
65
+ gen_report.py
66
+ transcript_assessment.txt
67
+ transcript_assessment_final.txt
68
+ transcript_groups.txt
69
+ transcript_hashes.txt
70
+ transcript_unique_groups.txt
71
+
72
+ # Monorepo
73
+ packages/*/node_modules
74
+ packages/*/.next
75
+ packages/*/dist
76
+ packages/*/.env.local
77
+ .turbo
78
+
79
+ # Local planning (superpowers)
80
+ docs/superpowers/
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: raredaysai
3
+ Version: 0.1.0
4
+ Summary: RareDaysAI Python SDK — RAG chatbot platform client
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.9
7
+ Requires-Dist: httpx>=0.27
8
+ Provides-Extra: dev
9
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
10
+ Requires-Dist: pytest>=8; extra == 'dev'
11
+ Requires-Dist: respx>=0.22; extra == 'dev'
@@ -0,0 +1,32 @@
1
+ # raredaysai
2
+
3
+ Python SDK for [RareDaysAI](https://raredays.ai) — RAG chatbot platform.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install raredaysai
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from raredaysai import RareDaysClient
15
+
16
+ client = RareDaysClient(secret_key="sk_live_xxx")
17
+
18
+ # Chat
19
+ response = client.chat.send("What activities help with dementia?")
20
+ print(response.text)
21
+
22
+ # Sessions (for frontend auth)
23
+ session = client.sessions.create("user_123")
24
+ print(session.token)
25
+
26
+ # Config
27
+ client.config.push({"identity": "You are a helpful bot", "rules": ["Be kind"]})
28
+
29
+ # Sync content
30
+ result = client.connectors.sync("cms")
31
+ print(f"Synced: {result.new_docs} new, {result.updated_docs} updated")
32
+ ```
@@ -0,0 +1,14 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "raredaysai"
7
+ version = "0.1.0"
8
+ description = "RareDaysAI Python SDK — RAG chatbot platform client"
9
+ requires-python = ">=3.9"
10
+ license = "MIT"
11
+ dependencies = ["httpx>=0.27"]
12
+
13
+ [project.optional-dependencies]
14
+ dev = ["pytest>=8", "pytest-asyncio>=0.24", "respx>=0.22"]
@@ -0,0 +1,9 @@
1
+ from .client import RareDaysClient, AsyncRareDaysClient
2
+ from .exceptions import RareDaysError
3
+ from .types import ChatResponse, Citation, SessionResponse, ConfigPushResponse, ConfigPullResponse, SyncResult
4
+
5
+ __all__ = [
6
+ "RareDaysClient", "AsyncRareDaysClient", "RareDaysError",
7
+ "ChatResponse", "Citation", "SessionResponse",
8
+ "ConfigPushResponse", "ConfigPullResponse", "SyncResult",
9
+ ]
@@ -0,0 +1,163 @@
1
+ from __future__ import annotations
2
+ from typing import Optional
3
+ import httpx
4
+ from .types import ChatResponse, Citation, SessionResponse, ConfigPushResponse, ConfigPullResponse, SyncResult
5
+ from .exceptions import RareDaysError
6
+
7
+
8
+ class _ChatNamespace:
9
+ def __init__(self, client: _BaseClient):
10
+ self._client = client
11
+
12
+ def send(self, message: str, conversation_id: Optional[str] = None) -> ChatResponse:
13
+ data = {"messages": [{"role": "user", "content": message}]}
14
+ if conversation_id:
15
+ data["conversationId"] = conversation_id
16
+ resp = self._client._request("POST", "/v1/chat/sync", json=data)
17
+ return ChatResponse(
18
+ text=resp["text"],
19
+ conversation_id=resp.get("conversationId"),
20
+ citations=[Citation(**c) for c in resp.get("citations", [])],
21
+ )
22
+
23
+
24
+ class _SessionsNamespace:
25
+ def __init__(self, client: _BaseClient):
26
+ self._client = client
27
+
28
+ def create(self, user_id: str, metadata: Optional[dict] = None, expires_in: Optional[str] = None) -> SessionResponse:
29
+ data: dict = {"userId": user_id}
30
+ if metadata:
31
+ data["metadata"] = metadata
32
+ if expires_in:
33
+ data["expiresIn"] = expires_in
34
+ resp = self._client._request("POST", "/v1/sessions", json=data)
35
+ return SessionResponse(token=resp["token"], expires_at=resp["expiresAt"])
36
+
37
+
38
+ class _ConfigNamespace:
39
+ def __init__(self, client: _BaseClient):
40
+ self._client = client
41
+
42
+ def push(self, config: dict) -> ConfigPushResponse:
43
+ resp = self._client._request("POST", "/v1/config/push", json={"config": config})
44
+ return ConfigPushResponse(version=resp["version"], pushed_at=resp["pushedAt"])
45
+
46
+ def pull(self) -> ConfigPullResponse:
47
+ resp = self._client._request("GET", "/v1/config/pull")
48
+ return ConfigPullResponse(config=resp["config"], version=resp["version"], pushed_at=resp["pushedAt"])
49
+
50
+
51
+ class _IngestNamespace:
52
+ def __init__(self, client: _BaseClient):
53
+ self._client = client
54
+
55
+ def upload(self, document_id: str, title: str, content: str, source_type: str = "api", metadata: Optional[dict] = None) -> dict:
56
+ data = {"documentId": document_id, "title": title, "content": content, "sourceType": source_type}
57
+ if metadata:
58
+ data["metadata"] = metadata
59
+ return self._client._request("POST", "/v1/ingest", json=data)
60
+
61
+ def delete(self, document_id: str) -> None:
62
+ self._client._request("DELETE", f"/v1/ingest/{document_id}")
63
+
64
+
65
+ class _ConnectorsNamespace:
66
+ def __init__(self, client: _BaseClient):
67
+ self._client = client
68
+
69
+ def sync(self, connector_id: str) -> SyncResult:
70
+ resp = self._client._request("POST", f"/v1/connectors/{connector_id}/sync")
71
+ r = resp["result"]
72
+ return SyncResult(
73
+ new_docs=r["newDocs"], updated_docs=r["updatedDocs"],
74
+ deleted_docs=r["deletedDocs"], skipped_docs=r["skippedDocs"],
75
+ failed_docs=r["failedDocs"], duration_ms=r["durationMs"],
76
+ errors=r.get("errors", []),
77
+ )
78
+
79
+ def status(self) -> list[dict]:
80
+ resp = self._client._request("GET", "/v1/connectors")
81
+ return resp["connectors"]
82
+
83
+
84
+ class _BaseClient:
85
+ def __init__(self, secret_key: Optional[str] = None, session_token: Optional[str] = None, base_url: str = "https://api.raredays.ai"):
86
+ if not secret_key and not session_token:
87
+ raise ValueError("secret_key or session_token is required")
88
+ self._token = secret_key or session_token
89
+ self._base_url = base_url.rstrip("/")
90
+ self._http = httpx.Client(
91
+ base_url=self._base_url,
92
+ headers={"Authorization": f"Bearer {self._token}", "Content-Type": "application/json"},
93
+ timeout=60,
94
+ )
95
+ self.chat = _ChatNamespace(self)
96
+ self.sessions = _SessionsNamespace(self)
97
+ self.config = _ConfigNamespace(self)
98
+ self.ingest = _IngestNamespace(self)
99
+ self.connectors = _ConnectorsNamespace(self)
100
+
101
+ def _request(self, method: str, path: str, **kwargs) -> dict:
102
+ resp = self._http.request(method, path, **kwargs)
103
+ if resp.status_code >= 400:
104
+ try:
105
+ body = resp.json()
106
+ error = body.get("error", {})
107
+ raise RareDaysError(error.get("code", "unknown"), error.get("message", resp.text), resp.status_code)
108
+ except (ValueError, KeyError):
109
+ raise RareDaysError("unknown", resp.text, resp.status_code)
110
+ if resp.status_code == 204:
111
+ return {}
112
+ return resp.json()
113
+
114
+ def close(self):
115
+ self._http.close()
116
+
117
+ def __enter__(self):
118
+ return self
119
+
120
+ def __exit__(self, *args):
121
+ self.close()
122
+
123
+
124
+ class RareDaysClient(_BaseClient):
125
+ """Synchronous RareDaysAI client."""
126
+ pass
127
+
128
+
129
+ class AsyncRareDaysClient:
130
+ """Async RareDaysAI client using httpx.AsyncClient."""
131
+
132
+ def __init__(self, secret_key: Optional[str] = None, session_token: Optional[str] = None, base_url: str = "https://api.raredays.ai"):
133
+ if not secret_key and not session_token:
134
+ raise ValueError("secret_key or session_token is required")
135
+ self._token = secret_key or session_token
136
+ self._base_url = base_url.rstrip("/")
137
+ self._http = httpx.AsyncClient(
138
+ base_url=self._base_url,
139
+ headers={"Authorization": f"Bearer {self._token}", "Content-Type": "application/json"},
140
+ timeout=60,
141
+ )
142
+
143
+ async def _request(self, method: str, path: str, **kwargs) -> dict:
144
+ resp = await self._http.request(method, path, **kwargs)
145
+ if resp.status_code >= 400:
146
+ try:
147
+ body = resp.json()
148
+ error = body.get("error", {})
149
+ raise RareDaysError(error.get("code", "unknown"), error.get("message", resp.text), resp.status_code)
150
+ except (ValueError, KeyError):
151
+ raise RareDaysError("unknown", resp.text, resp.status_code)
152
+ if resp.status_code == 204:
153
+ return {}
154
+ return resp.json()
155
+
156
+ async def close(self):
157
+ await self._http.aclose()
158
+
159
+ async def __aenter__(self):
160
+ return self
161
+
162
+ async def __aexit__(self, *args):
163
+ await self.close()
@@ -0,0 +1,5 @@
1
+ class RareDaysError(Exception):
2
+ def __init__(self, code: str, message: str, status: int):
3
+ super().__init__(message)
4
+ self.code = code
5
+ self.status = status
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field
3
+ from typing import Optional
4
+
5
+
6
+ @dataclass
7
+ class Citation:
8
+ index: int
9
+ title: str
10
+ section: Optional[str] = None
11
+ page: Optional[int] = None
12
+
13
+
14
+ @dataclass
15
+ class ChatResponse:
16
+ text: str
17
+ conversation_id: Optional[str] = None
18
+ citations: list[Citation] = field(default_factory=list)
19
+
20
+
21
+ @dataclass
22
+ class SessionResponse:
23
+ token: str
24
+ expires_at: str
25
+
26
+
27
+
28
+ @dataclass
29
+ class ConfigPushResponse:
30
+ version: int
31
+ pushed_at: str
32
+
33
+
34
+ @dataclass
35
+ class ConfigPullResponse:
36
+ config: dict
37
+ version: int
38
+ pushed_at: str
39
+
40
+
41
+ @dataclass
42
+ class SyncResult:
43
+ new_docs: int
44
+ updated_docs: int
45
+ deleted_docs: int
46
+ skipped_docs: int
47
+ failed_docs: int
48
+ duration_ms: int
49
+ errors: list[dict] = field(default_factory=list)
File without changes
@@ -0,0 +1,8 @@
1
+ import pytest
2
+
3
+
4
+ @pytest.fixture
5
+ def respx_mock():
6
+ import respx
7
+ with respx.mock:
8
+ yield respx.mock
@@ -0,0 +1,82 @@
1
+ import pytest
2
+ import httpx
3
+ from raredaysai import RareDaysClient, RareDaysError, ChatResponse, SessionResponse
4
+
5
+
6
+ def test_requires_auth():
7
+ with pytest.raises(ValueError, match="secret_key or session_token"):
8
+ RareDaysClient()
9
+
10
+
11
+ def test_chat_send(respx_mock):
12
+ respx_mock.post("https://api.raredays.ai/v1/chat/sync").mock(
13
+ return_value=httpx.Response(200, json={
14
+ "text": "Hello world",
15
+ "conversationId": "conv_123",
16
+ "citations": [{"index": 1, "title": "Doc A"}],
17
+ })
18
+ )
19
+ client = RareDaysClient(secret_key="sk_test_xxx")
20
+ resp = client.chat.send("Hi")
21
+ assert isinstance(resp, ChatResponse)
22
+ assert resp.text == "Hello world"
23
+ assert resp.conversation_id == "conv_123"
24
+ assert len(resp.citations) == 1
25
+
26
+
27
+ def test_session_create(respx_mock):
28
+ respx_mock.post("https://api.raredays.ai/v1/sessions").mock(
29
+ return_value=httpx.Response(200, json={"token": "jwt_xxx", "expiresAt": "2026-05-25T00:00:00Z"})
30
+ )
31
+ client = RareDaysClient(secret_key="sk_test_xxx")
32
+ resp = client.sessions.create("user_123")
33
+ assert isinstance(resp, SessionResponse)
34
+ assert resp.token == "jwt_xxx"
35
+
36
+
37
+ def test_config_push(respx_mock):
38
+ respx_mock.post("https://api.raredays.ai/v1/config/push").mock(
39
+ return_value=httpx.Response(200, json={"version": 1, "pushedAt": "2026-05-25T00:00:00Z"})
40
+ )
41
+ client = RareDaysClient(secret_key="sk_test_xxx")
42
+ resp = client.config.push({"identity": "Test"})
43
+ assert resp.version == 1
44
+
45
+
46
+ def test_config_pull(respx_mock):
47
+ respx_mock.get("https://api.raredays.ai/v1/config/pull").mock(
48
+ return_value=httpx.Response(200, json={"config": {"identity": "Test"}, "version": 2, "pushedAt": "2026-05-25T00:00:00Z"})
49
+ )
50
+ client = RareDaysClient(secret_key="sk_test_xxx")
51
+ resp = client.config.pull()
52
+ assert resp.config["identity"] == "Test"
53
+
54
+
55
+ def test_error_handling(respx_mock):
56
+ respx_mock.post("https://api.raredays.ai/v1/chat/sync").mock(
57
+ return_value=httpx.Response(401, json={"error": {"code": "unauthorized", "message": "Invalid key"}})
58
+ )
59
+ client = RareDaysClient(secret_key="sk_bad")
60
+ with pytest.raises(RareDaysError) as exc:
61
+ client.chat.send("Hi")
62
+ assert exc.value.status == 401
63
+ assert exc.value.code == "unauthorized"
64
+
65
+
66
+ def test_connector_sync(respx_mock):
67
+ respx_mock.post("https://api.raredays.ai/v1/connectors/cms/sync").mock(
68
+ return_value=httpx.Response(200, json={"result": {
69
+ "newDocs": 2, "updatedDocs": 1, "deletedDocs": 0,
70
+ "skippedDocs": 5, "failedDocs": 0, "durationMs": 3500, "errors": [],
71
+ }})
72
+ )
73
+ client = RareDaysClient(secret_key="sk_test_xxx")
74
+ result = client.connectors.sync("cms")
75
+ assert result.new_docs == 2
76
+ assert result.duration_ms == 3500
77
+
78
+
79
+ def test_context_manager():
80
+ client = RareDaysClient(secret_key="sk_test_xxx")
81
+ with client as c:
82
+ assert c is client