chore: stylize sse code; fix: add sseclient-py to requirements

This commit is contained in:
firedotguy
2026-02-10 17:53:48 +03:00
parent 51518ce0d7
commit 8e8b0b3bb9
5 changed files with 30 additions and 45 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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
""" """

View File

@@ -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"

View File

@@ -5,7 +5,7 @@ setup(
version='1.1.0', version='1.1.0',
packages=find_packages(), packages=find_packages(),
install_requires=[ install_requires=[
'requests', 'pydantic' 'requests', 'pydantic', 'sseclient-py'
], ],
python_requires=">=3.9" python_requires=">=3.9"
) )