itd-sdk 2.4.1__tar.gz → 2.4.3__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 (43) hide show
  1. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/PKG-INFO +1 -1
  2. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/base.py +13 -9
  3. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/client.py +3 -2
  4. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/comment.py +2 -2
  5. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/file.py +5 -7
  6. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/notification.py +1 -1
  7. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/post.py +7 -6
  8. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/report.py +1 -4
  9. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/user.py +7 -21
  10. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/pyproject.toml +1 -1
  11. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/README.md +0 -0
  12. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/__init__.py +0 -0
  13. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/_default.py +0 -0
  14. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/__init__.py +0 -0
  15. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/auth.py +0 -0
  16. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/comments.py +0 -0
  17. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/dwell.py +0 -0
  18. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/etc.py +0 -0
  19. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/files.py +0 -0
  20. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/hashtags.py +0 -0
  21. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/notifications.py +0 -0
  22. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/pins.py +0 -0
  23. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/platform.py +0 -0
  24. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/polls.py +0 -0
  25. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/posts.py +0 -0
  26. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/reports.py +0 -0
  27. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/search.py +0 -0
  28. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/sessions.py +0 -0
  29. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/subscription.py +0 -0
  30. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/users.py +0 -0
  31. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/verification.py +0 -0
  32. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/clan.py +0 -0
  33. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/enums.py +0 -0
  34. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/exceptions.py +0 -0
  35. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/hashtag.py +0 -0
  36. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/logger.py +0 -0
  37. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/pin.py +0 -0
  38. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/poll.py +0 -0
  39. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/request.py +0 -0
  40. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/session.py +0 -0
  41. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/span.py +0 -0
  42. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/utils.py +0 -0
  43. {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: itd-sdk
3
- Version: 2.4.1
3
+ Version: 2.4.3
4
4
  Summary: ITD client for python
5
5
  Keywords: itd,itdsocial
6
6
  Author: firedotguy
@@ -53,11 +53,12 @@ class ITDBaseModel:
53
53
 
54
54
  def __init__(self, client: Client | None = None) -> None:
55
55
  self._client = client or get_default_client()
56
- self._fields_from_data: set[str] = set()
56
+ self._loaded_attrs: set[str] = set()
57
57
 
58
58
  def __setattr__(self, name: str, value: Any) -> None:
59
59
  if isinstance(value, ITDBaseModel) and (client := _getattr(self, '_client')): # ai
60
60
  value._client = client
61
+
61
62
  object.__setattr__(self, name, value)
62
63
 
63
64
  def _post_refresh(self): ...
@@ -66,9 +67,11 @@ class ITDBaseModel:
66
67
  def client(self) -> Client:
67
68
  return self._client
68
69
 
69
- def _refresh(self, *, client: Client) -> dict: ...
70
+ def _refresh(self, *, client: Client) -> dict: raise NotImplementedError()
70
71
 
71
72
  def refresh(self, *, client: Client | None = None) -> Any:
73
+ if not self._refreshable:
74
+ l.warning(f'{self.__class__.__name__} is not refreshable but refresh is called')
72
75
  self.load_status = LoadStatus.LOADING
73
76
  self._fill_from_data(self._refresh(client=client or self.client))
74
77
  self.load_status = LoadStatus.FULL
@@ -78,7 +81,7 @@ class ITDBaseModel:
78
81
  def _fill_from_data(self, data: dict):
79
82
  assert self._validator, 'Unable to use fill_from_data without a validator'
80
83
  validated = self._validator().model_validate(data) # pyright: ignore[reportCallIssue]
81
- self._fields_from_data = validated.model_fields_set
84
+ # self._loaded_attrs = validated.model_fields_set # значения автоматически добавляются через __setattr__
82
85
  for name, value in validated.__dict__.items():
83
86
  setattr(self, name, value)
84
87
 
@@ -100,18 +103,20 @@ class ITDBaseModel:
100
103
  value = _getattr(self, name)
101
104
  if (
102
105
  name.startswith('_') or # приватный аттрибут
103
- name in ('client', 'model_fields_set', 'load_status') or
106
+ name in ('client', 'model_fields_set', 'load_status') or # спец. имя
104
107
  not _getattr(self, '_refreshable') or # не рефрешабельная модель
105
108
  not _getattr(self, 'client').config.auto_load or # отключено в конфиге
106
109
  callable(value) or # функция
107
- self.load_status == LoadStatus.LOADING or
108
- (not _getattr(self, 'client').config.load_comments_from_post and _is_comments_on_post(value, self)) or
110
+ self.load_status == LoadStatus.LOADING or # сейчас загружается (уже вроде как не надо, но пусть будет на всяк)
111
+ (not _getattr(self, 'client').config.load_comments_from_post and _is_comments_on_post(value, self)) or # коменты
112
+ name in self._loaded_attrs or name in self.__dict__ or # было загружено или добавлено через setattr
109
113
  (isinstance(value, ITDBaseModel) and not value._load_with_parent)
110
114
  ):
111
115
  # l.debug('return %s as is', name)
112
116
  return object.__getattribute__(self, name)
113
117
 
114
- if isinstance(value, FieldInfo) or (self.load_status != LoadStatus.FULL and name not in self._fields_from_data):
118
+ if isinstance(value, FieldInfo) or self.load_status != LoadStatus.FULL:
119
+ l.debug('attrs %s', list(self._loaded_attrs))
115
120
  l.info('refresh %s field=%s load_status=%s', self.__class__.__name__, name, self.load_status.value)
116
121
  self.refresh()
117
122
 
@@ -241,7 +246,6 @@ class ITDList[T](ITDBaseModel, list[T]):
241
246
  def __getitem__(self, index: slice) -> list[T]: ...
242
247
 
243
248
  def __getitem__(self, index: int | slice) -> T | list[T]: # pyright: ignore[reportIncompatibleMethodOverride]
244
-
245
249
  if isinstance(index, slice):
246
250
  value: int | None = index.stop
247
251
  else:
@@ -402,7 +406,7 @@ def rate_limit(delay_min: float | None = None, delay_mid: float | None = None, d
402
406
  l.error('too large rate limit')
403
407
  raise
404
408
 
405
- retry_after = getattr(e, 'retry_after', client.config.retry_delay)
409
+ retry_after = getattr(e, 'retry_after', client.config.retry_delay) or 10
406
410
  l.warning('%s on %s: wait %ss', e.__class__.__name__, func.__name__, retry_after)
407
411
  sleep(retry_after)
408
412
 
@@ -52,6 +52,7 @@ class Config:
52
52
 
53
53
  timeout: float = 30
54
54
  timeout_file: float = 120
55
+ timeout_file_download: float = 60
55
56
 
56
57
  url: str = 'xn--d1ah4a.com'
57
58
  url_api: str | None = None
@@ -64,7 +65,7 @@ class Config:
64
65
 
65
66
  retry_enabled: bool = True
66
67
  retry_delay: float = 10 # delay before next attempt (after rate limit error) if retry_after is not provided in request
67
- retry_max_retries: int | None = None # none for no limit
68
+ retry_max_retries: int | None = 10 # none for no limit
68
69
  retry_exceptions: tuple[type[Exception]] | list[type[Exception]] | None = None
69
70
  retry_max_retry_after: int = 500
70
71
 
@@ -286,7 +287,7 @@ class Client:
286
287
  tuple[list[User], list[Hashtag]]: Результат поиска
287
288
  """
288
289
  res = search(self, query, users_limit, hashtags_limit).json()['data']
289
- return [User._from_dict(user, client=self) for user in res['users']], [Hashtag._from_dict(hashtag, self) for hashtag in res['hashtags']]
290
+ return [User.from_dict(user, client=self) for user in res['users']], [Hashtag._from_dict(hashtag, self) for hashtag in res['hashtags']]
290
291
 
291
292
  def search_users(self, query: str, limit: int = 20) -> list[User]:
292
293
  """Поиск пользователей
@@ -152,12 +152,12 @@ class _CommentValidate(BaseModel, Comment):
152
152
  @classmethod
153
153
  def validate_reply_to(cls, reply_to: dict | None):
154
154
  if reply_to is not None:
155
- return User._from_dict(reply_to)
155
+ return User.from_dict(reply_to)
156
156
 
157
157
  @field_validator('author', mode='plain')
158
158
  @classmethod
159
159
  def validate_author(cls, author: dict):
160
- return User._from_dict(author)
160
+ return User.from_dict(author)
161
161
 
162
162
 
163
163
 
@@ -30,7 +30,7 @@ class File(ITDBaseModel):
30
30
  super().__init__(client)
31
31
  self.filename = name
32
32
  self.data = data
33
- self.refresh()
33
+ self._fill_from_data(upload_file(client or self.client, self.filename, self.data).json())
34
34
 
35
35
  @classmethod
36
36
  def from_path(cls, path: Path | str, *, client: Client | None = None):
@@ -51,15 +51,12 @@ class File(ITDBaseModel):
51
51
 
52
52
  return cls(name, data, client)
53
53
 
54
- def _refresh(self, *, client: Client):
55
- return upload_file(client, self.filename, self.data).json()
56
-
57
54
  def delete(self, *, client: Client | None = None) -> None:
58
55
  delete_file(client or self.client, self.id)
59
56
 
60
57
  def download(self, name: str | None = None) -> None:
61
58
  with open(name or self.filename, 'wb') as fl:
62
- fl.write(get(self.url, timeout=self.client.config.timeout_file).content)
59
+ fl.write(get(self.url, timeout=self.client.config.timeout_file_download).content)
63
60
 
64
61
  def __str__(self) -> str:
65
62
  return self.filename
@@ -73,6 +70,7 @@ class _FileValidate(BaseModel, File):
73
70
  class PostAttach(ITDBaseModel):
74
71
  _validator = lambda _: _PostAttachValidate
75
72
  _post: Post
73
+ _refreshable = False
76
74
 
77
75
  id: UUID
78
76
  type: AttachType = AttachType.IMAGE
@@ -94,7 +92,7 @@ class PostAttach(ITDBaseModel):
94
92
  name (str): Имя файла.
95
93
  """
96
94
  with open(name, 'wb') as fl:
97
- fl.write(get(self.url, timeout=self.client.config.timeout_file).content)
95
+ fl.write(get(self.url, timeout=self.client.config.timeout_file_download).content)
98
96
 
99
97
  def record_open(self, client: Client | None = None):
100
98
  """Записать событие открытия фото
@@ -163,7 +161,7 @@ class CommentAttach(PostAttach):
163
161
  name (str | None, optional): Имя файла. None - имя, под которым был загружен этот файл. Defaults to None.
164
162
  """
165
163
  with open(name or self.filename, 'wb') as fl:
166
- fl.write(get(self.url, timeout=self.client.config.timeout_file).content)
164
+ fl.write(get(self.url, timeout=self.client.config.timeout_file_download).content)
167
165
 
168
166
 
169
167
  class _CommentAttachValidate(BaseModel, CommentAttach):
@@ -109,7 +109,7 @@ class _NotificationValidate(BaseModel, Notification):
109
109
  @field_validator('actor', mode='plain')
110
110
  @classmethod
111
111
  def validate_actor(cls, actor: dict):
112
- return User._from_dict(actor)
112
+ return User.from_dict(actor)
113
113
 
114
114
 
115
115
 
@@ -289,6 +289,7 @@ class Post(ITDBaseModel):
289
289
  Returns:
290
290
  Post: Пост
291
291
  """
292
+ # TODO rewrite with from_dict
292
293
  instance = cls.__new__(cls)
293
294
  super(Post, instance).__init__(client)
294
295
 
@@ -311,7 +312,7 @@ class Post(ITDBaseModel):
311
312
  ).json()
312
313
 
313
314
  validated = _PostValidate.model_validate(post)
314
- instance._fields_from_data = validated.model_fields_set
315
+ instance._loaded_attrs = validated.model_fields_set
315
316
  for name, value in validated.__dict__.items():
316
317
  setattr(instance, name, value)
317
318
 
@@ -405,7 +406,7 @@ class Post(ITDBaseModel):
405
406
  Post: Пост
406
407
  """
407
408
  post = repost(client or self.client, self.id, content).json()
408
- if 'reposts_count' in self._fields_from_data:
409
+ if 'reposts_count' in self._loaded_attrs:
409
410
  self.reposts_count += 1
410
411
  if (client or self.client) == self.client:
411
412
  self.is_reposted = True
@@ -429,7 +430,7 @@ class Post(ITDBaseModel):
429
430
  view_post(c, self.id)
430
431
  if c == self.client:
431
432
  self.is_viewed = True
432
- if c.config.post_view_increment and 'views_count' in self._fields_from_data:
433
+ if c.config.post_view_increment and 'views_count' in self._loaded_attrs:
433
434
  self.views_count += 1
434
435
 
435
436
  def pin(self, client: Client | None = None) -> None:
@@ -505,7 +506,7 @@ class Post(ITDBaseModel):
505
506
  Comment: Комментарий
506
507
  """
507
508
  comment = self.comments.new(content, attachments, client or self.client)
508
- if 'comments_count' in self._fields_from_data:
509
+ if 'comments_count' in self._loaded_attrs:
509
510
  self.comments_count += 1
510
511
  return comment
511
512
 
@@ -588,13 +589,13 @@ class _PostValidate(BaseModel, Post): # BaseModel MUST be first or you ll have s
588
589
  return None
589
590
  if isinstance(author, _UserBase):
590
591
  return author
591
- return User._from_dict(author)
592
+ return User.from_dict(author)
592
593
 
593
594
  @field_validator('wall_recipient', mode='plain')
594
595
  @classmethod
595
596
  def validate_wall_recipient(cls, wall_recipient: dict | None):
596
597
  if wall_recipient is not None:
597
- return User._from_dict(wall_recipient)
598
+ return User.from_dict(wall_recipient)
598
599
 
599
600
 
600
601
 
@@ -26,10 +26,7 @@ class Report(ITDBaseModel):
26
26
  self.reason = reason
27
27
  self.description = description
28
28
 
29
- self.refresh()
30
-
31
- def _refresh(self, *, client: Client):
32
- return report(client or self.client, self.target_id, self.target_type, self.reason, self.description).json()['data']
29
+ self._fill_from_data(report(client or self.client, self.target_id, self.target_type, self.reason, self.description).json()['data'])
33
30
 
34
31
 
35
32
 
@@ -123,6 +123,7 @@ class _PrivacyValidate(BaseModel, Privacy):
123
123
  class Subscription(ITDBaseModel):
124
124
  _validator = lambda _: _SubscriptionValidate
125
125
  _payment_methods: list
126
+ _refreshable = False
126
127
 
127
128
  active: bool = Field(False, alias='isActive')
128
129
  expires_at: datetime | None = Field(None, alias='expiresAt')
@@ -249,17 +250,6 @@ class User(_UserBase):
249
250
 
250
251
  created_at: datetime | None = Field(None, alias='createdAt') # none if blocked
251
252
 
252
- @classmethod
253
- def _from_dict(cls, data: dict, client: Client | None = None):
254
- instance = cls(data['username'], client)
255
- validated = _UserValidate.model_validate(data)
256
- instance._fields_from_data = validated.model_fields_set
257
- for name, value in validated.__dict__.items():
258
- setattr(instance, name, value)
259
-
260
- instance.load_status = LoadStatus.PARTIALLY
261
- return instance
262
-
263
253
  @classmethod
264
254
  def by_id(cls, id: UUID | str) -> 'User':
265
255
  return cls(to_uuid(id))
@@ -381,13 +371,13 @@ class User(_UserBase):
381
371
  @property
382
372
  def following(self) -> list[User]:
383
373
  if not self._following:
384
- self._following = [User._from_dict(user, client=self.client) for user in get_following(self.client, self._identifier).json()['data']['users']]
374
+ self._following = [User.from_dict(user, client=self.client) for user in get_following(self.client, self._identifier).json()['data']['users']]
385
375
  return self._following
386
376
 
387
377
  @property
388
378
  def followers(self) -> list[User]:
389
379
  if not self._followers:
390
- self._followers = [User._from_dict(user, client=self.client) for user in get_followers(self.client, self._identifier).json()['data']['users']]
380
+ self._followers = [User.from_dict(user, client=self.client) for user in get_followers(self.client, self._identifier).json()['data']['users']]
391
381
  return self._followers
392
382
 
393
383
 
@@ -536,13 +526,9 @@ class Me(_UserBase):
536
526
  raise exc
537
527
  return user
538
528
 
539
- if not TYPE_CHECKING:
540
- def __getattribute__(self, name: str):
541
- value = super().__getattribute__(name)
542
- if name == 'pin' and value is not None and getattr(value, '_user', None) is None:
543
- value._user = self
544
- return value
545
-
529
+ def _post_refresh(self):
530
+ if self.pin:
531
+ self.pin._user = self
546
532
 
547
533
 
548
534
  class _MeValidate(BaseModel, Me):
@@ -585,7 +571,7 @@ class Followers(ITDList[User]):
585
571
  return data['pagination']['page'] + 1
586
572
 
587
573
  def _extend(self, objects: list, client: Client):
588
- self.extend([User._from_dict(user, client=client) for user in objects])
574
+ self.extend([User.from_dict(user, client=client) for user in objects])
589
575
 
590
576
  def __setattr__(self, name: str, value) -> None:
591
577
  if name == '_client':
@@ -14,7 +14,7 @@ version_provider = "uv"
14
14
 
15
15
  [project]
16
16
  name = "itd-sdk"
17
- version = "2.4.1"
17
+ version = "2.4.3"
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
File without changes
File without changes
File without changes
File without changes