marqetive-lib 0.1.20__tar.gz → 0.1.21__tar.gz
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_lib-0.1.20 → marqetive_lib-0.1.21}/PKG-INFO +1 -1
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/pyproject.toml +1 -1
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/core/exceptions.py +30 -1
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/factory.py +37 -5
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/utils/oauth.py +22 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/README.md +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/__init__.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/core/__init__.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/core/base.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/core/client.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/core/models.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/__init__.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/instagram/__init__.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/instagram/client.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/instagram/exceptions.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/instagram/media.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/instagram/models.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/linkedin/__init__.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/linkedin/client.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/linkedin/exceptions.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/linkedin/media.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/linkedin/models.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/tiktok/__init__.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/tiktok/client.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/tiktok/exceptions.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/tiktok/media.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/tiktok/models.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/twitter/__init__.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/twitter/client.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/twitter/exceptions.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/twitter/media.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/twitter/models.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/py.typed +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/utils/__init__.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/utils/file_handlers.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/utils/helpers.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/utils/media.py +0 -0
- {marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/utils/retry.py +0 -0
|
@@ -47,15 +47,44 @@ class PlatformAuthError(PlatformError):
|
|
|
47
47
|
- OAuth flow encounters errors
|
|
48
48
|
- Insufficient permissions for requested operation
|
|
49
49
|
|
|
50
|
+
Args:
|
|
51
|
+
message: Human-readable error message
|
|
52
|
+
platform: Name of the platform where error occurred
|
|
53
|
+
status_code: HTTP status code if applicable
|
|
54
|
+
requires_reconnection: If True, user must re-authenticate (token permanently invalid)
|
|
55
|
+
|
|
50
56
|
Example:
|
|
51
57
|
>>> raise PlatformAuthError(
|
|
52
58
|
... "Access token expired",
|
|
53
59
|
... platform="twitter",
|
|
54
60
|
... status_code=401
|
|
55
61
|
... )
|
|
62
|
+
>>> # For invalid refresh token requiring user re-auth:
|
|
63
|
+
>>> raise PlatformAuthError(
|
|
64
|
+
... "Refresh token invalid",
|
|
65
|
+
... platform="twitter",
|
|
66
|
+
... status_code=400,
|
|
67
|
+
... requires_reconnection=True
|
|
68
|
+
... )
|
|
56
69
|
"""
|
|
57
70
|
|
|
58
|
-
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
message: str,
|
|
74
|
+
platform: str | None = None,
|
|
75
|
+
status_code: int | None = None,
|
|
76
|
+
*,
|
|
77
|
+
requires_reconnection: bool = False,
|
|
78
|
+
) -> None:
|
|
79
|
+
self.requires_reconnection = requires_reconnection
|
|
80
|
+
super().__init__(message, platform, status_code)
|
|
81
|
+
|
|
82
|
+
def _format_message(self) -> str:
|
|
83
|
+
"""Format the error message with reconnection info."""
|
|
84
|
+
base_message = super()._format_message()
|
|
85
|
+
if self.requires_reconnection:
|
|
86
|
+
return f"{base_message} | Reconnection required"
|
|
87
|
+
return base_message
|
|
59
88
|
|
|
60
89
|
|
|
61
90
|
class RateLimitError(PlatformError):
|
|
@@ -25,6 +25,24 @@ logger = logging.getLogger(__name__)
|
|
|
25
25
|
# Supported platforms
|
|
26
26
|
SUPPORTED_PLATFORMS = frozenset({"twitter", "linkedin", "instagram", "tiktok"})
|
|
27
27
|
|
|
28
|
+
# Platform aliases (alternative names that map to canonical names)
|
|
29
|
+
PLATFORM_ALIASES: dict[str, str] = {
|
|
30
|
+
"x": "twitter",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def normalize_platform(platform: str) -> str:
|
|
35
|
+
"""Normalize platform name to canonical form.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
platform: Platform name (may be an alias like 'x').
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Canonical platform name (e.g., 'twitter').
|
|
42
|
+
"""
|
|
43
|
+
platform = platform.lower()
|
|
44
|
+
return PLATFORM_ALIASES.get(platform, platform)
|
|
45
|
+
|
|
28
46
|
|
|
29
47
|
def _create_client(
|
|
30
48
|
platform: str, credentials: "AuthCredentials"
|
|
@@ -172,7 +190,12 @@ class PlatformFactory:
|
|
|
172
190
|
>>> async with client:
|
|
173
191
|
... post = await client.create_post(request)
|
|
174
192
|
"""
|
|
175
|
-
platform
|
|
193
|
+
# Normalize platform name (handle aliases like 'x' -> 'twitter')
|
|
194
|
+
platform = normalize_platform(credentials.platform)
|
|
195
|
+
|
|
196
|
+
# Update credentials with normalized platform name
|
|
197
|
+
if platform != credentials.platform.lower():
|
|
198
|
+
credentials.platform = platform
|
|
176
199
|
|
|
177
200
|
# Validate platform-specific requirements
|
|
178
201
|
self._validate_credentials(credentials)
|
|
@@ -180,8 +203,17 @@ class PlatformFactory:
|
|
|
180
203
|
# Refresh token if needed
|
|
181
204
|
if auto_refresh and credentials.needs_refresh():
|
|
182
205
|
logger.info(f"Refreshing expired token for {platform}")
|
|
183
|
-
|
|
184
|
-
|
|
206
|
+
try:
|
|
207
|
+
credentials = await self._refresh_token(credentials)
|
|
208
|
+
credentials.mark_valid()
|
|
209
|
+
except PlatformAuthError as e:
|
|
210
|
+
# If refresh failed and requires reconnection, update credentials status
|
|
211
|
+
if e.requires_reconnection:
|
|
212
|
+
credentials.mark_reconnection_required()
|
|
213
|
+
logger.warning(
|
|
214
|
+
f"Token refresh for {platform} requires user reconnection"
|
|
215
|
+
)
|
|
216
|
+
raise
|
|
185
217
|
|
|
186
218
|
# Enrich credentials with API keys for Twitter (needed for media operations)
|
|
187
219
|
if platform == "twitter":
|
|
@@ -337,9 +369,9 @@ class PlatformFactory:
|
|
|
337
369
|
"""Get the set of supported platform names.
|
|
338
370
|
|
|
339
371
|
Returns:
|
|
340
|
-
Frozenset of supported platform names.
|
|
372
|
+
Frozenset of supported platform names (includes aliases).
|
|
341
373
|
"""
|
|
342
|
-
return SUPPORTED_PLATFORMS
|
|
374
|
+
return SUPPORTED_PLATFORMS | frozenset(PLATFORM_ALIASES.keys())
|
|
343
375
|
|
|
344
376
|
|
|
345
377
|
async def get_client(
|
|
@@ -180,10 +180,32 @@ async def refresh_twitter_token(
|
|
|
180
180
|
|
|
181
181
|
except httpx.HTTPStatusError as e:
|
|
182
182
|
logger.error(f"HTTP error refreshing Twitter token: {e.response.status_code}")
|
|
183
|
+
|
|
184
|
+
# Determine if this is a permanent failure requiring user re-authentication
|
|
185
|
+
# Twitter returns 400 with "invalid_request" or "invalid_grant" when:
|
|
186
|
+
# - Refresh token was already used (single-use tokens)
|
|
187
|
+
# - Refresh token expired
|
|
188
|
+
# - User revoked access
|
|
189
|
+
requires_reconnection = False
|
|
190
|
+
if e.response.status_code == 400:
|
|
191
|
+
try:
|
|
192
|
+
error_data = e.response.json()
|
|
193
|
+
error_code = error_data.get("error", "")
|
|
194
|
+
if error_code in ("invalid_request", "invalid_grant"):
|
|
195
|
+
requires_reconnection = True
|
|
196
|
+
logger.warning(
|
|
197
|
+
f"Twitter refresh token is invalid ({error_code}), "
|
|
198
|
+
"user needs to reconnect their account"
|
|
199
|
+
)
|
|
200
|
+
except Exception:
|
|
201
|
+
# If we can't parse the response, assume reconnection is needed for 400
|
|
202
|
+
requires_reconnection = True
|
|
203
|
+
|
|
183
204
|
raise PlatformAuthError(
|
|
184
205
|
f"Failed to refresh token: {_sanitize_response_text(e.response.text)}",
|
|
185
206
|
platform="twitter",
|
|
186
207
|
status_code=e.response.status_code,
|
|
208
|
+
requires_reconnection=requires_reconnection,
|
|
187
209
|
) from e
|
|
188
210
|
|
|
189
211
|
except httpx.HTTPError as e:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/instagram/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{marqetive_lib-0.1.20 → marqetive_lib-0.1.21}/src/marqetive/platforms/linkedin/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|