marqetive-lib 0.1.3__py3-none-any.whl → 0.1.4__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 +54 -55
- marqetive/factory.py +380 -0
- marqetive/platforms/base.py +33 -0
- marqetive/platforms/instagram/__init__.py +1 -3
- marqetive/platforms/instagram/client.py +5 -1
- marqetive/platforms/linkedin/__init__.py +1 -3
- marqetive/platforms/linkedin/client.py +5 -1
- marqetive/platforms/tiktok/__init__.py +1 -3
- marqetive/platforms/tiktok/client.py +10 -5
- marqetive/platforms/tiktok/media.py +1 -3
- marqetive/platforms/twitter/__init__.py +1 -3
- marqetive/platforms/twitter/client.py +5 -1
- {marqetive_lib-0.1.3.dist-info → marqetive_lib-0.1.4.dist-info}/METADATA +1 -1
- marqetive_lib-0.1.4.dist-info/RECORD +35 -0
- marqetive/core/account_factory.py +0 -212
- marqetive/core/base_manager.py +0 -303
- marqetive/core/progress.py +0 -291
- marqetive/core/registry.py +0 -257
- marqetive/platforms/instagram/factory.py +0 -106
- marqetive/platforms/instagram/manager.py +0 -112
- marqetive/platforms/linkedin/factory.py +0 -130
- marqetive/platforms/linkedin/manager.py +0 -119
- marqetive/platforms/tiktok/factory.py +0 -188
- marqetive/platforms/tiktok/manager.py +0 -115
- marqetive/platforms/twitter/factory.py +0 -150
- marqetive/platforms/twitter/manager.py +0 -121
- marqetive/registry_init.py +0 -68
- marqetive_lib-0.1.3.dist-info/RECORD +0 -47
- {marqetive_lib-0.1.3.dist-info → marqetive_lib-0.1.4.dist-info}/WHEEL +0 -0
marqetive/core/progress.py
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
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)
|
marqetive/core/registry.py
DELETED
|
@@ -1,257 +0,0 @@
|
|
|
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.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.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()
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
"""Instagram account factory for managing credentials and client creation."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
from collections.abc import Callable
|
|
5
|
-
|
|
6
|
-
from marqetive.core.account_factory import BaseAccountFactory
|
|
7
|
-
from marqetive.platforms.exceptions import PlatformAuthError
|
|
8
|
-
from marqetive.platforms.instagram.client import InstagramClient
|
|
9
|
-
from marqetive.platforms.models import AccountStatus, AuthCredentials
|
|
10
|
-
from marqetive.utils.oauth import refresh_instagram_token
|
|
11
|
-
|
|
12
|
-
logger = logging.getLogger(__name__)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class InstagramAccountFactory(BaseAccountFactory):
|
|
16
|
-
"""Factory for creating and managing Instagram accounts and clients.
|
|
17
|
-
|
|
18
|
-
Example:
|
|
19
|
-
>>> factory = InstagramAccountFactory()
|
|
20
|
-
>>> credentials = AuthCredentials(
|
|
21
|
-
... platform="instagram",
|
|
22
|
-
... access_token="token"
|
|
23
|
-
... )
|
|
24
|
-
>>> client = await factory.create_authenticated_client(credentials)
|
|
25
|
-
>>> async with client:
|
|
26
|
-
... post = await client.create_post(request)
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
def __init__(
|
|
30
|
-
self,
|
|
31
|
-
on_status_update: Callable[[str, AccountStatus], None] | None = None,
|
|
32
|
-
) -> None:
|
|
33
|
-
"""Initialize Instagram account factory.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
on_status_update: Optional callback when account status changes.
|
|
37
|
-
"""
|
|
38
|
-
super().__init__(on_status_update=on_status_update)
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def platform_name(self) -> str:
|
|
42
|
-
"""Get platform name."""
|
|
43
|
-
return "instagram"
|
|
44
|
-
|
|
45
|
-
async def refresh_token(self, credentials: AuthCredentials) -> AuthCredentials:
|
|
46
|
-
"""Refresh Instagram long-lived access token.
|
|
47
|
-
|
|
48
|
-
Args:
|
|
49
|
-
credentials: Current credentials.
|
|
50
|
-
|
|
51
|
-
Returns:
|
|
52
|
-
Updated credentials with refreshed token.
|
|
53
|
-
|
|
54
|
-
Raises:
|
|
55
|
-
PlatformAuthError: If refresh fails.
|
|
56
|
-
"""
|
|
57
|
-
logger.info("Refreshing Instagram access token...")
|
|
58
|
-
return await refresh_instagram_token(credentials)
|
|
59
|
-
|
|
60
|
-
async def create_client(self, credentials: AuthCredentials) -> InstagramClient:
|
|
61
|
-
"""Create Instagram API client.
|
|
62
|
-
|
|
63
|
-
Args:
|
|
64
|
-
credentials: Valid Instagram credentials.
|
|
65
|
-
|
|
66
|
-
Returns:
|
|
67
|
-
InstagramClient instance.
|
|
68
|
-
|
|
69
|
-
Raises:
|
|
70
|
-
PlatformAuthError: If credentials are invalid.
|
|
71
|
-
"""
|
|
72
|
-
if not credentials.access_token:
|
|
73
|
-
raise PlatformAuthError(
|
|
74
|
-
"Access token is required",
|
|
75
|
-
platform=self.platform_name,
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
# Instagram needs instagram_business_account_id in additional_data
|
|
79
|
-
instagram_business_account_id = credentials.additional_data.get(
|
|
80
|
-
"instagram_business_account_id"
|
|
81
|
-
)
|
|
82
|
-
if not instagram_business_account_id:
|
|
83
|
-
raise PlatformAuthError(
|
|
84
|
-
"instagram_business_account_id is required in additional_data",
|
|
85
|
-
platform=self.platform_name,
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
return InstagramClient(credentials=credentials)
|
|
89
|
-
|
|
90
|
-
async def validate_credentials(self, credentials: AuthCredentials) -> bool:
|
|
91
|
-
"""Validate Instagram credentials by making a test API call.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
credentials: Credentials to validate.
|
|
95
|
-
|
|
96
|
-
Returns:
|
|
97
|
-
True if credentials are valid, False otherwise.
|
|
98
|
-
"""
|
|
99
|
-
try:
|
|
100
|
-
client = await self.create_client(credentials)
|
|
101
|
-
async with client:
|
|
102
|
-
# Try to verify credentials
|
|
103
|
-
return await client.is_authenticated()
|
|
104
|
-
except Exception as e:
|
|
105
|
-
logger.error(f"Error validating Instagram credentials: {e}")
|
|
106
|
-
return False
|