Assistant: 
User: User: Привет. Я тут делаю связь микросервисов через вебсокет. Вот написал класс для главного сервиса. Он получает все микросервисы из базы данных и должен каждый мониторить. Он должен выставлять статусы для микросервисов, работают они в данный момент или нет. Если нет то переподключаться.

Посмотри на мой код, и сделай его более красивым и качественным. Комментарии в функциях кстати можешь тоже как то красиво написать

```
import asyncio
import traceback
import websockets
from asyncio import Task
from websockets.client import WebSocketClientProtocol
from dataclasses import dataclass, field
from typing import Dict, List, Optional

from loguru import logger
from pydantic import BaseModel

from ..database.crud import CommonCRUD
from ..database.models import Microservice


@dataclass
class MicroserviceInfo:
    id: int
    name: str
    description: str
    ws: WebSocketClientProtocol
    manage_task: Task
    status: bool = field(default=False)


class BotResponse(BaseModel):
    name: str
    description: str


class BotsManager:
    def __init__(self, db_crud: CommonCRUD, recheck_sec: int = 10) -> None:
        self.db_crud: CommonCRUD = db_crud
        self.recheck_sec: int = recheck_sec
        self.microservices: Dict[int, MicroserviceInfo] = {}
        self.control_task: Optional[Task] = None

    async def _microservice_ws(self, microservice_id: int, ws: WebSocketClientProtocol) -> None:
        """
        Функция обработки сообщений от микросервиса
        """
        try:
            async for msg in ws:
                try:
                    if microservice_id in self.microservices:
                        self.microservices[microservice_id].status = True
                except websockets.ConnectionClosed:
                    if microservice_id in self.microservices:
                        self.microservices[microservice_id].status = False
                    break
        except Exception:
            logger.error(f"An error occurred while managing microservice {microservice_id}: {traceback.format_exc()}")
        finally:
            await ws.close()

    async def _bots_control_task(self) -> None:
        """
        Основная задача по добавлению отслеживаний микросервисов.
        Здесь мы подтягиваем все микросервисы из базы данных и открываем сессию общения с каждым.
        """
        while True:
            try:
                microservices: List[Microservice] = await self.db_crud.get_microservices()

                for microservice in microservices:
                    if microservice.id in self.microservices and self.microservices[microservice.id].status:
                        continue
                    
                    if microservice.id in self.microservices:
                        if not self.microservices[microservice.id].manage_task.cancelled():
                            self.microservices[microservice.id].manage_task.cancel()

                        if not self.microservices[microservice.id].ws.closed:
                            await self.microservices[microservice.id].ws.close()

                    try:
                        ws: WebSocketClientProtocol = await websockets.connect(microservice.url)
                        task: Task = asyncio.create_task(self._microservice_ws(microservice.id, ws))
                    except Exception as e:
                        logger.error(f"Failed to connect to \"{microservice.name}\" ({microservice.url}): {e}")
                        continue

                    if microservice.id in self.microservices:
                        self.microservices[microservice.id].ws = ws
                        self.microservices[microservice.id].manage_task = task
                    else:
                        self.microservices[microservice.id] = MicroserviceInfo(
                            id=microservice.id,
                            name=microservice.name,
                            ws=ws,
                            manage_task=task,
                            description=microservice.description
                        )  

                await asyncio.sleep(self.recheck_sec)
            except asyncio.CancelledError:
                logger.info("Bots control task was cancelled.")
                break
            except Exception:
                logger.error(f"An error occurred in bots control task: {traceback.format_exc()}")

    async def manage_microservices(self) -> None:
        """
        Точка входа. 
        Запуск задач для отслеживания и общения с микросервисами
        """
        if self.control_task and not self.control_task.cancelled():
            self.control_task.cancel()
        
        self.control_task = asyncio.create_task(self._bots_control_task())
        logger.debug("Началось отслеживание микросервисов.")

    async def close_connections(self) -> None:
        """
        Завершение задач. Полная остановка.
        """
        logger.debug("Остановка отслеживания микросервисов.")
        if self.control_task and not self.control_task.cancelled():
            self.control_task.cancel()

        for m in self.microservices.values():
            if m.manage_task and not m.manage_task.cancelled():
                m.manage_task.cancel()
                await m.ws.close()
```
Assistant: В вашем коде уже проделана хорошая работа, но есть несколько способов сделать его более читабельным и надежным. Вот некоторые предложения по улучшению:

1. **Логирование**: Используйте более подробные сообщения в логах, если это возможно.
2. **Отдельный метод для переподключения**: Вынос логики переподключения в отдельный метод.
3. **Обработка ошибок**: Используйте более конкретные исключения.
4. **Улучшение структуры**: Немного упрощены логические ветвления.

Вот переписанный код с учётом этих пунктов:

```python
import asyncio
import traceback
import websockets
from asyncio import Task
from websockets.exceptions import ConnectionClosed, WebSocketException
from websockets.client import WebSocketClientProtocol
from dataclasses import dataclass, field
from typing import Dict, Optional, List

from loguru import logger
from pydantic import BaseModel

from ..database.crud import CommonCRUD
from ..database.models import Microservice


@dataclass
class MicroserviceInfo:
    id: int
    name: str
    description: str
    ws: Optional[WebSocketClientProtocol] = None
    manage_task: Optional[Task] = None
    status: bool = field(default=False)


class BotResponse(BaseModel):
    name: str
    description: str


class BotsManager:
    def __init__(self, db_crud: CommonCRUD, recheck_sec: int = 10) -> None:
        self.db_crud: CommonCRUD = db_crud
        self.recheck_sec: int = recheck_sec
        self.microservices: Dict[int, MicroserviceInfo] = {}
        self.control_task: Optional[Task] = None

    async def _microservice_ws(self, microservice_id: int, ws: WebSocketClientProtocol) -> None:
        """Receive and handle messages from a microservice."""
        try:
            async for _ in ws:
                if microservice_id in self.microservices:
                    self.microservices[microservice_id].status = True
        except ConnectionClosed:
            self._set_microservice_status(microservice_id, False)
        except Exception as e:
            logger.error(f"Error in microservice {microservice_id}: {str(e)} | {traceback.format_exc()}")
        finally:
            await ws.close()

    def _set_microservice_status(self, microservice_id: int, status: bool) -> None:
        """Set the status of a given microservice."""
        if microservice_id in self.microservices:
            self.microservices[microservice_id].status = status

    async def _reconnect_microservice(self, microservice: Microservice) -> bool:
        """Reconnect to a microservice by URL."""
        try:
            ws = await websockets.connect(microservice.url)
            task = asyncio.create_task(self._microservice_ws(microservice.id, ws))
            self.microservices[microservice.id] = MicroserviceInfo(
                id=microservice.id,
                name=microservice.name,
                description=microservice.description,
                ws=ws,
                manage_task=task
            )
            return True
        except WebSocketException as e:
            logger.error(f"Failed to connect to \"{microservice.name}\" ({microservice.url}): {str(e)}")
            return False

    async def _bots_control_task(self) -> None:
        """Manage connections and reconnections to all microservices."""
        while True:
            try:
                current_microservices = await self.db_crud.get_microservices()
                for microservice in current_microservices:
                    info = self.microservices.get(microservice.id)

                    if info and info.status:
                        continue
                    
                    if info and info.ws:
                        await info.ws.close()
                    
                    await self._reconnect_microservice(microservice)

                await asyncio.sleep(self.recheck_sec)
            except asyncio.CancelledError:
                logger.info("Bots control task was cancelled.")
                break
            except Exception as e:
                logger.error(f"Error in bots control task: {str(e)} | {traceback.format_exc()}")

    async def manage_microservices(self) -> None:
        """Start managing microservice connections."""
        if self.control_task and not self.control_task.cancelled():
            self.control_task.cancel()

        self.control_task = asyncio.create_task(self._bots_control_task())
        logger.debug("Microservices monitoring started.")

    async def close_connections(self) -> None:
        """Shutdown all connections and tasks."""
        logger.debug("Stopping microservice monitoring.")
        if self.control_task and not self.control_task.cancelled():
            self.control_task.cancel()

        for m in self.microservices.values():
            if m.manage_task and not m.manage_task.cancelled():
                m.manage_task.cancel()
            if m.ws:
                await m.ws.close()
```

**Объяснение изменений:**

- **Именование методов**: Методы с `manage_`, `close_` и т.д. дают понять, что именно планируется выполнить.
- **Отдельный метод _reconnect_microservice**: Выделение логики переподключения в отдельный метод упрощает понимание кода.
- **Улучшение логирования**: Прямое получение строки исключения для простоты восприятия.
- **Обработка ошибок**: В логировании ошибок и исключений улучшена читаемость за счет использования `str(e)`.
User: Отлично! Ты довольно хорошо всё оформил. Логику упростил. 
А можешь мне еще вынести общение по вебсокету с микросервисом в отдельный класс. Чтобы я мог там уже отдельно от этого класса прописывать логику что мне там приходит в сообщениях от микросервиса и обрабатывать.
Assistant: Конечно! Вынести общение по вебсокету в отдельный класс — отличная идея. Это поможет сделать код более организованным и удобным для дальнейшей работы. 

Вот пример, как можно это реализовать:

```python
import websocket

class WebSocketClient:
    def __init__(self, url):
        self.url = url
        self.ws = None

    def on_message(self, ws, message):
        # Здесь вы можете обрабатывать сообщения от микросервиса
        print(f"Получено сообщение: {message}")
        self.handle_message(message)

    def on_error(self, ws, error):
        print(f"Ошибка: {error}")

    def on_close(self, ws):
        print("Соединение закрыто")

    def on_open(self, ws):
        print("Соединение открыто")

    def handle_message(self, message):
        # Ваша логика обработки сообщений
        pass

    def run(self):
        self.ws = websocket.WebSocketApp(self.url,
                                          on_message=self.on_message,
                                          on_error=self.on_error,
                                          on_close=self.on_close)
        self.ws.on_open = self.on_open
        self.ws.run_forever()

# Пример использования
if __name__ == "__main__":
    client = WebSocketClient("ws://example.com/socket")
    client.run()
```

В этом примере класс `WebSocketClient` отвечает за управление соединением и обработку сообщений. Вы можете добавить свою логику в метод `handle_message`, чтобы обрабатывать входящие сообщения так, как вам нужно. Если у вас есть дополнительные вопросы или нужна помощь с реализацией, не стесняйтесь спрашивать!
User: а как его интегрировать с тем классом
Assistant: Извините за недоразумение с предыдущим примером, где я использовал библиотеку WebSocket. Давайте разберем, как интегрировать новый класс для работы с вебсокетами с вашим существующим кодом, используя библиотеку `websockets`, которую вы уже используете.

Чтобы интегрировать логику вебсокетов, мы создадим класс `WebSocketHandler`. Этот класс будет отвечать за установление соединения с вебсокетом и обработку сообщений.

Вот как можно реализовать и интегрировать этот класс:

```python
import asyncio
import traceback
from websockets.client import WebSocketClientProtocol
from websockets.exceptions import ConnectionClosed, WebSocketException

from loguru import logger


class WebSocketHandler:
    """Class to handle websocket communication with a microservice."""
    
    def __init__(self, url: str, microservice_id: int):
        self.url = url
        self.microservice_id = microservice_id
        self.ws: Optional[WebSocketClientProtocol] = None
    
    async def connect(self) -> WebSocketClientProtocol:
        """Establish a websocket connection."""
        try:
            self.ws = await websockets.connect(self.url)
            return self.ws
        except WebSocketException as e:
            logger.error(f"Connection error for microservice {self.microservice_id}: {str(e)}")
            raise
    
    async def receive_messages(self) -> None:
        """Receive and process messages."""
        if not self.ws:
            logger.error("Websocket is not connected.")
            return

        try:
            async for msg in self.ws:
                self.handle_message(msg)
        except ConnectionClosed:
            logger.info(f"Connection closed for microservice {self.microservice_id}.")
        except Exception as e:
            logger.error(f"Error receiving message from microservice {self.microservice_id}: {str(e)}")
        finally:
            await self.ws.close()

    def handle_message(self, message: str) -> None:
        """Handle incoming messages from the websocket."""
        logger.debug(f"Received message from microservice {self.microservice_id}: {message}")
        # Place your message handling logic here.

    async def close(self) -> None:
        """Close the websocket connection."""
        if self.ws:
            await self.ws.close()


# Modifying the BotsManager to use WebSocketHandler
class BotsManager:
    def __init__(self, db_crud: CommonCRUD, recheck_sec: int = 10) -> None:
        self.db_crud: CommonCRUD = db_crud
        self.recheck_sec: int = recheck_sec
        self.microservices: Dict[int, MicroserviceInfo] = {}
        self.control_task: Optional[Task] = None

    async def _microservice_ws(self, microservice_id: int, handler: WebSocketHandler) -> None:
        """Create a websocket connection and handle messages."""
        try:
            await handler.connect()
            self._set_microservice_status(microservice_id, True)
            await handler.receive_messages()
        except Exception:
            logger.error(f"An error occurred while managing microservice {microservice_id}: {traceback.format_exc()}")
        finally:
            await handler.close()
            self._set_microservice_status(microservice_id, False)

    def _set_microservice_status(self, microservice_id: int, status: bool) -> None:
        """Set the status of a given microservice."""
        if microservice_id in self.microservices:
            self.microservices[microservice_id].status = status

    async def _reconnect_microservice(self, microservice: Microservice) -> bool:
        """Reconnect to a microservice by URL."""
        try:
            handler = WebSocketHandler(microservice.url, microservice.id)
            task = asyncio.create_task(self._microservice_ws(microservice.id, handler))
            self.microservices[microservice.id] = MicroserviceInfo(
                id=microservice.id,
                name=microservice.name,
                description=microservice.description,
                manage_task=task
            )
            return True
        except WebSocketException as e:
            logger.error(f"Failed to connect to \"{microservice.name}\" ({microservice.url}): {str(e)}")
            return False

    async def _bots_control_task(self) -> None:
        """Manage connections and reconnections to all microservices."""
        while True:
            try:
                current_microservices = await self.db_crud.get_microservices()
                for microservice in current_microservices:
                    info = self.microservices.get(microservice.id)

                    if info and info.status:
                        continue
                    
                    if info and info.ws:
                        await info.ws.close()
                    
                    await self._reconnect_microservice(microservice)

                await asyncio.sleep(self.recheck_sec)
            except asyncio.CancelledError:
                logger.info("Bots control task was cancelled.")
                break
            except Exception as e:
                logger.error(f"Error in bots control task: {str(e)} | {traceback.format_exc()}")

    async def manage_microservices(self) -> None:
        """Start managing microservice connections."""
        if self.control_task and not self.control_task.cancelled():
            self.control_task.cancel()

        self.control_task = asyncio.create_task(self._bots_control_task())
        logger.debug("Microservices monitoring started.")

    async def close_connections(self) -> None:
        """Shutdown all connections and tasks."""
        logger.debug("Stopping microservice monitoring.")
        if self.control_task and not self.control_task.cancelled():
            self.control_task.cancel()

        for m in self.microservices.values():
            if m.manage_task and not m.manage_task.cancelled():
                m.manage_task.cancel()
            if m.ws:
                await m.ws.close()
```

**Объяснение изменений:**

1. **WebSocketHandler**: Это новый класс, который инкапсулирует логику управления вебсокет-соединением и обработкой сообщений. Вы можете расширять метод `handle_message`, чтобы добавить вашу конкретную логику обработки сообщений.

2. **Интеграция**: В `BotsManager` метод `_microservice_ws` теперь использует `WebSocketHandler` для подключения и получения сообщений. Это изолирует детали работы с вебсокетами, делая код `BotsManager` более чистым и его легче поддерживать.

Теперь вы можете модифицировать или дополнять обработку сообщений непосредственно в классе `WebSocketHandler`, не меняя логику работы с микросервисами в `BotsManager`.
User: Скинь весь код. Куда датаклассы пропали. И мне в датаклассах в self.microservices нужно поле websocket.
Мне потому что нужно будет иногда вне этого класса отправлять сообщения
Assistant:
Assistant: