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 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)
@@ -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
+ [![PyPI Version](https://img.shields.io/pypi/v/trendsagi.svg)](https://pypi.org/project/trendsagi/)
23
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
24
+ [![Python Versions](https://img.shields.io/pypi/pyversions/trendsagi.svg)](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,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
File without changes
@@ -0,0 +1 @@
1
+ trendsagi