Перейти к содержанию

gRPC-клиент на Python#

Установка#

Готовый пакет для Python не предоставляется. Клиентский код необходимо сгенерировать локально с помощью grpc_tools.protoc, как описано в разделе Генерация gRPC-клиента.

python3 -m venv .venv
.venv/bin/pip install grpcio-tools
.venv/bin/python3 -m grpc_tools.protoc -I proto \
  --python_out=gen/python --grpc_python_out=gen/python \
  $(find proto -name "*.proto")

Соединение и авторизация#

Для подключения необходимы три параметра:

  • GREENAPI_GRPC_HOST — адрес gRPC-сервера GREEN-API: grpc.green-api.com:443. Адрес указывается в формате host:port, без схемы https://;
  • GREENAPI_ID_INSTANCE — значение idInstance из личного кабинета;
  • GREENAPI_API_TOKEN_INSTANCE — значение apiTokenInstance из личного кабинета.

Соединение с gRPC-сервером устанавливается с использованием TLS. Данные авторизации передаются в каждом RPC-вызове отдельно. Подробнее см. раздел Авторизация в gRPC API.

Проверка подключения#

Для проверки подключения можно вызвать метод GetStateInstance, который возвращает текущее состояние инстанса. Если метод успешно выполнился и вернул состояние INSTANCE_STATE_AUTHORIZED, соединение с gRPC-сервером и авторизация настроены корректно, а инстанс готов к работе. Описание других состояний инстанса и возможных ошибок приведено в разделе Проверка подключения обзора gRPC API.

import os

import grpc
from greenapi.instance.v1 import instance_pb2, instance_pb2_grpc
from greenapi.type.v1 import enums_pb2

channel = grpc.secure_channel(
    os.environ["GREENAPI_GRPC_HOST"],  # "grpc.green-api.com:443"
    grpc.ssl_channel_credentials(),
)
client = instance_pb2_grpc.InstanceServiceStub(channel)

metadata = (
    ("x-instance-id", os.environ["GREENAPI_ID_INSTANCE"]),
    ("authorization", "Bearer " + os.environ["GREENAPI_API_TOKEN_INSTANCE"]),
)

try:
    response = client.GetStateInstance(instance_pb2.GetStateInstanceRequest(), metadata=metadata)
except grpc.RpcError as err:
    raise SystemExit(f"{err.code().name}: {err.details()}")

print(enums_pb2.InstanceState.Name(response.state_instance))  # INSTANCE_STATE_AUTHORIZED

Первый вызов#

import os

import grpc
from greenapi.message.v1 import message_pb2, message_pb2_grpc

channel = grpc.secure_channel(
    os.environ["GREENAPI_GRPC_HOST"],  # "grpc.green-api.com:443"
    grpc.ssl_channel_credentials(),
)
client = message_pb2_grpc.MessageServiceStub(channel)

metadata = (
    ("x-instance-id", os.environ["GREENAPI_ID_INSTANCE"]),
    ("authorization", "Bearer " + os.environ["GREENAPI_API_TOKEN_INSTANCE"]),
)

response = client.SendMessage(
    message_pb2.SendMessageRequest(
        chat_id="11001234567@c.us",
        message="привет из gRPC",
    ),
    metadata=metadata,
)
print(response.id_message)

Одно gRPC-соединение можно использовать для всех сервисов. Клиентские Stub остальных сервисов создаются на основе того же channel.

Как загрузить файл#

Файл передаётся с помощью клиентского потока. Первое сообщение UploadFileRequest содержит UploadFileMetadata с именем файла и типом содержимого. Затем клиент последовательно передаёт сообщения с chunk, содержащими части файла.

После завершения итератора запросов gRPC завершает отправку клиентского потока (half-close) и ожидает единственный ответ UploadFileResponse от сервера.

Общий контракт потоковой загрузки и требования к размеру чанков описаны в разделе Потоковые вызовы gRPC API.

В Python клиентский поток передаётся в виде итератора. Когда итератор завершается, gRPC прекращает отправку сообщений серверу и ожидает единственный ответ.

def upload_file(client, metadata, path, content_type, chunk_size=64 * 1024):
    def requests():
        yield message_pb2.UploadFileRequest(
            metadata=message_pb2.UploadFileMetadata(
                file_name=os.path.basename(path),
                content_type=content_type,
            )
        )
        with open(path, "rb") as f:
            while chunk := f.read(chunk_size):
                yield message_pb2.UploadFileRequest(chunk=chunk)

    response = client.UploadFile(requests(), metadata=metadata)
    return response.url_file
url = upload_file(client, metadata, "/path/to/photo.jpg", "image/jpeg")

Как получить QR-код#

Метод ScanQrCode использует серверный поток: сервер последовательно отправляет ответы с QR-кодами до завершения попытки авторизации. Статусы и поведение метода описаны в разделе Получение QR-кода по gRPC.

try:
    for resp in client.ScanQrCode(instance_pb2.ScanQrCodeRequest(), metadata=metadata):
        if resp.status == instance_pb2.QR_CODE_STATUS_QR_CODE:
            show_qr("data:image/png;base64," + base64.b64encode(resp.qr_png).decode())
        elif resp.status == instance_pb2.QR_CODE_STATUS_ALREADY_LOGGED:
            raise RuntimeError("инстанс уже авторизован, нужен Logout")
except grpc.RpcError as err:
    # DEADLINE_EXCEEDED — никто не отсканировал; FAILED_PRECONDITION — инстанс не готов
    print(err.code(), err.details())

Когда сервер завершает поток, итератор исчерпывается и цикл for завершается автоматически.

Обработка ошибок#

Статус gRPC можно получить с помощью err.code(), а описание ошибки — с помощью err.details():

try:
    response = client.SendMessage(request, metadata=metadata)
except grpc.RpcError as err:
    print(err.code(), err.details())

Описание возможных статусов gRPC и рекомендации по повторным вызовам приведены в разделе Коды ошибок gRPC API.

Перед автоматическим повтором методов отправки ознакомьтесь с разделом Повторная отправка сообщений: повторный вызов может привести к повторной отправке одного и того же сообщения.