chore: stylize sse code; fix: add sseclient-py to requirements
This commit is contained in:
@@ -2,8 +2,8 @@ from uuid import UUID
|
|||||||
from _io import BufferedReader
|
from _io import BufferedReader
|
||||||
from typing import cast, Iterator
|
from typing import cast, Iterator
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import json
|
from json import JSONDecodeError, loads
|
||||||
import time
|
from time import sleep
|
||||||
|
|
||||||
from requests.exceptions import ConnectionError, HTTPError
|
from requests.exceptions import ConnectionError, HTTPError
|
||||||
from sseclient import SSEClient
|
from sseclient import SSEClient
|
||||||
@@ -1105,59 +1105,59 @@ class Client:
|
|||||||
@refresh_on_error
|
@refresh_on_error
|
||||||
def stream_notifications(self) -> Iterator[StreamConnect | StreamNotification]:
|
def stream_notifications(self) -> Iterator[StreamConnect | StreamNotification]:
|
||||||
"""Слушать SSE поток уведомлений
|
"""Слушать SSE поток уведомлений
|
||||||
|
|
||||||
Yields:
|
Yields:
|
||||||
StreamConnect | StreamNotification: События подключения или уведомления
|
StreamConnect | StreamNotification: События подключения или уведомления
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```python
|
```python
|
||||||
from itd import ITDClient
|
from itd import ITDClient
|
||||||
|
|
||||||
client = ITDClient(cookies='refresh_token=...')
|
client = ITDClient(cookies='refresh_token=...')
|
||||||
|
|
||||||
# Запуск прослушивания
|
# Запуск прослушивания
|
||||||
for event in client.stream_notifications():
|
for event in client.stream_notifications():
|
||||||
if isinstance(event, StreamConnect):
|
if isinstance(event, StreamConnect):
|
||||||
print(f'Подключено: {event.user_id}')
|
print(f'Подключено: {event.user_id}')
|
||||||
else:
|
else:
|
||||||
print(f'Уведомление: {event.type} от {event.actor.username}')
|
print(f'Уведомление: {event.type} от {event.actor.username}')
|
||||||
|
|
||||||
# Остановка из другого потока или обработчика
|
# Остановка из другого потока или обработчика
|
||||||
# client.stop_stream()
|
# client.stop_stream()
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
self._stream_active = True
|
self._stream_active = True
|
||||||
|
|
||||||
while self._stream_active:
|
while self._stream_active:
|
||||||
try:
|
try:
|
||||||
response = stream_notifications(self.token)
|
response = stream_notifications(self.token)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
client = SSEClient(response)
|
client = SSEClient(response)
|
||||||
|
|
||||||
for event in client.events():
|
for event in client.events():
|
||||||
if not self._stream_active:
|
if not self._stream_active:
|
||||||
response.close()
|
response.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not event.data or event.data.strip() == '':
|
if not event.data or event.data.strip() == '':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
data = json.loads(event.data)
|
data = loads(event.data)
|
||||||
|
|
||||||
if 'userId' in data and 'timestamp' in data and 'type' not in data:
|
if 'userId' in data and 'timestamp' in data and 'type' not in data:
|
||||||
yield StreamConnect.model_validate(data)
|
yield StreamConnect.model_validate(data)
|
||||||
else:
|
else:
|
||||||
yield StreamNotification.model_validate(data)
|
yield StreamNotification.model_validate(data)
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
except JSONDecodeError:
|
||||||
print(f'Не удалось распарсить сообщение: {event.data}')
|
print(f'Не удалось распарсить сообщение: {event.data}')
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'Ошибка обработки события: {e}')
|
print(f'Ошибка обработки события: {e}')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
except Unauthorized:
|
except Unauthorized:
|
||||||
if self.cookies and self._stream_active:
|
if self.cookies and self._stream_active:
|
||||||
print('Токен истек, обновляем...')
|
print('Токен истек, обновляем...')
|
||||||
@@ -1169,27 +1169,27 @@ class Client:
|
|||||||
if not self._stream_active:
|
if not self._stream_active:
|
||||||
return
|
return
|
||||||
print(f'Ошибка соединения: {e}, переподключение через 5 секунд...')
|
print(f'Ошибка соединения: {e}, переподключение через 5 секунд...')
|
||||||
time.sleep(5)
|
sleep(5)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def stop_stream(self):
|
def stop_stream(self):
|
||||||
"""Остановить прослушивание SSE потока
|
"""Остановить прослушивание SSE потока
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```python
|
```python
|
||||||
import threading
|
import threading
|
||||||
from itd import ITDClient
|
from itd import ITDClient
|
||||||
|
|
||||||
client = ITDClient(cookies='refresh_token=...')
|
client = ITDClient(cookies='refresh_token=...')
|
||||||
|
|
||||||
# Запуск в отдельном потоке
|
# Запуск в отдельном потоке
|
||||||
def listen():
|
def listen():
|
||||||
for event in client.stream_notifications():
|
for event in client.stream_notifications():
|
||||||
print(event)
|
print(event)
|
||||||
|
|
||||||
thread = threading.Thread(target=listen)
|
thread = threading.Thread(target=listen)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
# Остановка через 10 секунд
|
# Остановка через 10 секунд
|
||||||
import time
|
import time
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
@@ -1197,4 +1197,5 @@ class Client:
|
|||||||
thread.join()
|
thread.join()
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
print('stop event')
|
||||||
self._stream_active = False
|
self._stream_active = False
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from datetime import datetime
|
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from itd.enums import NotificationType, NotificationTargetType
|
from itd.models.notification import Notification
|
||||||
from itd.models.user import UserNotification
|
|
||||||
|
|
||||||
|
|
||||||
class StreamConnect(BaseModel):
|
class StreamConnect(BaseModel):
|
||||||
@@ -14,20 +11,7 @@ class StreamConnect(BaseModel):
|
|||||||
timestamp: int
|
timestamp: int
|
||||||
|
|
||||||
|
|
||||||
class StreamNotification(BaseModel):
|
class StreamNotification(Notification):
|
||||||
"""Уведомление из SSE потока"""
|
"""Уведомление из SSE потока"""
|
||||||
id: UUID
|
|
||||||
type: NotificationType
|
|
||||||
|
|
||||||
target_type: NotificationTargetType | None = Field(None, alias='targetType')
|
|
||||||
target_id: UUID | None = Field(None, alias='targetId')
|
|
||||||
|
|
||||||
preview: str | None = None
|
|
||||||
read_at: datetime | None = Field(None, alias='readAt')
|
|
||||||
created_at: datetime = Field(alias='createdAt')
|
|
||||||
|
|
||||||
user_id: UUID = Field(alias='userId')
|
user_id: UUID = Field(alias='userId')
|
||||||
actor: UserNotification
|
|
||||||
|
|
||||||
read: bool = False
|
|
||||||
sound: bool = True
|
sound: bool = True
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ def get_unread_notifications_count(token: str):
|
|||||||
|
|
||||||
def stream_notifications(token: str):
|
def stream_notifications(token: str):
|
||||||
"""Получить SSE поток уведомлений
|
"""Получить SSE поток уведомлений
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response: Streaming response для SSE
|
Response: Streaming response для SSE
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ authors = [
|
|||||||
]
|
]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"requests", "pydantic"
|
"requests", "pydantic", "sseclient-py"
|
||||||
]
|
]
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
|
|||||||
Reference in New Issue
Block a user