marqetive-lib 0.1.3__py3-none-any.whl → 0.1.5__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 (42) hide show
  1. marqetive/__init__.py +54 -56
  2. marqetive/core/__init__.py +50 -1
  3. marqetive/{platforms → core}/base.py +35 -2
  4. marqetive/factory.py +380 -0
  5. marqetive/platforms/__init__.py +3 -3
  6. marqetive/platforms/instagram/__init__.py +1 -3
  7. marqetive/platforms/instagram/client.py +11 -7
  8. marqetive/platforms/instagram/exceptions.py +1 -1
  9. marqetive/platforms/instagram/media.py +1 -1
  10. marqetive/platforms/linkedin/__init__.py +1 -3
  11. marqetive/platforms/linkedin/client.py +8 -4
  12. marqetive/platforms/linkedin/exceptions.py +1 -1
  13. marqetive/platforms/linkedin/media.py +1 -1
  14. marqetive/platforms/tiktok/__init__.py +1 -3
  15. marqetive/platforms/tiktok/client.py +18 -9
  16. marqetive/platforms/tiktok/exceptions.py +1 -1
  17. marqetive/platforms/tiktok/media.py +2 -4
  18. marqetive/platforms/twitter/__init__.py +1 -3
  19. marqetive/platforms/twitter/client.py +7 -3
  20. marqetive/platforms/twitter/exceptions.py +1 -1
  21. marqetive/platforms/twitter/media.py +1 -1
  22. marqetive/utils/oauth.py +2 -2
  23. marqetive/utils/token_validator.py +1 -1
  24. {marqetive_lib-0.1.3.dist-info → marqetive_lib-0.1.5.dist-info}/METADATA +1 -1
  25. marqetive_lib-0.1.5.dist-info/RECORD +35 -0
  26. marqetive/core/account_factory.py +0 -212
  27. marqetive/core/base_manager.py +0 -303
  28. marqetive/core/progress.py +0 -291
  29. marqetive/core/registry.py +0 -257
  30. marqetive/platforms/instagram/factory.py +0 -106
  31. marqetive/platforms/instagram/manager.py +0 -112
  32. marqetive/platforms/linkedin/factory.py +0 -130
  33. marqetive/platforms/linkedin/manager.py +0 -119
  34. marqetive/platforms/tiktok/factory.py +0 -188
  35. marqetive/platforms/tiktok/manager.py +0 -115
  36. marqetive/platforms/twitter/factory.py +0 -150
  37. marqetive/platforms/twitter/manager.py +0 -121
  38. marqetive/registry_init.py +0 -68
  39. marqetive_lib-0.1.3.dist-info/RECORD +0 -47
  40. /marqetive/{platforms → core}/exceptions.py +0 -0
  41. /marqetive/{platforms → core}/models.py +0 -0
  42. {marqetive_lib-0.1.3.dist-info → marqetive_lib-0.1.5.dist-info}/WHEEL +0 -0
@@ -1,7 +1,5 @@
1
1
  """Instagram platform integration."""
2
2
 
3
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
4
 
7
- __all__ = ["InstagramClient", "InstagramAccountFactory", "InstagramPostManager"]
5
+ __all__ = ["InstagramClient"]
@@ -12,19 +12,15 @@ from typing import Any, Literal, cast
12
12
  import httpx
13
13
  from pydantic import HttpUrl
14
14
 
15
- from marqetive.platforms.base import SocialMediaPlatform
16
- from marqetive.platforms.exceptions import (
15
+ from marqetive.core.base import ProgressCallback, SocialMediaPlatform
16
+ from marqetive.core.exceptions import (
17
17
  MediaUploadError,
18
18
  PlatformAuthError,
19
19
  PlatformError,
20
20
  PostNotFoundError,
21
21
  ValidationError,
22
22
  )
23
- from marqetive.platforms.instagram.media import (
24
- InstagramMediaManager,
25
- MediaItem,
26
- )
27
- from marqetive.platforms.models import (
23
+ from marqetive.core.models import (
28
24
  AuthCredentials,
29
25
  Comment,
30
26
  CommentStatus,
@@ -35,6 +31,10 @@ from marqetive.platforms.models import (
35
31
  PostStatus,
36
32
  PostUpdateRequest,
37
33
  )
34
+ from marqetive.platforms.instagram.media import (
35
+ InstagramMediaManager,
36
+ MediaItem,
37
+ )
38
38
 
39
39
 
40
40
  class InstagramClient(SocialMediaPlatform):
@@ -69,6 +69,7 @@ class InstagramClient(SocialMediaPlatform):
69
69
  credentials: AuthCredentials,
70
70
  timeout: float = 30.0,
71
71
  api_version: str = "v21.0",
72
+ progress_callback: ProgressCallback | None = None,
72
73
  ) -> None:
73
74
  """Initialize Instagram client.
74
75
 
@@ -76,6 +77,8 @@ class InstagramClient(SocialMediaPlatform):
76
77
  credentials: Instagram authentication credentials
77
78
  timeout: Request timeout in seconds
78
79
  api_version: Instagram Graph API version
80
+ progress_callback: Optional callback for progress updates during
81
+ long-running operations like media uploads.
79
82
 
80
83
  Raises:
81
84
  PlatformAuthError: If credentials are invalid
@@ -86,6 +89,7 @@ class InstagramClient(SocialMediaPlatform):
86
89
  credentials=credentials,
87
90
  base_url=base_url,
88
91
  timeout=timeout,
92
+ progress_callback=progress_callback,
89
93
  )
90
94
  self.instagram_account_id = credentials.user_id
91
95
  self.api_version = api_version
@@ -5,7 +5,7 @@ This module provides comprehensive error handling for Instagram Graph API errors
5
5
 
6
6
  from typing import Any
7
7
 
8
- from marqetive.platforms.exceptions import (
8
+ from marqetive.core.exceptions import (
9
9
  MediaUploadError,
10
10
  PlatformAuthError,
11
11
  PlatformError,
@@ -19,7 +19,7 @@ from typing import Any, Literal
19
19
 
20
20
  import httpx
21
21
 
22
- from marqetive.platforms.exceptions import (
22
+ from marqetive.core.exceptions import (
23
23
  MediaUploadError,
24
24
  ValidationError,
25
25
  )
@@ -1,7 +1,5 @@
1
1
  """LinkedIn platform integration."""
2
2
 
3
3
  from marqetive.platforms.linkedin.client import LinkedInClient
4
- from marqetive.platforms.linkedin.factory import LinkedInAccountFactory
5
- from marqetive.platforms.linkedin.manager import LinkedInPostManager
6
4
 
7
- __all__ = ["LinkedInClient", "LinkedInAccountFactory", "LinkedInPostManager"]
5
+ __all__ = ["LinkedInClient"]
@@ -12,16 +12,15 @@ from typing import Any, cast
12
12
  import httpx
13
13
  from pydantic import HttpUrl
14
14
 
15
- from marqetive.platforms.base import SocialMediaPlatform
16
- from marqetive.platforms.exceptions import (
15
+ from marqetive.core.base import ProgressCallback, SocialMediaPlatform
16
+ from marqetive.core.exceptions import (
17
17
  MediaUploadError,
18
18
  PlatformAuthError,
19
19
  PlatformError,
20
20
  PostNotFoundError,
21
21
  ValidationError,
22
22
  )
23
- from marqetive.platforms.linkedin.media import LinkedInMediaManager, MediaAsset
24
- from marqetive.platforms.models import (
23
+ from marqetive.core.models import (
25
24
  AuthCredentials,
26
25
  Comment,
27
26
  CommentStatus,
@@ -32,6 +31,7 @@ from marqetive.platforms.models import (
32
31
  PostStatus,
33
32
  PostUpdateRequest,
34
33
  )
34
+ from marqetive.platforms.linkedin.media import LinkedInMediaManager, MediaAsset
35
35
 
36
36
 
37
37
  class LinkedInClient(SocialMediaPlatform):
@@ -66,6 +66,7 @@ class LinkedInClient(SocialMediaPlatform):
66
66
  credentials: AuthCredentials,
67
67
  timeout: float = 30.0,
68
68
  api_version: str = "v2",
69
+ progress_callback: ProgressCallback | None = None,
69
70
  ) -> None:
70
71
  """Initialize LinkedIn client.
71
72
 
@@ -73,6 +74,8 @@ class LinkedInClient(SocialMediaPlatform):
73
74
  credentials: LinkedIn authentication credentials
74
75
  timeout: Request timeout in seconds
75
76
  api_version: LinkedIn API version
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 LinkedInClient(SocialMediaPlatform):
83
86
  credentials=credentials,
84
87
  base_url=base_url,
85
88
  timeout=timeout,
89
+ progress_callback=progress_callback,
86
90
  )
87
91
  self.author_urn = (
88
92
  credentials.user_id
@@ -9,7 +9,7 @@ This module provides comprehensive error handling for LinkedIn API errors includ
9
9
 
10
10
  from typing import Any
11
11
 
12
- from marqetive.platforms.exceptions import (
12
+ from marqetive.core.exceptions import (
13
13
  MediaUploadError,
14
14
  PlatformAuthError,
15
15
  PlatformError,
@@ -20,7 +20,7 @@ from typing import Any, Literal
20
20
 
21
21
  import httpx
22
22
 
23
- from marqetive.platforms.exceptions import (
23
+ from marqetive.core.exceptions import (
24
24
  MediaUploadError,
25
25
  ValidationError,
26
26
  )
@@ -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", "TikTokAccountFactory", "TikTokPostManager"]
5
+ __all__ = ["TikTokClient"]
@@ -11,14 +11,14 @@ from typing import Any
11
11
 
12
12
  from pydantic import HttpUrl
13
13
 
14
- from marqetive.platforms.base import SocialMediaPlatform
15
- from marqetive.platforms.exceptions import (
14
+ from marqetive.core.base import ProgressCallback, SocialMediaPlatform
15
+ from marqetive.core.exceptions import (
16
16
  PlatformAuthError,
17
17
  PlatformError,
18
18
  PostNotFoundError,
19
19
  ValidationError,
20
20
  )
21
- from marqetive.platforms.models import (
21
+ from marqetive.core.models import (
22
22
  AuthCredentials,
23
23
  Comment,
24
24
  CommentStatus,
@@ -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,
@@ -290,7 +292,9 @@ class TikTokClient(SocialMediaPlatform):
290
292
  ) from e
291
293
 
292
294
  async def update_post(
293
- self, post_id: str, request: PostUpdateRequest # noqa: ARG002
295
+ self,
296
+ post_id: str,
297
+ request: PostUpdateRequest, # noqa: ARG002
294
298
  ) -> Post:
295
299
  """Update a TikTok video.
296
300
 
@@ -335,7 +339,9 @@ class TikTokClient(SocialMediaPlatform):
335
339
  )
336
340
 
337
341
  async def create_comment(
338
- self, post_id: str, content: str # noqa: ARG002
342
+ self,
343
+ post_id: str,
344
+ content: str, # noqa: ARG002
339
345
  ) -> Comment:
340
346
  """Create a comment on a TikTok video.
341
347
 
@@ -357,7 +363,10 @@ class TikTokClient(SocialMediaPlatform):
357
363
  )
358
364
 
359
365
  async def upload_media(
360
- self, media_url: str, media_type: str, alt_text: str | None = None # noqa: ARG002
366
+ self,
367
+ media_url: str,
368
+ media_type: str,
369
+ alt_text: str | None = None, # noqa: ARG002
361
370
  ) -> MediaAttachment:
362
371
  """Upload a video to TikTok.
363
372
 
@@ -9,7 +9,7 @@ TikTok API uses string error codes in the response format:
9
9
 
10
10
  from typing import Any
11
11
 
12
- from marqetive.platforms.exceptions import (
12
+ from marqetive.core.exceptions import (
13
13
  MediaUploadError,
14
14
  PlatformAuthError,
15
15
  PlatformError,
@@ -20,7 +20,7 @@ from typing import Any, Literal
20
20
 
21
21
  import httpx
22
22
 
23
- from marqetive.platforms.exceptions import (
23
+ from marqetive.core.exceptions import (
24
24
  InvalidFileTypeError,
25
25
  MediaUploadError,
26
26
  )
@@ -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", "TwitterAccountFactory", "TwitterPostManager"]
5
+ __all__ = ["TwitterClient"]
@@ -13,8 +13,8 @@ import httpx
13
13
  import tweepy
14
14
  from pydantic import HttpUrl
15
15
 
16
- from marqetive.platforms.base import SocialMediaPlatform
17
- from marqetive.platforms.exceptions import (
16
+ from marqetive.core.base import ProgressCallback, SocialMediaPlatform
17
+ from marqetive.core.exceptions import (
18
18
  MediaUploadError,
19
19
  PlatformAuthError,
20
20
  PlatformError,
@@ -22,7 +22,7 @@ from marqetive.platforms.exceptions import (
22
22
  RateLimitError,
23
23
  ValidationError,
24
24
  )
25
- from marqetive.platforms.models import (
25
+ from marqetive.core.models import (
26
26
  AuthCredentials,
27
27
  Comment,
28
28
  CommentStatus,
@@ -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
@@ -9,7 +9,7 @@ This module provides comprehensive error handling for Twitter API errors includi
9
9
 
10
10
  from typing import Any
11
11
 
12
- from marqetive.platforms.exceptions import (
12
+ from marqetive.core.exceptions import (
13
13
  MediaUploadError,
14
14
  PlatformAuthError,
15
15
  PlatformError,
@@ -18,7 +18,7 @@ from typing import Any, Literal
18
18
 
19
19
  import httpx
20
20
 
21
- from marqetive.platforms.exceptions import (
21
+ from marqetive.core.exceptions import (
22
22
  InvalidFileTypeError,
23
23
  MediaUploadError,
24
24
  )
marqetive/utils/oauth.py CHANGED
@@ -10,8 +10,8 @@ from typing import Any
10
10
 
11
11
  import httpx
12
12
 
13
- from marqetive.platforms.exceptions import PlatformAuthError
14
- from marqetive.platforms.models import AuthCredentials
13
+ from marqetive.core.exceptions import PlatformAuthError
14
+ from marqetive.core.models import AuthCredentials
15
15
 
16
16
  logger = logging.getLogger(__name__)
17
17
 
@@ -8,7 +8,7 @@ import re
8
8
  from datetime import datetime, timedelta
9
9
  from typing import Any
10
10
 
11
- from marqetive.platforms.models import AuthCredentials
11
+ from marqetive.core.models import AuthCredentials
12
12
 
13
13
 
14
14
  def is_token_expired(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: marqetive-lib
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Modern Python utilities for web APIs
5
5
  Keywords: api,utilities,web,http,marqetive
6
6
  Requires-Python: >=3.12
@@ -0,0 +1,35 @@
1
+ marqetive/__init__.py,sha256=TPjbyZ8ZRux2b6GqSG_7w9A7auPaTkxlamcfYIz0XVw,2907
2
+ marqetive/core/__init__.py,sha256=8n-Ys3qR4wcGUV2rZTz68ICknD1GrkYz82SxLo-dods,1129
3
+ marqetive/core/base.py,sha256=CKNTA6nErZ_Fx8uPtWxgNY054kmSTlGXL81zaF2d0R0,13248
4
+ marqetive/core/client.py,sha256=2_FoNpqaRglsWg10i5RTbyDg_kRQKhgWjYs6iDdFxLg,3210
5
+ marqetive/core/exceptions.py,sha256=Xyj0bzNiZm5VTErmzXgVW8T6IQnOpF92-HJiKPKjIio,7076
6
+ marqetive/core/models.py,sha256=W_yOuRWItWSn82n8vXRNN_ScdNkzY1De2qqXaVN2RGU,10974
7
+ marqetive/factory.py,sha256=irZ5oN8a__kXZH70UN2uI7TzqTXu66d4QZ1FoxSoiK8,14092
8
+ marqetive/platforms/__init__.py,sha256=RBxlQSGyELsulSnwf5uaE1ohxFc7jC61OO9CrKaZp48,1312
9
+ marqetive/platforms/instagram/__init__.py,sha256=7ClfTovAcCHac2DzKS7z1MFuZpy9lcwet7YP5d6MPeY,135
10
+ marqetive/platforms/instagram/client.py,sha256=C5T9v1Aina8F3_Dk3d_I_RiVt7VvxTwwqdYeizDr0iQ,25187
11
+ marqetive/platforms/instagram/exceptions.py,sha256=TcD_pX4eSx_k4yW8DgfA6SGPiAz3VW7cMqM8DmiXIhg,8978
12
+ marqetive/platforms/instagram/media.py,sha256=sZKbBpTac4hIR3OvVV3wL21uHOSQUUpBBKXrvC1zXPc,21161
13
+ marqetive/platforms/linkedin/__init__.py,sha256=zCnokoPYs56iA1sBSYIlaZW2J50L3CbnQpJSaOLrzP8,131
14
+ marqetive/platforms/linkedin/client.py,sha256=cU22ympiWxDPXSUIB0juAQUV3RCpBg-m2nb4D38N5Go,23806
15
+ marqetive/platforms/linkedin/exceptions.py,sha256=i5fARUkZik46bS3htZBwUInVzetsZx1APpKEXLrCKf0,9762
16
+ marqetive/platforms/linkedin/media.py,sha256=lYUKJWbI3mOsvdSOZfUytc4BWb_nIf4YjiKNpC7EpT0,16896
17
+ marqetive/platforms/tiktok/__init__.py,sha256=BQtxdECd2bW9_vV9W-MY4A1rdXi_xurGWWmzTjTUpMM,123
18
+ marqetive/platforms/tiktok/client.py,sha256=i_nyhVywh4feFu5vv4LrMQOkoLsxfxgpH6aKa9JjdOc,17060
19
+ marqetive/platforms/tiktok/exceptions.py,sha256=vxwyAKujMGZJh0LetG1QsLF95QfUs_kR6ujsWSHGqL0,10124
20
+ marqetive/platforms/tiktok/media.py,sha256=p42E0D3bp9x0RgdK7lqcBl2v5G70ZOpY4JTYS-oCgD4,23766
21
+ marqetive/platforms/twitter/__init__.py,sha256=AA5BELRvZyl2WE_7-puSEWArxZjaXcTJ_i8NGOWrv6k,129
22
+ marqetive/platforms/twitter/client.py,sha256=nVXFU1nZBimjzxq0uwCzi0hQy7tdkbW42bbV_A2w8jk,19627
23
+ marqetive/platforms/twitter/exceptions.py,sha256=eZ-dJKOXH_-bAMg29zWKbEqMFud29piEJ5IWfC9wFts,8926
24
+ marqetive/platforms/twitter/media.py,sha256=N8f9UZv1JPJoFDTrPOrvxqdShFU-DQPFBScBoCrZci4,24963
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=LQLXpThZUe0XbSpO3dJ5oW3sPRJuKjSk3_f5_3baUzA,12095
31
+ marqetive/utils/retry.py,sha256=lAniJLMNWp9XsHrvU0XBNifpNEjfde4MGfd5hlFTPfA,7636
32
+ marqetive/utils/token_validator.py,sha256=dNvDeHs2Du5UyMMH2ZOW6ydR7OwOEKA4c9e-rG0f9-0,6698
33
+ marqetive_lib-0.1.5.dist-info/METADATA,sha256=3P2xdFayfDwcCkMLSnPty2QJY5C3od4STpI-pg22-WY,7798
34
+ marqetive_lib-0.1.5.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
35
+ marqetive_lib-0.1.5.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}")