marqetive-lib 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.
- marqetive/__init__.py +113 -0
- marqetive/core/__init__.py +5 -0
- marqetive/core/account_factory.py +212 -0
- marqetive/core/base_manager.py +303 -0
- marqetive/core/client.py +108 -0
- marqetive/core/progress.py +291 -0
- marqetive/core/registry.py +257 -0
- marqetive/platforms/__init__.py +55 -0
- marqetive/platforms/base.py +390 -0
- marqetive/platforms/exceptions.py +238 -0
- marqetive/platforms/instagram/__init__.py +7 -0
- marqetive/platforms/instagram/client.py +786 -0
- marqetive/platforms/instagram/exceptions.py +311 -0
- marqetive/platforms/instagram/factory.py +106 -0
- marqetive/platforms/instagram/manager.py +112 -0
- marqetive/platforms/instagram/media.py +669 -0
- marqetive/platforms/linkedin/__init__.py +7 -0
- marqetive/platforms/linkedin/client.py +733 -0
- marqetive/platforms/linkedin/exceptions.py +335 -0
- marqetive/platforms/linkedin/factory.py +130 -0
- marqetive/platforms/linkedin/manager.py +119 -0
- marqetive/platforms/linkedin/media.py +549 -0
- marqetive/platforms/models.py +345 -0
- marqetive/platforms/tiktok/__init__.py +0 -0
- marqetive/platforms/twitter/__init__.py +7 -0
- marqetive/platforms/twitter/client.py +647 -0
- marqetive/platforms/twitter/exceptions.py +311 -0
- marqetive/platforms/twitter/factory.py +151 -0
- marqetive/platforms/twitter/manager.py +121 -0
- marqetive/platforms/twitter/media.py +779 -0
- marqetive/platforms/twitter/threads.py +442 -0
- marqetive/py.typed +0 -0
- marqetive/registry_init.py +66 -0
- marqetive/utils/__init__.py +45 -0
- marqetive/utils/file_handlers.py +438 -0
- marqetive/utils/helpers.py +99 -0
- marqetive/utils/media.py +399 -0
- marqetive/utils/oauth.py +265 -0
- marqetive/utils/retry.py +239 -0
- marqetive/utils/token_validator.py +240 -0
- marqetive_lib-0.1.0.dist-info/METADATA +261 -0
- marqetive_lib-0.1.0.dist-info/RECORD +43 -0
- marqetive_lib-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"""Abstract base class for social media platform integrations.
|
|
2
|
+
|
|
3
|
+
This module defines the SocialMediaPlatform ABC that serves as a blueprint
|
|
4
|
+
for implementing platform-specific clients (Instagram, Twitter, LinkedIn, etc.).
|
|
5
|
+
All concrete implementations must implement the abstract methods defined here.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from traceback import TracebackException
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from marqetive.core.client import APIClient
|
|
14
|
+
from marqetive.platforms.exceptions import (
|
|
15
|
+
PlatformAuthError,
|
|
16
|
+
RateLimitError,
|
|
17
|
+
)
|
|
18
|
+
from marqetive.platforms.models import (
|
|
19
|
+
AuthCredentials,
|
|
20
|
+
Comment,
|
|
21
|
+
MediaAttachment,
|
|
22
|
+
PlatformResponse,
|
|
23
|
+
Post,
|
|
24
|
+
PostCreateRequest,
|
|
25
|
+
PostUpdateRequest,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SocialMediaPlatform(ABC):
|
|
30
|
+
"""Abstract base class for social media platform integrations.
|
|
31
|
+
|
|
32
|
+
This class defines the common interface that all platform-specific clients
|
|
33
|
+
must implement. It uses composition to include an APIClient instance and
|
|
34
|
+
provides both abstract methods (must be implemented) and concrete utility
|
|
35
|
+
methods (can be used as-is or overridden).
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
platform_name: Name of the platform (e.g., "instagram", "twitter")
|
|
39
|
+
credentials: Authentication credentials for the platform
|
|
40
|
+
api_client: HTTPx-based API client for making requests
|
|
41
|
+
base_url: Base URL for the platform's API
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
>>> class TwitterClient(SocialMediaPlatform):
|
|
45
|
+
... def __init__(self, credentials: AuthCredentials):
|
|
46
|
+
... super().__init__(
|
|
47
|
+
... platform_name="twitter",
|
|
48
|
+
... credentials=credentials,
|
|
49
|
+
... base_url="https://api.x.com/2"
|
|
50
|
+
... )
|
|
51
|
+
... # Implement abstract methods...
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
platform_name: str,
|
|
57
|
+
credentials: AuthCredentials,
|
|
58
|
+
base_url: str,
|
|
59
|
+
timeout: float = 30.0,
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Initialize the platform client.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
platform_name: Name of the platform
|
|
65
|
+
credentials: Authentication credentials
|
|
66
|
+
base_url: Base URL for the platform API
|
|
67
|
+
timeout: Request timeout in seconds
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
PlatformAuthError: If credentials are invalid or expired
|
|
71
|
+
"""
|
|
72
|
+
self.platform_name = platform_name
|
|
73
|
+
self.credentials = credentials
|
|
74
|
+
self.base_url = base_url
|
|
75
|
+
self.timeout = timeout
|
|
76
|
+
self.api_client: APIClient | None = None
|
|
77
|
+
self._rate_limit_remaining: int | None = None
|
|
78
|
+
self._rate_limit_reset: datetime | None = None
|
|
79
|
+
|
|
80
|
+
# Validate credentials on initialization
|
|
81
|
+
if self.credentials.is_expired():
|
|
82
|
+
raise PlatformAuthError(
|
|
83
|
+
"Access token has expired",
|
|
84
|
+
platform=platform_name,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
async def __aenter__(self) -> "SocialMediaPlatform":
|
|
88
|
+
"""Async context manager entry.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Self for use in context manager.
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
>>> async with TwitterClient(creds) as client:
|
|
95
|
+
... post = await client.get_post("12345")
|
|
96
|
+
"""
|
|
97
|
+
headers = self._build_auth_headers()
|
|
98
|
+
self.api_client = APIClient(
|
|
99
|
+
base_url=self.base_url,
|
|
100
|
+
timeout=self.timeout,
|
|
101
|
+
headers=headers,
|
|
102
|
+
)
|
|
103
|
+
await self.api_client.__aenter__()
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
async def __aexit__(
|
|
107
|
+
self, exc_type: type[Exception], exc_val: Any, exc_tb: TracebackException
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Async context manager exit."""
|
|
110
|
+
if self.api_client:
|
|
111
|
+
await self.api_client.__aexit__(exc_type, exc_val, exc_tb)
|
|
112
|
+
|
|
113
|
+
def _build_auth_headers(self) -> dict[str, str]:
|
|
114
|
+
"""Build authentication headers for API requests.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Dictionary of headers including authorization.
|
|
118
|
+
"""
|
|
119
|
+
return {
|
|
120
|
+
"Authorization": f"{self.credentials.token_type} {self.credentials.access_token}",
|
|
121
|
+
"Content-Type": "application/json",
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
def _check_rate_limit(self) -> None:
|
|
125
|
+
"""Check if rate limit has been exceeded.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
RateLimitError: If rate limit is exceeded.
|
|
129
|
+
"""
|
|
130
|
+
if self._rate_limit_remaining is not None and self._rate_limit_remaining <= 0:
|
|
131
|
+
retry_after = None
|
|
132
|
+
if self._rate_limit_reset:
|
|
133
|
+
retry_after = int(
|
|
134
|
+
(self._rate_limit_reset - datetime.now()).total_seconds()
|
|
135
|
+
)
|
|
136
|
+
raise RateLimitError(
|
|
137
|
+
"Rate limit exceeded",
|
|
138
|
+
platform=self.platform_name,
|
|
139
|
+
status_code=429,
|
|
140
|
+
retry_after=retry_after,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def _update_rate_limit_info(
|
|
144
|
+
self, remaining: int | None, reset_time: datetime | None
|
|
145
|
+
) -> None:
|
|
146
|
+
"""Update rate limit information from API response.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
remaining: Number of remaining requests in current window
|
|
150
|
+
reset_time: Timestamp when rate limit resets
|
|
151
|
+
"""
|
|
152
|
+
self._rate_limit_remaining = remaining
|
|
153
|
+
self._rate_limit_reset = reset_time
|
|
154
|
+
|
|
155
|
+
# ==================== Abstract Authentication Methods ====================
|
|
156
|
+
|
|
157
|
+
@abstractmethod
|
|
158
|
+
async def authenticate(self) -> AuthCredentials:
|
|
159
|
+
"""Perform platform-specific authentication flow.
|
|
160
|
+
|
|
161
|
+
Each platform implements its own authentication mechanism (OAuth2,
|
|
162
|
+
API keys, etc.). This method should handle the complete auth flow
|
|
163
|
+
and return valid credentials.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
AuthCredentials object with access tokens.
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
PlatformAuthError: If authentication fails.
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
>>> async with TwitterClient(creds) as client:
|
|
173
|
+
... new_creds = await client.authenticate()
|
|
174
|
+
"""
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
@abstractmethod
|
|
178
|
+
async def refresh_token(self) -> AuthCredentials:
|
|
179
|
+
"""Refresh the access token using refresh token.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Updated AuthCredentials with new access token.
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
PlatformAuthError: If token refresh fails.
|
|
186
|
+
"""
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
@abstractmethod
|
|
190
|
+
async def is_authenticated(self) -> bool:
|
|
191
|
+
"""Check if current credentials are valid.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
True if authenticated and token is valid, False otherwise.
|
|
195
|
+
"""
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
# ==================== Abstract Post CRUD Methods ====================
|
|
199
|
+
|
|
200
|
+
@abstractmethod
|
|
201
|
+
async def create_post(self, request: PostCreateRequest) -> Post:
|
|
202
|
+
"""Create and publish a new post.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
request: Post creation request with content and media.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Created Post object with platform-assigned ID.
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
PlatformAuthError: If not authenticated.
|
|
212
|
+
ValidationError: If request data is invalid.
|
|
213
|
+
MediaUploadError: If media upload fails.
|
|
214
|
+
|
|
215
|
+
Example:
|
|
216
|
+
>>> request = PostCreateRequest(
|
|
217
|
+
... content="Hello world!",
|
|
218
|
+
... media_urls=["https://example.com/image.jpg"]
|
|
219
|
+
... )
|
|
220
|
+
>>> post = await client.create_post(request)
|
|
221
|
+
"""
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
@abstractmethod
|
|
225
|
+
async def get_post(self, post_id: str) -> Post:
|
|
226
|
+
"""Retrieve a post by its ID.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
post_id: Platform-specific post identifier.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Post object with current data.
|
|
233
|
+
|
|
234
|
+
Raises:
|
|
235
|
+
PostNotFoundError: If post doesn't exist.
|
|
236
|
+
PlatformAuthError: If not authenticated.
|
|
237
|
+
"""
|
|
238
|
+
pass
|
|
239
|
+
|
|
240
|
+
@abstractmethod
|
|
241
|
+
async def update_post(self, post_id: str, request: PostUpdateRequest) -> Post:
|
|
242
|
+
"""Update an existing post.
|
|
243
|
+
|
|
244
|
+
Note: Not all platforms support editing posts. Implementation should
|
|
245
|
+
raise an appropriate error if editing is not supported.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
post_id: Platform-specific post identifier.
|
|
249
|
+
request: Post update request with new content.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Updated Post object.
|
|
253
|
+
|
|
254
|
+
Raises:
|
|
255
|
+
PostNotFoundError: If post doesn't exist.
|
|
256
|
+
ValidationError: If update data is invalid.
|
|
257
|
+
PlatformError: If platform doesn't support editing.
|
|
258
|
+
"""
|
|
259
|
+
pass
|
|
260
|
+
|
|
261
|
+
@abstractmethod
|
|
262
|
+
async def delete_post(self, post_id: str) -> bool:
|
|
263
|
+
"""Delete a post.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
post_id: Platform-specific post identifier.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
True if deletion was successful.
|
|
270
|
+
|
|
271
|
+
Raises:
|
|
272
|
+
PostNotFoundError: If post doesn't exist.
|
|
273
|
+
PlatformAuthError: If not authenticated or authorized.
|
|
274
|
+
"""
|
|
275
|
+
pass
|
|
276
|
+
|
|
277
|
+
# ==================== Abstract Comment Methods ====================
|
|
278
|
+
|
|
279
|
+
@abstractmethod
|
|
280
|
+
async def get_comments(
|
|
281
|
+
self,
|
|
282
|
+
post_id: str,
|
|
283
|
+
limit: int = 50,
|
|
284
|
+
offset: int = 0,
|
|
285
|
+
) -> list[Comment]:
|
|
286
|
+
"""Retrieve comments for a post.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
post_id: Platform-specific post identifier.
|
|
290
|
+
limit: Maximum number of comments to retrieve.
|
|
291
|
+
offset: Number of comments to skip (for pagination).
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
List of Comment objects.
|
|
295
|
+
|
|
296
|
+
Raises:
|
|
297
|
+
PostNotFoundError: If post doesn't exist.
|
|
298
|
+
"""
|
|
299
|
+
pass
|
|
300
|
+
|
|
301
|
+
@abstractmethod
|
|
302
|
+
async def create_comment(self, post_id: str, content: str) -> Comment:
|
|
303
|
+
"""Add a comment to a post.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
post_id: Platform-specific post identifier.
|
|
307
|
+
content: Text content of the comment.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Created Comment object.
|
|
311
|
+
|
|
312
|
+
Raises:
|
|
313
|
+
PostNotFoundError: If post doesn't exist.
|
|
314
|
+
ValidationError: If comment content is invalid.
|
|
315
|
+
"""
|
|
316
|
+
pass
|
|
317
|
+
|
|
318
|
+
@abstractmethod
|
|
319
|
+
async def delete_comment(self, comment_id: str) -> bool:
|
|
320
|
+
"""Delete a comment.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
comment_id: Platform-specific comment identifier.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
True if deletion was successful.
|
|
327
|
+
|
|
328
|
+
Raises:
|
|
329
|
+
PlatformError: If comment doesn't exist or can't be deleted.
|
|
330
|
+
"""
|
|
331
|
+
pass
|
|
332
|
+
|
|
333
|
+
# ==================== Abstract Media Methods ====================
|
|
334
|
+
|
|
335
|
+
@abstractmethod
|
|
336
|
+
async def upload_media(
|
|
337
|
+
self,
|
|
338
|
+
media_url: str,
|
|
339
|
+
media_type: str,
|
|
340
|
+
alt_text: str | None = None,
|
|
341
|
+
) -> MediaAttachment:
|
|
342
|
+
"""Upload media to the platform.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
media_url: URL of the media file to upload.
|
|
346
|
+
media_type: Type of media (image, video, etc.).
|
|
347
|
+
alt_text: Alternative text for accessibility.
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
MediaAttachment object with platform-assigned media ID.
|
|
351
|
+
|
|
352
|
+
Raises:
|
|
353
|
+
MediaUploadError: If upload fails.
|
|
354
|
+
ValidationError: If media type or format is not supported.
|
|
355
|
+
"""
|
|
356
|
+
pass
|
|
357
|
+
|
|
358
|
+
# ==================== Concrete Utility Methods ====================
|
|
359
|
+
|
|
360
|
+
def get_rate_limit_info(self) -> dict[str, Any]:
|
|
361
|
+
"""Get current rate limit information.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
Dictionary with rate limit details.
|
|
365
|
+
"""
|
|
366
|
+
return {
|
|
367
|
+
"remaining": self._rate_limit_remaining,
|
|
368
|
+
"reset_time": self._rate_limit_reset,
|
|
369
|
+
"platform": self.platform_name,
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async def validate_credentials(self) -> PlatformResponse:
|
|
373
|
+
"""Validate current credentials by checking authentication status.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
PlatformResponse indicating if credentials are valid.
|
|
377
|
+
"""
|
|
378
|
+
try:
|
|
379
|
+
is_valid = await self.is_authenticated()
|
|
380
|
+
return PlatformResponse(
|
|
381
|
+
success=is_valid,
|
|
382
|
+
platform=self.platform_name,
|
|
383
|
+
data={"valid": is_valid},
|
|
384
|
+
)
|
|
385
|
+
except Exception as e:
|
|
386
|
+
return PlatformResponse(
|
|
387
|
+
success=False,
|
|
388
|
+
platform=self.platform_name,
|
|
389
|
+
error_message=str(e),
|
|
390
|
+
)
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Custom exceptions for social media platform integrations.
|
|
2
|
+
|
|
3
|
+
This module defines platform-specific exceptions for handling errors
|
|
4
|
+
that may occur during API interactions with various social media platforms.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PlatformError(Exception):
|
|
9
|
+
"""Base exception for all platform-related errors.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
message: Human-readable error message
|
|
13
|
+
platform: Name of the platform where error occurred
|
|
14
|
+
status_code: HTTP status code if applicable
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
>>> raise PlatformError("API request failed", platform="instagram")
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
message: str,
|
|
23
|
+
platform: str | None = None,
|
|
24
|
+
status_code: int | None = None,
|
|
25
|
+
) -> None:
|
|
26
|
+
self.message = message
|
|
27
|
+
self.platform = platform
|
|
28
|
+
self.status_code = status_code
|
|
29
|
+
super().__init__(self._format_message())
|
|
30
|
+
|
|
31
|
+
def _format_message(self) -> str:
|
|
32
|
+
"""Format the error message with platform and status code."""
|
|
33
|
+
parts = [self.message]
|
|
34
|
+
if self.platform:
|
|
35
|
+
parts.append(f"Platform: {self.platform}")
|
|
36
|
+
if self.status_code:
|
|
37
|
+
parts.append(f"Status: {self.status_code}")
|
|
38
|
+
return " | ".join(parts)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PlatformAuthError(PlatformError):
|
|
42
|
+
"""Raised when authentication or authorization fails.
|
|
43
|
+
|
|
44
|
+
This exception is raised when:
|
|
45
|
+
- Authentication credentials are invalid or expired
|
|
46
|
+
- Access token refresh fails
|
|
47
|
+
- OAuth flow encounters errors
|
|
48
|
+
- Insufficient permissions for requested operation
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
>>> raise PlatformAuthError(
|
|
52
|
+
... "Access token expired",
|
|
53
|
+
... platform="twitter",
|
|
54
|
+
... status_code=401
|
|
55
|
+
... )
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class RateLimitError(PlatformError):
|
|
62
|
+
"""Raised when API rate limit is exceeded.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
message: Human-readable error message
|
|
66
|
+
platform: Name of the platform where error occurred
|
|
67
|
+
status_code: HTTP status code (typically 429)
|
|
68
|
+
retry_after: Seconds until rate limit resets
|
|
69
|
+
|
|
70
|
+
Example:
|
|
71
|
+
>>> raise RateLimitError(
|
|
72
|
+
... "Rate limit exceeded",
|
|
73
|
+
... platform="instagram",
|
|
74
|
+
... status_code=429,
|
|
75
|
+
... retry_after=3600
|
|
76
|
+
... )
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
message: str,
|
|
82
|
+
platform: str | None = None,
|
|
83
|
+
status_code: int | None = None,
|
|
84
|
+
retry_after: int | None = None,
|
|
85
|
+
) -> None:
|
|
86
|
+
self.retry_after = retry_after
|
|
87
|
+
super().__init__(message, platform, status_code)
|
|
88
|
+
|
|
89
|
+
def _format_message(self) -> str:
|
|
90
|
+
"""Format the error message with retry information."""
|
|
91
|
+
base_message = super()._format_message()
|
|
92
|
+
if self.retry_after:
|
|
93
|
+
return f"{base_message} | Retry after: {self.retry_after}s"
|
|
94
|
+
return base_message
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class PostNotFoundError(PlatformError):
|
|
98
|
+
"""Raised when a requested post does not exist.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
post_id: ID of the post that was not found
|
|
102
|
+
platform: Name of the platform where error occurred
|
|
103
|
+
status_code: HTTP status code (typically 404)
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
>>> raise PostNotFoundError(
|
|
107
|
+
... post_id="12345",
|
|
108
|
+
... platform="linkedin",
|
|
109
|
+
... status_code=404
|
|
110
|
+
... )
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
post_id: str,
|
|
116
|
+
platform: str | None = None,
|
|
117
|
+
status_code: int | None = None,
|
|
118
|
+
) -> None:
|
|
119
|
+
self.post_id = post_id
|
|
120
|
+
message = f"Post not found: {post_id}"
|
|
121
|
+
super().__init__(message, platform, status_code)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class MediaUploadError(PlatformError):
|
|
125
|
+
"""Raised when media upload fails.
|
|
126
|
+
|
|
127
|
+
This exception is raised when:
|
|
128
|
+
- Media file format is not supported
|
|
129
|
+
- Media file size exceeds platform limits
|
|
130
|
+
- Network error during upload
|
|
131
|
+
- Platform-specific upload validation fails
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
message: Human-readable error message
|
|
135
|
+
platform: Name of the platform where error occurred
|
|
136
|
+
status_code: HTTP status code if applicable
|
|
137
|
+
media_type: Type of media that failed to upload (image, video, etc.)
|
|
138
|
+
|
|
139
|
+
Example:
|
|
140
|
+
>>> raise MediaUploadError(
|
|
141
|
+
... "File size exceeds limit",
|
|
142
|
+
... platform="twitter",
|
|
143
|
+
... media_type="video"
|
|
144
|
+
... )
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
def __init__(
|
|
148
|
+
self,
|
|
149
|
+
message: str,
|
|
150
|
+
platform: str | None = None,
|
|
151
|
+
status_code: int | None = None,
|
|
152
|
+
media_type: str | None = None,
|
|
153
|
+
) -> None:
|
|
154
|
+
self.media_type = media_type
|
|
155
|
+
super().__init__(message, platform, status_code)
|
|
156
|
+
|
|
157
|
+
def _format_message(self) -> str:
|
|
158
|
+
"""Format the error message with media type information."""
|
|
159
|
+
base_message = super()._format_message()
|
|
160
|
+
if self.media_type:
|
|
161
|
+
return f"{base_message} | Media type: {self.media_type}"
|
|
162
|
+
return base_message
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class ValidationError(PlatformError):
|
|
166
|
+
"""Raised when input validation fails.
|
|
167
|
+
|
|
168
|
+
This exception is raised when:
|
|
169
|
+
- Required fields are missing
|
|
170
|
+
- Field values are invalid or out of range
|
|
171
|
+
- Data format doesn't match platform requirements
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
message: Human-readable error message
|
|
175
|
+
platform: Name of the platform where error occurred
|
|
176
|
+
field: Name of the field that failed validation
|
|
177
|
+
|
|
178
|
+
Example:
|
|
179
|
+
>>> raise ValidationError(
|
|
180
|
+
... "Caption exceeds maximum length",
|
|
181
|
+
... platform="instagram",
|
|
182
|
+
... field="caption"
|
|
183
|
+
... )
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def __init__(
|
|
187
|
+
self,
|
|
188
|
+
message: str,
|
|
189
|
+
platform: str | None = None,
|
|
190
|
+
field: str | None = None,
|
|
191
|
+
) -> None:
|
|
192
|
+
self.field = field
|
|
193
|
+
super().__init__(message, platform)
|
|
194
|
+
|
|
195
|
+
def _format_message(self) -> str:
|
|
196
|
+
"""Format the error message with field information."""
|
|
197
|
+
base_message = super()._format_message()
|
|
198
|
+
if self.field:
|
|
199
|
+
return f"{base_message} | Field: {self.field}"
|
|
200
|
+
return base_message
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class InvalidFileTypeError(PlatformError):
|
|
204
|
+
"""Raised when file type is not supported by the platform.
|
|
205
|
+
|
|
206
|
+
This exception is raised when:
|
|
207
|
+
- File MIME type is not supported
|
|
208
|
+
- File extension doesn't match content
|
|
209
|
+
- Platform doesn't accept the file format
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
message: Human-readable error message
|
|
213
|
+
platform: Name of the platform where error occurred
|
|
214
|
+
file_type: The invalid file type/MIME type
|
|
215
|
+
|
|
216
|
+
Example:
|
|
217
|
+
>>> raise InvalidFileTypeError(
|
|
218
|
+
... "BMP images not supported",
|
|
219
|
+
... platform="instagram",
|
|
220
|
+
... file_type="image/bmp"
|
|
221
|
+
... )
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
def __init__(
|
|
225
|
+
self,
|
|
226
|
+
message: str,
|
|
227
|
+
platform: str | None = None,
|
|
228
|
+
file_type: str | None = None,
|
|
229
|
+
) -> None:
|
|
230
|
+
self.file_type = file_type
|
|
231
|
+
super().__init__(message, platform)
|
|
232
|
+
|
|
233
|
+
def _format_message(self) -> str:
|
|
234
|
+
"""Format the error message with file type information."""
|
|
235
|
+
base_message = super()._format_message()
|
|
236
|
+
if self.file_type:
|
|
237
|
+
return f"{base_message} | File type: {self.file_type}"
|
|
238
|
+
return base_message
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Instagram platform integration."""
|
|
2
|
+
|
|
3
|
+
from marqetive.platforms.instagram.client import InstagramClient
|
|
4
|
+
from marqetive.platforms.instagram.factory import InstagramAccountFactory
|
|
5
|
+
from marqetive.platforms.instagram.manager import InstagramPostManager
|
|
6
|
+
|
|
7
|
+
__all__ = ["InstagramClient", "InstagramAccountFactory", "InstagramPostManager"]
|