itd-sdk 2.1.0__tar.gz → 2.1.2__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.
Files changed (38) hide show
  1. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/PKG-INFO +1 -1
  2. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/comments.py +3 -3
  3. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/posts.py +4 -4
  4. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/base.py +8 -4
  5. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/client.py +2 -0
  6. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/exceptions.py +6 -0
  7. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/post.py +10 -11
  8. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/user.py +12 -10
  9. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/pyproject.toml +1 -1
  10. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/README.md +0 -0
  11. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/__init__.py +0 -0
  12. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/_default.py +0 -0
  13. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/__init__.py +0 -0
  14. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/auth.py +0 -0
  15. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/etc.py +0 -0
  16. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/files.py +0 -0
  17. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/hashtags.py +0 -0
  18. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/notifications.py +0 -0
  19. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/pins.py +0 -0
  20. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/polls.py +0 -0
  21. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/reports.py +0 -0
  22. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/search.py +0 -0
  23. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/subscription.py +0 -0
  24. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/users.py +0 -0
  25. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/verification.py +0 -0
  26. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/clan.py +0 -0
  27. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/comment.py +0 -0
  28. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/enums.py +0 -0
  29. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/file.py +0 -0
  30. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/hashtag.py +0 -0
  31. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/logger.py +0 -0
  32. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/notification.py +0 -0
  33. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/pin.py +0 -0
  34. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/poll.py +0 -0
  35. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/report.py +0 -0
  36. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/request.py +0 -0
  37. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/span.py +0 -0
  38. {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: itd-sdk
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Summary: ITD client for python
5
5
  Keywords: itd,itdsocial
6
6
  Author: firedotguy
@@ -3,18 +3,18 @@ from uuid import UUID
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  from itd.base import catch_errors, rate_limit
6
- from itd.exceptions import ValidationError, NotFoundError, AlreadyDeletedError, BannedWordError
6
+ from itd.exceptions import ValidationError, NotFoundError, AlreadyDeletedError, BannedWordError, ForbiddenError, RequiresSubscriptionError
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from itd.client import Client
10
10
 
11
11
  @rate_limit(5, 20, 80)
12
- @catch_errors(ValidationError(), NotFoundError('Post'), BannedWordError('Comment'))
12
+ @catch_errors(ValidationError(), NotFoundError('Post'), BannedWordError('Comment'), ForbiddenError('comment - some files not owned'), RequiresSubscriptionError('Video uploading'))
13
13
  def add_comment(client: Client, post_id: UUID, content: str | None = None, attachment_ids: list[UUID] = []):
14
14
  return client.request('post', f'posts/{post_id}/comments', {'content': content or '', "attachmentIds": list(map(str, attachment_ids))})
15
15
 
16
16
  @rate_limit(1, 10, 30)
17
- @catch_errors(ValidationError(), NotFoundError('Comment'), NotFoundError('User', res_check=lambda res: res.status_code == 500 and 'Failed query' in res.text), BannedWordError('Reply'))
17
+ @catch_errors(ValidationError(), NotFoundError('Comment'), NotFoundError('User', res_check=lambda res: res.status_code == 500 and 'Failed query' in res.text), BannedWordError('Reply'), ForbiddenError('reply - some files not owned'), RequiresSubscriptionError('Video uploading'))
18
18
  def add_reply_comment(client: Client, comment_id: UUID, author_id: UUID, content: str | None = None, attachment_ids: list[UUID] = []):
19
19
  return client.request('post', f'comments/{comment_id}/replies', {'content': content or '', 'replyToUserId': str(author_id), "attachmentIds": list(map(str, attachment_ids))})
20
20
 
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
6
6
  from itd.enums import PostsTab, UserPostSorting
7
7
  from itd.poll import NewPoll
8
8
  from itd.exceptions import (
9
- NotFoundError, ForbiddenError, RequiresVerificationError, ValidationError, AlreadyRepostedError, CantRepostYourselfError,
9
+ NotFoundError, ForbiddenError, RequiresSubscriptionError, ValidationError, AlreadyRepostedError, CantRepostYourselfError,
10
10
  NotPinnedError, EditExpiredError, BannedWordError
11
11
  )
12
12
  from itd.base import rate_limit, catch_errors
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
14
14
  from itd.client import Client
15
15
 
16
16
  @rate_limit(1, 10, 30)
17
- @catch_errors(NotFoundError('Wall recipient'), ForbiddenError('post - some files not owned'), RequiresVerificationError('Video uploading'), BannedWordError('Post'), ValidationError())
17
+ @catch_errors(NotFoundError('Wall recipient'), ForbiddenError('post - some files not owned'), RequiresSubscriptionError('Video uploading'), BannedWordError('Post'), ValidationError())
18
18
  def create_post(
19
19
  client: Client,
20
20
  content: str | None = None,
@@ -49,7 +49,7 @@ def get_post(client: Client, id: UUID):
49
49
  return client.request('get', f'posts/{id}')
50
50
 
51
51
  @rate_limit(None, 1, 5)
52
- @catch_errors(NotFoundError('Post'), ForbiddenError('edit post'), EditExpiredError())
52
+ @catch_errors(NotFoundError('Post'), ForbiddenError('edit post'), EditExpiredError(), BannedWordError('Post'))
53
53
  def edit_post(client: Client, id: UUID, content: str, spans: list[dict] = []):
54
54
  return client.request('put', f'posts/{id}', {'content': content, 'spans': spans})
55
55
 
@@ -74,7 +74,7 @@ def unpin_post(client: Client, id: UUID):
74
74
  return client.request('delete', f'posts/{id}/pin')
75
75
 
76
76
  @rate_limit(1, 10, 30)
77
- @catch_errors(NotFoundError('Post'), AlreadyRepostedError(), CantRepostYourselfError(), ValidationError())
77
+ @catch_errors(NotFoundError('Post'), AlreadyRepostedError(), CantRepostYourselfError(), ValidationError(), BannedWordError('Post'))
78
78
  def repost(client: Client, id: UUID, content: str | None = None):
79
79
  data = {}
80
80
  if content:
@@ -12,7 +12,7 @@ from pydantic_core import PydanticUndefinedType
12
12
 
13
13
  from itd._default import get_default_client
14
14
  from itd.logger import get_logger
15
- from itd.exceptions import ITDException, ValidationError, RateLimitError, UnauthorizedError, AccessTokenExpiredError, DEFAULT_ERRORS
15
+ from itd.exceptions import ITDException, ValidationError, RateLimitError, UnauthorizedError, AccessTokenExpiredError, DEFAULT_ERRORS, InternalError
16
16
  from itd.enums import All, ALL, DebugResponseMode, RateLimitMode, BATCH, Batch
17
17
  if TYPE_CHECKING:
18
18
  from itd.client import Client
@@ -370,12 +370,16 @@ def rate_limit(delay_min: float | None = None, delay_mid: float | None = None, d
370
370
  sleep(max(delay, 0))
371
371
  client.last_actions[func.__name__] = datetime.now()
372
372
 
373
+ if not client.config.retry_on_rate_limits:
374
+ return func(client, *args, **kwargs)
375
+
373
376
  while True:
374
377
  try:
375
378
  return func(client, *args, **kwargs)
376
- except RateLimitError as e:
377
- l.info('rate limit on %s; wait %ss', func.__name__, e.retry_after or 10)
378
- sleep(e.retry_after or 10)
379
+ except (RateLimitError, InternalError) as e:
380
+ retry_after = getattr(e, 'retry_after', client.config.rate_limit_wait) or client.config.rate_limit_wait
381
+ l.info('rate limit on %s; wait %ss', func.__name__, retry_after)
382
+ sleep(retry_after)
379
383
 
380
384
  return wrapper
381
385
  return decorator
@@ -45,6 +45,8 @@ class Config:
45
45
  solve_challenge: bool = True
46
46
  load_comments_from_post: bool = False
47
47
  parse_mode: ParseMode = ParseMode.NO
48
+ rate_limit_wait: int = 10 # delay before next attempt (after rate limit error) if retry_after is not provided
49
+ retry_on_rate_limits: bool = True
48
50
 
49
51
  def __post_init__(self):
50
52
  if self.rate_limit_default:
@@ -129,6 +129,11 @@ class RequiresVerificationError(NoRightsError):
129
129
  def __init__(self, obj: str):
130
130
  self.text = f'{obj} allowed only for verificated users'
131
131
 
132
+ class RequiresSubscriptionError(NoRightsError):
133
+ code = 'VIDEO_REQUIRES_NUKSTA'
134
+ def __init__(self, obj: str):
135
+ self.text = f'{obj} allowed only for users with НУКСТА subscription'
136
+
132
137
 
133
138
  class UsernameTakenError(ValidateError):
134
139
  code = 'USERNAME_TAKEN'
@@ -226,6 +231,7 @@ class NotPinnedError(ITDException):
226
231
  text = 'Post not found or is not pinned'
227
232
 
228
233
  class InternalError(ITDException):
234
+ status_code = 502
229
235
  code = 'INTERNAL_ERROR'
230
236
  text = 'Internal server error'
231
237
 
@@ -1,10 +1,9 @@
1
1
  from uuid import UUID
2
2
  from datetime import datetime
3
- from typing import Literal, overload
3
+ from typing import Literal, overload, TYPE_CHECKING
4
4
  from time import sleep
5
5
 
6
6
  from pydantic import Field, BaseModel, field_validator
7
- from pydantic.fields import FieldInfo
8
7
 
9
8
  from itd.base import ITDBaseModel, refresh_wrapper, ITDList
10
9
  from itd.client import Client
@@ -238,7 +237,7 @@ class Post(ITDBaseModel):
238
237
  client (Client | None, optional): Клиент. Defaults to None.
239
238
  """
240
239
  pin_post(client or self.client, self.id)
241
- self.is_pinned_post = True
240
+ self.is_pinned = True
242
241
  (client or self.client).user.pinned_post_id = self.id
243
242
 
244
243
  def unpin(self, client: Client | None = None) -> None:
@@ -248,7 +247,7 @@ class Post(ITDBaseModel):
248
247
  client (Client | None, optional): Клиент. Defaults to None.
249
248
  """
250
249
  unpin_post(client or self.client, self.id)
251
- self.is_pinned_post = False
250
+ self.is_pinned = False
252
251
  (client or self.client).user.pinned_post_id = None
253
252
 
254
253
  def delete(self, client: Client | None = None) -> None:
@@ -314,13 +313,13 @@ class Post(ITDBaseModel):
314
313
  def url(self) -> str:
315
314
  return f'https://xn--d1ah4a.com/@{self.author.username}/post/{self.id}'
316
315
 
317
-
318
- def __getattribute__(self, name: str):
319
- value = super().__getattribute__(name)
320
- if name == 'comments' and getattr(value, '_post_id', None) is None:
321
- value = Comments()
322
- value._post_id = self.id
323
- return value
316
+ if not TYPE_CHECKING:
317
+ def __getattribute__(self, name: str):
318
+ value = super().__getattribute__(name)
319
+ if name == 'comments' and getattr(value, '_post_id', None) is None:
320
+ value = Comments()
321
+ value._post_id = self.id
322
+ return value
324
323
 
325
324
 
326
325
 
@@ -106,10 +106,11 @@ class Privacy(ITDBaseModel):
106
106
  instance._user = user
107
107
  return instance
108
108
 
109
- def __getattribute__(self, name: str):
110
- if name in ('is_private', 'wall_access', 'likes_visibility') and object.__getattribute__(self, '_user'):
111
- setattr(self, name, getattr(object.__getattribute__(self, '_user'), name))
112
- return super().__getattribute__(name)
109
+ if not TYPE_CHECKING:
110
+ def __getattribute__(self, name: str):
111
+ if name in ('is_private', 'wall_access', 'likes_visibility') and object.__getattribute__(self, '_user'):
112
+ setattr(self, name, getattr(object.__getattribute__(self, '_user'), name))
113
+ return super().__getattribute__(name)
113
114
 
114
115
 
115
116
  class _PrivacyValidate(BaseModel, Privacy):
@@ -204,7 +205,7 @@ class _UserBase(ITDBaseModel):
204
205
 
205
206
  @property
206
207
  def liked_posts(self) -> LikedPosts:
207
- if not hasattr(self, '_posts'):
208
+ if not hasattr(self, '_liked_posts'):
208
209
  from itd.post import LikedPosts
209
210
  self._liked_posts = LikedPosts(self, client=self.client)
210
211
  return self._liked_posts
@@ -515,11 +516,12 @@ class Me(_UserBase):
515
516
  self._pins = [Pin(pin, self) for pin in get_pins(self.client).json()['data']['pins']]
516
517
  return self._pins
517
518
 
518
- def __getattribute__(self, name: str):
519
- value = super().__getattribute__(name)
520
- if name == 'pin' and value is not None and getattr(value, '_user', None) is None:
521
- value._user = self
522
- return value
519
+ if not TYPE_CHECKING:
520
+ def __getattribute__(self, name: str):
521
+ value = super().__getattribute__(name)
522
+ if name == 'pin' and value is not None and getattr(value, '_user', None) is None:
523
+ value._user = self
524
+ return value
523
525
 
524
526
 
525
527
 
@@ -14,7 +14,7 @@ version_provider = "uv"
14
14
 
15
15
  [project]
16
16
  name = "itd-sdk"
17
- version = "2.1.0"
17
+ version = "2.1.2"
18
18
  description = "ITD client for python"
19
19
  readme = "README.md"
20
20
  authors = [
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
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