reportify-sdk 0.2.9__tar.gz → 0.2.10__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.
Files changed (25) hide show
  1. {reportify_sdk-0.2.9/reportify_sdk.egg-info → reportify_sdk-0.2.10}/PKG-INFO +1 -1
  2. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/pyproject.toml +1 -1
  3. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/__init__.py +1 -1
  4. reportify_sdk-0.2.10/reportify_sdk/client.py +262 -0
  5. reportify_sdk-0.2.10/reportify_sdk/search.py +357 -0
  6. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10/reportify_sdk.egg-info}/PKG-INFO +1 -1
  7. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk.egg-info/SOURCES.txt +1 -0
  8. reportify_sdk-0.2.9/reportify_sdk/client.py +0 -580
  9. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/LICENSE +0 -0
  10. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/README.md +0 -0
  11. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/agent.py +0 -0
  12. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/channels.py +0 -0
  13. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/chat.py +0 -0
  14. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/concepts.py +0 -0
  15. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/docs.py +0 -0
  16. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/exceptions.py +0 -0
  17. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/kb.py +0 -0
  18. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/quant.py +0 -0
  19. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/stock.py +0 -0
  20. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/timeline.py +0 -0
  21. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk/user.py +0 -0
  22. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk.egg-info/dependency_links.txt +0 -0
  23. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk.egg-info/requires.txt +0 -0
  24. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/reportify_sdk.egg-info/top_level.txt +0 -0
  25. {reportify_sdk-0.2.9 → reportify_sdk-0.2.10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reportify-sdk
3
- Version: 0.2.9
3
+ Version: 0.2.10
4
4
  Summary: Python SDK for Reportify API - Financial data and document search
5
5
  Author-email: Reportify <support@reportify.cn>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "reportify-sdk"
7
- version = "0.2.9"
7
+ version = "0.2.10"
8
8
  description = "Python SDK for Reportify API - Financial data and document search"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -20,7 +20,7 @@ from reportify_sdk.exceptions import (
20
20
  APIError,
21
21
  )
22
22
 
23
- __version__ = "0.2.9"
23
+ __version__ = "0.2.10"
24
24
  __all__ = [
25
25
  "Reportify",
26
26
  "ReportifyError",
@@ -0,0 +1,262 @@
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
+ self._quant = None
65
+ self._concepts = None
66
+ self._channels = None
67
+ self._chat = None
68
+ self._agent = None
69
+ self._user = None
70
+ self._search = None
71
+
72
+ def _get_headers(self) -> dict[str, str]:
73
+ """Get default headers for API requests"""
74
+ return {
75
+ "Authorization": f"Bearer {self.api_key}",
76
+ "Content-Type": "application/json",
77
+ "User-Agent": "reportify-sdk-python/0.2.10",
78
+ }
79
+
80
+ def _request(
81
+ self,
82
+ method: str,
83
+ path: str,
84
+ params: dict[str, Any] | None = None,
85
+ json: dict[str, Any] | None = None,
86
+ ) -> dict[str, Any]:
87
+ """
88
+ Make an HTTP request to the API
89
+
90
+ Args:
91
+ method: HTTP method (GET, POST, etc.)
92
+ path: API endpoint path
93
+ params: Query parameters
94
+ json: JSON body data
95
+
96
+ Returns:
97
+ Response data as dictionary
98
+
99
+ Raises:
100
+ AuthenticationError: If API key is invalid
101
+ RateLimitError: If rate limit is exceeded
102
+ NotFoundError: If resource is not found
103
+ APIError: For other API errors
104
+ """
105
+ try:
106
+ response = self._client.request(
107
+ method=method,
108
+ url=path,
109
+ params=params,
110
+ json=json,
111
+ )
112
+
113
+ # Handle error responses
114
+ if response.status_code == 401:
115
+ raise AuthenticationError()
116
+ elif response.status_code == 429:
117
+ raise RateLimitError()
118
+ elif response.status_code == 404:
119
+ raise NotFoundError()
120
+ elif response.status_code >= 400:
121
+ error_msg = response.text
122
+ try:
123
+ error_data = response.json()
124
+ error_msg = error_data.get("detail", error_data.get("message", error_msg))
125
+ except Exception:
126
+ pass
127
+ raise APIError(error_msg, status_code=response.status_code)
128
+
129
+ return response.json()
130
+
131
+ except httpx.TimeoutException:
132
+ raise APIError("Request timeout", status_code=408)
133
+ except httpx.RequestError as e:
134
+ raise APIError(f"Request failed: {str(e)}")
135
+
136
+ def _get(self, path: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
137
+ """Make a GET request"""
138
+ return self._request("GET", path, params=params)
139
+
140
+ def _post(self, path: str, json: dict[str, Any] | None = None) -> dict[str, Any]:
141
+ """Make a POST request"""
142
+ return self._request("POST", path, json=json)
143
+
144
+ def _get_bytes(self, path: str) -> bytes:
145
+ """Make a GET request and return raw bytes (for file downloads)"""
146
+ try:
147
+ response = self._client.get(path)
148
+ response.raise_for_status()
149
+ return response.content
150
+ except httpx.HTTPStatusError as e:
151
+ status_code = e.response.status_code
152
+ if status_code == 401:
153
+ raise AuthenticationError("Invalid API key")
154
+ elif status_code == 404:
155
+ raise NotFoundError(f"Resource not found: {path}")
156
+ elif status_code == 429:
157
+ raise RateLimitError("Rate limit exceeded")
158
+ else:
159
+ raise APIError(f"API error: {status_code}")
160
+
161
+
162
+ # -------------------------------------------------------------------------
163
+ # Sub-modules (lazy loading)
164
+ # -------------------------------------------------------------------------
165
+
166
+ @property
167
+ def search(self):
168
+ """Search module for document search across all categories"""
169
+ if self._search is None:
170
+ from reportify_sdk.search import SearchModule
171
+ self._search = SearchModule(self)
172
+ return self._search
173
+
174
+ @property
175
+ def stock(self):
176
+ """Stock data module for financial statements, prices, etc."""
177
+ if self._stock is None:
178
+ from reportify_sdk.stock import StockModule
179
+ self._stock = StockModule(self)
180
+ return self._stock
181
+
182
+ @property
183
+ def timeline(self):
184
+ """Timeline module for following companies, topics, etc."""
185
+ if self._timeline is None:
186
+ from reportify_sdk.timeline import TimelineModule
187
+ self._timeline = TimelineModule(self)
188
+ return self._timeline
189
+
190
+ @property
191
+ def kb(self):
192
+ """Knowledge base module for searching user uploaded documents"""
193
+ if self._kb is None:
194
+ from reportify_sdk.kb import KBModule
195
+ self._kb = KBModule(self)
196
+ return self._kb
197
+
198
+ @property
199
+ def docs(self):
200
+ """Documents module for accessing document content and metadata"""
201
+ if self._docs is None:
202
+ from reportify_sdk.docs import DocsModule
203
+ self._docs = DocsModule(self)
204
+ return self._docs
205
+
206
+ @property
207
+ def quant(self):
208
+ """Quant module for indicators, factors, quotes, and backtesting"""
209
+ if self._quant is None:
210
+ from reportify_sdk.quant import QuantModule
211
+ self._quant = QuantModule(self)
212
+ return self._quant
213
+
214
+ @property
215
+ def concepts(self):
216
+ """Concepts module for accessing concept data and feeds"""
217
+ if self._concepts is None:
218
+ from reportify_sdk.concepts import ConceptsModule
219
+ self._concepts = ConceptsModule(self)
220
+ return self._concepts
221
+
222
+ @property
223
+ def channels(self):
224
+ """Channels module for searching and following channels"""
225
+ if self._channels is None:
226
+ from reportify_sdk.channels import ChannelsModule
227
+ self._channels = ChannelsModule(self)
228
+ return self._channels
229
+
230
+ @property
231
+ def chat(self):
232
+ """Chat module for document-based Q&A"""
233
+ if self._chat is None:
234
+ from reportify_sdk.chat import ChatModule
235
+ self._chat = ChatModule(self)
236
+ return self._chat
237
+
238
+ @property
239
+ def agent(self):
240
+ """Agent module for AI-powered conversations and workflows"""
241
+ if self._agent is None:
242
+ from reportify_sdk.agent import AgentModule
243
+ self._agent = AgentModule(self)
244
+ return self._agent
245
+
246
+ @property
247
+ def user(self):
248
+ """User module for user-related tools and data"""
249
+ if self._user is None:
250
+ from reportify_sdk.user import UserModule
251
+ self._user = UserModule(self)
252
+ return self._user
253
+
254
+ def close(self):
255
+ """Close the HTTP client"""
256
+ self._client.close()
257
+
258
+ def __enter__(self):
259
+ return self
260
+
261
+ def __exit__(self, exc_type, exc_val, exc_tb):
262
+ self.close()
@@ -0,0 +1,357 @@
1
+ """
2
+ Search Module
3
+
4
+ Provides document search functionality across various categories.
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 SearchModule:
14
+ """
15
+ Search module for document search functionality.
16
+
17
+ Access via client.search:
18
+ >>> results = client.search.all("Tesla earnings")
19
+ >>> news = client.search.news("Apple iPhone")
20
+ """
21
+
22
+ def __init__(self, client: "Reportify"):
23
+ self._client = client
24
+
25
+ def _post(self, path: str, json: dict[str, Any] | None = None) -> dict[str, Any]:
26
+ return self._client._post(path, json=json)
27
+
28
+ def all(
29
+ self,
30
+ query: str,
31
+ *,
32
+ num: int = 10,
33
+ categories: list[str] | None = None,
34
+ symbols: list[str] | None = None,
35
+ industries: list[str] | None = None,
36
+ channel_ids: list[str] | None = None,
37
+ start_datetime: str | None = None,
38
+ end_datetime: str | None = None,
39
+ ) -> list[dict[str, Any]]:
40
+ """
41
+ Search documents across all categories
42
+
43
+ Args:
44
+ query: Search query string
45
+ num: Number of results to return (default: 10, max: 100)
46
+ categories: Filter by categories (news, reports, filings, transcripts, socials)
47
+ symbols: Filter by stock symbols (e.g., ["US:AAPL", "HK:0700"])
48
+ industries: Filter by industries
49
+ channel_ids: Filter by channel IDs
50
+ start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
51
+ end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
52
+
53
+ Returns:
54
+ List of matching documents
55
+ """
56
+ data: dict[str, Any] = {"query": query, "num": num}
57
+ if categories:
58
+ data["categories"] = categories
59
+ if symbols:
60
+ data["symbols"] = symbols
61
+ if industries:
62
+ data["industries"] = industries
63
+ if channel_ids:
64
+ data["channel_ids"] = channel_ids
65
+ if start_datetime:
66
+ data["start_datetime"] = start_datetime
67
+ if end_datetime:
68
+ data["end_datetime"] = end_datetime
69
+
70
+ response = self._post("/v2/search", json=data)
71
+ return response.get("docs", [])
72
+
73
+ def news(
74
+ self,
75
+ query: str,
76
+ *,
77
+ num: int = 10,
78
+ symbols: list[str] | None = None,
79
+ channel_ids: list[str] | None = None,
80
+ start_datetime: str | None = None,
81
+ end_datetime: str | None = None,
82
+ ) -> list[dict[str, Any]]:
83
+ """
84
+ Search news articles
85
+
86
+ Args:
87
+ query: Search query string
88
+ num: Number of results to return
89
+ symbols: Filter by stock symbols
90
+ channel_ids: Filter by channel IDs
91
+ start_datetime: Start datetime filter
92
+ end_datetime: End datetime filter
93
+
94
+ Returns:
95
+ List of news documents
96
+ """
97
+ data: dict[str, Any] = {"query": query, "num": num}
98
+ if symbols:
99
+ data["symbols"] = symbols
100
+ if channel_ids:
101
+ data["channel_ids"] = channel_ids
102
+ if start_datetime:
103
+ data["start_datetime"] = start_datetime
104
+ if end_datetime:
105
+ data["end_datetime"] = end_datetime
106
+
107
+ response = self._post("/v2/search/news", json=data)
108
+ return response.get("docs", [])
109
+
110
+ def reports(
111
+ self,
112
+ query: str,
113
+ *,
114
+ num: int = 10,
115
+ symbols: list[str] | None = None,
116
+ industries: list[str] | None = None,
117
+ channel_ids: list[str] | None = None,
118
+ start_datetime: str | None = None,
119
+ end_datetime: str | None = None,
120
+ ) -> list[dict[str, Any]]:
121
+ """
122
+ Search research reports
123
+
124
+ Args:
125
+ query: Search query string
126
+ num: Number of results to return
127
+ symbols: Filter by stock symbols
128
+ industries: Filter by industries
129
+ channel_ids: Filter by channel IDs
130
+ start_datetime: Start datetime filter
131
+ end_datetime: End datetime filter
132
+
133
+ Returns:
134
+ List of research reports
135
+ """
136
+ data: dict[str, Any] = {"query": query, "num": num}
137
+ if symbols:
138
+ data["symbols"] = symbols
139
+ if industries:
140
+ data["industries"] = industries
141
+ if channel_ids:
142
+ data["channel_ids"] = channel_ids
143
+ if start_datetime:
144
+ data["start_datetime"] = start_datetime
145
+ if end_datetime:
146
+ data["end_datetime"] = end_datetime
147
+
148
+ response = self._post("/v2/search/reports", json=data)
149
+ return response.get("docs", [])
150
+
151
+ def filings(
152
+ self,
153
+ query: str,
154
+ symbols: list[str],
155
+ *,
156
+ num: int = 10,
157
+ start_datetime: str | None = None,
158
+ end_datetime: str | None = None,
159
+ ) -> list[dict[str, Any]]:
160
+ """
161
+ Search company filings
162
+
163
+ Args:
164
+ query: Search query string
165
+ symbols: Stock symbols to filter by (required)
166
+ num: Number of results to return
167
+ start_datetime: Start datetime filter
168
+ end_datetime: End datetime filter
169
+
170
+ Returns:
171
+ List of filing documents
172
+ """
173
+ data: dict[str, Any] = {"query": query, "symbols": symbols, "num": num}
174
+ if start_datetime:
175
+ data["start_datetime"] = start_datetime
176
+ if end_datetime:
177
+ data["end_datetime"] = end_datetime
178
+
179
+ response = self._post("/v2/search/filings", json=data)
180
+ return response.get("docs", [])
181
+
182
+ def conference_calls(
183
+ self,
184
+ query: str,
185
+ symbols: list[str],
186
+ *,
187
+ num: int = 10,
188
+ start_datetime: str | None = None,
189
+ end_datetime: str | None = None,
190
+ fiscal_year: str | None = None,
191
+ fiscal_quarter: str | None = None,
192
+ ) -> list[dict[str, Any]]:
193
+ """
194
+ Search earnings call transcripts and slides
195
+
196
+ Args:
197
+ query: Search query string
198
+ symbols: Stock symbols to filter by (required)
199
+ num: Number of results to return
200
+ start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
201
+ end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
202
+ fiscal_year: Fiscal year filter (e.g., '2025', '2026')
203
+ fiscal_quarter: Fiscal quarter filter (e.g., 'Q1', 'Q2', 'Q3', 'Q4')
204
+
205
+ Returns:
206
+ List of conference call documents
207
+ """
208
+ data: dict[str, Any] = {"query": query, "symbols": symbols, "num": num}
209
+ if start_datetime:
210
+ data["start_datetime"] = start_datetime
211
+ if end_datetime:
212
+ data["end_datetime"] = end_datetime
213
+ if fiscal_year:
214
+ data["fiscal_year"] = fiscal_year
215
+ if fiscal_quarter:
216
+ data["fiscal_quarter"] = fiscal_quarter
217
+
218
+ response = self._post("/v2/search/conference-calls", json=data)
219
+ return response.get("docs", [])
220
+
221
+ def earnings_pack(
222
+ self,
223
+ query: str,
224
+ symbols: list[str],
225
+ *,
226
+ num: int = 10,
227
+ start_datetime: str | None = None,
228
+ end_datetime: str | None = None,
229
+ fiscal_year: str | None = None,
230
+ fiscal_quarter: str | None = None,
231
+ ) -> list[dict[str, Any]]:
232
+ """
233
+ Search earnings financial reports
234
+
235
+ Args:
236
+ query: Search query string
237
+ symbols: Stock symbols to filter by (required)
238
+ num: Number of results to return
239
+ start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
240
+ end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
241
+ fiscal_year: Fiscal year filter (e.g., '2025', '2026')
242
+ fiscal_quarter: Fiscal quarter filter (e.g., 'Q1', 'Q2', 'Q3', 'Q4')
243
+
244
+ Returns:
245
+ List of earnings pack documents
246
+ """
247
+ data: dict[str, Any] = {"query": query, "symbols": symbols, "num": num}
248
+ if start_datetime:
249
+ data["start_datetime"] = start_datetime
250
+ if end_datetime:
251
+ data["end_datetime"] = end_datetime
252
+ if fiscal_year:
253
+ data["fiscal_year"] = fiscal_year
254
+ if fiscal_quarter:
255
+ data["fiscal_quarter"] = fiscal_quarter
256
+
257
+ response = self._post("/v2/search/earnings-pack", json=data)
258
+ return response.get("docs", [])
259
+
260
+ def minutes(
261
+ self,
262
+ query: str,
263
+ *,
264
+ num: int = 10,
265
+ symbols: list[str] | None = None,
266
+ start_datetime: str | None = None,
267
+ end_datetime: str | None = None,
268
+ ) -> list[dict[str, Any]]:
269
+ """
270
+ Search conference calls and IR (Investor Relations) meetings
271
+
272
+ Args:
273
+ query: Search query string
274
+ num: Number of results to return
275
+ symbols: Filter by stock symbols
276
+ start_datetime: Start datetime filter
277
+ end_datetime: End datetime filter
278
+
279
+ Returns:
280
+ List of minutes documents
281
+ """
282
+ data: dict[str, Any] = {"query": query, "num": num}
283
+ if symbols:
284
+ data["symbols"] = symbols
285
+ if start_datetime:
286
+ data["start_datetime"] = start_datetime
287
+ if end_datetime:
288
+ data["end_datetime"] = end_datetime
289
+
290
+ response = self._post("/v2/search/minutes", json=data)
291
+ return response.get("docs", [])
292
+
293
+ def socials(
294
+ self,
295
+ query: str,
296
+ *,
297
+ num: int = 10,
298
+ symbols: list[str] | None = None,
299
+ channel_ids: list[str] | None = None,
300
+ start_datetime: str | None = None,
301
+ end_datetime: str | None = None,
302
+ ) -> list[dict[str, Any]]:
303
+ """
304
+ Search social media content
305
+
306
+ Args:
307
+ query: Search query string
308
+ num: Number of results to return
309
+ symbols: Filter by stock symbols
310
+ channel_ids: Filter by channel IDs
311
+ start_datetime: Start datetime filter
312
+ end_datetime: End datetime filter
313
+
314
+ Returns:
315
+ List of social media documents
316
+ """
317
+ data: dict[str, Any] = {"query": query, "num": num}
318
+ if symbols:
319
+ data["symbols"] = symbols
320
+ if channel_ids:
321
+ data["channel_ids"] = channel_ids
322
+ if start_datetime:
323
+ data["start_datetime"] = start_datetime
324
+ if end_datetime:
325
+ data["end_datetime"] = end_datetime
326
+
327
+ response = self._post("/v2/search/socials", json=data)
328
+ return response.get("docs", [])
329
+
330
+ def webpages(
331
+ self,
332
+ query: str,
333
+ *,
334
+ num: int = 10,
335
+ start_datetime: str | None = None,
336
+ end_datetime: str | None = None,
337
+ ) -> list[dict[str, Any]]:
338
+ """
339
+ Search webpage content
340
+
341
+ Args:
342
+ query: Search query string
343
+ num: Number of results to return
344
+ start_datetime: Start datetime filter
345
+ end_datetime: End datetime filter
346
+
347
+ Returns:
348
+ List of webpage content
349
+ """
350
+ data: dict[str, Any] = {"query": query, "num": num}
351
+ if start_datetime:
352
+ data["start_datetime"] = start_datetime
353
+ if end_datetime:
354
+ data["end_datetime"] = end_datetime
355
+
356
+ response = self._post("/v2/search/webpages", json=data)
357
+ return response.get("docs", [])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reportify-sdk
3
- Version: 0.2.9
3
+ Version: 0.2.10
4
4
  Summary: Python SDK for Reportify API - Financial data and document search
5
5
  Author-email: Reportify <support@reportify.cn>
6
6
  License-Expression: MIT
@@ -11,6 +11,7 @@ reportify_sdk/docs.py
11
11
  reportify_sdk/exceptions.py
12
12
  reportify_sdk/kb.py
13
13
  reportify_sdk/quant.py
14
+ reportify_sdk/search.py
14
15
  reportify_sdk/stock.py
15
16
  reportify_sdk/timeline.py
16
17
  reportify_sdk/user.py
@@ -1,580 +0,0 @@
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
- self._quant = None
65
- self._concepts = None
66
- self._channels = None
67
- self._chat = None
68
- self._agent = None
69
- self._user = None
70
-
71
- def _get_headers(self) -> dict[str, str]:
72
- """Get default headers for API requests"""
73
- return {
74
- "Authorization": f"Bearer {self.api_key}",
75
- "Content-Type": "application/json",
76
- "User-Agent": "reportify-sdk-python/0.2.9",
77
- }
78
-
79
- def _request(
80
- self,
81
- method: str,
82
- path: str,
83
- params: dict[str, Any] | None = None,
84
- json: dict[str, Any] | None = None,
85
- ) -> dict[str, Any]:
86
- """
87
- Make an HTTP request to the API
88
-
89
- Args:
90
- method: HTTP method (GET, POST, etc.)
91
- path: API endpoint path
92
- params: Query parameters
93
- json: JSON body data
94
-
95
- Returns:
96
- Response data as dictionary
97
-
98
- Raises:
99
- AuthenticationError: If API key is invalid
100
- RateLimitError: If rate limit is exceeded
101
- NotFoundError: If resource is not found
102
- APIError: For other API errors
103
- """
104
- try:
105
- response = self._client.request(
106
- method=method,
107
- url=path,
108
- params=params,
109
- json=json,
110
- )
111
-
112
- # Handle error responses
113
- if response.status_code == 401:
114
- raise AuthenticationError()
115
- elif response.status_code == 429:
116
- raise RateLimitError()
117
- elif response.status_code == 404:
118
- raise NotFoundError()
119
- elif response.status_code >= 400:
120
- error_msg = response.text
121
- try:
122
- error_data = response.json()
123
- error_msg = error_data.get("detail", error_data.get("message", error_msg))
124
- except Exception:
125
- pass
126
- raise APIError(error_msg, status_code=response.status_code)
127
-
128
- return response.json()
129
-
130
- except httpx.TimeoutException:
131
- raise APIError("Request timeout", status_code=408)
132
- except httpx.RequestError as e:
133
- raise APIError(f"Request failed: {str(e)}")
134
-
135
- def _get(self, path: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
136
- """Make a GET request"""
137
- return self._request("GET", path, params=params)
138
-
139
- def _post(self, path: str, json: dict[str, Any] | None = None) -> dict[str, Any]:
140
- """Make a POST request"""
141
- return self._request("POST", path, json=json)
142
-
143
- def _get_bytes(self, path: str) -> bytes:
144
- """Make a GET request and return raw bytes (for file downloads)"""
145
- try:
146
- response = self._client.get(path)
147
- response.raise_for_status()
148
- return response.content
149
- except httpx.HTTPStatusError as e:
150
- status_code = e.response.status_code
151
- if status_code == 401:
152
- raise AuthenticationError("Invalid API key")
153
- elif status_code == 404:
154
- raise NotFoundError(f"Resource not found: {path}")
155
- elif status_code == 429:
156
- raise RateLimitError("Rate limit exceeded")
157
- else:
158
- raise APIError(f"API error: {status_code}")
159
-
160
- # -------------------------------------------------------------------------
161
- # Search Methods
162
- # -------------------------------------------------------------------------
163
-
164
- def search(
165
- self,
166
- query: str,
167
- *,
168
- num: int = 10,
169
- symbols: list[str] | None = None,
170
- industries: list[str] | None = None,
171
- channel_ids: list[str] | None = None,
172
- start_datetime: str | None = None,
173
- end_datetime: str | None = None,
174
- ) -> list[dict[str, Any]]:
175
- """
176
- Search documents across all categories
177
-
178
- Args:
179
- query: Search query string
180
- num: Number of results to return (default: 10, max: 100)
181
- symbols: Filter by stock symbols (e.g., ["US:AAPL", "HK:0700"])
182
- industries: Filter by industries
183
- channel_ids: Filter by channel IDs
184
- start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
185
- end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
186
-
187
- Returns:
188
- List of matching documents
189
-
190
- Example:
191
- >>> docs = client.search("Tesla earnings", num=10)
192
- >>> for doc in docs:
193
- ... print(doc["title"])
194
- """
195
- data: dict[str, Any] = {
196
- "query": query,
197
- "num": num,
198
- }
199
- if symbols:
200
- data["symbols"] = symbols
201
- if industries:
202
- data["industries"] = industries
203
- if channel_ids:
204
- data["channel_ids"] = channel_ids
205
- if start_datetime:
206
- data["start_datetime"] = start_datetime
207
- if end_datetime:
208
- data["end_datetime"] = end_datetime
209
-
210
- response = self._post("/v2/search", json=data)
211
- return response.get("docs", [])
212
-
213
- def search_news(
214
- self,
215
- query: str,
216
- *,
217
- num: int = 10,
218
- symbols: list[str] | None = None,
219
- channel_ids: list[str] | None = None,
220
- start_datetime: str | None = None,
221
- end_datetime: str | None = None,
222
- ) -> list[dict[str, Any]]:
223
- """
224
- Search news articles
225
-
226
- Args:
227
- query: Search query string
228
- num: Number of results to return
229
- symbols: Filter by stock symbols
230
- channel_ids: Filter by channel IDs
231
- start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
232
- end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
233
-
234
- Returns:
235
- List of news articles
236
- """
237
- data: dict[str, Any] = {"query": query, "num": num}
238
- if symbols:
239
- data["symbols"] = symbols
240
- if channel_ids:
241
- data["channel_ids"] = channel_ids
242
- if start_datetime:
243
- data["start_datetime"] = start_datetime
244
- if end_datetime:
245
- data["end_datetime"] = end_datetime
246
-
247
- response = self._post("/v2/search/news", json=data)
248
- return response.get("docs", [])
249
-
250
- def search_reports(
251
- self,
252
- query: str,
253
- *,
254
- num: int = 10,
255
- symbols: list[str] | None = None,
256
- industries: list[str] | None = None,
257
- channel_ids: list[str] | None = None,
258
- start_datetime: str | None = None,
259
- end_datetime: str | None = None,
260
- ) -> list[dict[str, Any]]:
261
- """
262
- Search research reports
263
-
264
- Args:
265
- query: Search query string
266
- num: Number of results to return
267
- symbols: Filter by stock symbols
268
- industries: Filter by industries
269
- channel_ids: Filter by channel IDs
270
- start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
271
- end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
272
-
273
- Returns:
274
- List of research reports
275
- """
276
- data: dict[str, Any] = {"query": query, "num": num}
277
- if symbols:
278
- data["symbols"] = symbols
279
- if industries:
280
- data["industries"] = industries
281
- if channel_ids:
282
- data["channel_ids"] = channel_ids
283
- if start_datetime:
284
- data["start_datetime"] = start_datetime
285
- if end_datetime:
286
- data["end_datetime"] = end_datetime
287
-
288
- response = self._post("/v2/search/reports", json=data)
289
- return response.get("docs", [])
290
-
291
- def search_filings(
292
- self,
293
- query: str,
294
- symbols: list[str],
295
- *,
296
- num: int = 10,
297
- start_datetime: str | None = None,
298
- end_datetime: str | None = None,
299
- ) -> list[dict[str, Any]]:
300
- """
301
- Search company filings and announcements
302
-
303
- Args:
304
- query: Search query string
305
- symbols: Stock symbols to filter by (required)
306
- num: Number of results to return
307
- start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
308
- end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
309
-
310
- Returns:
311
- List of filings
312
- """
313
- data: dict[str, Any] = {"query": query, "symbols": symbols, "num": num}
314
- if start_datetime:
315
- data["start_datetime"] = start_datetime
316
- if end_datetime:
317
- data["end_datetime"] = end_datetime
318
-
319
- response = self._post("/v2/search/filings", json=data)
320
- return response.get("docs", [])
321
-
322
- def search_transcripts(
323
- self,
324
- query: str,
325
- symbols: list[str],
326
- *,
327
- num: int = 10,
328
- start_datetime: str | None = None,
329
- end_datetime: str | None = None,
330
- ) -> list[dict[str, Any]]:
331
- """
332
- Search earnings call transcripts (conference calls)
333
-
334
- Args:
335
- query: Search query string
336
- symbols: Stock symbols to filter by (required)
337
- num: Number of results to return
338
- start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
339
- end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
340
-
341
- Returns:
342
- List of transcripts
343
- """
344
- data: dict[str, Any] = {"query": query, "symbols": symbols, "num": num}
345
- if start_datetime:
346
- data["start_datetime"] = start_datetime
347
- if end_datetime:
348
- data["end_datetime"] = end_datetime
349
-
350
- response = self._post("/v2/search/conference-calls", json=data)
351
- return response.get("docs", [])
352
-
353
- def search_earnings_pack(
354
- self,
355
- query: str,
356
- symbols: list[str],
357
- *,
358
- num: int = 10,
359
- start_datetime: str | None = None,
360
- end_datetime: str | None = None,
361
- ) -> list[dict[str, Any]]:
362
- """
363
- Search earnings pack documents
364
-
365
- Includes financial reports, earnings call transcripts,
366
- presentation materials, and press releases.
367
-
368
- Args:
369
- query: Search query string
370
- symbols: Stock symbols to filter by (required)
371
- num: Number of results to return
372
- start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
373
- end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
374
-
375
- Returns:
376
- List of earnings pack documents
377
- """
378
- data: dict[str, Any] = {"query": query, "symbols": symbols, "num": num}
379
- if start_datetime:
380
- data["start_datetime"] = start_datetime
381
- if end_datetime:
382
- data["end_datetime"] = end_datetime
383
-
384
- response = self._post("/v2/search/earnings-pack", json=data)
385
- return response.get("docs", [])
386
-
387
- def search_minutes(
388
- self,
389
- query: str,
390
- *,
391
- num: int = 10,
392
- symbols: list[str] | None = None,
393
- start_datetime: str | None = None,
394
- end_datetime: str | None = None,
395
- ) -> list[dict[str, Any]]:
396
- """
397
- Search minutes transcripts
398
-
399
- Includes conference calls and IR (Investor Relations) meetings.
400
-
401
- Args:
402
- query: Search query string
403
- num: Number of results to return
404
- symbols: Filter by stock symbols
405
- start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
406
- end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
407
-
408
- Returns:
409
- List of minutes transcripts
410
- """
411
- data: dict[str, Any] = {"query": query, "num": num}
412
- if symbols:
413
- data["symbols"] = symbols
414
- if start_datetime:
415
- data["start_datetime"] = start_datetime
416
- if end_datetime:
417
- data["end_datetime"] = end_datetime
418
-
419
- response = self._post("/v2/search/minutes", json=data)
420
- return response.get("docs", [])
421
-
422
- def search_socials(
423
- self,
424
- query: str,
425
- *,
426
- num: int = 10,
427
- symbols: list[str] | None = None,
428
- channel_ids: list[str] | None = None,
429
- start_datetime: str | None = None,
430
- end_datetime: str | None = None,
431
- ) -> list[dict[str, Any]]:
432
- """
433
- Search social media content and market sentiment
434
-
435
- Args:
436
- query: Search query string
437
- num: Number of results to return
438
- symbols: Filter by stock symbols
439
- channel_ids: Filter by channel IDs
440
- start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
441
- end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
442
-
443
- Returns:
444
- List of social media content
445
- """
446
- data: dict[str, Any] = {"query": query, "num": num}
447
- if symbols:
448
- data["symbols"] = symbols
449
- if channel_ids:
450
- data["channel_ids"] = channel_ids
451
- if start_datetime:
452
- data["start_datetime"] = start_datetime
453
- if end_datetime:
454
- data["end_datetime"] = end_datetime
455
-
456
- response = self._post("/v2/search/socials", json=data)
457
- return response.get("docs", [])
458
-
459
- def search_webpages(
460
- self,
461
- query: str,
462
- *,
463
- num: int = 10,
464
- start_datetime: str | None = None,
465
- end_datetime: str | None = None,
466
- ) -> list[dict[str, Any]]:
467
- """
468
- Search webpage content
469
-
470
- Args:
471
- query: Search query string
472
- num: Number of results to return
473
- start_datetime: Start datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
474
- end_datetime: End datetime filter (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)
475
-
476
- Returns:
477
- List of webpage content
478
- """
479
- data: dict[str, Any] = {"query": query, "num": num}
480
- if start_datetime:
481
- data["start_datetime"] = start_datetime
482
- if end_datetime:
483
- data["end_datetime"] = end_datetime
484
-
485
- response = self._post("/v2/search/webpages", json=data)
486
- return response.get("docs", [])
487
-
488
- # -------------------------------------------------------------------------
489
- # Sub-modules (lazy loading)
490
- # -------------------------------------------------------------------------
491
-
492
- @property
493
- def stock(self):
494
- """Stock data module for financial statements, prices, etc."""
495
- if self._stock is None:
496
- from reportify_sdk.stock import StockModule
497
- self._stock = StockModule(self)
498
- return self._stock
499
-
500
- @property
501
- def timeline(self):
502
- """Timeline module for following companies, topics, etc."""
503
- if self._timeline is None:
504
- from reportify_sdk.timeline import TimelineModule
505
- self._timeline = TimelineModule(self)
506
- return self._timeline
507
-
508
- @property
509
- def kb(self):
510
- """Knowledge base module for searching user uploaded documents"""
511
- if self._kb is None:
512
- from reportify_sdk.kb import KBModule
513
- self._kb = KBModule(self)
514
- return self._kb
515
-
516
- @property
517
- def docs(self):
518
- """Documents module for accessing document content and metadata"""
519
- if self._docs is None:
520
- from reportify_sdk.docs import DocsModule
521
- self._docs = DocsModule(self)
522
- return self._docs
523
-
524
- @property
525
- def quant(self):
526
- """Quant module for indicators, factors, quotes, and backtesting"""
527
- if self._quant is None:
528
- from reportify_sdk.quant import QuantModule
529
- self._quant = QuantModule(self)
530
- return self._quant
531
-
532
- @property
533
- def concepts(self):
534
- """Concepts module for accessing concept data and feeds"""
535
- if self._concepts is None:
536
- from reportify_sdk.concepts import ConceptsModule
537
- self._concepts = ConceptsModule(self)
538
- return self._concepts
539
-
540
- @property
541
- def channels(self):
542
- """Channels module for searching and following channels"""
543
- if self._channels is None:
544
- from reportify_sdk.channels import ChannelsModule
545
- self._channels = ChannelsModule(self)
546
- return self._channels
547
-
548
- @property
549
- def chat(self):
550
- """Chat module for document-based Q&A"""
551
- if self._chat is None:
552
- from reportify_sdk.chat import ChatModule
553
- self._chat = ChatModule(self)
554
- return self._chat
555
-
556
- @property
557
- def agent(self):
558
- """Agent module for AI-powered conversations and workflows"""
559
- if self._agent is None:
560
- from reportify_sdk.agent import AgentModule
561
- self._agent = AgentModule(self)
562
- return self._agent
563
-
564
- @property
565
- def user(self):
566
- """User module for user-related tools and data"""
567
- if self._user is None:
568
- from reportify_sdk.user import UserModule
569
- self._user = UserModule(self)
570
- return self._user
571
-
572
- def close(self):
573
- """Close the HTTP client"""
574
- self._client.close()
575
-
576
- def __enter__(self):
577
- return self
578
-
579
- def __exit__(self, exc_type, exc_val, exc_tb):
580
- self.close()
File without changes
File without changes
File without changes