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.
Files changed (43) hide show
  1. marqetive/__init__.py +113 -0
  2. marqetive/core/__init__.py +5 -0
  3. marqetive/core/account_factory.py +212 -0
  4. marqetive/core/base_manager.py +303 -0
  5. marqetive/core/client.py +108 -0
  6. marqetive/core/progress.py +291 -0
  7. marqetive/core/registry.py +257 -0
  8. marqetive/platforms/__init__.py +55 -0
  9. marqetive/platforms/base.py +390 -0
  10. marqetive/platforms/exceptions.py +238 -0
  11. marqetive/platforms/instagram/__init__.py +7 -0
  12. marqetive/platforms/instagram/client.py +786 -0
  13. marqetive/platforms/instagram/exceptions.py +311 -0
  14. marqetive/platforms/instagram/factory.py +106 -0
  15. marqetive/platforms/instagram/manager.py +112 -0
  16. marqetive/platforms/instagram/media.py +669 -0
  17. marqetive/platforms/linkedin/__init__.py +7 -0
  18. marqetive/platforms/linkedin/client.py +733 -0
  19. marqetive/platforms/linkedin/exceptions.py +335 -0
  20. marqetive/platforms/linkedin/factory.py +130 -0
  21. marqetive/platforms/linkedin/manager.py +119 -0
  22. marqetive/platforms/linkedin/media.py +549 -0
  23. marqetive/platforms/models.py +345 -0
  24. marqetive/platforms/tiktok/__init__.py +0 -0
  25. marqetive/platforms/twitter/__init__.py +7 -0
  26. marqetive/platforms/twitter/client.py +647 -0
  27. marqetive/platforms/twitter/exceptions.py +311 -0
  28. marqetive/platforms/twitter/factory.py +151 -0
  29. marqetive/platforms/twitter/manager.py +121 -0
  30. marqetive/platforms/twitter/media.py +779 -0
  31. marqetive/platforms/twitter/threads.py +442 -0
  32. marqetive/py.typed +0 -0
  33. marqetive/registry_init.py +66 -0
  34. marqetive/utils/__init__.py +45 -0
  35. marqetive/utils/file_handlers.py +438 -0
  36. marqetive/utils/helpers.py +99 -0
  37. marqetive/utils/media.py +399 -0
  38. marqetive/utils/oauth.py +265 -0
  39. marqetive/utils/retry.py +239 -0
  40. marqetive/utils/token_validator.py +240 -0
  41. marqetive_lib-0.1.0.dist-info/METADATA +261 -0
  42. marqetive_lib-0.1.0.dist-info/RECORD +43 -0
  43. marqetive_lib-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,108 @@
1
+ """API client implementation for MarqetiveLib."""
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+ from pydantic import BaseModel
7
+
8
+
9
+ class APIResponse(BaseModel):
10
+ """Response model for API calls."""
11
+
12
+ status_code: int
13
+ data: dict[str, Any]
14
+ headers: dict[str, str]
15
+
16
+
17
+ class APIClient:
18
+ """A simple HTTP API client with type hints.
19
+
20
+ This client provides a clean interface for making HTTP requests
21
+ with automatic response parsing and error handling.
22
+
23
+ Args:
24
+ base_url: The base URL for API requests
25
+ timeout: Request timeout in seconds (default: 30)
26
+ headers: Optional default headers for all requests
27
+
28
+ Example:
29
+ >>> client = APIClient(base_url="https://api.example.com")
30
+ >>> response = await client.get("/users/1")
31
+ >>> print(response.data)
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ base_url: str,
37
+ timeout: float = 30.0,
38
+ headers: dict[str, str] | None = None,
39
+ ) -> None:
40
+ """Initialize the API client."""
41
+ self.base_url = base_url.rstrip("/")
42
+ self.timeout = timeout
43
+ self.default_headers = headers or {}
44
+ self._client: httpx.AsyncClient | None = None
45
+
46
+ async def __aenter__(self) -> "APIClient":
47
+ """Async context manager entry."""
48
+ self._client = httpx.AsyncClient(
49
+ base_url=self.base_url,
50
+ timeout=self.timeout,
51
+ headers=self.default_headers,
52
+ )
53
+ return self
54
+
55
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
56
+ """Async context manager exit."""
57
+ if self._client:
58
+ await self._client.aclose()
59
+
60
+ async def get(self, path: str, params: dict[str, Any] | None = None) -> APIResponse:
61
+ """Make a GET request.
62
+
63
+ Args:
64
+ path: The endpoint path
65
+ params: Optional query parameters
66
+
67
+ Returns:
68
+ APIResponse object containing status, data, and headers
69
+
70
+ Raises:
71
+ httpx.HTTPError: If the request fails
72
+ """
73
+ if not self._client:
74
+ raise RuntimeError("Client not initialized. Use async context manager.")
75
+
76
+ response: httpx.Response = await self._client.get(path, params=params)
77
+ response.raise_for_status()
78
+
79
+ return APIResponse(
80
+ status_code=response.status_code,
81
+ data=response.json() if response.content else {},
82
+ headers=dict(response.headers),
83
+ )
84
+
85
+ async def post(self, path: str, data: dict[str, Any] | None = None) -> APIResponse:
86
+ """Make a POST request.
87
+
88
+ Args:
89
+ path: The endpoint path
90
+ data: Optional request body data
91
+
92
+ Returns:
93
+ APIResponse object containing status, data, and headers
94
+
95
+ Raises:
96
+ httpx.HTTPError: If the request fails
97
+ """
98
+ if not self._client:
99
+ raise RuntimeError("Client not initialized. Use async context manager.")
100
+
101
+ response = await self._client.post(path, json=data)
102
+ response.raise_for_status()
103
+
104
+ return APIResponse(
105
+ status_code=response.status_code,
106
+ data=response.json() if response.content else {},
107
+ headers=dict(response.headers),
108
+ )
@@ -0,0 +1,291 @@
1
+ """Progress tracking system for long-running operations.
2
+
3
+ This module provides models and utilities for tracking progress of operations
4
+ like media uploads, post creation, and other long-running tasks.
5
+ """
6
+
7
+ from collections.abc import Callable
8
+ from datetime import datetime
9
+ from enum import Enum
10
+ from typing import Any
11
+
12
+ from pydantic import BaseModel, Field
13
+
14
+
15
+ class ProgressStatus(str, Enum):
16
+ """Status of a progress event."""
17
+
18
+ STARTED = "started"
19
+ IN_PROGRESS = "in_progress"
20
+ COMPLETED = "completed"
21
+ FAILED = "failed"
22
+ CANCELLED = "cancelled"
23
+
24
+
25
+ class ProgressEvent(BaseModel):
26
+ """Represents a progress event for an operation.
27
+
28
+ Attributes:
29
+ operation: Name of the operation (e.g., "upload_media", "create_post").
30
+ progress: Current progress value (e.g., bytes uploaded).
31
+ total: Total expected value (e.g., total bytes).
32
+ status: Current status of the operation.
33
+ message: Optional human-readable message.
34
+ metadata: Additional operation-specific data.
35
+ timestamp: When this event occurred.
36
+
37
+ Example:
38
+ >>> event = ProgressEvent(
39
+ ... operation="upload_media",
40
+ ... progress=500,
41
+ ... total=1000,
42
+ ... status=ProgressStatus.IN_PROGRESS,
43
+ ... message="Uploading image..."
44
+ ... )
45
+ >>> print(f"Progress: {event.percentage}%")
46
+ Progress: 50.0%
47
+ """
48
+
49
+ operation: str
50
+ progress: int | float
51
+ total: int | float | None = None
52
+ status: ProgressStatus
53
+ message: str | None = None
54
+ metadata: dict[str, Any] = Field(default_factory=dict)
55
+ timestamp: datetime = Field(default_factory=datetime.now)
56
+
57
+ @property
58
+ def percentage(self) -> float:
59
+ """Calculate progress as a percentage.
60
+
61
+ Returns:
62
+ Progress percentage (0-100), or 0 if total is None.
63
+ """
64
+ if self.total is None or self.total == 0:
65
+ return 0.0
66
+ return (self.progress / self.total) * 100
67
+
68
+ def is_complete(self) -> bool:
69
+ """Check if operation is complete.
70
+
71
+ Returns:
72
+ True if status is COMPLETED, False otherwise.
73
+ """
74
+ return self.status == ProgressStatus.COMPLETED
75
+
76
+ def is_failed(self) -> bool:
77
+ """Check if operation failed.
78
+
79
+ Returns:
80
+ True if status is FAILED, False otherwise.
81
+ """
82
+ return self.status == ProgressStatus.FAILED
83
+
84
+
85
+ # Type alias for progress callback functions
86
+ ProgressCallback = Callable[[ProgressEvent], None]
87
+
88
+
89
+ class ProgressTracker:
90
+ """Tracks and emits progress events for operations.
91
+
92
+ Manages multiple progress callbacks and provides utilities for
93
+ emitting progress events with consistent formatting.
94
+
95
+ Example:
96
+ >>> tracker = ProgressTracker()
97
+ >>> tracker.add_callback(lambda e: print(f"{e.operation}: {e.percentage}%"))
98
+ >>>
99
+ >>> tracker.emit_start("upload_media")
100
+ >>> tracker.emit_progress("upload_media", 500, 1000, "Uploading...")
101
+ >>> tracker.emit_complete("upload_media", "Upload complete")
102
+ """
103
+
104
+ def __init__(self) -> None:
105
+ """Initialize progress tracker."""
106
+ self._callbacks: list[ProgressCallback] = []
107
+
108
+ def add_callback(self, callback: ProgressCallback) -> None:
109
+ """Add a progress callback.
110
+
111
+ Args:
112
+ callback: Function to call when progress events occur.
113
+
114
+ Example:
115
+ >>> def my_callback(event: ProgressEvent) -> None:
116
+ ... print(f"{event.operation}: {event.percentage}%")
117
+ >>>
118
+ >>> tracker.add_callback(my_callback)
119
+ """
120
+ self._callbacks.append(callback)
121
+
122
+ def remove_callback(self, callback: ProgressCallback) -> None:
123
+ """Remove a progress callback.
124
+
125
+ Args:
126
+ callback: Callback to remove.
127
+ """
128
+ if callback in self._callbacks:
129
+ self._callbacks.remove(callback)
130
+
131
+ def clear_callbacks(self) -> None:
132
+ """Remove all callbacks."""
133
+ self._callbacks.clear()
134
+
135
+ def emit(self, event: ProgressEvent) -> None:
136
+ """Emit a progress event to all callbacks.
137
+
138
+ Args:
139
+ event: Progress event to emit.
140
+ """
141
+ import contextlib
142
+
143
+ for callback in self._callbacks:
144
+ with contextlib.suppress(Exception):
145
+ # Silently ignore callback errors to prevent disrupting operations
146
+ callback(event)
147
+
148
+ def emit_start(
149
+ self,
150
+ operation: str,
151
+ total: int | float | None = None,
152
+ message: str | None = None,
153
+ **metadata: Any,
154
+ ) -> None:
155
+ """Emit an operation start event.
156
+
157
+ Args:
158
+ operation: Name of the operation.
159
+ total: Total expected value (optional).
160
+ message: Optional message.
161
+ **metadata: Additional metadata.
162
+
163
+ Example:
164
+ >>> tracker.emit_start("upload_media", total=1024000,
165
+ ... message="Starting upload...")
166
+ """
167
+ event = ProgressEvent(
168
+ operation=operation,
169
+ progress=0,
170
+ total=total,
171
+ status=ProgressStatus.STARTED,
172
+ message=message or f"Starting {operation}",
173
+ metadata=metadata,
174
+ )
175
+ self.emit(event)
176
+
177
+ def emit_progress(
178
+ self,
179
+ operation: str,
180
+ progress: int | float,
181
+ total: int | float | None = None,
182
+ message: str | None = None,
183
+ **metadata: Any,
184
+ ) -> None:
185
+ """Emit a progress update event.
186
+
187
+ Args:
188
+ operation: Name of the operation.
189
+ progress: Current progress value.
190
+ total: Total expected value (optional).
191
+ message: Optional message.
192
+ **metadata: Additional metadata.
193
+
194
+ Example:
195
+ >>> tracker.emit_progress("upload_media", 512000, 1024000,
196
+ ... message="Uploading...")
197
+ """
198
+ event = ProgressEvent(
199
+ operation=operation,
200
+ progress=progress,
201
+ total=total,
202
+ status=ProgressStatus.IN_PROGRESS,
203
+ message=message,
204
+ metadata=metadata,
205
+ )
206
+ self.emit(event)
207
+
208
+ def emit_complete(
209
+ self,
210
+ operation: str,
211
+ message: str | None = None,
212
+ **metadata: Any,
213
+ ) -> None:
214
+ """Emit an operation complete event.
215
+
216
+ Args:
217
+ operation: Name of the operation.
218
+ message: Optional message.
219
+ **metadata: Additional metadata.
220
+
221
+ Example:
222
+ >>> tracker.emit_complete("upload_media",
223
+ ... message="Upload successful!")
224
+ """
225
+ event = ProgressEvent(
226
+ operation=operation,
227
+ progress=100,
228
+ total=100,
229
+ status=ProgressStatus.COMPLETED,
230
+ message=message or f"{operation} completed",
231
+ metadata=metadata,
232
+ )
233
+ self.emit(event)
234
+
235
+ def emit_failed(
236
+ self,
237
+ operation: str,
238
+ error: str | Exception,
239
+ **metadata: Any,
240
+ ) -> None:
241
+ """Emit an operation failed event.
242
+
243
+ Args:
244
+ operation: Name of the operation.
245
+ error: Error message or exception.
246
+ **metadata: Additional metadata.
247
+
248
+ Example:
249
+ >>> try:
250
+ ... # Some operation
251
+ ... pass
252
+ ... except Exception as e:
253
+ ... tracker.emit_failed("upload_media", e)
254
+ """
255
+ message = str(error) if isinstance(error, Exception) else error
256
+ event = ProgressEvent(
257
+ operation=operation,
258
+ progress=0,
259
+ total=100,
260
+ status=ProgressStatus.FAILED,
261
+ message=message,
262
+ metadata=metadata,
263
+ )
264
+ self.emit(event)
265
+
266
+ def emit_cancelled(
267
+ self,
268
+ operation: str,
269
+ message: str | None = None,
270
+ **metadata: Any,
271
+ ) -> None:
272
+ """Emit an operation cancelled event.
273
+
274
+ Args:
275
+ operation: Name of the operation.
276
+ message: Optional message.
277
+ **metadata: Additional metadata.
278
+
279
+ Example:
280
+ >>> tracker.emit_cancelled("upload_media",
281
+ ... message="Upload cancelled by user")
282
+ """
283
+ event = ProgressEvent(
284
+ operation=operation,
285
+ progress=0,
286
+ total=100,
287
+ status=ProgressStatus.CANCELLED,
288
+ message=message or f"{operation} cancelled",
289
+ metadata=metadata,
290
+ )
291
+ self.emit(event)
@@ -0,0 +1,257 @@
1
+ """Platform registry for managing platform manager instances.
2
+
3
+ This module provides a singleton registry pattern for registering and accessing
4
+ platform managers across the application.
5
+ """
6
+
7
+ import threading
8
+ from typing import Any, TypeVar
9
+
10
+ from marqetive.platforms.models import AuthCredentials
11
+
12
+ # Type variable for manager classes
13
+ ManagerType = TypeVar("ManagerType")
14
+
15
+
16
+ class PlatformRegistry:
17
+ """Singleton registry for platform managers.
18
+
19
+ The registry maintains a mapping of platform names to their manager classes
20
+ and provides caching of manager instances to avoid unnecessary recreation.
21
+
22
+ Thread-safe implementation using threading.Lock.
23
+
24
+ Example:
25
+ >>> from marqetive_lib.platforms.twitter.manager import TwitterPostManager
26
+ >>> registry = PlatformRegistry()
27
+ >>> registry.register_platform("twitter", TwitterPostManager)
28
+ >>> manager = registry.get_manager("twitter", credentials=creds)
29
+ """
30
+
31
+ _instance: "PlatformRegistry | None" = None
32
+ _lock: threading.Lock = threading.Lock()
33
+
34
+ def __new__(cls) -> "PlatformRegistry":
35
+ """Ensure only one instance exists (singleton pattern)."""
36
+ if cls._instance is None:
37
+ with cls._lock:
38
+ # Double-check locking pattern
39
+ if cls._instance is None:
40
+ cls._instance = super().__new__(cls)
41
+ cls._instance._initialized = False
42
+ return cls._instance
43
+
44
+ def __init__(self) -> None:
45
+ """Initialize the registry."""
46
+ # Only initialize once
47
+ if self._initialized:
48
+ return
49
+
50
+ self._platforms: dict[str, type] = {}
51
+ self._manager_cache: dict[str, Any] = {}
52
+ self._cache_lock = threading.Lock()
53
+ self._initialized = True
54
+
55
+ def register_platform(self, platform_name: str, manager_class: type) -> None:
56
+ """Register a platform manager class.
57
+
58
+ Args:
59
+ platform_name: Name of the platform (e.g., "twitter", "linkedin").
60
+ manager_class: The manager class to register.
61
+
62
+ Raises:
63
+ ValueError: If platform is already registered.
64
+
65
+ Example:
66
+ >>> registry.register_platform("twitter", TwitterPostManager)
67
+ """
68
+ if platform_name in self._platforms:
69
+ raise ValueError(f"Platform '{platform_name}' is already registered")
70
+
71
+ self._platforms[platform_name] = manager_class
72
+
73
+ def unregister_platform(self, platform_name: str) -> None:
74
+ """Unregister a platform and clear its cached instances.
75
+
76
+ Args:
77
+ platform_name: Name of the platform to unregister.
78
+
79
+ Example:
80
+ >>> registry.unregister_platform("twitter")
81
+ """
82
+ with self._cache_lock:
83
+ self._platforms.pop(platform_name, None)
84
+ # Clear cached instances for this platform
85
+ keys_to_remove = [
86
+ k for k in self._manager_cache if k.startswith(f"{platform_name}:")
87
+ ]
88
+ for key in keys_to_remove:
89
+ self._manager_cache.pop(key)
90
+
91
+ def get_manager(
92
+ self,
93
+ platform_name: str,
94
+ use_cache: bool = True,
95
+ **kwargs: Any,
96
+ ) -> Any:
97
+ """Get or create a manager instance for the specified platform.
98
+
99
+ Args:
100
+ platform_name: Name of the platform (e.g., "twitter", "linkedin").
101
+ use_cache: Whether to use cached instance (default: True).
102
+ **kwargs: Arguments to pass to the manager constructor.
103
+
104
+ Returns:
105
+ Manager instance for the platform.
106
+
107
+ Raises:
108
+ ValueError: If platform is not registered.
109
+
110
+ Example:
111
+ >>> manager = registry.get_manager("twitter", credentials=creds)
112
+ >>> post = await manager.execute_post(...)
113
+ """
114
+ if platform_name not in self._platforms:
115
+ available = ", ".join(self.get_available_platforms())
116
+ raise ValueError(
117
+ f"Platform '{platform_name}' is not registered. "
118
+ f"Available platforms: {available}"
119
+ )
120
+
121
+ # Generate cache key from platform name and kwargs
122
+ cache_key = self._generate_cache_key(platform_name, **kwargs)
123
+
124
+ if use_cache:
125
+ with self._cache_lock:
126
+ if cache_key in self._manager_cache:
127
+ return self._manager_cache[cache_key]
128
+
129
+ # Create new manager instance
130
+ manager_class = self._platforms[platform_name]
131
+ manager = manager_class(**kwargs)
132
+
133
+ if use_cache:
134
+ with self._cache_lock:
135
+ self._manager_cache[cache_key] = manager
136
+
137
+ return manager
138
+
139
+ def get_available_platforms(self) -> list[str]:
140
+ """Get list of all registered platform names.
141
+
142
+ Returns:
143
+ List of platform names.
144
+
145
+ Example:
146
+ >>> platforms = registry.get_available_platforms()
147
+ >>> print(platforms)
148
+ ['twitter', 'linkedin', 'instagram', 'tiktok']
149
+ """
150
+ return list(self._platforms.keys())
151
+
152
+ def clear_cache(self) -> None:
153
+ """Clear all cached manager instances.
154
+
155
+ Example:
156
+ >>> registry.clear_cache()
157
+ """
158
+ with self._cache_lock:
159
+ self._manager_cache.clear()
160
+
161
+ def _generate_cache_key(self, platform_name: str, **kwargs: Any) -> str:
162
+ """Generate a cache key for manager instance.
163
+
164
+ Args:
165
+ platform_name: Name of the platform.
166
+ **kwargs: Manager constructor arguments.
167
+
168
+ Returns:
169
+ Cache key string.
170
+ """
171
+ # For credentials, use account_id if available
172
+ if "credentials" in kwargs:
173
+ creds = kwargs["credentials"]
174
+ if isinstance(creds, AuthCredentials) and creds.user_id:
175
+ return f"{platform_name}:user_id:{creds.user_id}"
176
+
177
+ # Default to platform name only
178
+ return platform_name
179
+
180
+
181
+ # Global registry instance
182
+ _global_registry: PlatformRegistry | None = None
183
+ _global_lock = threading.Lock()
184
+
185
+
186
+ def get_registry() -> PlatformRegistry:
187
+ """Get the global platform registry instance.
188
+
189
+ Returns:
190
+ Global PlatformRegistry instance.
191
+
192
+ Example:
193
+ >>> registry = get_registry()
194
+ >>> manager = registry.get_manager("twitter", credentials=creds)
195
+ """
196
+ global _global_registry
197
+
198
+ if _global_registry is None:
199
+ with _global_lock:
200
+ if _global_registry is None:
201
+ _global_registry = PlatformRegistry()
202
+
203
+ return _global_registry
204
+
205
+
206
+ def register_platform(platform_name: str, manager_class: type) -> None:
207
+ """Register a platform in the global registry.
208
+
209
+ Convenience function that uses the global registry.
210
+
211
+ Args:
212
+ platform_name: Name of the platform.
213
+ manager_class: The manager class to register.
214
+
215
+ Example:
216
+ >>> from marqetive_lib.platforms.twitter.manager import TwitterPostManager
217
+ >>> register_platform("twitter", TwitterPostManager)
218
+ """
219
+ registry = get_registry()
220
+ registry.register_platform(platform_name, manager_class)
221
+
222
+
223
+ def get_manager_for_platform(platform_name: str, **kwargs: Any) -> Any:
224
+ """Get a manager instance for the specified platform.
225
+
226
+ Convenience function that uses the global registry.
227
+
228
+ Args:
229
+ platform_name: Name of the platform.
230
+ **kwargs: Arguments to pass to the manager constructor.
231
+
232
+ Returns:
233
+ Manager instance for the platform.
234
+
235
+ Example:
236
+ >>> manager = get_manager_for_platform("twitter", credentials=creds)
237
+ >>> post = await manager.execute_post(...)
238
+ """
239
+ registry = get_registry()
240
+ return registry.get_manager(platform_name, **kwargs)
241
+
242
+
243
+ def get_available_platforms() -> list[str]:
244
+ """Get list of all registered platforms.
245
+
246
+ Convenience function that uses the global registry.
247
+
248
+ Returns:
249
+ List of platform names.
250
+
251
+ Example:
252
+ >>> platforms = get_available_platforms()
253
+ >>> print(platforms)
254
+ ['twitter', 'linkedin', 'instagram']
255
+ """
256
+ registry = get_registry()
257
+ return registry.get_available_platforms()
@@ -0,0 +1,55 @@
1
+ """Social media platform integrations.
2
+
3
+ This package provides a unified interface for interacting with various social
4
+ media platforms including Instagram, Twitter/X, and LinkedIn.
5
+
6
+ Platform clients are available via their respective subpackages:
7
+ - from marqetive_lib.platforms.twitter import TwitterClient
8
+ - from marqetive_lib.platforms.linkedin import LinkedInClient
9
+ - from marqetive_lib.platforms.instagram import InstagramClient
10
+ """
11
+
12
+ from marqetive.platforms.base import SocialMediaPlatform
13
+ from marqetive.platforms.exceptions import (
14
+ MediaUploadError,
15
+ PlatformAuthError,
16
+ PlatformError,
17
+ PostNotFoundError,
18
+ RateLimitError,
19
+ ValidationError,
20
+ )
21
+ from marqetive.platforms.models import (
22
+ AuthCredentials,
23
+ Comment,
24
+ CommentStatus,
25
+ MediaAttachment,
26
+ MediaType,
27
+ PlatformResponse,
28
+ Post,
29
+ PostCreateRequest,
30
+ PostStatus,
31
+ PostUpdateRequest,
32
+ )
33
+
34
+ __all__ = [
35
+ # Base class
36
+ "SocialMediaPlatform",
37
+ # Models
38
+ "AuthCredentials",
39
+ "Comment",
40
+ "CommentStatus",
41
+ "MediaAttachment",
42
+ "MediaType",
43
+ "PlatformResponse",
44
+ "Post",
45
+ "PostCreateRequest",
46
+ "PostStatus",
47
+ "PostUpdateRequest",
48
+ # Exceptions
49
+ "MediaUploadError",
50
+ "PlatformAuthError",
51
+ "PlatformError",
52
+ "PostNotFoundError",
53
+ "RateLimitError",
54
+ "ValidationError",
55
+ ]