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.
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/PKG-INFO +1 -1
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/base.py +13 -9
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/client.py +3 -2
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/comment.py +2 -2
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/file.py +5 -7
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/notification.py +1 -1
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/post.py +7 -6
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/report.py +1 -4
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/user.py +7 -21
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/pyproject.toml +1 -1
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/README.md +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/__init__.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/_default.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/__init__.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/auth.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/comments.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/dwell.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/etc.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/files.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/hashtags.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/notifications.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/pins.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/platform.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/polls.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/posts.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/reports.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/search.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/sessions.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/subscription.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/users.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/api/verification.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/clan.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/enums.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/exceptions.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/hashtag.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/logger.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/pin.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/poll.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/request.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/session.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/span.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/utils.py +0 -0
- {itd_sdk-2.4.1 → itd_sdk-2.4.3}/itd/version.py +0 -0
|
@@ -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.
|
|
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.
|
|
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
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
164
|
+
fl.write(get(self.url, timeout=self.client.config.timeout_file_download).content)
|
|
167
165
|
|
|
168
166
|
|
|
169
167
|
class _CommentAttachValidate(BaseModel, CommentAttach):
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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.
|
|
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':
|
|
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
|
|
File without changes
|