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
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""TikTok platform integration."""
|
|
2
2
|
|
|
3
3
|
from marqetive.platforms.tiktok.client import TikTokClient
|
|
4
|
-
from marqetive.platforms.tiktok.factory import TikTokAccountFactory
|
|
5
|
-
from marqetive.platforms.tiktok.manager import TikTokPostManager
|
|
6
4
|
|
|
7
|
-
__all__ = ["TikTokClient"
|
|
5
|
+
__all__ = ["TikTokClient"]
|
|
@@ -11,7 +11,7 @@ from typing import Any
|
|
|
11
11
|
|
|
12
12
|
from pydantic import HttpUrl
|
|
13
13
|
|
|
14
|
-
from marqetive.platforms.base import SocialMediaPlatform
|
|
14
|
+
from marqetive.platforms.base import ProgressCallback, SocialMediaPlatform
|
|
15
15
|
from marqetive.platforms.exceptions import (
|
|
16
16
|
PlatformAuthError,
|
|
17
17
|
PlatformError,
|
|
@@ -71,18 +71,22 @@ class TikTokClient(SocialMediaPlatform):
|
|
|
71
71
|
self,
|
|
72
72
|
credentials: AuthCredentials,
|
|
73
73
|
timeout: float = 300.0,
|
|
74
|
+
progress_callback: ProgressCallback | None = None,
|
|
74
75
|
) -> None:
|
|
75
76
|
"""Initialize TikTok client.
|
|
76
77
|
|
|
77
78
|
Args:
|
|
78
79
|
credentials: OAuth credentials with access_token and open_id in additional_data.
|
|
79
80
|
timeout: Request timeout in seconds (default 300s for video processing).
|
|
81
|
+
progress_callback: Optional callback for progress updates during
|
|
82
|
+
long-running operations like video uploads.
|
|
80
83
|
"""
|
|
81
84
|
super().__init__(
|
|
82
85
|
platform_name="tiktok",
|
|
83
86
|
credentials=credentials,
|
|
84
87
|
base_url=TIKTOK_API_BASE,
|
|
85
88
|
timeout=timeout,
|
|
89
|
+
progress_callback=progress_callback,
|
|
86
90
|
)
|
|
87
91
|
self._media_manager: TikTokMediaManager | None = None
|
|
88
92
|
self._creator_info: CreatorInfo | None = None
|
|
@@ -96,9 +100,7 @@ class TikTokClient(SocialMediaPlatform):
|
|
|
96
100
|
not self.credentials.additional_data
|
|
97
101
|
or "open_id" not in self.credentials.additional_data
|
|
98
102
|
):
|
|
99
|
-
raise PlatformAuthError(
|
|
100
|
-
"open_id is required in additional_data", "tiktok"
|
|
101
|
-
)
|
|
103
|
+
raise PlatformAuthError("open_id is required in additional_data", "tiktok")
|
|
102
104
|
|
|
103
105
|
self._media_manager = TikTokMediaManager(
|
|
104
106
|
access_token=self.credentials.access_token,
|
|
@@ -357,7 +359,10 @@ class TikTokClient(SocialMediaPlatform):
|
|
|
357
359
|
)
|
|
358
360
|
|
|
359
361
|
async def upload_media(
|
|
360
|
-
self,
|
|
362
|
+
self,
|
|
363
|
+
media_url: str,
|
|
364
|
+
media_type: str,
|
|
365
|
+
alt_text: str | None = None, # noqa: ARG002
|
|
361
366
|
) -> MediaAttachment:
|
|
362
367
|
"""Upload a video to TikTok.
|
|
363
368
|
|
|
@@ -664,9 +664,7 @@ class TikTokMediaManager:
|
|
|
664
664
|
media_type=mime_type,
|
|
665
665
|
)
|
|
666
666
|
|
|
667
|
-
def _check_response_error(
|
|
668
|
-
self, status_code: int, data: dict[str, Any]
|
|
669
|
-
) -> None:
|
|
667
|
+
def _check_response_error(self, status_code: int, data: dict[str, Any]) -> None:
|
|
670
668
|
"""Check API response for errors and raise appropriate exception.
|
|
671
669
|
|
|
672
670
|
Args:
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""Twitter/X platform integration."""
|
|
2
2
|
|
|
3
3
|
from marqetive.platforms.twitter.client import TwitterClient
|
|
4
|
-
from marqetive.platforms.twitter.factory import TwitterAccountFactory
|
|
5
|
-
from marqetive.platforms.twitter.manager import TwitterPostManager
|
|
6
4
|
|
|
7
|
-
__all__ = ["TwitterClient"
|
|
5
|
+
__all__ = ["TwitterClient"]
|
|
@@ -13,7 +13,7 @@ import httpx
|
|
|
13
13
|
import tweepy
|
|
14
14
|
from pydantic import HttpUrl
|
|
15
15
|
|
|
16
|
-
from marqetive.platforms.base import SocialMediaPlatform
|
|
16
|
+
from marqetive.platforms.base import ProgressCallback, SocialMediaPlatform
|
|
17
17
|
from marqetive.platforms.exceptions import (
|
|
18
18
|
MediaUploadError,
|
|
19
19
|
PlatformAuthError,
|
|
@@ -67,12 +67,15 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
67
67
|
self,
|
|
68
68
|
credentials: AuthCredentials,
|
|
69
69
|
timeout: float = 30.0,
|
|
70
|
+
progress_callback: ProgressCallback | None = None,
|
|
70
71
|
) -> None:
|
|
71
72
|
"""Initialize Twitter client.
|
|
72
73
|
|
|
73
74
|
Args:
|
|
74
75
|
credentials: Twitter authentication credentials
|
|
75
76
|
timeout: Request timeout in seconds
|
|
77
|
+
progress_callback: Optional callback for progress updates during
|
|
78
|
+
long-running operations like media uploads.
|
|
76
79
|
|
|
77
80
|
Raises:
|
|
78
81
|
PlatformAuthError: If credentials are invalid
|
|
@@ -83,6 +86,7 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
83
86
|
credentials=credentials,
|
|
84
87
|
base_url=base_url,
|
|
85
88
|
timeout=timeout,
|
|
89
|
+
progress_callback=progress_callback,
|
|
86
90
|
)
|
|
87
91
|
|
|
88
92
|
# Initialize tweepy client
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
marqetive/__init__.py,sha256=LaGrC2HSkbpJBj9mKWuVOflTSN1upTzHowZm6Aw0nOY,2923
|
|
2
|
+
marqetive/core/__init__.py,sha256=qOC6JhGjWOpj9_7umtc6VGH16ZJZE6iNDhA6iDZm8kg,113
|
|
3
|
+
marqetive/core/client.py,sha256=2_FoNpqaRglsWg10i5RTbyDg_kRQKhgWjYs6iDdFxLg,3210
|
|
4
|
+
marqetive/factory.py,sha256=W-8O7eowx_M5ykmL9ZEnjZVoMqNKq8rsDWpyU6X7mxg,14107
|
|
5
|
+
marqetive/platforms/__init__.py,sha256=jMzSg8fI5nCO20aTNaoF6cQ6RIZoxzVhEDvq3ZpAbXc,1327
|
|
6
|
+
marqetive/platforms/base.py,sha256=o0bY9tcV350uGYOHpo3SbSYI61gvx6Ll9KyHaSYFXko,13258
|
|
7
|
+
marqetive/platforms/exceptions.py,sha256=Xyj0bzNiZm5VTErmzXgVW8T6IQnOpF92-HJiKPKjIio,7076
|
|
8
|
+
marqetive/platforms/instagram/__init__.py,sha256=7ClfTovAcCHac2DzKS7z1MFuZpy9lcwet7YP5d6MPeY,135
|
|
9
|
+
marqetive/platforms/instagram/client.py,sha256=Y7jnIDoIRnzwP3QtubwKd8aRi89zrxr5ZLgCqS43xwI,25202
|
|
10
|
+
marqetive/platforms/instagram/exceptions.py,sha256=zwTc90VbG8uz_6O5bKw68Pp53SoevLQUqPiXj4ZgThU,8983
|
|
11
|
+
marqetive/platforms/instagram/media.py,sha256=JDRFB66UlhSjAf0PFR3raGqYxtNTSd5yIZ_2xTGmZRk,21166
|
|
12
|
+
marqetive/platforms/linkedin/__init__.py,sha256=zCnokoPYs56iA1sBSYIlaZW2J50L3CbnQpJSaOLrzP8,131
|
|
13
|
+
marqetive/platforms/linkedin/client.py,sha256=GSWUq66KkerS4H9Vh8j3F6KrMzrlz1DYokbERNyrrCc,23821
|
|
14
|
+
marqetive/platforms/linkedin/exceptions.py,sha256=WHjMZ-XJnKgT6uzzi7LXqFt4lD7nSCjzMzaqtOQA3hM,9767
|
|
15
|
+
marqetive/platforms/linkedin/media.py,sha256=360OT1sdBuJRjuL3wJ-T1irAX5gHmcf4AwAU__cvwwA,16901
|
|
16
|
+
marqetive/platforms/models.py,sha256=W_yOuRWItWSn82n8vXRNN_ScdNkzY1De2qqXaVN2RGU,10974
|
|
17
|
+
marqetive/platforms/tiktok/__init__.py,sha256=BQtxdECd2bW9_vV9W-MY4A1rdXi_xurGWWmzTjTUpMM,123
|
|
18
|
+
marqetive/platforms/tiktok/client.py,sha256=UkljwzrSybIgm4opT5ItT_EXdGx3OGbV4ig8yPoaCbM,17041
|
|
19
|
+
marqetive/platforms/tiktok/exceptions.py,sha256=yOjkZgHyo_t9Chrhws_2n-2sbiDDkCUBv5fdW2nfiHI,10129
|
|
20
|
+
marqetive/platforms/tiktok/media.py,sha256=pAT9AVZX7ZnAnhgoameFLszKDyX16nY43we_CHc5z9s,23771
|
|
21
|
+
marqetive/platforms/twitter/__init__.py,sha256=AA5BELRvZyl2WE_7-puSEWArxZjaXcTJ_i8NGOWrv6k,129
|
|
22
|
+
marqetive/platforms/twitter/client.py,sha256=vZ9wvFPaTOuBD-L48RD1UH6MCPZoF2EHRvFWe0m_cfc,19642
|
|
23
|
+
marqetive/platforms/twitter/exceptions.py,sha256=aKrjUZL07KWQ8hatj5-U7UfnSTl-n8DnTgGGigYCrIY,8931
|
|
24
|
+
marqetive/platforms/twitter/media.py,sha256=16atvKwSukvK_YC_gIm2qsl3pmidWEBAwIfJBPG9HQo,24968
|
|
25
|
+
marqetive/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
marqetive/utils/__init__.py,sha256=bSrNajbxYBSKQayrPviLz8JeGjplnyK8y_NGDtgb7yQ,977
|
|
27
|
+
marqetive/utils/file_handlers.py,sha256=4TP5kmWofNTSZmlS683CM1UYP83WvRd_NubMbqtXv-g,12568
|
|
28
|
+
marqetive/utils/helpers.py,sha256=8-ljhL47SremKcQO2GF8DIHOPODEv1rSioVNuSPCbec,2634
|
|
29
|
+
marqetive/utils/media.py,sha256=Rvxw9XKU65n-z4G1bEihG3wXZBmjSDZUqClfjGFrg6k,12013
|
|
30
|
+
marqetive/utils/oauth.py,sha256=NjHh3o5iKlXMJmzSFJHq-pn5yn82DvmpB7DMTJT0ht8,12105
|
|
31
|
+
marqetive/utils/retry.py,sha256=lAniJLMNWp9XsHrvU0XBNifpNEjfde4MGfd5hlFTPfA,7636
|
|
32
|
+
marqetive/utils/token_validator.py,sha256=pNimFr_FNJLcYGV5oADhoBjflKfEbLQ3Epwnwg_nTbg,6703
|
|
33
|
+
marqetive_lib-0.1.4.dist-info/METADATA,sha256=KawBEFdT6w-WUGCF5ulti5nfi6cWer0oOqP-DFXDDaM,7798
|
|
34
|
+
marqetive_lib-0.1.4.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
35
|
+
marqetive_lib-0.1.4.dist-info/RECORD,,
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
"""Base account factory for managing platform credentials and client creation.
|
|
2
|
-
|
|
3
|
-
This module provides an abstract base class for platform-specific account
|
|
4
|
-
factories that handle credential management, token refresh, and client creation.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import logging
|
|
8
|
-
from abc import ABC, abstractmethod
|
|
9
|
-
from collections.abc import Callable
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from marqetive.platforms.exceptions import PlatformAuthError
|
|
13
|
-
from marqetive.platforms.models import AccountStatus, AuthCredentials
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class BaseAccountFactory(ABC):
|
|
19
|
-
"""Abstract base class for platform account factories.
|
|
20
|
-
|
|
21
|
-
Account factories manage the lifecycle of platform credentials including:
|
|
22
|
-
- Token expiry checking and automatic refresh
|
|
23
|
-
- Account status management
|
|
24
|
-
- Platform-specific client creation
|
|
25
|
-
|
|
26
|
-
Subclasses must implement:
|
|
27
|
-
- refresh_token(): Platform-specific token refresh logic
|
|
28
|
-
- create_client(): Create platform-specific API client
|
|
29
|
-
- validate_credentials(): Check if credentials are valid
|
|
30
|
-
|
|
31
|
-
Example:
|
|
32
|
-
>>> class TwitterAccountFactory(BaseAccountFactory):
|
|
33
|
-
... async def refresh_token(self, credentials):
|
|
34
|
-
... # Implement Twitter OAuth token refresh
|
|
35
|
-
... pass
|
|
36
|
-
...
|
|
37
|
-
... async def create_client(self, credentials):
|
|
38
|
-
... return TwitterClient(credentials=credentials)
|
|
39
|
-
...
|
|
40
|
-
... async def validate_credentials(self, credentials):
|
|
41
|
-
... # Check if credentials work
|
|
42
|
-
... pass
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
def __init__(
|
|
46
|
-
self,
|
|
47
|
-
on_status_update: Callable[[str, AccountStatus], None] | None = None,
|
|
48
|
-
) -> None:
|
|
49
|
-
"""Initialize the account factory.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
on_status_update: Optional callback when account status changes.
|
|
53
|
-
Called with (user_id, new_status).
|
|
54
|
-
"""
|
|
55
|
-
self.on_status_update = on_status_update
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
@abstractmethod
|
|
59
|
-
def platform_name(self) -> str:
|
|
60
|
-
"""Get the name of the platform this factory manages.
|
|
61
|
-
|
|
62
|
-
Returns:
|
|
63
|
-
Platform name (e.g., "twitter", "linkedin").
|
|
64
|
-
"""
|
|
65
|
-
pass
|
|
66
|
-
|
|
67
|
-
@abstractmethod
|
|
68
|
-
async def refresh_token(self, credentials: AuthCredentials) -> AuthCredentials:
|
|
69
|
-
"""Refresh the OAuth access token.
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
credentials: Current credentials with expired token.
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
Updated credentials with new token.
|
|
76
|
-
|
|
77
|
-
Raises:
|
|
78
|
-
PlatformAuthError: If token refresh fails.
|
|
79
|
-
"""
|
|
80
|
-
pass
|
|
81
|
-
|
|
82
|
-
@abstractmethod
|
|
83
|
-
async def create_client(self, credentials: AuthCredentials) -> Any:
|
|
84
|
-
"""Create a platform-specific API client.
|
|
85
|
-
|
|
86
|
-
Args:
|
|
87
|
-
credentials: Valid credentials for the platform.
|
|
88
|
-
|
|
89
|
-
Returns:
|
|
90
|
-
Platform-specific API client instance.
|
|
91
|
-
|
|
92
|
-
Raises:
|
|
93
|
-
PlatformAuthError: If credentials are invalid.
|
|
94
|
-
"""
|
|
95
|
-
pass
|
|
96
|
-
|
|
97
|
-
@abstractmethod
|
|
98
|
-
async def validate_credentials(self, credentials: AuthCredentials) -> bool:
|
|
99
|
-
"""Validate that credentials are working.
|
|
100
|
-
|
|
101
|
-
Args:
|
|
102
|
-
credentials: Credentials to validate.
|
|
103
|
-
|
|
104
|
-
Returns:
|
|
105
|
-
True if credentials are valid, False otherwise.
|
|
106
|
-
"""
|
|
107
|
-
pass
|
|
108
|
-
|
|
109
|
-
async def get_credentials(
|
|
110
|
-
self,
|
|
111
|
-
credentials: AuthCredentials,
|
|
112
|
-
auto_refresh: bool = True,
|
|
113
|
-
) -> AuthCredentials:
|
|
114
|
-
"""Get credentials, refreshing if necessary.
|
|
115
|
-
|
|
116
|
-
This method checks if credentials are expired and automatically
|
|
117
|
-
refreshes them if auto_refresh is True.
|
|
118
|
-
|
|
119
|
-
Args:
|
|
120
|
-
credentials: Current credentials.
|
|
121
|
-
auto_refresh: Whether to automatically refresh expired tokens.
|
|
122
|
-
|
|
123
|
-
Returns:
|
|
124
|
-
Valid credentials (refreshed if necessary).
|
|
125
|
-
|
|
126
|
-
Raises:
|
|
127
|
-
PlatformAuthError: If refresh fails or credentials are invalid.
|
|
128
|
-
"""
|
|
129
|
-
# Check if token needs refresh
|
|
130
|
-
if credentials.needs_refresh():
|
|
131
|
-
if not auto_refresh:
|
|
132
|
-
logger.warning(
|
|
133
|
-
f"Credentials for {credentials.platform} are expired "
|
|
134
|
-
"but auto_refresh is disabled"
|
|
135
|
-
)
|
|
136
|
-
return credentials
|
|
137
|
-
|
|
138
|
-
logger.info(
|
|
139
|
-
f"Token expired for {credentials.platform}, attempting refresh..."
|
|
140
|
-
)
|
|
141
|
-
try:
|
|
142
|
-
refreshed_creds = await self.refresh_token(credentials)
|
|
143
|
-
refreshed_creds.mark_valid()
|
|
144
|
-
|
|
145
|
-
# Notify status update
|
|
146
|
-
if self.on_status_update and refreshed_creds.user_id:
|
|
147
|
-
self.on_status_update(refreshed_creds.user_id, AccountStatus.VALID)
|
|
148
|
-
|
|
149
|
-
logger.info(f"Successfully refreshed token for {credentials.platform}")
|
|
150
|
-
return refreshed_creds
|
|
151
|
-
|
|
152
|
-
except PlatformAuthError as e:
|
|
153
|
-
# Determine if this is an OAuth error requiring reconnection
|
|
154
|
-
if "oauth" in str(e).lower() or "authorization" in str(e).lower():
|
|
155
|
-
credentials.mark_reconnection_required()
|
|
156
|
-
if self.on_status_update and credentials.user_id:
|
|
157
|
-
self.on_status_update(
|
|
158
|
-
credentials.user_id, AccountStatus.RECONNECTION_REQUIRED
|
|
159
|
-
)
|
|
160
|
-
logger.error(
|
|
161
|
-
f"OAuth error refreshing token for {credentials.platform}: {e}"
|
|
162
|
-
)
|
|
163
|
-
else:
|
|
164
|
-
credentials.mark_error()
|
|
165
|
-
if self.on_status_update and credentials.user_id:
|
|
166
|
-
self.on_status_update(credentials.user_id, AccountStatus.ERROR)
|
|
167
|
-
logger.error(
|
|
168
|
-
f"Error refreshing token for {credentials.platform}: {e}"
|
|
169
|
-
)
|
|
170
|
-
raise
|
|
171
|
-
|
|
172
|
-
return credentials
|
|
173
|
-
|
|
174
|
-
async def create_authenticated_client(
|
|
175
|
-
self,
|
|
176
|
-
credentials: AuthCredentials,
|
|
177
|
-
auto_refresh: bool = True,
|
|
178
|
-
) -> Any:
|
|
179
|
-
"""Create an authenticated client, refreshing credentials if needed.
|
|
180
|
-
|
|
181
|
-
This is the main method to use for getting a ready-to-use client.
|
|
182
|
-
|
|
183
|
-
Args:
|
|
184
|
-
credentials: Platform credentials.
|
|
185
|
-
auto_refresh: Whether to automatically refresh expired tokens.
|
|
186
|
-
|
|
187
|
-
Returns:
|
|
188
|
-
Authenticated platform client.
|
|
189
|
-
|
|
190
|
-
Raises:
|
|
191
|
-
PlatformAuthError: If authentication fails.
|
|
192
|
-
"""
|
|
193
|
-
# Get valid credentials (refresh if needed)
|
|
194
|
-
valid_creds = await self.get_credentials(credentials, auto_refresh=auto_refresh)
|
|
195
|
-
|
|
196
|
-
# Create client
|
|
197
|
-
client = await self.create_client(valid_creds)
|
|
198
|
-
|
|
199
|
-
return client
|
|
200
|
-
|
|
201
|
-
def _update_status(self, user_id: str | None, status: AccountStatus) -> None:
|
|
202
|
-
"""Internal method to update account status.
|
|
203
|
-
|
|
204
|
-
Args:
|
|
205
|
-
user_id: User ID for the account.
|
|
206
|
-
status: New status.
|
|
207
|
-
"""
|
|
208
|
-
if self.on_status_update and user_id:
|
|
209
|
-
try:
|
|
210
|
-
self.on_status_update(user_id, status)
|
|
211
|
-
except Exception as e:
|
|
212
|
-
logger.error(f"Error calling status update callback: {e}")
|
marqetive/core/base_manager.py
DELETED
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
"""Base manager for platform post operations.
|
|
2
|
-
|
|
3
|
-
This module provides an abstract base class for managing post operations
|
|
4
|
-
across different social media platforms with consistent patterns for
|
|
5
|
-
progress tracking, error handling, and state management.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
import logging
|
|
10
|
-
from abc import ABC, abstractmethod
|
|
11
|
-
from typing import Any
|
|
12
|
-
|
|
13
|
-
from marqetive.core.account_factory import BaseAccountFactory
|
|
14
|
-
from marqetive.core.progress import ProgressCallback, ProgressTracker
|
|
15
|
-
from marqetive.platforms.exceptions import PlatformError
|
|
16
|
-
from marqetive.platforms.models import AuthCredentials, Post, PostCreateRequest
|
|
17
|
-
|
|
18
|
-
logger = logging.getLogger(__name__)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class BasePostManager(ABC):
|
|
22
|
-
"""Abstract base class for platform post managers.
|
|
23
|
-
|
|
24
|
-
Managers coordinate the posting process including:
|
|
25
|
-
- Creating authenticated clients via account factories
|
|
26
|
-
- Tracking operation progress
|
|
27
|
-
- Handling errors consistently
|
|
28
|
-
- Supporting cancellation
|
|
29
|
-
|
|
30
|
-
Subclasses must implement:
|
|
31
|
-
- _execute_post_impl(): Platform-specific posting logic
|
|
32
|
-
- platform_name: Property returning the platform name
|
|
33
|
-
|
|
34
|
-
Example:
|
|
35
|
-
>>> class TwitterPostManager(BasePostManager):
|
|
36
|
-
... @property
|
|
37
|
-
... def platform_name(self) -> str:
|
|
38
|
-
... return "twitter"
|
|
39
|
-
...
|
|
40
|
-
... async def _execute_post_impl(self, client, request):
|
|
41
|
-
... return await client.create_post(request)
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
def __init__(
|
|
45
|
-
self,
|
|
46
|
-
account_factory: BaseAccountFactory | None = None,
|
|
47
|
-
) -> None:
|
|
48
|
-
"""Initialize the post manager.
|
|
49
|
-
|
|
50
|
-
Args:
|
|
51
|
-
account_factory: Account factory for creating clients.
|
|
52
|
-
If None, subclass must provide default.
|
|
53
|
-
"""
|
|
54
|
-
self._account_factory = account_factory
|
|
55
|
-
self._progress_tracker = ProgressTracker()
|
|
56
|
-
self._cancel_event = asyncio.Event()
|
|
57
|
-
|
|
58
|
-
@property
|
|
59
|
-
@abstractmethod
|
|
60
|
-
def platform_name(self) -> str:
|
|
61
|
-
"""Get the name of the platform this manager handles.
|
|
62
|
-
|
|
63
|
-
Returns:
|
|
64
|
-
Platform name (e.g., "twitter", "linkedin").
|
|
65
|
-
"""
|
|
66
|
-
pass
|
|
67
|
-
|
|
68
|
-
@property
|
|
69
|
-
def account_factory(self) -> BaseAccountFactory:
|
|
70
|
-
"""Get the account factory.
|
|
71
|
-
|
|
72
|
-
Returns:
|
|
73
|
-
Account factory instance.
|
|
74
|
-
|
|
75
|
-
Raises:
|
|
76
|
-
RuntimeError: If account factory not set.
|
|
77
|
-
"""
|
|
78
|
-
if self._account_factory is None:
|
|
79
|
-
raise RuntimeError(
|
|
80
|
-
f"{self.__class__.__name__} requires an account_factory. "
|
|
81
|
-
"Either pass it to __init__ or override this property."
|
|
82
|
-
)
|
|
83
|
-
return self._account_factory
|
|
84
|
-
|
|
85
|
-
def add_progress_callback(self, callback: ProgressCallback) -> None:
|
|
86
|
-
"""Add a callback to receive progress updates.
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
callback: Function that receives ProgressEvent objects.
|
|
90
|
-
|
|
91
|
-
Example:
|
|
92
|
-
>>> def my_callback(event):
|
|
93
|
-
... print(f"{event.operation}: {event.percentage}%")
|
|
94
|
-
>>>
|
|
95
|
-
>>> manager.add_progress_callback(my_callback)
|
|
96
|
-
"""
|
|
97
|
-
self._progress_tracker.add_callback(callback)
|
|
98
|
-
|
|
99
|
-
def remove_progress_callback(self, callback: ProgressCallback) -> None:
|
|
100
|
-
"""Remove a progress callback.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
callback: Callback to remove.
|
|
104
|
-
"""
|
|
105
|
-
self._progress_tracker.remove_callback(callback)
|
|
106
|
-
|
|
107
|
-
def clear_progress_callbacks(self) -> None:
|
|
108
|
-
"""Remove all progress callbacks."""
|
|
109
|
-
self._progress_tracker.clear_callbacks()
|
|
110
|
-
|
|
111
|
-
async def execute_post(
|
|
112
|
-
self,
|
|
113
|
-
credentials: AuthCredentials,
|
|
114
|
-
request: PostCreateRequest,
|
|
115
|
-
) -> Post:
|
|
116
|
-
"""Execute post creation with progress tracking and error handling.
|
|
117
|
-
|
|
118
|
-
This is the main entry point for creating posts. It handles:
|
|
119
|
-
- Client creation via account factory
|
|
120
|
-
- Progress tracking
|
|
121
|
-
- Cancellation support
|
|
122
|
-
- Consistent error handling
|
|
123
|
-
|
|
124
|
-
Args:
|
|
125
|
-
credentials: Platform credentials.
|
|
126
|
-
request: Post creation request.
|
|
127
|
-
|
|
128
|
-
Returns:
|
|
129
|
-
Created Post object.
|
|
130
|
-
|
|
131
|
-
Raises:
|
|
132
|
-
PlatformError: If post creation fails.
|
|
133
|
-
asyncio.CancelledError: If operation is cancelled.
|
|
134
|
-
|
|
135
|
-
Example:
|
|
136
|
-
>>> credentials = AuthCredentials(platform="twitter", ...)
|
|
137
|
-
>>> request = PostCreateRequest(content="Hello world!")
|
|
138
|
-
>>> post = await manager.execute_post(credentials, request)
|
|
139
|
-
>>> print(f"Posted: {post.post_id}")
|
|
140
|
-
"""
|
|
141
|
-
# Reset cancel event
|
|
142
|
-
self._cancel_event.clear()
|
|
143
|
-
|
|
144
|
-
try:
|
|
145
|
-
# Emit start event
|
|
146
|
-
self._progress_tracker.emit_start(
|
|
147
|
-
"execute_post",
|
|
148
|
-
message=f"Starting post to {self.platform_name}",
|
|
149
|
-
platform=self.platform_name,
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
# Check for cancellation
|
|
153
|
-
if self._cancel_event.is_set():
|
|
154
|
-
self._progress_tracker.emit_cancelled("execute_post")
|
|
155
|
-
raise asyncio.CancelledError("Post execution was cancelled")
|
|
156
|
-
|
|
157
|
-
# Create authenticated client
|
|
158
|
-
logger.info(f"Creating {self.platform_name} client...")
|
|
159
|
-
client = await self.account_factory.create_authenticated_client(credentials)
|
|
160
|
-
|
|
161
|
-
# Check for cancellation
|
|
162
|
-
if self._cancel_event.is_set():
|
|
163
|
-
self._progress_tracker.emit_cancelled("execute_post")
|
|
164
|
-
raise asyncio.CancelledError("Post execution was cancelled")
|
|
165
|
-
|
|
166
|
-
# Execute platform-specific posting logic
|
|
167
|
-
logger.info(f"Creating post on {self.platform_name}...")
|
|
168
|
-
post = await self._execute_post_impl(client, request, credentials)
|
|
169
|
-
|
|
170
|
-
# Emit completion event
|
|
171
|
-
self._progress_tracker.emit_complete(
|
|
172
|
-
"execute_post",
|
|
173
|
-
message=f"Post created successfully on {self.platform_name}",
|
|
174
|
-
post_id=post.post_id,
|
|
175
|
-
platform=self.platform_name,
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
logger.info(
|
|
179
|
-
f"Successfully created post {post.post_id} on {self.platform_name}"
|
|
180
|
-
)
|
|
181
|
-
return post
|
|
182
|
-
|
|
183
|
-
except asyncio.CancelledError:
|
|
184
|
-
self._progress_tracker.emit_cancelled(
|
|
185
|
-
"execute_post",
|
|
186
|
-
message=f"Post creation cancelled on {self.platform_name}",
|
|
187
|
-
)
|
|
188
|
-
raise
|
|
189
|
-
|
|
190
|
-
except Exception as e:
|
|
191
|
-
error_msg = f"Failed to create post on {self.platform_name}: {str(e)}"
|
|
192
|
-
logger.error(error_msg)
|
|
193
|
-
self._progress_tracker.emit_failed("execute_post", e)
|
|
194
|
-
|
|
195
|
-
# Wrap in PlatformError if not already
|
|
196
|
-
if not isinstance(e, PlatformError):
|
|
197
|
-
raise PlatformError(error_msg, platform=self.platform_name) from e
|
|
198
|
-
raise
|
|
199
|
-
|
|
200
|
-
@abstractmethod
|
|
201
|
-
async def _execute_post_impl(
|
|
202
|
-
self,
|
|
203
|
-
client: Any,
|
|
204
|
-
request: PostCreateRequest,
|
|
205
|
-
credentials: AuthCredentials,
|
|
206
|
-
) -> Post:
|
|
207
|
-
"""Platform-specific post creation implementation.
|
|
208
|
-
|
|
209
|
-
Subclasses must implement this to handle the actual post creation
|
|
210
|
-
logic for their specific platform.
|
|
211
|
-
|
|
212
|
-
Args:
|
|
213
|
-
client: Platform-specific authenticated client.
|
|
214
|
-
request: Post creation request.
|
|
215
|
-
credentials: Platform credentials (for context).
|
|
216
|
-
|
|
217
|
-
Returns:
|
|
218
|
-
Created Post object.
|
|
219
|
-
|
|
220
|
-
Raises:
|
|
221
|
-
PlatformError: If post creation fails.
|
|
222
|
-
"""
|
|
223
|
-
pass
|
|
224
|
-
|
|
225
|
-
def cancel_post(self) -> None:
|
|
226
|
-
"""Cancel the current post operation.
|
|
227
|
-
|
|
228
|
-
Sets a cancellation flag that is checked at key points during
|
|
229
|
-
post execution. The operation may not stop immediately.
|
|
230
|
-
|
|
231
|
-
Example:
|
|
232
|
-
>>> task = asyncio.create_task(manager.execute_post(...))
|
|
233
|
-
>>> # Later...
|
|
234
|
-
>>> manager.cancel_post()
|
|
235
|
-
>>> try:
|
|
236
|
-
... await task
|
|
237
|
-
... except asyncio.CancelledError:
|
|
238
|
-
... print("Operation was cancelled")
|
|
239
|
-
"""
|
|
240
|
-
self._cancel_event.set()
|
|
241
|
-
logger.info(f"Cancellation requested for {self.platform_name} post")
|
|
242
|
-
|
|
243
|
-
def is_cancelled(self) -> bool:
|
|
244
|
-
"""Check if cancellation has been requested.
|
|
245
|
-
|
|
246
|
-
Returns:
|
|
247
|
-
True if cancel_post() was called, False otherwise.
|
|
248
|
-
"""
|
|
249
|
-
return self._cancel_event.is_set()
|
|
250
|
-
|
|
251
|
-
async def get_post_status(
|
|
252
|
-
self,
|
|
253
|
-
credentials: AuthCredentials,
|
|
254
|
-
post_id: str,
|
|
255
|
-
) -> Post:
|
|
256
|
-
"""Get the current status of a post.
|
|
257
|
-
|
|
258
|
-
Args:
|
|
259
|
-
credentials: Platform credentials.
|
|
260
|
-
post_id: Platform-specific post ID.
|
|
261
|
-
|
|
262
|
-
Returns:
|
|
263
|
-
Post object with current status.
|
|
264
|
-
|
|
265
|
-
Raises:
|
|
266
|
-
PlatformError: If fetching post fails.
|
|
267
|
-
"""
|
|
268
|
-
try:
|
|
269
|
-
client = await self.account_factory.create_authenticated_client(credentials)
|
|
270
|
-
return await client.get_post(post_id)
|
|
271
|
-
except Exception as e:
|
|
272
|
-
error_msg = f"Failed to get post status from {self.platform_name}: {str(e)}"
|
|
273
|
-
logger.error(error_msg)
|
|
274
|
-
if not isinstance(e, PlatformError):
|
|
275
|
-
raise PlatformError(error_msg, platform=self.platform_name) from e
|
|
276
|
-
raise
|
|
277
|
-
|
|
278
|
-
async def delete_post(
|
|
279
|
-
self,
|
|
280
|
-
credentials: AuthCredentials,
|
|
281
|
-
post_id: str,
|
|
282
|
-
) -> bool:
|
|
283
|
-
"""Delete a post.
|
|
284
|
-
|
|
285
|
-
Args:
|
|
286
|
-
credentials: Platform credentials.
|
|
287
|
-
post_id: Platform-specific post ID.
|
|
288
|
-
|
|
289
|
-
Returns:
|
|
290
|
-
True if deletion was successful.
|
|
291
|
-
|
|
292
|
-
Raises:
|
|
293
|
-
PlatformError: If deletion fails.
|
|
294
|
-
"""
|
|
295
|
-
try:
|
|
296
|
-
client = await self.account_factory.create_authenticated_client(credentials)
|
|
297
|
-
return await client.delete_post(post_id)
|
|
298
|
-
except Exception as e:
|
|
299
|
-
error_msg = f"Failed to delete post from {self.platform_name}: {str(e)}"
|
|
300
|
-
logger.error(error_msg)
|
|
301
|
-
if not isinstance(e, PlatformError):
|
|
302
|
-
raise PlatformError(error_msg, platform=self.platform_name) from e
|
|
303
|
-
raise
|