trendsagi 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.
- trendsagi/__init__.py +5 -0
- trendsagi/client.py +337 -0
- trendsagi/exceptions.py +28 -0
- trendsagi/models.py +281 -0
- trendsagi-0.1.0.dist-info/METADATA +327 -0
- trendsagi-0.1.0.dist-info/RECORD +9 -0
- trendsagi-0.1.0.dist-info/WHEEL +5 -0
- trendsagi-0.1.0.dist-info/licenses/LICENSE +0 -0
- trendsagi-0.1.0.dist-info/top_level.txt +1 -0
trendsagi/__init__.py
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
# File: trendsagi/__init__.py
|
2
|
+
|
3
|
+
from .client import TrendsAGIClient # Assuming TrendsAGIClient is in trendsagi/client.py
|
4
|
+
from . import exceptions # Assuming exceptions are defined in trendsagi/exceptions.py
|
5
|
+
# Or: from .errors import exceptions if it's in trendsagi/errors.py
|
trendsagi/client.py
ADDED
@@ -0,0 +1,337 @@
|
|
1
|
+
import requests
|
2
|
+
from typing import Optional, List, Dict, Any
|
3
|
+
|
4
|
+
from . import models
|
5
|
+
from . import exceptions
|
6
|
+
|
7
|
+
class TrendsAGIClient:
|
8
|
+
"""
|
9
|
+
The main client for interacting with the TrendsAGI API.
|
10
|
+
|
11
|
+
:param api_key: Your TrendsAGI API key, generated from your profile page.
|
12
|
+
:param base_url: The base URL of the TrendsAGI API. Defaults to the production URL.
|
13
|
+
Override this for development or testing against a local server.
|
14
|
+
Example for local dev: base_url="http://localhost:8000"
|
15
|
+
"""
|
16
|
+
def __init__(self, api_key: str, base_url: str = "https://api.trendsagi.com"):
|
17
|
+
if not api_key:
|
18
|
+
raise exceptions.AuthenticationError("API key is required.")
|
19
|
+
|
20
|
+
self.base_url = base_url.rstrip('/')
|
21
|
+
self._session = requests.Session()
|
22
|
+
self._session.headers.update({
|
23
|
+
"Authorization": f"Bearer {api_key}",
|
24
|
+
"Content-Type": "application/json",
|
25
|
+
"Accept": "application/json"
|
26
|
+
})
|
27
|
+
|
28
|
+
def _request(self, method: str, endpoint: str, **kwargs) -> Any:
|
29
|
+
"""Internal helper for making API requests."""
|
30
|
+
url = f"{self.base_url}{endpoint}"
|
31
|
+
try:
|
32
|
+
response = self._session.request(method, url, **kwargs)
|
33
|
+
|
34
|
+
if 200 <= response.status_code < 300:
|
35
|
+
if response.status_code == 204:
|
36
|
+
return None
|
37
|
+
return response.json()
|
38
|
+
|
39
|
+
try:
|
40
|
+
error_detail = response.json().get('detail', response.text)
|
41
|
+
except requests.exceptions.JSONDecodeError:
|
42
|
+
error_detail = response.text
|
43
|
+
|
44
|
+
if response.status_code == 401:
|
45
|
+
raise exceptions.AuthenticationError(error_detail)
|
46
|
+
if response.status_code == 404:
|
47
|
+
raise exceptions.NotFoundError(response.status_code, error_detail)
|
48
|
+
if response.status_code == 409:
|
49
|
+
raise exceptions.ConflictError(response.status_code, error_detail)
|
50
|
+
if response.status_code == 429:
|
51
|
+
raise exceptions.RateLimitError(response.status_code, error_detail)
|
52
|
+
|
53
|
+
raise exceptions.APIError(response.status_code, error_detail)
|
54
|
+
|
55
|
+
except requests.exceptions.RequestException as e:
|
56
|
+
raise exceptions.TrendsAGIError(f"Network error communicating with API: {e}")
|
57
|
+
|
58
|
+
# --- Trends & Insights Methods ---
|
59
|
+
|
60
|
+
def get_trends(
|
61
|
+
self,
|
62
|
+
search: Optional[str] = None,
|
63
|
+
sort_by: str = 'volume',
|
64
|
+
order: str = 'desc',
|
65
|
+
limit: int = 20,
|
66
|
+
offset: int = 0,
|
67
|
+
period: str = '24h',
|
68
|
+
category: Optional[str] = None
|
69
|
+
) -> models.TrendListResponse:
|
70
|
+
"""
|
71
|
+
Retrieve a list of currently trending topics.
|
72
|
+
"""
|
73
|
+
params = {k: v for k, v in locals().items() if v is not None and k != 'self'}
|
74
|
+
response_data = self._request('GET', '/api/trends', params=params)
|
75
|
+
return models.TrendListResponse.model_validate(response_data)
|
76
|
+
|
77
|
+
def get_trend_details(self, trend_id: int) -> models.TrendDetail:
|
78
|
+
"""
|
79
|
+
Retrieve detailed information for a single trend, including associated tweets.
|
80
|
+
"""
|
81
|
+
response_data = self._request('GET', f'/api/trends/{trend_id}')
|
82
|
+
return models.TrendDetail.model_validate(response_data)
|
83
|
+
|
84
|
+
def get_trend_analytics(self, trend_id: int, period: str = '7d', start_date: Optional[str] = None, end_date: Optional[str] = None) -> models.TrendAnalytics:
|
85
|
+
"""
|
86
|
+
Retrieve historical data points for a specific trend.
|
87
|
+
"""
|
88
|
+
params = {"period": period, "start_date": start_date, "end_date": end_date}
|
89
|
+
params = {k: v for k, v in params.items() if v is not None}
|
90
|
+
response_data = self._request('GET', f'/api/trends/{trend_id}/analytics', params=params)
|
91
|
+
return models.TrendAnalytics.model_validate(response_data)
|
92
|
+
|
93
|
+
def search_insights(
|
94
|
+
self,
|
95
|
+
key_theme_contains: Optional[str] = None,
|
96
|
+
audience_keyword: Optional[str] = None,
|
97
|
+
angle_contains: Optional[str] = None,
|
98
|
+
sentiment_category: Optional[str] = None,
|
99
|
+
overall_topic_category_llm: Optional[str] = None,
|
100
|
+
trend_name_contains: Optional[str] = None,
|
101
|
+
limit: int = 20,
|
102
|
+
offset: int = 0,
|
103
|
+
sort_by: str = 'timestamp',
|
104
|
+
order: str = 'desc'
|
105
|
+
) -> models.InsightSearchResponse:
|
106
|
+
"""
|
107
|
+
Search for trends based on the content of their AI-generated insights.
|
108
|
+
"""
|
109
|
+
params = {
|
110
|
+
"keyThemeContains": key_theme_contains, "audienceKeyword": audience_keyword,
|
111
|
+
"angleContains": angle_contains, "sentimentCategory": sentiment_category,
|
112
|
+
"overallTopicCategoryLlm": overall_topic_category_llm, "trendNameContains": trend_name_contains,
|
113
|
+
"limit": limit, "offset": offset, "sort_by": sort_by, "order": order
|
114
|
+
}
|
115
|
+
params = {k: v for k, v in params.items() if v is not None}
|
116
|
+
response_data = self._request('GET', '/api/insights/search', params=params)
|
117
|
+
return models.InsightSearchResponse.model_validate(response_data)
|
118
|
+
|
119
|
+
def get_ai_insights(self, trend_id: int, force_refresh: bool = False) -> Optional[models.AIInsight]:
|
120
|
+
"""
|
121
|
+
Get or generate AI-powered insights for a specific trend.
|
122
|
+
"""
|
123
|
+
response_data = self._request('GET', f'/api/trends/{trend_id}/ai-insights', params={"force_refresh": force_refresh})
|
124
|
+
return models.AIInsight.model_validate(response_data) if response_data else None
|
125
|
+
|
126
|
+
# --- Custom Reports Methods ---
|
127
|
+
|
128
|
+
def generate_custom_report(self, report_request: Dict[str, Any]) -> models.CustomReport:
|
129
|
+
"""
|
130
|
+
Generate a custom report based on specified dimensions, metrics, and filters.
|
131
|
+
"""
|
132
|
+
response_data = self._request('POST', '/api/reports/custom', json=report_request)
|
133
|
+
return models.CustomReport.model_validate(response_data)
|
134
|
+
|
135
|
+
# --- Intelligence Suite Methods ---
|
136
|
+
|
137
|
+
def get_recommendations(
|
138
|
+
self,
|
139
|
+
limit: int = 10, offset: int = 0, recommendation_type: Optional[str] = None,
|
140
|
+
source_trend_query: Optional[str] = None, priority: Optional[str] = None, status: str = 'new'
|
141
|
+
) -> models.RecommendationListResponse:
|
142
|
+
"""
|
143
|
+
Get actionable recommendations generated for the user.
|
144
|
+
"""
|
145
|
+
params = {
|
146
|
+
"limit": limit, "offset": offset, "type": recommendation_type,
|
147
|
+
"sourceTrendQ": source_trend_query, "priority": priority, "status": status
|
148
|
+
}
|
149
|
+
params = {k: v for k, v in params.items() if v is not None}
|
150
|
+
response_data = self._request('GET', '/api/intelligence/recommendations', params=params)
|
151
|
+
return models.RecommendationListResponse.model_validate(response_data)
|
152
|
+
|
153
|
+
def perform_recommendation_action(self, recommendation_id: int, action: Optional[str] = None, feedback: Optional[str] = None) -> models.Recommendation:
|
154
|
+
"""
|
155
|
+
Update a recommendation's status or provide feedback.
|
156
|
+
"""
|
157
|
+
if action and feedback:
|
158
|
+
raise ValueError("Only one of 'action' or 'feedback' can be provided at a time.")
|
159
|
+
if not action and not feedback:
|
160
|
+
raise ValueError("Either 'action' or 'feedback' must be provided.")
|
161
|
+
|
162
|
+
payload = {"action": action, "feedback": feedback}
|
163
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
164
|
+
response_data = self._request('POST', f'/api/intelligence/recommendations/{recommendation_id}/action', json=payload)
|
165
|
+
return models.Recommendation.model_validate(response_data)
|
166
|
+
|
167
|
+
def get_tracked_x_users(self, q: Optional[str] = None, min_followers: Optional[int] = None, sort_by: str = 'name_asc') -> models.MarketEntityListResponse:
|
168
|
+
"""
|
169
|
+
Get a list of tracked X Users.
|
170
|
+
"""
|
171
|
+
params = {"q": q, "min_followers": min_followers, "sort_by": sort_by}
|
172
|
+
params = {k: v for k, v in params.items() if v is not None}
|
173
|
+
response_data = self._request('GET', '/api/intelligence/market/x-users', params=params)
|
174
|
+
return models.MarketEntityListResponse.model_validate(response_data)
|
175
|
+
|
176
|
+
def get_tracked_x_user(self, entity_id: int) -> models.MarketEntity:
|
177
|
+
"""
|
178
|
+
Retrieve a single tracked X User by their unique entity ID.
|
179
|
+
"""
|
180
|
+
response_data = self._request('GET', f'/api/intelligence/market/x-users/{entity_id}')
|
181
|
+
return models.MarketEntity.model_validate(response_data)
|
182
|
+
|
183
|
+
def create_tracked_x_user(self, handle: str, name: Optional[str] = None, description: Optional[str] = None, notes: Optional[str] = None) -> models.MarketEntity:
|
184
|
+
"""
|
185
|
+
Add a new X User to track.
|
186
|
+
"""
|
187
|
+
payload = {"handle": handle, "name": name, "description": description, "notes": notes}
|
188
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
189
|
+
response_data = self._request('POST', '/api/intelligence/market/x-users', json=payload)
|
190
|
+
return models.MarketEntity.model_validate(response_data)
|
191
|
+
|
192
|
+
def update_tracked_x_user(self, entity_id: int, updates: Dict[str, Any]) -> models.MarketEntity:
|
193
|
+
"""
|
194
|
+
Update details of a tracked X User.
|
195
|
+
"""
|
196
|
+
response_data = self._request('PUT', f'/api/intelligence/market/x-users/{entity_id}', json=updates)
|
197
|
+
return models.MarketEntity.model_validate(response_data)
|
198
|
+
|
199
|
+
def delete_tracked_x_user(self, entity_id: int) -> None:
|
200
|
+
"""Stop tracking an X User."""
|
201
|
+
self._request('DELETE', f'/api/intelligence/market/x-users/{entity_id}')
|
202
|
+
|
203
|
+
def get_crisis_events(
|
204
|
+
self,
|
205
|
+
limit: int = 10, offset: int = 0, status: str = 'active', keyword: Optional[str] = None,
|
206
|
+
severity: Optional[str] = None, time_range: str = '24h',
|
207
|
+
start_date: Optional[str] = None, end_date: Optional[str] = None
|
208
|
+
) -> models.CrisisEventListResponse:
|
209
|
+
"""
|
210
|
+
Get crisis events detected for the user.
|
211
|
+
"""
|
212
|
+
params = {
|
213
|
+
"limit": limit, "offset": offset, "status": status, "keyword": keyword,
|
214
|
+
"severity": severity, "timeRange": time_range, "startDate": start_date, "endDate": end_date
|
215
|
+
}
|
216
|
+
params = {k: v for k, v in params.items() if v is not None}
|
217
|
+
response_data = self._request('GET', '/api/intelligence/crisis-events', params=params)
|
218
|
+
return models.CrisisEventListResponse.model_validate(response_data)
|
219
|
+
|
220
|
+
def get_crisis_event(self, event_id: int) -> models.CrisisEvent:
|
221
|
+
"""
|
222
|
+
Retrieve a single crisis event by its unique ID.
|
223
|
+
"""
|
224
|
+
response_data = self._request('GET', f'/api/intelligence/crisis-events/{event_id}')
|
225
|
+
return models.CrisisEvent.model_validate(response_data)
|
226
|
+
|
227
|
+
def perform_crisis_event_action(self, event_id: int, action: str) -> models.CrisisEvent:
|
228
|
+
"""
|
229
|
+
Update the status of a crisis event (e.g., "acknowledge", "archive").
|
230
|
+
"""
|
231
|
+
response_data = self._request('POST', f'/api/intelligence/crisis-events/{event_id}/action', json={"action": action})
|
232
|
+
return models.CrisisEvent.model_validate(response_data)
|
233
|
+
|
234
|
+
def perform_deep_analysis(self, query: str, force_refresh: bool = False) -> models.DeepAnalysis:
|
235
|
+
"""
|
236
|
+
Perform deep AI analysis on a topic.
|
237
|
+
"""
|
238
|
+
response_data = self._request('POST', '/api/intelligence/deep-analysis', json={"query": query, "force_refresh": force_refresh})
|
239
|
+
return models.DeepAnalysis.model_validate(response_data)
|
240
|
+
|
241
|
+
# --- User & Account Management Methods (Non-sensitive) ---
|
242
|
+
|
243
|
+
def get_topic_interests(self) -> List[models.TopicInterest]:
|
244
|
+
"""Retrieve the list of topic interests tracked by the user."""
|
245
|
+
response_data = self._request('GET', '/api/user/topic-interests')
|
246
|
+
return [models.TopicInterest.model_validate(item) for item in response_data]
|
247
|
+
|
248
|
+
def create_topic_interest(
|
249
|
+
self,
|
250
|
+
keyword: str, alert_condition_type: str,
|
251
|
+
volume_threshold_value: Optional[int] = None, percentage_growth_value: Optional[float] = None
|
252
|
+
) -> models.TopicInterest:
|
253
|
+
"""
|
254
|
+
Create a new topic interest.
|
255
|
+
"""
|
256
|
+
payload = {
|
257
|
+
"keyword": keyword, "alert_condition_type": alert_condition_type,
|
258
|
+
"volume_threshold_value": volume_threshold_value, "percentage_growth_value": percentage_growth_value
|
259
|
+
}
|
260
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
261
|
+
response_data = self._request('POST', '/api/user/topic-interests', json=payload)
|
262
|
+
return models.TopicInterest.model_validate(response_data)
|
263
|
+
|
264
|
+
def delete_topic_interest(self, interest_id: int) -> None:
|
265
|
+
"""Delete a specific topic interest."""
|
266
|
+
self._request('DELETE', f'/api/user/topic-interests/{interest_id}')
|
267
|
+
|
268
|
+
def get_export_settings(self) -> List[models.ExportConfiguration]:
|
269
|
+
"""Get all of the user's data export configurations."""
|
270
|
+
response_data = self._request('GET', '/api/user/export/settings')
|
271
|
+
return [models.ExportConfiguration.model_validate(item) for item in response_data]
|
272
|
+
|
273
|
+
def save_export_settings(
|
274
|
+
self,
|
275
|
+
destination: str, config: Dict[str, Any], schedule: str = "none",
|
276
|
+
schedule_time: Optional[str] = None, is_active: bool = False, config_id: Optional[int] = None
|
277
|
+
) -> models.ExportConfiguration:
|
278
|
+
"""
|
279
|
+
Create or update an export configuration.
|
280
|
+
"""
|
281
|
+
payload = {
|
282
|
+
"id": config_id, "destination": destination, "config": config,
|
283
|
+
"schedule": schedule, "schedule_time": schedule_time, "is_active": is_active,
|
284
|
+
}
|
285
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
286
|
+
response_data = self._request('POST', '/api/user/export/settings', json=payload)
|
287
|
+
return models.ExportConfiguration.model_validate(response_data)
|
288
|
+
|
289
|
+
def delete_export_setting(self, config_id: int) -> None:
|
290
|
+
"""Delete an export configuration."""
|
291
|
+
self._request('DELETE', f'/api/user/export/settings/{config_id}')
|
292
|
+
|
293
|
+
def get_export_history(self, limit: int = 15, offset: int = 0) -> models.ExportHistoryResponse:
|
294
|
+
"""Get the user's export execution history."""
|
295
|
+
response_data = self._request('GET', '/api/user/export/history', params={"limit": limit, "offset": offset})
|
296
|
+
return models.ExportHistoryResponse.model_validate(response_data)
|
297
|
+
|
298
|
+
def run_export_now(self, config_id: int) -> models.ExportExecutionLog:
|
299
|
+
"""Trigger an immediate export."""
|
300
|
+
response_data = self._request('POST', f'/api/user/export/configurations/{config_id}/run-now')
|
301
|
+
return models.ExportExecutionLog.model_validate(response_data)
|
302
|
+
|
303
|
+
def get_dashboard_stats(self) -> models.DashboardStats:
|
304
|
+
"""Get key statistics for the user's dashboard."""
|
305
|
+
response_data = self._request('GET', '/api/user/dashboard/stats')
|
306
|
+
return models.DashboardStats.model_validate(response_data)
|
307
|
+
|
308
|
+
def get_recent_notifications(self, limit: int = 10) -> models.NotificationListResponse:
|
309
|
+
"""Get recent notifications for the user."""
|
310
|
+
response_data = self._request('GET', '/api/user/notifications/recent', params={"limit": limit})
|
311
|
+
return models.NotificationListResponse.model_validate(response_data)
|
312
|
+
|
313
|
+
def mark_notifications_read(self, ids: Optional[List[int]] = None) -> Dict[str, Any]:
|
314
|
+
"""Mark notifications as read. If ids is None, marks all as read."""
|
315
|
+
payload = {"ids": ids if ids is not None else []}
|
316
|
+
return self._request('POST', '/api/user/notifications/mark-read', json=payload)
|
317
|
+
|
318
|
+
# --- Public Information & Status Methods ---
|
319
|
+
|
320
|
+
def get_available_plans(self) -> List[models.SubscriptionPlan]:
|
321
|
+
"""Retrieve a list of all publicly available subscription plans."""
|
322
|
+
response_data = self._request('GET', '/api/plans')
|
323
|
+
return [models.SubscriptionPlan.model_validate(plan) for plan in response_data]
|
324
|
+
|
325
|
+
def get_api_status(self) -> models.StatusPage:
|
326
|
+
"""
|
327
|
+
Retrieve the current operational status of the API and its components.
|
328
|
+
"""
|
329
|
+
response_data = self._request('GET', '/api/status')
|
330
|
+
return models.StatusPage.model_validate(response_data)
|
331
|
+
|
332
|
+
def get_api_status_history(self) -> models.StatusHistoryResponse:
|
333
|
+
"""
|
334
|
+
Retrieve the 90-day uptime history for all API components.
|
335
|
+
"""
|
336
|
+
response_data = self._request('GET', '/api/status-history')
|
337
|
+
return models.StatusHistoryResponse.model_validate(response_data)
|
trendsagi/exceptions.py
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# File: trendsagi-client/trendsagi/exceptions.py
|
2
|
+
|
3
|
+
class TrendsAGIError(Exception):
|
4
|
+
"""Base exception for the TrendsAGI client library."""
|
5
|
+
pass
|
6
|
+
|
7
|
+
class AuthenticationError(TrendsAGIError):
|
8
|
+
"""Raised when authentication fails (e.g., invalid API key)."""
|
9
|
+
pass
|
10
|
+
|
11
|
+
class APIError(TrendsAGIError):
|
12
|
+
"""Raised for non-2xx API responses."""
|
13
|
+
def __init__(self, status_code, error_detail):
|
14
|
+
self.status_code = status_code
|
15
|
+
self.error_detail = error_detail
|
16
|
+
super().__init__(f"API request failed with status {status_code}: {error_detail}")
|
17
|
+
|
18
|
+
class NotFoundError(APIError):
|
19
|
+
"""Raised for 404 Not Found errors."""
|
20
|
+
pass
|
21
|
+
|
22
|
+
class RateLimitError(APIError):
|
23
|
+
"""Raised for 429 Too Many Requests errors."""
|
24
|
+
pass
|
25
|
+
|
26
|
+
class ConflictError(APIError):
|
27
|
+
"""Raised for 409 Conflict errors."""
|
28
|
+
pass
|
trendsagi/models.py
ADDED
@@ -0,0 +1,281 @@
|
|
1
|
+
# File: trendsagi-client/trendsagi/models.py
|
2
|
+
|
3
|
+
from pydantic import BaseModel, Field, HttpUrl, EmailStr
|
4
|
+
from typing import List, Optional, Any, Dict
|
5
|
+
from datetime import datetime, date
|
6
|
+
|
7
|
+
# --- Base & Helper Models ---
|
8
|
+
class OrmBaseModel(BaseModel):
|
9
|
+
class Config:
|
10
|
+
from_attributes = True
|
11
|
+
|
12
|
+
class PaginationMeta(OrmBaseModel):
|
13
|
+
total: int
|
14
|
+
limit: int
|
15
|
+
offset: int
|
16
|
+
period: Optional[str] = None
|
17
|
+
sort_by: Optional[str] = None
|
18
|
+
order: Optional[str] = None
|
19
|
+
search: Optional[str] = None
|
20
|
+
category: Optional[str] = None
|
21
|
+
|
22
|
+
# --- Trends & Insights Models ---
|
23
|
+
class TrendItem(OrmBaseModel):
|
24
|
+
id: int
|
25
|
+
name: str
|
26
|
+
volume: Optional[int] = None
|
27
|
+
timestamp: datetime
|
28
|
+
meta_description: Optional[str] = None
|
29
|
+
category: Optional[str] = None
|
30
|
+
growth: Optional[float] = None
|
31
|
+
previous_volume: Optional[int] = None
|
32
|
+
absolute_change: Optional[int] = None
|
33
|
+
|
34
|
+
class TrendListResponse(OrmBaseModel):
|
35
|
+
trends: List[TrendItem]
|
36
|
+
meta: PaginationMeta
|
37
|
+
|
38
|
+
class TweetUser(OrmBaseModel):
|
39
|
+
id: int
|
40
|
+
user_id: int
|
41
|
+
screen_name: str
|
42
|
+
name: Optional[str] = None
|
43
|
+
|
44
|
+
class Tweet(OrmBaseModel):
|
45
|
+
id: int
|
46
|
+
tweet_id: int
|
47
|
+
text: str
|
48
|
+
created_at: datetime
|
49
|
+
user: Optional[TweetUser] = None
|
50
|
+
|
51
|
+
class TrendDetail(TrendItem):
|
52
|
+
tweets: List[Tweet] = Field(default_factory=list)
|
53
|
+
|
54
|
+
class TrendDataPoint(OrmBaseModel):
|
55
|
+
date: datetime
|
56
|
+
volume: Optional[int] = None
|
57
|
+
growth_rate: Optional[float] = None
|
58
|
+
|
59
|
+
class TrendAnalytics(OrmBaseModel):
|
60
|
+
trend_id: int
|
61
|
+
name: str
|
62
|
+
period: str
|
63
|
+
start_date: Optional[datetime] = None
|
64
|
+
end_date: Optional[datetime] = None
|
65
|
+
data: List[TrendDataPoint]
|
66
|
+
|
67
|
+
class TrendSearchResultItem(OrmBaseModel):
|
68
|
+
id: int
|
69
|
+
name: str
|
70
|
+
category: Optional[str] = None
|
71
|
+
volume: Optional[int] = None
|
72
|
+
timestamp: Optional[datetime] = None
|
73
|
+
meta_description: Optional[str] = None
|
74
|
+
|
75
|
+
class InsightSearchResponse(OrmBaseModel):
|
76
|
+
trends: List[TrendSearchResultItem]
|
77
|
+
meta: PaginationMeta
|
78
|
+
|
79
|
+
class AIInsightContentBrief(OrmBaseModel):
|
80
|
+
target_audience_segments: List[str]
|
81
|
+
key_angles_for_content: List[str]
|
82
|
+
suggested_content_formats: List[str]
|
83
|
+
call_to_action_ideas: List[str]
|
84
|
+
|
85
|
+
class AIInsightAdTargeting(OrmBaseModel):
|
86
|
+
primary_audience_keywords: List[str]
|
87
|
+
secondary_audience_keywords: List[str]
|
88
|
+
potential_demographics_summary: Optional[str]
|
89
|
+
|
90
|
+
class AIInsight(OrmBaseModel):
|
91
|
+
trend_id: int
|
92
|
+
trend_name: str
|
93
|
+
sentiment_summary: Optional[str]
|
94
|
+
sentiment_category: Optional[str]
|
95
|
+
key_themes: List[str]
|
96
|
+
content_brief: Optional[AIInsightContentBrief]
|
97
|
+
ad_platform_targeting: Optional[AIInsightAdTargeting]
|
98
|
+
potential_risks_or_controversies: List[str]
|
99
|
+
overall_topic_category_llm: Optional[str]
|
100
|
+
generated_at: datetime
|
101
|
+
llm_model_used: str
|
102
|
+
|
103
|
+
# --- Custom Report Models ---
|
104
|
+
class ReportMeta(OrmBaseModel):
|
105
|
+
row_count: int
|
106
|
+
limit_applied: Optional[int] = None
|
107
|
+
time_period: str
|
108
|
+
start_date: Optional[str] = None
|
109
|
+
end_date: Optional[str] = None
|
110
|
+
|
111
|
+
class CustomReport(OrmBaseModel):
|
112
|
+
columns: List[str]
|
113
|
+
rows: List[Dict[str, Any]]
|
114
|
+
meta: ReportMeta
|
115
|
+
|
116
|
+
# --- Intelligence Suite Models ---
|
117
|
+
class Recommendation(OrmBaseModel):
|
118
|
+
id: int
|
119
|
+
user_id: int
|
120
|
+
type: str
|
121
|
+
title: str
|
122
|
+
details: str
|
123
|
+
source_trend_id: Optional[str] = None
|
124
|
+
source_trend_name: Optional[str] = None
|
125
|
+
priority: str
|
126
|
+
status: str
|
127
|
+
created_at: datetime
|
128
|
+
updated_at: datetime
|
129
|
+
user_feedback: Optional[str] = None
|
130
|
+
|
131
|
+
class RecommendationListResponse(OrmBaseModel):
|
132
|
+
recommendations: List[Recommendation]
|
133
|
+
meta: PaginationMeta
|
134
|
+
|
135
|
+
class MarketEntity(OrmBaseModel):
|
136
|
+
id: int
|
137
|
+
user_id: int
|
138
|
+
name: Optional[str] = None
|
139
|
+
handle: str
|
140
|
+
website: Optional[HttpUrl] = None
|
141
|
+
description: Optional[str] = None
|
142
|
+
notes: Optional[str] = None
|
143
|
+
followers_count: Optional[int] = None
|
144
|
+
overall_sentiment: Optional[str] = None
|
145
|
+
top_keywords: Optional[List[str]] = Field(default_factory=list, alias="top_keywords_json")
|
146
|
+
recent_topics: Optional[List[str]] = Field(default_factory=list, alias="recent_topics_json")
|
147
|
+
last_analyzed_at: Optional[datetime] = Field(None, alias="last_analyzed")
|
148
|
+
created_at: datetime
|
149
|
+
updated_at: datetime
|
150
|
+
|
151
|
+
class Config:
|
152
|
+
populate_by_name = True
|
153
|
+
|
154
|
+
class MarketEntityListResponse(OrmBaseModel):
|
155
|
+
items: List[MarketEntity]
|
156
|
+
|
157
|
+
class CrisisEvent(OrmBaseModel):
|
158
|
+
id: int
|
159
|
+
user_id: int
|
160
|
+
title: str
|
161
|
+
summary: str
|
162
|
+
severity: str
|
163
|
+
status: str
|
164
|
+
detected_at: datetime
|
165
|
+
source_keywords: Optional[List[str]] = Field(None, alias="source_keywords_json")
|
166
|
+
impacted_entity: Optional[str] = None
|
167
|
+
trend_snapshot_link: Optional[HttpUrl] = None
|
168
|
+
created_at: datetime
|
169
|
+
updated_at: datetime
|
170
|
+
|
171
|
+
class Config:
|
172
|
+
populate_by_name = True
|
173
|
+
|
174
|
+
class CrisisEventListResponse(OrmBaseModel):
|
175
|
+
events: List[CrisisEvent]
|
176
|
+
meta: PaginationMeta
|
177
|
+
|
178
|
+
class DeepAnalysisSentiment(OrmBaseModel):
|
179
|
+
overall_sentiment_category: str
|
180
|
+
positive_nuances: List[str]
|
181
|
+
negative_nuances: List[str]
|
182
|
+
neutral_aspects: List[str]
|
183
|
+
|
184
|
+
class DeepAnalysisActionableInsights(OrmBaseModel):
|
185
|
+
marketing_pr: List[str]
|
186
|
+
product_development: List[str]
|
187
|
+
crm_strategy: List[str]
|
188
|
+
|
189
|
+
class DeepAnalysisRelatedTrend(OrmBaseModel):
|
190
|
+
id: str
|
191
|
+
name: str
|
192
|
+
|
193
|
+
class DeepAnalysis(OrmBaseModel):
|
194
|
+
query_analyzed: str
|
195
|
+
generated_at: datetime
|
196
|
+
llm_model_used: str
|
197
|
+
overall_summary: str
|
198
|
+
key_findings: List[str]
|
199
|
+
sentiment_analysis: DeepAnalysisSentiment
|
200
|
+
causal_factors: List[str]
|
201
|
+
emerging_sub_topics: List[str]
|
202
|
+
future_outlook_and_predictions: List[str]
|
203
|
+
actionable_insights_for_roles: DeepAnalysisActionableInsights
|
204
|
+
related_trends: List[DeepAnalysisRelatedTrend]
|
205
|
+
|
206
|
+
# --- User & Account Management Models ---
|
207
|
+
class TopicInterest(OrmBaseModel):
|
208
|
+
id: int
|
209
|
+
user_id: int
|
210
|
+
keyword: str
|
211
|
+
alert_condition_type: str
|
212
|
+
volume_threshold_value: Optional[int] = None
|
213
|
+
percentage_growth_value: Optional[float] = None
|
214
|
+
created_at: datetime
|
215
|
+
|
216
|
+
class ExportConfiguration(OrmBaseModel):
|
217
|
+
id: int
|
218
|
+
destination: str
|
219
|
+
config: Dict[str, Any]
|
220
|
+
schedule: str
|
221
|
+
schedule_time: Optional[str] = None
|
222
|
+
is_active: bool
|
223
|
+
|
224
|
+
class ExportExecutionLog(OrmBaseModel):
|
225
|
+
id: int
|
226
|
+
execution_time: datetime
|
227
|
+
duration_seconds: Optional[float] = None
|
228
|
+
destination: str
|
229
|
+
status: str
|
230
|
+
message: Optional[str] = None
|
231
|
+
records_exported: Optional[int] = None
|
232
|
+
export_configuration_id: Optional[int] = None
|
233
|
+
|
234
|
+
class ExportHistoryResponse(OrmBaseModel):
|
235
|
+
history: List[ExportExecutionLog]
|
236
|
+
meta: PaginationMeta
|
237
|
+
|
238
|
+
class DashboardStats(OrmBaseModel):
|
239
|
+
active_trends: int
|
240
|
+
alerts_today: int
|
241
|
+
topic_interests: int
|
242
|
+
avg_growth: Optional[float] = None
|
243
|
+
|
244
|
+
class Notification(OrmBaseModel):
|
245
|
+
id: int
|
246
|
+
title: str
|
247
|
+
message: str
|
248
|
+
notification_type: str
|
249
|
+
is_read: bool
|
250
|
+
created_at: datetime
|
251
|
+
read_at: Optional[datetime] = None
|
252
|
+
data: Optional[Dict[str, Any]] = None
|
253
|
+
|
254
|
+
class NotificationListResponse(OrmBaseModel):
|
255
|
+
notifications: List[Notification]
|
256
|
+
unread_count: int
|
257
|
+
|
258
|
+
# --- Public Information & Status Models ---
|
259
|
+
class SubscriptionPlan(OrmBaseModel):
|
260
|
+
id: int
|
261
|
+
name: str
|
262
|
+
description: Optional[str] = None
|
263
|
+
price_monthly: Optional[float] = None
|
264
|
+
price_yearly: Optional[float] = None
|
265
|
+
is_custom: bool
|
266
|
+
features: Optional[Dict[str, Any]] = Field(default_factory=dict)
|
267
|
+
|
268
|
+
class ComponentStatus(OrmBaseModel):
|
269
|
+
name: str
|
270
|
+
status: str
|
271
|
+
description: str
|
272
|
+
|
273
|
+
class StatusPage(OrmBaseModel):
|
274
|
+
overall_status: str
|
275
|
+
last_updated: datetime
|
276
|
+
components: List[ComponentStatus]
|
277
|
+
|
278
|
+
# NEW: Model for API Status History
|
279
|
+
class StatusHistoryResponse(OrmBaseModel):
|
280
|
+
uptime_percentages: Dict[str, float]
|
281
|
+
daily_statuses: Dict[str, Dict[str, str]]
|
@@ -0,0 +1,327 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: trendsagi
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: The official Python client for the TrendsAGI API.
|
5
|
+
Author-email: TrendsAGI <contact@trendsagi.com>
|
6
|
+
Project-URL: Homepage, https://github.com/TrendsAGI/TrendsAGI
|
7
|
+
Project-URL: Bug Tracker, https://github.com/TrendsAGI/TrendsAGI/issues
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
10
|
+
Classifier: Operating System :: OS Independent
|
11
|
+
Classifier: Intended Audience :: Developers
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
13
|
+
Requires-Python: >=3.8
|
14
|
+
Description-Content-Type: text/markdown
|
15
|
+
License-File: LICENSE
|
16
|
+
Requires-Dist: requests>=2.28.0
|
17
|
+
Requires-Dist: pydantic>=2.0
|
18
|
+
Dynamic: license-file
|
19
|
+
|
20
|
+
# TrendsAGI Official Python Client
|
21
|
+
|
22
|
+
[](https://pypi.org/project/trendsagi/)
|
23
|
+
[](https://opensource.org/licenses/MIT)
|
24
|
+
[](https://pypi.org/project/trendsagi/)
|
25
|
+
|
26
|
+
The official Python client for the [TrendsAGI API](https://trendsagi.com), providing a simple and convenient way to access real-time trend data, AI-powered insights, and the full intelligence suite.
|
27
|
+
|
28
|
+
This library is fully typed with Pydantic models for all API responses, giving you excellent editor support (like autocompletion and type checking) and data validation out of the box.
|
29
|
+
|
30
|
+
## Table of Contents
|
31
|
+
|
32
|
+
- [Features](#features)
|
33
|
+
- [Installation](#installation)
|
34
|
+
- [Getting Started](#getting-started)
|
35
|
+
- [Authentication](#authentication)
|
36
|
+
- [Quickstart Example](#quickstart-example)
|
37
|
+
- [Usage Examples](#usage-examples)
|
38
|
+
- [Get AI-Powered Insights for a Trend](#get-ai-powered-insights-for-a-trend)
|
39
|
+
- [Perform a Deep Analysis on a Topic](#perform-a-deep-analysis-on-a-topic)
|
40
|
+
- [Track an X (Twitter) User](#track-an-x-twitter-user)
|
41
|
+
- [Monitor for Crisis Events](#monitor-for-crisis-events)
|
42
|
+
- [Handling Errors and Exceptions](#handling-errors-and-exceptions)
|
43
|
+
- [Full API Documentation](#full-api-documentation)
|
44
|
+
- [Contributing](#contributing)
|
45
|
+
- [License](#license)
|
46
|
+
|
47
|
+
## Features
|
48
|
+
|
49
|
+
- Access real-time and historical trend data
|
50
|
+
- Leverage powerful AI-driven insights, sentiment analysis, and content briefs for any trend
|
51
|
+
- Perform deep, causal analysis on any topic or query
|
52
|
+
- Utilize the Intelligence Suite for actionable recommendations, crisis monitoring, and market tracking
|
53
|
+
- Manage topic interests, alerts, and data export configurations
|
54
|
+
- Simple, intuitive methods mirroring the API structure
|
55
|
+
- Robust error handling with custom exceptions
|
56
|
+
- Data validation and rich type-hinting powered by Pydantic
|
57
|
+
|
58
|
+
## Installation
|
59
|
+
|
60
|
+
Install the library directly from PyPI:
|
61
|
+
|
62
|
+
```bash
|
63
|
+
pip install trendsagi
|
64
|
+
```
|
65
|
+
|
66
|
+
## Getting Started
|
67
|
+
|
68
|
+
### Authentication
|
69
|
+
|
70
|
+
First, you'll need a TrendsAGI account and an API key. You can sign up and generate a key from your dashboard.
|
71
|
+
|
72
|
+
We strongly recommend storing your API key as an environment variable to avoid committing it to version control.
|
73
|
+
|
74
|
+
```bash
|
75
|
+
export TRENDSAGI_API_KEY="your_api_key_here"
|
76
|
+
```
|
77
|
+
|
78
|
+
### Quickstart Example
|
79
|
+
|
80
|
+
This example demonstrates how to initialize the client and fetch the latest trending topics.
|
81
|
+
|
82
|
+
```python
|
83
|
+
import os
|
84
|
+
import trendsagi
|
85
|
+
from trendsagi import exceptions
|
86
|
+
|
87
|
+
# It's recommended to load your API key from an environment variable
|
88
|
+
API_KEY = os.environ.get("TRENDSAGI_API_KEY")
|
89
|
+
|
90
|
+
if not API_KEY:
|
91
|
+
raise ValueError("Please set the TRENDSAGI_API_KEY environment variable.")
|
92
|
+
|
93
|
+
# Initialize the client
|
94
|
+
client = trendsagi.TrendsAGIClient(api_key=API_KEY)
|
95
|
+
|
96
|
+
try:
|
97
|
+
# Get the top 5 trending topics from the last 24 hours
|
98
|
+
print("Fetching top 5 trending topics...")
|
99
|
+
response = client.get_trends(limit=5, period='24h')
|
100
|
+
|
101
|
+
print(f"\nFound {response.meta.total} total trends. Displaying the top {len(response.trends)}:")
|
102
|
+
for trend in response.trends:
|
103
|
+
print(f"- ID: {trend.id}, Name: '{trend.name}', Volume: {trend.volume}")
|
104
|
+
|
105
|
+
except exceptions.AuthenticationError:
|
106
|
+
print("Authentication failed. Please check your API key.")
|
107
|
+
except exceptions.APIError as e:
|
108
|
+
print(f"An API error occurred: Status {e.status_code}, Details: {e.error_detail}")
|
109
|
+
except exceptions.TrendsAGIError as e:
|
110
|
+
print(f"A client-side error occurred: {e}")
|
111
|
+
```
|
112
|
+
|
113
|
+
## Usage Examples
|
114
|
+
|
115
|
+
### Get AI-Powered Insights for a Trend
|
116
|
+
|
117
|
+
Retrieve AI-generated insights for a specific trend, such as key themes, target audiences, and content ideas.
|
118
|
+
|
119
|
+
```python
|
120
|
+
# Assuming 'client' is an initialized TrendsAGIClient
|
121
|
+
# and you have a trend_id from a call to get_trends()
|
122
|
+
TREND_ID = 12345
|
123
|
+
|
124
|
+
try:
|
125
|
+
print(f"\nGetting AI insights for trend ID {TREND_ID}...")
|
126
|
+
ai_insight = client.get_ai_insights(trend_id=TREND_ID)
|
127
|
+
|
128
|
+
if ai_insight:
|
129
|
+
print(f" Sentiment: {ai_insight.sentiment_category}")
|
130
|
+
print(" Key Themes:")
|
131
|
+
for theme in ai_insight.key_themes[:3]: # show first 3
|
132
|
+
print(f" - {theme}")
|
133
|
+
print(" Suggested Content Angle:")
|
134
|
+
print(f" - {ai_insight.content_brief.key_angles_for_content[0]}")
|
135
|
+
|
136
|
+
except exceptions.NotFoundError:
|
137
|
+
print(f"Trend with ID {TREND_ID} not found.")
|
138
|
+
except exceptions.APIError as e:
|
139
|
+
print(f"An API error occurred: {e}")
|
140
|
+
```
|
141
|
+
|
142
|
+
### Perform a Deep Analysis on a Topic
|
143
|
+
|
144
|
+
```python
|
145
|
+
# Assuming 'client' is an initialized TrendsAGIClient
|
146
|
+
try:
|
147
|
+
print("\nPerforming deep analysis on 'artificial intelligence'...")
|
148
|
+
analysis = client.perform_deep_analysis(
|
149
|
+
query="artificial intelligence",
|
150
|
+
analysis_type="comprehensive"
|
151
|
+
)
|
152
|
+
|
153
|
+
print(f"Analysis completed. Key findings:")
|
154
|
+
print(f"- Market sentiment: {analysis.market_sentiment}")
|
155
|
+
print(f"- Growth trajectory: {analysis.growth_projection}")
|
156
|
+
print(f"- Key influencers: {', '.join(analysis.top_influencers[:3])}")
|
157
|
+
|
158
|
+
except exceptions.APIError as e:
|
159
|
+
print(f"An API error occurred: {e}")
|
160
|
+
```
|
161
|
+
|
162
|
+
### Track an X (Twitter) User
|
163
|
+
|
164
|
+
Add a user to your tracked market entities in the Intelligence Suite.
|
165
|
+
|
166
|
+
```python
|
167
|
+
# Assuming 'client' is an initialized TrendsAGIClient
|
168
|
+
try:
|
169
|
+
print("\nAdding a new X user to track...")
|
170
|
+
new_entity = client.create_tracked_x_user(
|
171
|
+
handle="OpenAI",
|
172
|
+
name="OpenAI",
|
173
|
+
notes="Key player in the AI industry."
|
174
|
+
)
|
175
|
+
print(f"Successfully started tracking '{new_entity.name}' (ID: {new_entity.id})")
|
176
|
+
|
177
|
+
except exceptions.ConflictError:
|
178
|
+
print("This user is already being tracked.")
|
179
|
+
except exceptions.APIError as e:
|
180
|
+
print(f"An API error occurred: {e}")
|
181
|
+
```
|
182
|
+
|
183
|
+
### Monitor for Crisis Events
|
184
|
+
|
185
|
+
Retrieve any active crisis events detected by the system.
|
186
|
+
|
187
|
+
```python
|
188
|
+
# Assuming 'client' is an initialized TrendsAGIClient
|
189
|
+
try:
|
190
|
+
print("\nChecking for active crisis events...")
|
191
|
+
crisis_response = client.get_crisis_events(status='active', limit=5)
|
192
|
+
|
193
|
+
if not crisis_response.events:
|
194
|
+
print("No active crisis events found.")
|
195
|
+
else:
|
196
|
+
for event in crisis_response.events:
|
197
|
+
print(f"- [SEVERITY: {event.severity}] {event.title}")
|
198
|
+
print(f" Summary: {event.summary}\n")
|
199
|
+
|
200
|
+
except exceptions.APIError as e:
|
201
|
+
print(f"An API error occurred: {e}")
|
202
|
+
```
|
203
|
+
|
204
|
+
## Handling Errors and Exceptions
|
205
|
+
|
206
|
+
The library raises specific exceptions for different types of errors, all inheriting from `trendsagi.exceptions.TrendsAGIError`. This allows for granular error handling.
|
207
|
+
|
208
|
+
- **`TrendsAGIError`**: The base exception for all library-specific errors
|
209
|
+
- **`AuthenticationError`**: Raised on 401 errors for an invalid or missing API key
|
210
|
+
- **`APIError`**: The base class for all non-2xx API responses
|
211
|
+
- **`NotFoundError`**: Raised on 404 errors when a resource is not found
|
212
|
+
- **`ConflictError`**: Raised on 409 errors, e.g., when trying to create a resource that already exists
|
213
|
+
- **`RateLimitError`**: Raised on 429 errors when you have exceeded your API rate limit
|
214
|
+
|
215
|
+
Example error handling:
|
216
|
+
|
217
|
+
```python
|
218
|
+
try:
|
219
|
+
response = client.get_trends()
|
220
|
+
except exceptions.AuthenticationError:
|
221
|
+
print("Invalid API key. Please check your credentials.")
|
222
|
+
except exceptions.RateLimitError as e:
|
223
|
+
print(f"Rate limit exceeded. Retry after: {e.retry_after} seconds")
|
224
|
+
except exceptions.NotFoundError:
|
225
|
+
print("The requested resource was not found.")
|
226
|
+
except exceptions.APIError as e:
|
227
|
+
print(f"API error: {e.status_code} - {e.error_detail}")
|
228
|
+
except exceptions.TrendsAGIError as e:
|
229
|
+
print(f"Client error: {e}")
|
230
|
+
```
|
231
|
+
|
232
|
+
## Advanced Usage
|
233
|
+
|
234
|
+
### Working with Pagination
|
235
|
+
|
236
|
+
```python
|
237
|
+
# Get all trends with pagination
|
238
|
+
all_trends = []
|
239
|
+
page = 1
|
240
|
+
while True:
|
241
|
+
response = client.get_trends(page=page, limit=100)
|
242
|
+
all_trends.extend(response.trends)
|
243
|
+
|
244
|
+
if page >= response.meta.total_pages:
|
245
|
+
break
|
246
|
+
page += 1
|
247
|
+
|
248
|
+
print(f"Retrieved {len(all_trends)} total trends")
|
249
|
+
```
|
250
|
+
|
251
|
+
### Setting Up Alerts
|
252
|
+
|
253
|
+
```python
|
254
|
+
# Create a new trend alert
|
255
|
+
alert = client.create_alert(
|
256
|
+
name="AI Technology Alert",
|
257
|
+
keywords=["artificial intelligence", "machine learning", "AI"],
|
258
|
+
threshold_volume=1000,
|
259
|
+
notification_method="email"
|
260
|
+
)
|
261
|
+
print(f"Created alert: {alert.name} (ID: {alert.id})")
|
262
|
+
```
|
263
|
+
|
264
|
+
### Export Data
|
265
|
+
|
266
|
+
```python
|
267
|
+
# Export trend data to CSV
|
268
|
+
export_job = client.export_trends(
|
269
|
+
format="csv",
|
270
|
+
date_range="last_7_days",
|
271
|
+
filters={"category": "technology"}
|
272
|
+
)
|
273
|
+
print(f"Export job started: {export_job.job_id}")
|
274
|
+
|
275
|
+
# Check export status
|
276
|
+
status = client.get_export_status(export_job.job_id)
|
277
|
+
if status.is_complete:
|
278
|
+
print(f"Export ready for download: {status.download_url}")
|
279
|
+
```
|
280
|
+
|
281
|
+
## Full API Documentation
|
282
|
+
|
283
|
+
This library is a client for the TrendsAGI REST API. For complete details on all available API endpoints, parameters, data models, rate limits, and best practices, please refer to our official [API Documentation](https://trendsagi.com/api-docs).
|
284
|
+
|
285
|
+
## Contributing
|
286
|
+
|
287
|
+
Contributions are welcome! If you find a bug or have a feature request, please open an issue on our GitHub Issues page. If you'd like to contribute code, please fork the repository and open a pull request.
|
288
|
+
|
289
|
+
1. Fork the Project
|
290
|
+
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
291
|
+
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
292
|
+
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
293
|
+
5. Open a Pull Request
|
294
|
+
|
295
|
+
### Development Setup
|
296
|
+
|
297
|
+
```bash
|
298
|
+
# Clone the repository
|
299
|
+
git clone https://github.com/trendsagi/TrendsAGI.git
|
300
|
+
cd TrendsAGI
|
301
|
+
|
302
|
+
# Install development dependencies
|
303
|
+
pip install -e ".[dev]"
|
304
|
+
|
305
|
+
# Run tests
|
306
|
+
pytest
|
307
|
+
|
308
|
+
# Run linting
|
309
|
+
flake8 trendsagi/
|
310
|
+
mypy trendsagi/
|
311
|
+
```
|
312
|
+
|
313
|
+
## Support
|
314
|
+
|
315
|
+
|
316
|
+
- **API Reference**: [https://trendsagi.com/api-docs](https://trendsagi.com/api-docs)
|
317
|
+
- **Support Email**: contact@trendsagi.com
|
318
|
+
- **GitHub Issues**: [https://github.com/TrendsAGI/TrendsAGI/issues](https://github.com/TrendsAGI/TrendsAGI/issues)
|
319
|
+
|
320
|
+
|
321
|
+
## License
|
322
|
+
|
323
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
324
|
+
|
325
|
+
---
|
326
|
+
|
327
|
+
**Built with ❤️ by the TrendsAGI Team**
|
@@ -0,0 +1,9 @@
|
|
1
|
+
trendsagi/__init__.py,sha256=f1Pzinj-7c-LlZoxGhKA9RerL0NAZoV1nukGFTjXTo0,320
|
2
|
+
trendsagi/client.py,sha256=PnG6y1ozBY1wR6Bri5dqt38VVGKL57NYk3eHym51Tt4,16502
|
3
|
+
trendsagi/exceptions.py,sha256=t8hUNK8PxHltvgkuvxrnGOhOaNXj5k2Yuvy9GNhd1Io,829
|
4
|
+
trendsagi/models.py,sha256=uRCKCl69SpqadTyo2QPGZ_rldkdE7UzDbvBJMpUZejc,7744
|
5
|
+
trendsagi-0.1.0.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
trendsagi-0.1.0.dist-info/METADATA,sha256=pzLt3bvAtad_yJJwwwHNb5Dt1Xrq1S1wGAxbPaGZ-p8,10825
|
7
|
+
trendsagi-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
8
|
+
trendsagi-0.1.0.dist-info/top_level.txt,sha256=fzNg3zxSm70ICBiM_K5UF1XFq5XN0qUdcYub9U7it0g,10
|
9
|
+
trendsagi-0.1.0.dist-info/RECORD,,
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
trendsagi
|