cloudtips 0.3.1__tar.gz → 0.4.1__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 (35) hide show
  1. cloudtips-0.4.1/.github/workflows/docs.yml +31 -0
  2. cloudtips-0.4.1/.gitignore +4 -0
  3. cloudtips-0.4.1/CNAME +1 -0
  4. cloudtips-0.4.1/PKG-INFO +207 -0
  5. cloudtips-0.4.1/README.md +185 -0
  6. {cloudtips-0.3.1 → cloudtips-0.4.1}/cloudtips/__init__.py +2 -2
  7. {cloudtips-0.3.1 → cloudtips-0.4.1}/cloudtips/auth.py +44 -35
  8. cloudtips-0.4.1/cloudtips/client.py +374 -0
  9. {cloudtips-0.3.1 → cloudtips-0.4.1}/cloudtips/models.py +15 -15
  10. cloudtips-0.4.1/docs/CNAME +1 -0
  11. cloudtips-0.4.1/docs/auth.md +94 -0
  12. cloudtips-0.4.1/docs/cards.md +73 -0
  13. cloudtips-0.4.1/docs/client.md +77 -0
  14. cloudtips-0.4.1/docs/donations.md +97 -0
  15. cloudtips-0.4.1/docs/errors.md +86 -0
  16. cloudtips-0.4.1/docs/index.md +74 -0
  17. cloudtips-0.4.1/docs/models/accumulation_summary.md +44 -0
  18. cloudtips-0.4.1/docs/models/card.md +65 -0
  19. cloudtips-0.4.1/docs/models/donation.md +60 -0
  20. cloudtips-0.4.1/docs/models/payout_fee_info.md +39 -0
  21. cloudtips-0.4.1/docs/models/receiver_profile.md +60 -0
  22. cloudtips-0.4.1/docs/models/token_data.md +38 -0
  23. cloudtips-0.4.1/docs/overrides/.gitkeep +0 -0
  24. cloudtips-0.4.1/docs/payouts.md +89 -0
  25. cloudtips-0.4.1/docs/polling.md +102 -0
  26. cloudtips-0.4.1/docs/profile.md +49 -0
  27. cloudtips-0.4.1/docs/quickstart.md +78 -0
  28. cloudtips-0.4.1/docs/stylesheets/irring.css +71 -0
  29. cloudtips-0.4.1/mkdocs.yml +54 -0
  30. {cloudtips-0.3.1 → cloudtips-0.4.1}/pyproject.toml +5 -4
  31. cloudtips-0.3.1/PKG-INFO +0 -151
  32. cloudtips-0.3.1/README.md +0 -130
  33. cloudtips-0.3.1/cloudtips/client.py +0 -335
  34. {cloudtips-0.3.1 → cloudtips-0.4.1}/.github/workflows/publish.yml +0 -0
  35. {cloudtips-0.3.1 → cloudtips-0.4.1}/LICENSE +0 -0
@@ -0,0 +1,31 @@
1
+ name: Deploy Docs
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - "docs/**"
9
+ - "mkdocs.yml"
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: write
14
+
15
+ jobs:
16
+ deploy:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.11"
26
+
27
+ - name: Install dependencies
28
+ run: pip install mkdocs-material
29
+
30
+ - name: Deploy to GitHub Pages
31
+ run: mkdocs gh-deploy --force
@@ -0,0 +1,4 @@
1
+ site/
2
+ __pycache__/
3
+ *.pyc
4
+ .DS_Store
cloudtips-0.4.1/CNAME ADDED
@@ -0,0 +1 @@
1
+ docs.cloudtips.irring.ru
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.4
2
+ Name: cloudtips
3
+ Version: 0.4.1
4
+ Summary: Неофициальная асинхронная Python-библиотека для CloudTips (получение донатов, поллинг, обновление токенов)
5
+ Project-URL: Repository, https://github.com/IRRatium/cloudtips-api
6
+ Project-URL: Issues, https://github.com/IRRatium/cloudtips-api/issues
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Keywords: api,async,asyncio,cloudtips,donations,payments,tips
10
+ Classifier: Framework :: AsyncIO
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Internet :: WWW/HTTP
19
+ Requires-Python: >=3.9
20
+ Requires-Dist: aiohttp>=3.9
21
+ Description-Content-Type: text/markdown
22
+
23
+ # CloudtipsAPI
24
+ <p align="center">
25
+ <img src="https://static.tildacdn.com/tild3431-6231-4938-b464-663831306266/Horiz.svg" alt="CloudTips" height="120">
26
+ </p>
27
+
28
+ <p align="center">
29
+ <a href="https://pypi.org/project/cloudtips/"><img src="https://img.shields.io/pypi/v/cloudtips?color=blue&label=PyPI" alt="PyPI"></a>
30
+ <a href="https://pepy.tech/project/cloudtips"><img src="https://static.pepy.tech/badge/cloudtips" alt="Downloads"></a>
31
+ <a href="https://github.com/IRRatium/cloudtips-api/blob/main/LICENSE"><img src="https://img.shields.io/github/license/IRRatium/cloudtips-api" alt="License"></a>
32
+ <img src="https://img.shields.io/badge/python-3.9+-blue" alt="Python 3.9+">
33
+ <a href="https://docs.cloudtips.irring.ru"><img src="https://img.shields.io/badge/docs-docs.cloudtips.irring.ru-blue" alt="Docs"></a>
34
+ </p>
35
+
36
+ Неофициальная асинхронная Python-библиотека для [CloudTips](https://cloudtips.ru) — получение донатов, поллинг новых поступлений и автоматическое обновление токенов.
37
+
38
+ ## Установка
39
+
40
+ ```bash
41
+ pip install cloudtips
42
+ ```
43
+
44
+ ## Быстрый старт
45
+
46
+ ```python
47
+ import asyncio
48
+ import json
49
+ from cloudtips import CloudTipsAuth, CloudTipsClient, TokenData
50
+
51
+ with open("donate.json") as f:
52
+ config = json.load(f)
53
+
54
+ # Колбэк вызывается каждый раз при обновлении токенов.
55
+ # Поддерживаются как async, так и обычные функции.
56
+ # Refresh-токен одноразовый — обязательно сохраняйте новые!
57
+ async def on_token_refresh(token_data: TokenData):
58
+ config["cloudtips_token"] = token_data.access_token
59
+ config["cloudtips_refresh_token"] = token_data.refresh_token
60
+ config["cloudtips_expires_at"] = token_data.expires_at
61
+ with open("donate.json", "w") as f:
62
+ json.dump(config, f, ensure_ascii=False, indent=2)
63
+
64
+ auth = CloudTipsAuth(
65
+ token=config["cloudtips_token"],
66
+ refresh_token=config["cloudtips_refresh_token"],
67
+ expires_at=config["cloudtips_expires_at"],
68
+ on_token_refresh=on_token_refresh,
69
+ )
70
+
71
+ async def main():
72
+ async with CloudTipsClient(auth) as client:
73
+ donations = await client.get_all_donations()
74
+ for d in donations:
75
+ print(d)
76
+ # [2024-01-15 14:30] Антон → 100₽ — "спасибо за контент"
77
+
78
+ asyncio.run(main())
79
+
80
+ ```
81
+
82
+ ## Получение донатов
83
+
84
+ ```python
85
+ async with CloudTipsClient(auth) as client:
86
+ # Все донаты за последние 24 часа
87
+ donations = await client.get_all_donations()
88
+ for d in donations:
89
+ print(d.name) # Антон
90
+ print(d.amount) # 50.0
91
+ print(d.comment) # спасибо за сервис
92
+ print(d.date) # 2026-04-10 20:44:00+03:00
93
+
94
+ # За конкретный период
95
+ from datetime import datetime, timedelta, timezone
96
+
97
+ week_ago = datetime.now(tz=timezone.utc) - timedelta(days=7)
98
+ weekly = await client.get_all_donations(since=week_ago)
99
+ print(f"Всего за неделю: {len(weekly)} донатов")
100
+ print(f"Сумма: {sum(d.amount for d in weekly)}₽")
101
+
102
+ ```
103
+
104
+ ## Поллинг новых донатов
105
+
106
+ ### Вариант 1 — async-генератор
107
+
108
+ ```python
109
+ async with CloudTipsClient(auth) as client:
110
+ async for donation in client.poll(interval=30):
111
+ print(f"💰 {donation.name} задонатил {donation.amount}₽")
112
+ if donation.comment:
113
+ print(f" Комментарий: {donation.comment}")
114
+
115
+ ```
116
+
117
+ ### Вариант 2 — async-колбэк
118
+
119
+ ```python
120
+ async def handle_donation(donation):
121
+ print(f"Новый донат от {donation.name}: {donation.amount}₽")
122
+ # await bot.send_message(...)
123
+
124
+ async with CloudTipsClient(auth) as client:
125
+ async for _ in client.poll(interval=15, callback=handle_donation):
126
+ pass
127
+
128
+ ```
129
+
130
+ ### Вариант 3 — фоновая задача asyncio
131
+
132
+ ```python
133
+ async def poll_task(client):
134
+ async for donation in client.poll(interval=30):
135
+ print(f"Новый донат: {donation}")
136
+
137
+ async def main():
138
+ async with CloudTipsClient(auth) as client:
139
+ task = asyncio.create_task(poll_task(client))
140
+ await asyncio.sleep(3600) # работаем час
141
+ task.cancel()
142
+
143
+ asyncio.run(main())
144
+
145
+ ```
146
+
147
+ ## Профиль, карты и баланс
148
+
149
+ ```python
150
+ async with CloudTipsClient(auth) as client:
151
+ # Профиль пользователя
152
+ me = await client.get_me()
153
+ print(me.full_name) # Иван Иванов
154
+ print(me.payout_method) # Accumulation
155
+
156
+ # Привязанные карты
157
+ for card in await client.get_cards():
158
+ print(card) # MIR *0000 (T-BANK, до 00/00) [по умолчанию]
159
+
160
+ # Баланс к выводу
161
+ s = await client.get_accumulation_summary()
162
+ print(f"Накоплено: {s.accumulated_amount}₽")
163
+ print(f"Комиссия: {s.commission_percent}%")
164
+ print(f"Следующая выплата: {s.next_payout_date or 'не запланирована'}")
165
+
166
+ # Информация о комиссиях
167
+ fee = await client.get_payout_fee_info()
168
+ print(fee.text)
169
+
170
+ # Смена метода выплат
171
+ await client.set_payout_method("Instant") # мгновенно
172
+ await client.set_payout_method("Accumulation") # накопительно
173
+
174
+ # Удаление карты
175
+ for card in await client.get_cards():
176
+ if not card.is_default:
177
+ await client.delete_card(card.token)
178
+
179
+ ```
180
+
181
+ ## Обработка ошибок
182
+
183
+ ```python
184
+ from cloudtips import CloudTipsAuthError, CloudTipsAPIError
185
+
186
+ try:
187
+ async with CloudTipsClient(auth) as client:
188
+ donations = await client.get_all_donations()
189
+ except CloudTipsAuthError as e:
190
+ print(f"Проблема с аутентификацией: {e}")
191
+ except CloudTipsAPIError as e:
192
+ print(f"Ошибка API (HTTP {e.status_code}): {e.detail}")
193
+
194
+ ```
195
+
196
+ ## Примечания
197
+
198
+ * **Refresh-токен одноразовый.** После каждого обновления старый токен становится недействительным. Всегда передавайте `on_token_refresh` и сохраняйте новые токены.
199
+ * `on_token_refresh` поддерживает как обычные (`def`), так и async-функции (`async def`).
200
+ * Библиотека автоматически обновляет токен за 2 минуты до истечения.
201
+ * Поллинг отслеживает уже виденные `transaction_id`, поэтому дублей не будет.
202
+ * Клиент — контекстный менеджер (`async with`), это рекомендуемый способ использования.
203
+ * Для логирования внутренних процессов и ошибок сети библиотека использует стандартный модуль `logging`. Настройте ваш логгер для отображения сообщений: `logging.basicConfig(level=logging.INFO)`.
204
+
205
+ ## Лицензия
206
+
207
+ MIT
@@ -0,0 +1,185 @@
1
+ # CloudtipsAPI
2
+ <p align="center">
3
+ <img src="https://static.tildacdn.com/tild3431-6231-4938-b464-663831306266/Horiz.svg" alt="CloudTips" height="120">
4
+ </p>
5
+
6
+ <p align="center">
7
+ <a href="https://pypi.org/project/cloudtips/"><img src="https://img.shields.io/pypi/v/cloudtips?color=blue&label=PyPI" alt="PyPI"></a>
8
+ <a href="https://pepy.tech/project/cloudtips"><img src="https://static.pepy.tech/badge/cloudtips" alt="Downloads"></a>
9
+ <a href="https://github.com/IRRatium/cloudtips-api/blob/main/LICENSE"><img src="https://img.shields.io/github/license/IRRatium/cloudtips-api" alt="License"></a>
10
+ <img src="https://img.shields.io/badge/python-3.9+-blue" alt="Python 3.9+">
11
+ <a href="https://docs.cloudtips.irring.ru"><img src="https://img.shields.io/badge/docs-docs.cloudtips.irring.ru-blue" alt="Docs"></a>
12
+ </p>
13
+
14
+ Неофициальная асинхронная Python-библиотека для [CloudTips](https://cloudtips.ru) — получение донатов, поллинг новых поступлений и автоматическое обновление токенов.
15
+
16
+ ## Установка
17
+
18
+ ```bash
19
+ pip install cloudtips
20
+ ```
21
+
22
+ ## Быстрый старт
23
+
24
+ ```python
25
+ import asyncio
26
+ import json
27
+ from cloudtips import CloudTipsAuth, CloudTipsClient, TokenData
28
+
29
+ with open("donate.json") as f:
30
+ config = json.load(f)
31
+
32
+ # Колбэк вызывается каждый раз при обновлении токенов.
33
+ # Поддерживаются как async, так и обычные функции.
34
+ # Refresh-токен одноразовый — обязательно сохраняйте новые!
35
+ async def on_token_refresh(token_data: TokenData):
36
+ config["cloudtips_token"] = token_data.access_token
37
+ config["cloudtips_refresh_token"] = token_data.refresh_token
38
+ config["cloudtips_expires_at"] = token_data.expires_at
39
+ with open("donate.json", "w") as f:
40
+ json.dump(config, f, ensure_ascii=False, indent=2)
41
+
42
+ auth = CloudTipsAuth(
43
+ token=config["cloudtips_token"],
44
+ refresh_token=config["cloudtips_refresh_token"],
45
+ expires_at=config["cloudtips_expires_at"],
46
+ on_token_refresh=on_token_refresh,
47
+ )
48
+
49
+ async def main():
50
+ async with CloudTipsClient(auth) as client:
51
+ donations = await client.get_all_donations()
52
+ for d in donations:
53
+ print(d)
54
+ # [2024-01-15 14:30] Антон → 100₽ — "спасибо за контент"
55
+
56
+ asyncio.run(main())
57
+
58
+ ```
59
+
60
+ ## Получение донатов
61
+
62
+ ```python
63
+ async with CloudTipsClient(auth) as client:
64
+ # Все донаты за последние 24 часа
65
+ donations = await client.get_all_donations()
66
+ for d in donations:
67
+ print(d.name) # Антон
68
+ print(d.amount) # 50.0
69
+ print(d.comment) # спасибо за сервис
70
+ print(d.date) # 2026-04-10 20:44:00+03:00
71
+
72
+ # За конкретный период
73
+ from datetime import datetime, timedelta, timezone
74
+
75
+ week_ago = datetime.now(tz=timezone.utc) - timedelta(days=7)
76
+ weekly = await client.get_all_donations(since=week_ago)
77
+ print(f"Всего за неделю: {len(weekly)} донатов")
78
+ print(f"Сумма: {sum(d.amount for d in weekly)}₽")
79
+
80
+ ```
81
+
82
+ ## Поллинг новых донатов
83
+
84
+ ### Вариант 1 — async-генератор
85
+
86
+ ```python
87
+ async with CloudTipsClient(auth) as client:
88
+ async for donation in client.poll(interval=30):
89
+ print(f"💰 {donation.name} задонатил {donation.amount}₽")
90
+ if donation.comment:
91
+ print(f" Комментарий: {donation.comment}")
92
+
93
+ ```
94
+
95
+ ### Вариант 2 — async-колбэк
96
+
97
+ ```python
98
+ async def handle_donation(donation):
99
+ print(f"Новый донат от {donation.name}: {donation.amount}₽")
100
+ # await bot.send_message(...)
101
+
102
+ async with CloudTipsClient(auth) as client:
103
+ async for _ in client.poll(interval=15, callback=handle_donation):
104
+ pass
105
+
106
+ ```
107
+
108
+ ### Вариант 3 — фоновая задача asyncio
109
+
110
+ ```python
111
+ async def poll_task(client):
112
+ async for donation in client.poll(interval=30):
113
+ print(f"Новый донат: {donation}")
114
+
115
+ async def main():
116
+ async with CloudTipsClient(auth) as client:
117
+ task = asyncio.create_task(poll_task(client))
118
+ await asyncio.sleep(3600) # работаем час
119
+ task.cancel()
120
+
121
+ asyncio.run(main())
122
+
123
+ ```
124
+
125
+ ## Профиль, карты и баланс
126
+
127
+ ```python
128
+ async with CloudTipsClient(auth) as client:
129
+ # Профиль пользователя
130
+ me = await client.get_me()
131
+ print(me.full_name) # Иван Иванов
132
+ print(me.payout_method) # Accumulation
133
+
134
+ # Привязанные карты
135
+ for card in await client.get_cards():
136
+ print(card) # MIR *0000 (T-BANK, до 00/00) [по умолчанию]
137
+
138
+ # Баланс к выводу
139
+ s = await client.get_accumulation_summary()
140
+ print(f"Накоплено: {s.accumulated_amount}₽")
141
+ print(f"Комиссия: {s.commission_percent}%")
142
+ print(f"Следующая выплата: {s.next_payout_date or 'не запланирована'}")
143
+
144
+ # Информация о комиссиях
145
+ fee = await client.get_payout_fee_info()
146
+ print(fee.text)
147
+
148
+ # Смена метода выплат
149
+ await client.set_payout_method("Instant") # мгновенно
150
+ await client.set_payout_method("Accumulation") # накопительно
151
+
152
+ # Удаление карты
153
+ for card in await client.get_cards():
154
+ if not card.is_default:
155
+ await client.delete_card(card.token)
156
+
157
+ ```
158
+
159
+ ## Обработка ошибок
160
+
161
+ ```python
162
+ from cloudtips import CloudTipsAuthError, CloudTipsAPIError
163
+
164
+ try:
165
+ async with CloudTipsClient(auth) as client:
166
+ donations = await client.get_all_donations()
167
+ except CloudTipsAuthError as e:
168
+ print(f"Проблема с аутентификацией: {e}")
169
+ except CloudTipsAPIError as e:
170
+ print(f"Ошибка API (HTTP {e.status_code}): {e.detail}")
171
+
172
+ ```
173
+
174
+ ## Примечания
175
+
176
+ * **Refresh-токен одноразовый.** После каждого обновления старый токен становится недействительным. Всегда передавайте `on_token_refresh` и сохраняйте новые токены.
177
+ * `on_token_refresh` поддерживает как обычные (`def`), так и async-функции (`async def`).
178
+ * Библиотека автоматически обновляет токен за 2 минуты до истечения.
179
+ * Поллинг отслеживает уже виденные `transaction_id`, поэтому дублей не будет.
180
+ * Клиент — контекстный менеджер (`async with`), это рекомендуемый способ использования.
181
+ * Для логирования внутренних процессов и ошибок сети библиотека использует стандартный модуль `logging`. Настройте ваш логгер для отображения сообщений: `logging.basicConfig(level=logging.INFO)`.
182
+
183
+ ## Лицензия
184
+
185
+ MIT
@@ -1,12 +1,12 @@
1
1
  """
2
- cloudtips — неофициальная Python-библиотека для CloudTips.
2
+ cloudtips — неофициальная асинхронная Python-библиотека для CloudTips.
3
3
  """
4
4
 
5
5
  from .auth import CloudTipsAuth, CloudTipsAuthError
6
6
  from .client import CloudTipsAPIError, CloudTipsClient
7
7
  from .models import AccumulationSummary, Card, Donation, PayoutFeeInfo, ReceiverProfile, TokenData
8
8
 
9
- __version__ = "0.3.1"
9
+ __version__ = "0.4.1"
10
10
  __all__ = [
11
11
  "CloudTipsAuth",
12
12
  "CloudTipsAuthError",
@@ -1,7 +1,11 @@
1
+ """
2
+ Аутентификация CloudTips — управление access/refresh токенами.
3
+ """
4
+ import asyncio
1
5
  import time
2
6
  from typing import Callable, Optional
3
7
 
4
- import requests
8
+ import aiohttp
5
9
 
6
10
  from .models import TokenData
7
11
 
@@ -14,21 +18,21 @@ class CloudTipsAuth:
14
18
  """
15
19
  Управляет access/refresh токенами CloudTips.
16
20
 
17
- Параметр `on_token_refresh`это колбэк, который вызывается каждый раз
18
- после успешного обновления токенов. Используйте его, чтобы сохранить
19
- новые токены в файл/БД/переменные окружения — refresh-токен одноразовый!
21
+ Параметр ``on_token_refresh`` — колбэк, вызываемый после каждого
22
+ успешного обновления токенов. Refresh-токен одноразовый, поэтому
23
+ обязательно сохраняйте новые значения.
20
24
 
21
25
  Пример::
22
26
 
23
- def save_tokens(token_data: TokenData):
27
+ async def save_tokens(token_data: TokenData):
24
28
  config["cloudtips_token"] = token_data.access_token
25
29
  config["cloudtips_refresh_token"] = token_data.refresh_token
26
30
  config["cloudtips_expires_at"] = token_data.expires_at
27
- save_config(config)
31
+ await write_config(config)
28
32
 
29
33
  auth = CloudTipsAuth(
30
- token="...",
31
- refresh_token="...",
34
+ token="eyJ...",
35
+ refresh_token="abc123...",
32
36
  expires_at=1776099728.0,
33
37
  on_token_refresh=save_tokens,
34
38
  )
@@ -45,36 +49,40 @@ class CloudTipsAuth:
45
49
  self._refresh_token = refresh_token
46
50
  self._expires_at = expires_at
47
51
  self._on_token_refresh = on_token_refresh
52
+ self._lock = asyncio.Lock() # Защита от Race Condition при параллельных запросах
48
53
 
49
54
  # ------------------------------------------------------------------
50
55
  # Public
51
56
  # ------------------------------------------------------------------
52
57
 
53
- @property
54
- def token(self) -> str:
58
+ async def get_token(self) -> str:
55
59
  """Возвращает актуальный access-токен, автоматически обновляя при необходимости."""
56
60
  if self._is_expired():
57
- self.refresh()
61
+ async with self._lock:
62
+ # Повторная проверка: пока текущий таск ждал лока, другой таск уже мог обновить токен
63
+ if self._is_expired():
64
+ await self.refresh()
58
65
  return self._token
59
66
 
60
67
  @property
61
68
  def expires_at(self) -> float:
62
69
  return self._expires_at
63
70
 
64
- def refresh(self) -> TokenData:
71
+ async def refresh(self) -> TokenData:
65
72
  """Принудительно обновляет токены через CloudTips Identity Server."""
66
- response = requests.post(
67
- _TOKEN_URL,
68
- data={
69
- "grant_type": "refresh_token",
70
- "refresh_token": self._refresh_token,
71
- "client_id": _CLIENT_ID,
72
- },
73
- timeout=10,
74
- )
75
- _raise_for_status(response)
73
+ async with aiohttp.ClientSession() as session:
74
+ async with session.post(
75
+ _TOKEN_URL,
76
+ data={
77
+ "grant_type": "refresh_token",
78
+ "refresh_token": self._refresh_token,
79
+ "client_id": _CLIENT_ID,
80
+ },
81
+ timeout=aiohttp.ClientTimeout(total=10),
82
+ ) as response:
83
+ await _raise_for_status(response)
84
+ data = await response.json()
76
85
 
77
- data = response.json()
78
86
  token_data = TokenData(
79
87
  access_token=data["access_token"],
80
88
  refresh_token=data["refresh_token"],
@@ -86,15 +94,17 @@ class CloudTipsAuth:
86
94
  self._expires_at = token_data.expires_at
87
95
 
88
96
  if self._on_token_refresh:
89
- self._on_token_refresh(token_data)
97
+ result = self._on_token_refresh(token_data)
98
+ if asyncio.iscoroutine(result):
99
+ await result
90
100
 
91
101
  return token_data
92
102
 
93
- def headers(self) -> dict:
103
+ async def headers(self) -> dict:
94
104
  """Готовые Authorization-заголовки для запросов."""
95
105
  return {
96
- "Authorization": f"Bearer {self.token}",
97
- "Content-Type": "application/json",
106
+ "Authorization": f"Bearer {await self.get_token()}",
107
+ "Content-Type": "application/json",
98
108
  }
99
109
 
100
110
  # ------------------------------------------------------------------
@@ -105,18 +115,17 @@ class CloudTipsAuth:
105
115
  return time.time() >= self._expires_at - _EXPIRE_BUFFER
106
116
 
107
117
 
108
- def _raise_for_status(response: requests.Response) -> None:
109
- try:
110
- response.raise_for_status()
111
- except requests.HTTPError as e:
118
+ async def _raise_for_status(response: aiohttp.ClientResponse) -> None:
119
+ if not response.ok:
112
120
  try:
113
- detail = response.json()
121
+ detail = await response.json()
114
122
  except Exception:
115
- detail = response.text
123
+ detail = await response.text()
116
124
  raise CloudTipsAuthError(
117
- f"Ошибка при обновлении токена: {response.status_code} — {detail}"
118
- ) from e
125
+ f"Ошибка при обновлении токена: {response.status} — {detail}"
126
+ )
119
127
 
120
128
 
121
129
  class CloudTipsAuthError(Exception):
122
130
  """Ошибка аутентификации CloudTips."""
131
+ pass