{"id":30766931,"url":"https://github.com/antonglyzin/mail_pigeon","last_synced_at":"2026-01-20T17:36:07.348Z","repository":{"id":312819711,"uuid":"1048796235","full_name":"AntonGlyzin/mail_pigeon","owner":"AntonGlyzin","description":"Асинхронная клиент-серверная библиотека с файловой очередью на стороне клиента.","archived":false,"fork":false,"pushed_at":"2025-09-02T07:39:49.000Z","size":37,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-02T08:19:59.436Z","etag":null,"topics":["client","files","python","queue","server","zmq"],"latest_commit_sha":null,"homepage":"https://mail-pigeon.readthedocs.io/ru/stable/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AntonGlyzin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-02T03:29:00.000Z","updated_at":"2025-09-02T07:39:26.000Z","dependencies_parsed_at":"2025-09-02T08:20:27.989Z","dependency_job_id":"83dd65a5-61ff-4315-9699-3e5f4e6f88de","html_url":"https://github.com/AntonGlyzin/mail_pigeon","commit_stats":null,"previous_names":["antonglyzin/mail_pigeon"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/AntonGlyzin/mail_pigeon","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AntonGlyzin%2Fmail_pigeon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AntonGlyzin%2Fmail_pigeon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AntonGlyzin%2Fmail_pigeon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AntonGlyzin%2Fmail_pigeon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AntonGlyzin","download_url":"https://codeload.github.com/AntonGlyzin/mail_pigeon/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AntonGlyzin%2Fmail_pigeon/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273666005,"owners_count":25146276,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-04T02:00:08.968Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","files","python","queue","server","zmq"],"created_at":"2025-09-04T19:59:26.550Z","updated_at":"2026-01-20T17:36:07.341Z","avatar_url":"https://github.com/AntonGlyzin.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# Асинхронная клиент-серверная библиотека с файловой очередью на стороне клиента\n\nПочтовый голубь - библиотека для взаимодействия с приложениями на уровне протоколов ZMQ. Библиотека обеспечивает стабильное отправления сообщений и их получения. При потери связи с сервером клиент накапливает файловую очередь, а после восстановления связи отправляет все сообщения получателю через сервер переадресаций. В каждом клиенте находится свой сервер переадресаций, который способен запустиься для поддержания рассылки сообщений. Для безопасного общения по сети можно настроить сертификаты с двумя ключами. Также поддерживается шифрование сообщений на уровне клиента, что делает не доступным сообщения со стороны сервера. Данная библиотека является асинхронной, клиент не ждет мгновенного подтверждения. Когда сообщение посылается получателю, отправитель еще имеет копию сообщения, которое при случае может снова отправиться.\n\n1. [Установка](#установка)\n2. [Создание клиента с файловой очередью](#создание-клиента-с-файловой-очередью)\n3. [Ожидаем получения сообщений](#ожидаем-получения-сообщений)\n4. [Отправить и забыть](#отправить-и-забыть)\n5. [Отправить и ждать ответа](#отправить-и-ждать-ответа)\n6. [Запасной сервер переадресаций](#запасной-сервер-переадресаций)\n7. [Клиент без файловой очереди](#клиент-без-файловой-очереди)\n8. [Клиент-серверная синхронизация](#клиент-серверная-синхронизация)\n9. [Пользовательская файловая очередь](#пользовательская-файловая-очередь)\n10. [Безопасность и изолированность](#безопасность-и-изолированность)\n11. [CURVE аутентификация](#curve-аутентификация)\n12. [Аутентификация и изолированность](#аутентификация-и-изолированность)\n\n---\n## Установка\n\n```\npip install mail-pigeon\n```\n\n---\n## Создание клиента с файловой очередью\n\nИспользование синхронного клиента.\n\n```python\nfrom pathlib import Path\nfrom mail_pigeon import MailClient\nfrom mail_pigeon.queue import FilesBox\n\nname = 'app1'\n\npath = Path(__file__).parent / name # где будут скапливаться файлы на отправку\nfb = FilesBox(str(path)) # очередь писем на отправку\nclient = MailClient(\n        name_client=name, \n        is_master=True, \n        out_queue=fb\n    )\nclient.wait_server() # ожидает запуска сервера\n```\n\nПараметры `MailClient`:\n- `name_client` : Название клиента латиницей без пробелов. Оно пригодится для отправки сообщений другим участникам.\n- `host_server` : Адрес клиента мастера. По умолчанию - `27.0.0.1`.\n- `port_server` : Порт подключения. По умолчанию - `5555`.\n- `is_master` : Будет ли этот клиент сервером. Если у вас несколько приложений, то только один может быть сервером переадресаций. Либо можно указать значение `None`, что скажет клиенту, запустить свой сервер, если нет другого. Если установить `True`, но сервер уже запущен в другом приложение, то будет ошибка. В этом случе в других приложениях нужно указать `False`.\n- `out_queue` : Очередь писем на отправку. По умолчанию используется очередь внути памяти процесса. Также можно определить свой класс очереди, к примеру очеред через редис.\n- `cert_dir`: Путь до сертификата или  до пустой директории для генерации ключа.\n\n\nПараметры `FilesBox`:\n\n- `folder` : Путь до директории с очерелью сообщений.\n\n\nИспользование асинхронного клиента.\n\n```python\nfrom anyio import Path as AsyncPath\nfrom mail_pigeon import AsyncMailClient\nfrom mail_pigeon.queue import AsyncFilesBox\n\nname = 'app1'\n\npath = AsyncPath(__file__).parent / name # где будут скапливаться файлы на отправку\nfb = AsyncFilesBox(str(path)) # очередь писем на отправку\nclient = AsyncMailClient(\n        name_client=name, \n        is_master=True,\n        out_queue=fb\n    )\nawait client.wait_server() # ожидает запуска сервера\n```\n\nПараметры `AsyncMailClient`:\n- `name_client` : Название клиента латиницей без пробелов. Оно пригодится для отправки сообщений другим участникам.\n- `host_server` : Адрес клиента мастера. По умолчанию - `27.0.0.1`.\n- `port_server` : Порт подключения. По умолчанию - `5555`.\n- `is_master` : Будет ли этот клиент сервером. Если у вас несколько приложений, то только один может быть сервером переадресаций на хосте. Либо можно указать значение `None`, что скажет клиенту, запустить свой сервер, если нет другого. Если установить `True`, но сервер уже запущен в другом приложение, то будет ошибка. В этом случе в других приложениях нужно указать `False`.\n- `out_queue` : Очередь писем на отправку. По умолчанию используется очередь внути памяти процесса. Также можно определить свой класс очереди, к примеру очеред через редис.\n- `cert_dir`: Путь до сертификата или  до пустой директории для генерации ключа.\n\nПараметры `AsyncFilesBox`:\n\n- `folder` : Путь до директории с очередью сообщений.\n\nВне зависимости какой клиент будет использоваться, письма отправлются асинхронно в любом клиенте. Почти все методы, что есть в синхронном варианте клиента, присутствуют и в асинхронном клиенте.\n\n---\n## Ожидаем получения сообщений\n\n```python\n...\n\nwhile True:\n    msg = client.get()\n    print('')\n    print(f'key: {msg.key}') # ИД сообщения в очереди отправителя\n    print(f'sender: {msg.sender}') # отправитель сообщения\n    print(f'recipient: {msg.recipient}') # получатель сообщения\n    print(f'content: {msg.content}') # контент\n    print('===========')\n```\n\nМетод `.get()` можно использовать как с блокировкой, так и с временной блокировкой. Когда метод используется с временной блокировкой `timeout = 3` в секундах, то если результата не будет, метод вернет пустоту `None`. При успешном получение ответа, данные запроса будут находится в `msg.content`.\n\n---\n## Отправить и забыть\n\nДанный метод отправляет текст в другое приложение с названием клиента `app2`. Метод `.send()` способен отправить сообщение и про него забыть. Другая сторона должна ожидать сообщение через метод `.get()`.\n\n```python\nclient.send(recipient='app2', content='hello world')\n```\n\nПараметры метода `.send()`:\n- `recipient` : Получатель.\n- `content` : Содержимое.\n- `wait` : Ожидать ли получения ответа от запроса.\n\n---\n## Отправить и ждать ответа\n\nЕсли есть потребность в ожидания ответа от другого приложения, то последовательность действий будет такая:\n\n- Приложение `app1` посылает запрос и ждет ответа.\n\n```python\n# app1\ncontent = 'hello'\nmsg = client.send(recipient='app2', content=content, wait=True) \nif msg is None:\n    return\ncontent = f'{content} {msg.content}'\nprint(f'key: {msg.key}') # ИД сообщения в очереди у отправителя\nprint(f'sender: {msg.sender}') # отправитель сообщения\nprint(f'recipient: {msg.recipient}') # получатель сообщения\nprint(f'content: {content}') # контент 'hello world'\nprint('===========')\n```\nМожет быть случай, когда от нас ушло сообщение, но на той стороне клиент выпал с ошибкой, и чтобы не ждать вечно потерянного сообщения, можно выставить на случай потере `timeout`. Если не использовать `timeout`, то как только на текущий клиент придет уведомление, что другой клиент отключился, то ожидание само прекратиться и `client.send` вернет пустоту. Также ожидание прекращается, если отключается сервер. Это тоже вернет пустоту. Такое ожидание может быть полезным, если нужно определить состояние связи с удаленным клиентом.\n\n- Приложение `app2` получает запрос, обрабатывает его и посылает ответ с таким же ключом как в запросе. Вот как это выглядит:\n\n```python\n# app2\nwhile True:\n    msg = client.get()\n    print('')\n    print(f'key: {msg.key}') # ИД сообщения в очереди у отправителя\n    print(f'sender: {msg.sender}') # отправитель сообщения\n    print(f'recipient: {msg.recipient}') # получатель сообщения\n    print(f'content: {msg.content}') # контент 'hello'\n    print('===========')\n    # обработка запроса \n    # ...\n    # и отправка\n    client.send(recipient=msg.sender, content='world') # под капотом используется ключ msg.key\n```\n\n- Или так... Приложение `app2` получает запрос, обрабатывает его и посылает ответ с таким же ключом, но при помощи другого потока.\n\n```python\n# app2\n\nfrom queue import Queue\nq = Queue()\n# thread 1\nwhile True:\n    msg = client.get()\n    print('')\n    print(f'key: {msg.key}') # ИД сообщения в очереди у отправителя\n    print(f'sender: {msg.sender}') # из другого приложения\n    print(f'recipient: {msg.recipient}') # здесь название нашего приложения\n    print(f'content: {msg.content}') # контент 'hello'\n    print('===========')\n    q.put(msg)\n    # обработка запроса и посылаем на отправку в другой поток через очередь\n    \n\n# thread 2\nwhile True:\n    # Получаем из очереди и отправляем\n    msg = q.get()\n    client.send(recipient=msg.sender, content='world') # здесь тоже будет использован ключ из thread 1\n```\n\n---\n## Запасной сервер переадресаций\n\nПредположим у вас есть клиент-сервер, который занимается переадресацией. Но если это приложение упадет или остановиться, то связь между клиентами будет нарушена. В этом случае можно сделать так, чтобы было возможно запустить дополнительный сервер в клиенте.\n\nПриложение 1.\n```python\nfrom pathlib import Path\nfrom mail_pigeon import MailClient\nfrom mail_pigeon.queue import FilesBox\n\nname = 'app1'\n\npath = Path(__file__).parent / name # очередь писем на отправку\nclient = MailClient(\n        name_client=name, \n        is_master=None, \n        out_queue=FilesBox(str(path))\n    )\nclient.wait_server() # ожидает запуска сервера\n```\n\nПриложение 2.\n```python\nfrom pathlib import Path\nfrom mail_pigeon import MailClient\nfrom mail_pigeon.queue import FilesBox\n\nname = 'app2'\n\npath = Path(__file__).parent / name # очередь писем на отправку\nclient = MailClient(\n        name_client=name, \n        is_master=None, \n        out_queue=FilesBox(str(path))\n    )\nclient.wait_server() # ожидает запуска сервера\n```\n\nКогда установлен атрибут `is_master=None`, то это говорит клиенту, что запустить сервер, если нет другого. Если приложение `app2` упадет, то будет запушен сервер внутри `app1`. Эти сервера должны находится на одном хосте.\n\n---\n## Клиент без файловой очереди\n\nЕсли нет необходимости в сохранение сообщений, когда приложение падает или выключается, то можно упустить создание очереди.\n\n```python\nfrom mail_pigeon import MailClient\nfrom mail_pigeon.queue import FilesBox\n\nname = 'app1'\n\nclient = MailClient(\n        name_client=name, \n        is_master=None, \n    )\nclient.wait_server() # ожидает запуска сервера\n```\nВ этом случае будет применена очеред внутри самого клиента. Она находится в памяти процесса.\n\n---\n## Клиент-серверная синхронизация\n\nУ каждого клиента имеется список имен других клиентов. Только если клиент знает об участнике, он может отправлять ему сообщения. Об этом заботиться сервер, который обовещает всех клиентов, кто присоединяется, а кто уходит. Сервер в свою очеред поддерживает связь со всеми клиентами, и если один клиент перестает отвечать, то сервер его отключает и всех уведомляет об его уходе.\n\n---\n## Пользовательская файловая очередь\n\nДля создания своей очереди, к примеру через редис, есть специальный базовый класс `BaseQueue` или `BaseAsyncQueue`. Понадобиться определить следующие методы.\n\n```python\nfrom typing import List\nfrom mail_pigeon.queue import BaseQueue\n\n\nclass SimpleBox(BaseQueue):\n    \n    def __init__(self, timeout_processing: int = None):\n        super().__init__(timeout_processing)\n        self._simple_box = {}\n\n    def _init_live_queue(self) -\u003e List[str]:\n        \"\"\"Инициализация очереди при создание экземпляра.\n\n        Returns:\n            List[str]: Список.\n        \"\"\"\n        return []\n            \n    def _remove_data(self, key: str):\n        \"\"\"Удаляет данные одного элемента.\n\n        Args:\n            key (str): Ключ.\n        \"\"\"\n        if key in self._simple_box:\n            del self._simple_box[key]\n\n    def _read_data(self, key: str) -\u003e str:\n        \"\"\"Чтение данных по ключу.\n\n        Args:\n            key (str): Название.\n\n        Returns:\n            str: Прочитанные данные.\n        \"\"\"\n        return self._simple_box[key]\n\n    def _save_data(self, key: str, value: str):\n        \"\"\"Сохраняет данные.\n\n        Args:\n            value (str): Ключ.\n            value (str): Значение.\n        \"\"\"\n        self._simple_box[key] = value\n```\n\nВ этих методах вам не нужно заботиться о конкурентности данных. Просто опишите откуда читать и куда сохранять.\n\n---\n## Безопасность и изолированность\n\nУ библиотеки есть возможность отправлять сообщения в зашифрованном виде. В этом случае с таким клиентом можно будет общаться только при наличие общего пароля. Такие данные отправлются на сервер в зашифрованном виде. Единственное, что не шифруется это системные команды синхронизации между клиентом и сервером. Данный тип шифрование можно использовать, если мы не хотим, чтобы на сервере могли прочитать сообщение предназначенное другому клиенту.\n\n```python\nfrom mail_pigeon import MailClient\nfrom mail_pigeon.security import TypesEncryptors\n\n# app1\nencript1 = TypesEncryptors.HMAC('admin1')\n# этот клиент выступает как сервер переадресаций\nclient1 = MailClient('app1', is_master=True, encryptor=encryptor1)\nclient1.wait_server() # ожидает запуска сервера\n\n# app2\nencript2 = TypesEncryptors.HMAC('admin2')\nclient2 = MailClient('app2', is_master=False, encryptor=encryptor2)\nclient2.wait_server() # ожидает запуска сервера\n# app3\nclient3 = MailClient('app3', is_master=False, encryptor=encryptor2)\nclient3.wait_server() # ожидает запуска сервера\n```\n\nНесмотря на то, что сервер переадресаций находится в `app1`, но `app2` и `app3` могут общаться между собой, а `app1` не может с ними общаться. Даже если `app1` получить их сообщения из своего сервера в ручную, он не сможет их расшифровать. Это дает изолированность между отдельными группами и безопасное отправление сообщений. \n\n\n---\n## CURVE аутентификация\n\nЭто сертификат с двумя ключами. Все сообщения между клиентами и сервером будут в защифрованном виде. Любой клиент который имеет публичный ключ сервера сможет соединиться с ним. В данном случае даже системные сообщения синхронизации будут в зашифрованном виде. При использование только этого метода защиты сообщения могут быть прочитаны только на сервере.\n\n```python\nfrom mail_pigeon import MailClient\n# app1\nclient1 = MailClient('app1', is_master=True, cert_dir='/certificate')\nclient1.wait_server() # ожидает запуска сервера\n```\nДля `app1` будет создана директория по пути `cert_dir`. Для `client1` там будет храниться приватный и публичный ключ сервера, а также приватный и публичный ключ текущего клиента.\n\nЧтобы запустить второй клиент, нам потребуется публичный ключ сервера `app1`. Его нужно будет скопировать самостоятельно и положить в директорию для второго клиента.\n```python\nfrom mail_pigeon import MailClient\n# app2\nclient2 = MailClient('app2', is_master=False, cert_dir='/certificate2')\nclient2.wait_server() # ожидает запуска сервера\n```\n\n---\n## Аутентификация и изолированность\n\nЭто комбинированный подход к защите информации. В этом случае сообщения смогут прочитать только клиенты, которые имееют и публичный ключ сервера и пароль от группы. \n\n```python\nfrom mail_pigeon import MailClient\nfrom mail_pigeon.security import TypesEncryptors\n\n# app1\nencript1 = TypesEncryptors.HMAC('admin')\nclient1 = MailClient('app1', is_master=True, cert_dir='/certificate', encryptor=encryptor1)\nclient1.wait_server() # ожидает запуска сервера\n```\n\n---\n## Внешние ссылки\n\n- [Журнал изменений](https://github.com/AntonGlyzin/mail_pigeon/releases)\n\n- [На проект в Github](https://github.com/AntonGlyzin/mail_pigeon)\n\n- [PYPI](https://pypi.org/project/mail_pigeon/)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantonglyzin%2Fmail_pigeon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fantonglyzin%2Fmail_pigeon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantonglyzin%2Fmail_pigeon/lists"}