reportify-sdk 0.1.0__py3-none-any.whl

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,31 @@
1
+ """
2
+ Reportify SDK - Python client for Reportify API
3
+
4
+ A user-friendly SDK for accessing financial data, document search,
5
+ and knowledge base through the Reportify API.
6
+
7
+ Usage:
8
+ from reportify_sdk import Reportify
9
+
10
+ client = Reportify(api_key="your-api-key")
11
+ docs = client.search("Tesla earnings", num=10)
12
+ """
13
+
14
+ from reportify_sdk.client import Reportify
15
+ from reportify_sdk.exceptions import (
16
+ ReportifyError,
17
+ AuthenticationError,
18
+ RateLimitError,
19
+ NotFoundError,
20
+ APIError,
21
+ )
22
+
23
+ __version__ = "0.1.0"
24
+ __all__ = [
25
+ "Reportify",
26
+ "ReportifyError",
27
+ "AuthenticationError",
28
+ "RateLimitError",
29
+ "NotFoundError",
30
+ "APIError",
31
+ ]
@@ -0,0 +1,362 @@
1
+ """
2
+ Reportify Client
3
+
4
+ Main client class for interacting with the Reportify API.
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ import httpx
10
+
11
+ from reportify_sdk.exceptions import (
12
+ APIError,
13
+ AuthenticationError,
14
+ NotFoundError,
15
+ RateLimitError,
16
+ )
17
+
18
+
19
+ class Reportify:
20
+ """
21
+ Reportify API Client
22
+
23
+ A user-friendly client for accessing financial data, document search,
24
+ and knowledge base through the Reportify API.
25
+
26
+ Args:
27
+ api_key: Your Reportify API key
28
+ base_url: Base URL for the API (default: https://api.reportify.cn)
29
+ timeout: Request timeout in seconds (default: 30)
30
+
31
+ Example:
32
+ >>> from reportify_sdk import Reportify
33
+ >>> client = Reportify(api_key="your-api-key")
34
+ >>> docs = client.search("Tesla earnings", num=10)
35
+ """
36
+
37
+ DEFAULT_BASE_URL = "https://api.reportify.cn"
38
+
39
+ def __init__(
40
+ self,
41
+ api_key: str,
42
+ base_url: str | None = None,
43
+ timeout: float = 30.0,
44
+ ):
45
+ if not api_key:
46
+ raise AuthenticationError("API key is required")
47
+
48
+ self.api_key = api_key
49
+ self.base_url = (base_url or self.DEFAULT_BASE_URL).rstrip("/")
50
+ self.timeout = timeout
51
+
52
+ self._client = httpx.Client(
53
+ base_url=self.base_url,
54
+ headers=self._get_headers(),
55
+ timeout=timeout,
56
+ )
57
+
58
+ # Initialize sub-modules (lazy loading)
59
+ self._stock = None
60
+ self._timeline = None
61
+ self._kb = None
62
+ self._docs = None
63
+ self._tools = None
64
+
65
+ def _get_headers(self) -> dict[str, str]:
66
+ """Get default headers for API requests"""
67
+ return {
68
+ "Authorization": f"Bearer {self.api_key}",
69
+ "Content-Type": "application/json",
70
+ "User-Agent": "reportify-sdk-python/0.1.0",
71
+ }
72
+
73
+ def _request(
74
+ self,
75
+ method: str,
76
+ path: str,
77
+ params: dict[str, Any] | None = None,
78
+ json: dict[str, Any] | None = None,
79
+ ) -> dict[str, Any]:
80
+ """
81
+ Make an HTTP request to the API
82
+
83
+ Args:
84
+ method: HTTP method (GET, POST, etc.)
85
+ path: API endpoint path
86
+ params: Query parameters
87
+ json: JSON body data
88
+
89
+ Returns:
90
+ Response data as dictionary
91
+
92
+ Raises:
93
+ AuthenticationError: If API key is invalid
94
+ RateLimitError: If rate limit is exceeded
95
+ NotFoundError: If resource is not found
96
+ APIError: For other API errors
97
+ """
98
+ try:
99
+ response = self._client.request(
100
+ method=method,
101
+ url=path,
102
+ params=params,
103
+ json=json,
104
+ )
105
+
106
+ # Handle error responses
107
+ if response.status_code == 401:
108
+ raise AuthenticationError()
109
+ elif response.status_code == 429:
110
+ raise RateLimitError()
111
+ elif response.status_code == 404:
112
+ raise NotFoundError()
113
+ elif response.status_code >= 400:
114
+ error_msg = response.text
115
+ try:
116
+ error_data = response.json()
117
+ error_msg = error_data.get("detail", error_data.get("message", error_msg))
118
+ except Exception:
119
+ pass
120
+ raise APIError(error_msg, status_code=response.status_code)
121
+
122
+ return response.json()
123
+
124
+ except httpx.TimeoutException:
125
+ raise APIError("Request timeout", status_code=408)
126
+ except httpx.RequestError as e:
127
+ raise APIError(f"Request failed: {str(e)}")
128
+
129
+ def _get(self, path: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
130
+ """Make a GET request"""
131
+ return self._request("GET", path, params=params)
132
+
133
+ def _post(self, path: str, json: dict[str, Any] | None = None) -> dict[str, Any]:
134
+ """Make a POST request"""
135
+ return self._request("POST", path, json=json)
136
+
137
+ # -------------------------------------------------------------------------
138
+ # Search Methods
139
+ # -------------------------------------------------------------------------
140
+
141
+ def search(
142
+ self,
143
+ query: str,
144
+ *,
145
+ num: int = 10,
146
+ categories: list[str] | None = None,
147
+ symbols: list[str] | None = None,
148
+ start_date: str | None = None,
149
+ end_date: str | None = None,
150
+ ) -> list[dict[str, Any]]:
151
+ """
152
+ Search documents across all categories
153
+
154
+ Args:
155
+ query: Search query string
156
+ num: Number of results to return (default: 10, max: 100)
157
+ categories: Filter by document categories
158
+ symbols: Filter by stock symbols (e.g., ["US:AAPL", "HK:0700"])
159
+ start_date: Start date filter (YYYY-MM-DD)
160
+ end_date: End date filter (YYYY-MM-DD)
161
+
162
+ Returns:
163
+ List of matching documents
164
+
165
+ Example:
166
+ >>> docs = client.search("Tesla earnings", num=10)
167
+ >>> for doc in docs:
168
+ ... print(doc["title"])
169
+ """
170
+ data = {
171
+ "query": query,
172
+ "num": num,
173
+ }
174
+ if categories:
175
+ data["categories"] = categories
176
+ if symbols:
177
+ data["symbols"] = symbols
178
+ if start_date:
179
+ data["start_date"] = start_date
180
+ if end_date:
181
+ data["end_date"] = end_date
182
+
183
+ response = self._post("/v2/search", json=data)
184
+ return response.get("docs", [])
185
+
186
+ def search_news(
187
+ self,
188
+ query: str,
189
+ *,
190
+ num: int = 10,
191
+ symbols: list[str] | None = None,
192
+ start_date: str | None = None,
193
+ end_date: str | None = None,
194
+ ) -> list[dict[str, Any]]:
195
+ """
196
+ Search news articles
197
+
198
+ Args:
199
+ query: Search query string
200
+ num: Number of results to return
201
+ symbols: Filter by stock symbols
202
+ start_date: Start date filter (YYYY-MM-DD)
203
+ end_date: End date filter (YYYY-MM-DD)
204
+
205
+ Returns:
206
+ List of news articles
207
+ """
208
+ data = {"query": query, "num": num}
209
+ if symbols:
210
+ data["symbols"] = symbols
211
+ if start_date:
212
+ data["start_date"] = start_date
213
+ if end_date:
214
+ data["end_date"] = end_date
215
+
216
+ response = self._post("/v2/search/news", json=data)
217
+ return response.get("docs", [])
218
+
219
+ def search_reports(
220
+ self,
221
+ query: str,
222
+ *,
223
+ num: int = 10,
224
+ symbols: list[str] | None = None,
225
+ start_date: str | None = None,
226
+ end_date: str | None = None,
227
+ ) -> list[dict[str, Any]]:
228
+ """
229
+ Search research reports
230
+
231
+ Args:
232
+ query: Search query string
233
+ num: Number of results to return
234
+ symbols: Filter by stock symbols
235
+ start_date: Start date filter (YYYY-MM-DD)
236
+ end_date: End date filter (YYYY-MM-DD)
237
+
238
+ Returns:
239
+ List of research reports
240
+ """
241
+ data = {"query": query, "num": num}
242
+ if symbols:
243
+ data["symbols"] = symbols
244
+ if start_date:
245
+ data["start_date"] = start_date
246
+ if end_date:
247
+ data["end_date"] = end_date
248
+
249
+ response = self._post("/v2/search/reports", json=data)
250
+ return response.get("docs", [])
251
+
252
+ def search_filings(
253
+ self,
254
+ query: str,
255
+ *,
256
+ num: int = 10,
257
+ symbols: list[str] | None = None,
258
+ start_date: str | None = None,
259
+ end_date: str | None = None,
260
+ ) -> list[dict[str, Any]]:
261
+ """
262
+ Search company filings and announcements
263
+
264
+ Args:
265
+ query: Search query string
266
+ num: Number of results to return
267
+ symbols: Filter by stock symbols
268
+ start_date: Start date filter (YYYY-MM-DD)
269
+ end_date: End date filter (YYYY-MM-DD)
270
+
271
+ Returns:
272
+ List of filings
273
+ """
274
+ data = {"query": query, "num": num}
275
+ if symbols:
276
+ data["symbols"] = symbols
277
+ if start_date:
278
+ data["start_date"] = start_date
279
+ if end_date:
280
+ data["end_date"] = end_date
281
+
282
+ response = self._post("/v2/search/filings", json=data)
283
+ return response.get("docs", [])
284
+
285
+ def search_transcripts(
286
+ self,
287
+ query: str,
288
+ *,
289
+ num: int = 10,
290
+ symbols: list[str] | None = None,
291
+ start_date: str | None = None,
292
+ end_date: str | None = None,
293
+ ) -> list[dict[str, Any]]:
294
+ """
295
+ Search earnings call transcripts
296
+
297
+ Args:
298
+ query: Search query string
299
+ num: Number of results to return
300
+ symbols: Filter by stock symbols
301
+ start_date: Start date filter (YYYY-MM-DD)
302
+ end_date: End date filter (YYYY-MM-DD)
303
+
304
+ Returns:
305
+ List of transcripts
306
+ """
307
+ data = {"query": query, "num": num}
308
+ if symbols:
309
+ data["symbols"] = symbols
310
+ if start_date:
311
+ data["start_date"] = start_date
312
+ if end_date:
313
+ data["end_date"] = end_date
314
+
315
+ response = self._post("/v2/search/conference-calls", json=data)
316
+ return response.get("docs", [])
317
+
318
+ # -------------------------------------------------------------------------
319
+ # Sub-modules (lazy loading)
320
+ # -------------------------------------------------------------------------
321
+
322
+ @property
323
+ def stock(self):
324
+ """Stock data module for financial statements, prices, etc."""
325
+ if self._stock is None:
326
+ from reportify_sdk.stock import StockModule
327
+ self._stock = StockModule(self)
328
+ return self._stock
329
+
330
+ @property
331
+ def timeline(self):
332
+ """Timeline module for following companies, topics, etc."""
333
+ if self._timeline is None:
334
+ from reportify_sdk.timeline import TimelineModule
335
+ self._timeline = TimelineModule(self)
336
+ return self._timeline
337
+
338
+ @property
339
+ def kb(self):
340
+ """Knowledge base module for searching user uploaded documents"""
341
+ if self._kb is None:
342
+ from reportify_sdk.kb import KBModule
343
+ self._kb = KBModule(self)
344
+ return self._kb
345
+
346
+ @property
347
+ def docs(self):
348
+ """Documents module for accessing document content and metadata"""
349
+ if self._docs is None:
350
+ from reportify_sdk.docs import DocsModule
351
+ self._docs = DocsModule(self)
352
+ return self._docs
353
+
354
+ def close(self):
355
+ """Close the HTTP client"""
356
+ self._client.close()
357
+
358
+ def __enter__(self):
359
+ return self
360
+
361
+ def __exit__(self, exc_type, exc_val, exc_tb):
362
+ self.close()
reportify_sdk/docs.py ADDED
@@ -0,0 +1,173 @@
1
+ """
2
+ Documents Module
3
+
4
+ Provides access to document content, metadata, and summaries.
5
+ """
6
+
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ if TYPE_CHECKING:
10
+ from reportify_sdk.client import Reportify
11
+
12
+
13
+ class DocsModule:
14
+ """
15
+ Documents module for accessing document content and metadata
16
+
17
+ Access through the main client:
18
+ >>> client = Reportify(api_key="xxx")
19
+ >>> doc = client.docs.get("doc_id")
20
+ """
21
+
22
+ def __init__(self, client: "Reportify"):
23
+ self._client = client
24
+
25
+ def _get(self, path: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
26
+ return self._client._get(path, params=params)
27
+
28
+ def _post(self, path: str, json: dict[str, Any] | None = None) -> dict[str, Any]:
29
+ return self._client._post(path, json=json)
30
+
31
+ def get(self, doc_id: str) -> dict[str, Any]:
32
+ """
33
+ Get document content and metadata
34
+
35
+ Retrieves the full content of a document including chunks.
36
+
37
+ Args:
38
+ doc_id: Document ID
39
+
40
+ Returns:
41
+ Document dictionary with content and metadata
42
+
43
+ Example:
44
+ >>> doc = client.docs.get("abc123")
45
+ >>> print(doc["title"])
46
+ >>> print(doc["content"][:500])
47
+ """
48
+ return self._get(f"/v1/docs/{doc_id}")
49
+
50
+ def summary(self, doc_id: str) -> dict[str, Any]:
51
+ """
52
+ Get document summary
53
+
54
+ Retrieves the AI-generated summary of a document.
55
+
56
+ Args:
57
+ doc_id: Document ID
58
+
59
+ Returns:
60
+ Summary dictionary with title, summary text, and key points
61
+
62
+ Example:
63
+ >>> summary = client.docs.summary("abc123")
64
+ >>> print(summary["summary"])
65
+ >>> for point in summary.get("key_points", []):
66
+ ... print(f"- {point}")
67
+ """
68
+ return self._get(f"/v1/docs/{doc_id}/summary")
69
+
70
+ def raw_content(self, doc_id: str) -> dict[str, Any]:
71
+ """
72
+ Get raw document content
73
+
74
+ Retrieves the original content of a document without processing.
75
+
76
+ Args:
77
+ doc_id: Document ID
78
+
79
+ Returns:
80
+ Raw content dictionary
81
+ """
82
+ return self._get(f"/v1/docs/{doc_id}/raw-content")
83
+
84
+ def list(
85
+ self,
86
+ *,
87
+ symbols: list[str] | None = None,
88
+ categories: list[str] | None = None,
89
+ start_date: str | None = None,
90
+ end_date: str | None = None,
91
+ page: int = 1,
92
+ page_size: int = 20,
93
+ ) -> dict[str, Any]:
94
+ """
95
+ List documents with filters
96
+
97
+ Args:
98
+ symbols: Filter by stock symbols
99
+ categories: Filter by document categories
100
+ start_date: Start date filter (YYYY-MM-DD)
101
+ end_date: End date filter (YYYY-MM-DD)
102
+ page: Page number (default: 1)
103
+ page_size: Number of items per page (default: 20)
104
+
105
+ Returns:
106
+ Dictionary with documents list and pagination info
107
+
108
+ Example:
109
+ >>> result = client.docs.list(symbols=["US:AAPL"], page_size=10)
110
+ >>> for doc in result["docs"]:
111
+ ... print(doc["title"])
112
+ """
113
+ data: dict[str, Any] = {
114
+ "page_num": page,
115
+ "page_size": page_size,
116
+ }
117
+ if symbols:
118
+ data["symbols"] = symbols
119
+ if categories:
120
+ data["categories"] = categories
121
+ if start_date:
122
+ data["start_date"] = start_date
123
+ if end_date:
124
+ data["end_date"] = end_date
125
+
126
+ return self._post("/v1/docs", json=data)
127
+
128
+ def search_chunks(
129
+ self,
130
+ query: str,
131
+ *,
132
+ symbols: list[str] | None = None,
133
+ categories: list[str] | None = None,
134
+ start_date: str | None = None,
135
+ end_date: str | None = None,
136
+ num: int = 10,
137
+ ) -> list[dict[str, Any]]:
138
+ """
139
+ Search document chunks semantically
140
+
141
+ Performs semantic search across document chunks.
142
+
143
+ Args:
144
+ query: Search query string
145
+ symbols: Filter by stock symbols
146
+ categories: Filter by document categories
147
+ start_date: Start date filter (YYYY-MM-DD)
148
+ end_date: End date filter (YYYY-MM-DD)
149
+ num: Number of results to return
150
+
151
+ Returns:
152
+ List of matching chunks with document info
153
+
154
+ Example:
155
+ >>> chunks = client.docs.search_chunks("revenue guidance", num=5)
156
+ >>> for chunk in chunks:
157
+ ... print(chunk["content"])
158
+ """
159
+ data: dict[str, Any] = {
160
+ "query": query,
161
+ "num": num,
162
+ }
163
+ if symbols:
164
+ data["symbols"] = symbols
165
+ if categories:
166
+ data["categories"] = categories
167
+ if start_date:
168
+ data["start_date"] = start_date
169
+ if end_date:
170
+ data["end_date"] = end_date
171
+
172
+ response = self._post("/v1/search/chunks", json=data)
173
+ return response.get("chunks", [])
@@ -0,0 +1,41 @@
1
+ """
2
+ Reportify SDK Exceptions
3
+
4
+ Custom exception classes for handling API errors.
5
+ """
6
+
7
+
8
+ class ReportifyError(Exception):
9
+ """Base exception for Reportify SDK"""
10
+
11
+ def __init__(self, message: str, status_code: int | None = None):
12
+ self.message = message
13
+ self.status_code = status_code
14
+ super().__init__(self.message)
15
+
16
+
17
+ class AuthenticationError(ReportifyError):
18
+ """Raised when API key is invalid or missing"""
19
+
20
+ def __init__(self, message: str = "Invalid or missing API key"):
21
+ super().__init__(message, status_code=401)
22
+
23
+
24
+ class RateLimitError(ReportifyError):
25
+ """Raised when rate limit is exceeded"""
26
+
27
+ def __init__(self, message: str = "Rate limit exceeded"):
28
+ super().__init__(message, status_code=429)
29
+
30
+
31
+ class NotFoundError(ReportifyError):
32
+ """Raised when requested resource is not found"""
33
+
34
+ def __init__(self, message: str = "Resource not found"):
35
+ super().__init__(message, status_code=404)
36
+
37
+
38
+ class APIError(ReportifyError):
39
+ """Raised for general API errors"""
40
+
41
+ pass
reportify_sdk/kb.py ADDED
@@ -0,0 +1,76 @@
1
+ """
2
+ Knowledge Base Module
3
+
4
+ Provides access to user's personal knowledge base for searching
5
+ uploaded documents and folders.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ if TYPE_CHECKING:
11
+ from reportify_sdk.client import Reportify
12
+
13
+
14
+ class KBModule:
15
+ """
16
+ Knowledge base module for searching user uploaded documents
17
+
18
+ Access through the main client:
19
+ >>> client = Reportify(api_key="xxx")
20
+ >>> results = client.kb.search("revenue breakdown")
21
+ """
22
+
23
+ def __init__(self, client: "Reportify"):
24
+ self._client = client
25
+
26
+ def _post(self, path: str, json: dict[str, Any] | None = None) -> dict[str, Any]:
27
+ return self._client._post(path, json=json)
28
+
29
+ def search(
30
+ self,
31
+ query: str,
32
+ *,
33
+ folder_ids: list[str] | None = None,
34
+ doc_ids: list[str] | None = None,
35
+ start_date: str | None = None,
36
+ end_date: str | None = None,
37
+ num: int = 10,
38
+ ) -> list[dict[str, Any]]:
39
+ """
40
+ Search user's knowledge base
41
+
42
+ Performs semantic search across documents the user has uploaded
43
+ to their personal knowledge base.
44
+
45
+ Args:
46
+ query: Search query string
47
+ folder_ids: Filter by specific folder IDs
48
+ doc_ids: Filter by specific document IDs
49
+ start_date: Start date filter (YYYY-MM-DD)
50
+ end_date: End date filter (YYYY-MM-DD)
51
+ num: Number of results to return (default: 10)
52
+
53
+ Returns:
54
+ List of matching chunks with document information
55
+
56
+ Example:
57
+ >>> results = client.kb.search("quarterly revenue", num=5)
58
+ >>> for chunk in results:
59
+ ... print(chunk["content"][:100])
60
+ ... print(f"From: {chunk['doc']['title']}")
61
+ """
62
+ data: dict[str, Any] = {
63
+ "query": query,
64
+ "num": num,
65
+ }
66
+ if folder_ids:
67
+ data["folder_ids"] = folder_ids
68
+ if doc_ids:
69
+ data["doc_ids"] = doc_ids
70
+ if start_date:
71
+ data["start_date"] = start_date
72
+ if end_date:
73
+ data["end_date"] = end_date
74
+
75
+ response = self._post("/v1/tools/kb/search", json=data)
76
+ return response.get("chunks", [])