marqetive-lib 0.1.17__py3-none-any.whl → 0.1.20__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/core/base.py CHANGED
@@ -119,7 +119,10 @@ class SocialMediaPlatform(ABC):
119
119
  return self
120
120
 
121
121
  async def __aexit__(
122
- self, exc_type: type[Exception] | None, exc_val: Exception | None, exc_tb: TracebackType | None
122
+ self,
123
+ exc_type: type[Exception] | None,
124
+ exc_val: Exception | None,
125
+ exc_tb: TracebackType | None,
123
126
  ) -> None:
124
127
  """Async context manager exit."""
125
128
  if self.api_client:
@@ -228,6 +231,35 @@ class SocialMediaPlatform(ABC):
228
231
  if inspect.iscoroutine(result):
229
232
  await result
230
233
 
234
+ # ==================== Validation Helpers ====================
235
+
236
+ @abstractmethod
237
+ def _validate_create_post_request(self, request: PostCreateRequest) -> None:
238
+ """Validate a post creation request.
239
+
240
+ This base implementation performs no validation. Subclasses should
241
+ override this method to implement platform-specific validation rules.
242
+
243
+ The validation method should raise ValidationError with descriptive
244
+ messages including platform name and field name for consistency.
245
+
246
+ Args:
247
+ request: The post creation request to validate.
248
+
249
+ Raises:
250
+ ValidationError: If validation fails.
251
+
252
+ Example:
253
+ >>> def _validate_create_post_request(self, request):
254
+ ... if not request.content:
255
+ ... raise ValidationError(
256
+ ... "Content is required",
257
+ ... platform=self.platform_name,
258
+ ... field="content",
259
+ ... )
260
+ """
261
+ pass
262
+
231
263
  # ==================== Abstract Authentication Methods ====================
232
264
 
233
265
  @abstractmethod
marqetive/core/models.py CHANGED
@@ -217,6 +217,7 @@ class AuthCredentials(BaseModel):
217
217
  expires_at: Timestamp when access token expires
218
218
  scope: List of permission scopes granted
219
219
  user_id: ID of the authenticated user
220
+ username: Username/handle of the authenticated user
220
221
  status: Current status of the account credentials
221
222
  additional_data: Platform-specific auth data
222
223
 
@@ -237,6 +238,7 @@ class AuthCredentials(BaseModel):
237
238
  expires_at: datetime | None = None
238
239
  scope: list[str] = Field(default_factory=list)
239
240
  user_id: str | None = None
241
+ username: str | None = None
240
242
  status: AccountStatus = AccountStatus.VALID
241
243
  additional_data: dict[str, Any] = Field(default_factory=dict)
242
244
 
@@ -211,6 +211,39 @@ class InstagramClient(SocialMediaPlatform):
211
211
  except httpx.HTTPError:
212
212
  return False
213
213
 
214
+ # ==================== Validation ====================
215
+
216
+ def _validate_create_post_request(self, request: PostCreateRequest) -> None:
217
+ """Validate Instagram post creation request.
218
+
219
+ Instagram Requirements:
220
+ - Media is ALWAYS required (Instagram is a visual platform)
221
+ - For carousels: 2-10 images
222
+ - For reels: 1 video (3 sec - 15 min)
223
+ - Caption max 2200 characters
224
+ - Media must be publicly accessible URLs
225
+
226
+ Args:
227
+ request: Post creation request to validate.
228
+
229
+ Raises:
230
+ ValidationError: If validation fails.
231
+ """
232
+ if not request.media_urls and not request.media_ids:
233
+ raise ValidationError(
234
+ "Instagram posts require at least one media attachment. "
235
+ "Instagram is a visual platform - text-only posts are not supported.",
236
+ platform=self.platform_name,
237
+ field="media",
238
+ )
239
+
240
+ if request.content and len(request.content) > 2200:
241
+ raise ValidationError(
242
+ f"Caption exceeds 2200 characters ({len(request.content)} characters)",
243
+ platform=self.platform_name,
244
+ field="content",
245
+ )
246
+
214
247
  # ==================== Post CRUD Methods ====================
215
248
 
216
249
  async def create_post(self, request: PostCreateRequest) -> Post:
@@ -256,12 +289,8 @@ class InstagramClient(SocialMediaPlatform):
256
289
  if not self.api_client:
257
290
  raise RuntimeError("Client must be used as async context manager")
258
291
 
259
- if not request.media_urls and not request.media_ids:
260
- raise ValidationError(
261
- "Instagram posts require at least one media attachment",
262
- platform=self.platform_name,
263
- field="media",
264
- )
292
+ # Validate request
293
+ self._validate_create_post_request(request)
265
294
 
266
295
  # Determine content type from request
267
296
  media_type = self._get_media_type(request)
@@ -375,7 +404,9 @@ class InstagramClient(SocialMediaPlatform):
375
404
  content=request.content,
376
405
  status=PostStatus.PUBLISHED,
377
406
  created_at=datetime.now(),
407
+ author_id=self.instagram_account_id,
378
408
  url=cast(HttpUrl, result.permalink) if result.permalink else None,
409
+ raw_data={"container_id": container_ids[0]},
379
410
  )
380
411
 
381
412
  async def _create_carousel_post(self, request: PostCreateRequest) -> Post:
@@ -826,7 +857,9 @@ class InstagramClient(SocialMediaPlatform):
826
857
  content=caption,
827
858
  status=PostStatus.PUBLISHED,
828
859
  created_at=datetime.now(),
860
+ author_id=self.instagram_account_id,
829
861
  url=cast(HttpUrl, result.permalink) if result.permalink else None,
862
+ raw_data={"container_id": container_ids[0]},
830
863
  )
831
864
 
832
865
  async def create_reel(
@@ -890,7 +923,9 @@ class InstagramClient(SocialMediaPlatform):
890
923
  content=caption,
891
924
  status=PostStatus.PUBLISHED,
892
925
  created_at=datetime.now(),
926
+ author_id=self.instagram_account_id,
893
927
  url=cast(HttpUrl, result.permalink) if result.permalink else None,
928
+ raw_data={"container_id": container_id},
894
929
  )
895
930
 
896
931
  async def create_story(
@@ -941,7 +976,9 @@ class InstagramClient(SocialMediaPlatform):
941
976
  content=None, # Stories don't have captions
942
977
  status=PostStatus.PUBLISHED,
943
978
  created_at=datetime.now(),
979
+ author_id=self.instagram_account_id,
944
980
  url=cast(HttpUrl, result.permalink) if result.permalink else None,
981
+ raw_data={"container_id": container_id},
945
982
  )
946
983
 
947
984
  # ==================== Helper Methods ====================