Comienza a usar gRPC-Python

1. Introducción

En este codelab, usarás gRPC-Python para crear un cliente y un servidor que formen la base de una aplicación de asignación de rutas escrita en Python.

Al final del instructivo, tendrás un cliente que se conecta a un servidor remoto con gRPC para obtener el nombre o la dirección postal de lo que se encuentra en coordenadas específicas en un mapa. Una aplicación completa podría usar este diseño cliente-servidor para enumerar o resumir los puntos de interés a lo largo de una ruta.

El servicio se define en un archivo de Protocol Buffers, que se usará para generar código estándar para el cliente y el servidor, de modo que puedan comunicarse entre sí, lo que te ahorrará tiempo y esfuerzo en la implementación de esa funcionalidad.

Este código generado se encarga no solo de las complejidades de la comunicación entre el servidor y el cliente, sino también de la serialización y deserialización de datos.

Qué aprenderás

  • Cómo usar los búferes de protocolo para definir una API de servicio
  • Cómo compilar un cliente y un servidor basados en gRPC a partir de una definición de Protocol Buffers con la generación de código automatizada
  • Conocimiento de la comunicación cliente-servidor con gRPC

Este codelab está dirigido a desarrolladores de Python que no conocen gRPC o que desean repasar sus conceptos básicos, o bien a cualquier persona interesada en crear sistemas distribuidos. No se requiere experiencia previa con gRPC.

2. Antes de comenzar

Requisitos

  • Python 3.9 o una versión posterior Te recomendamos Python 3.13. Para obtener instrucciones de instalación específicas de la plataforma, consulta Configuración y uso de Python. Como alternativa, instala un Python que no sea del sistema con herramientas como uv o pyenv.
  • pip para instalar paquetes de Python
  • venv para crear entornos virtuales de Python

Los paquetes ensurepip y venv forman parte de la biblioteca estándar de Python y, por lo general, están disponibles de forma predeterminada.

Sin embargo, algunas distribuciones basadas en Debian (incluido Ubuntu) optan por excluirlos cuando redistribuyen Python. Para instalar los paquetes, ejecuta el siguiente comando:

sudo apt install python3-pip python3-venv

Obtén el código

Para optimizar tu aprendizaje, este codelab ofrece un código fuente precompilado que te ayudará a comenzar. En los siguientes pasos, se te guiará para completar la aplicación, incluida la generación de código gRPC con el complemento del compilador de Protocol Buffer grpc_tools.protoc.

grpc-codelabs

El código fuente del scaffold para este codelab está disponible en el directorio codelabs/grpc-python-getting-started/start_here. Si prefieres no implementar el código por tu cuenta, el código fuente completo está disponible en el directorio completed.

Primero, crea el directorio de trabajo del codelab y cámbiate a él con el comando cd:

mkdir grpc-python-getting-started && cd grpc-python-getting-started

Descarga y extrae el codelab:

curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
  | tar xvz --strip-components=4 \
  grpc-codelabs-1/codelabs/grpc-python-getting-started/start_here

También puedes descargar el archivo .zip que contiene solo el directorio del codelab y descomprimirlo de forma manual.

3. Define el servicio

El primer paso es definir el servicio de gRPC de la aplicación, su método de RPC y sus tipos de mensajes de solicitud y respuesta con el lenguaje de definición de interfaz de búferes de protocolo. Tu servicio proporcionará lo siguiente:

  • Un método de RPC llamado GetFeature que el servidor implementa y el cliente llama.
  • Los tipos de mensajes Point y Feature son estructuras de datos que se intercambian entre el cliente y el servidor cuando se usa el método GetFeature. El cliente proporciona coordenadas de mapa como un Point en su solicitud GetFeature al servidor, y el servidor responde con un Feature correspondiente que describe lo que se encuentra en esas coordenadas.

Este método de RPC y sus tipos de mensajes se definirán en el archivo protos/route_guide.proto del código fuente proporcionado.

Los búferes de protocolo se conocen comúnmente como protobuf. Para obtener más información sobre la terminología de gRPC, consulta los conceptos básicos, la arquitectura y el ciclo de vida de gRPC.

Tipos de mensajes

En el archivo protos/route_guide.proto del código fuente, primero define el tipo de mensaje Point. Un objeto Point representa un par de coordenadas de latitud y longitud en un mapa. En este codelab, usa números enteros para las coordenadas:

message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

Los números 1 y 2 son números de ID únicos para cada uno de los campos de la estructura message.

A continuación, define el tipo de mensaje Feature. Un Feature usa un campo string para el nombre o la dirección postal de algo en una ubicación especificada por un Point:

message Feature {
  // The name or address of the feature.
  string name = 1;

  // The point where the feature is located.
  Point location = 2;
}

Método de servicio

El archivo route_guide.proto tiene una estructura service llamada RouteGuide que define uno o más métodos proporcionados por el servicio de la aplicación.

Agrega el método rpc GetFeature dentro de la definición de RouteGuide. Como se explicó anteriormente, este método buscará el nombre o la dirección de una ubicación a partir de un conjunto de coordenadas determinado, por lo que GetFeature devolverá un Feature para un Point determinado:

service RouteGuide {
  // Definition of the service goes here

  // Obtains the feature at a given position.
  rpc GetFeature(Point) returns (Feature) {}
}

Este es un método de RPC unario: una RPC simple en la que el cliente envía una solicitud al servidor y espera a que vuelva una respuesta, al igual que una llamada a función local.

4. Genera el código del cliente y del servidor

A continuación, genera el código gRPC estándar para el cliente y el servidor desde el archivo .proto con el compilador de búfer de protocolo.

Para la generación de código de Python de gRPC, creamos grpcio-tools. Incluye lo siguiente:

  1. El compilador protoc normal que genera código de Python a partir de definiciones de message
  2. Es un complemento de protobuf de gRPC que genera código de Python (stubs de cliente y servidor) a partir de las definiciones de service.

Instalaremos el paquete de Python grpcio-tools con pip. Creemos un nuevo entorno virtual de Python (venv) para aislar las dependencias de tu proyecto de los paquetes del sistema:

python3 -m venv --upgrade-deps .venv

Para activar el entorno virtual en el shell de bash/zsh, haz lo siguiente:

source .venv/bin/activate

Para Windows y shells no estándar, consulta la tabla en https://docs.python.org/3/library/venv.html#how-venvs-work.

A continuación, instala grpcio-tools (esto también instala el paquete grpcio):

pip install grpcio-tools

Usa el siguiente comando para generar el código de plantilla de Python:

python -m grpc_tools.protoc --proto_path=./protos  \
 --python_out=. --pyi_out=. --grpc_python_out=. \
 ./protos/route_guide.proto

Esto generará los siguientes archivos para las interfaces que definimos en route_guide.proto:

  1. route_guide_pb2.py contiene el código que crea clases de forma dinámica generadas a partir de las definiciones de message.
  2. route_guide_pb2.pyi es un "archivo de código auxiliar" o "archivo de sugerencias de tipo" que se genera a partir de las definiciones de message. Solo contiene las firmas sin implementación. Los IDE pueden usar archivos de código auxiliar para proporcionar una mejor detección de errores y autocompletado.
  3. route_guide_pb2_grpc.py se genera a partir de las definiciones de service y contiene clases y funciones específicas de gRPC.

El código específico de gRPC contiene lo siguiente:

  1. RouteGuideStub, que puede usar un cliente de gRPC para invocar RPC de RouteGuide.
  2. RouteGuideServicer, que define la interfaz para las implementaciones del servicio RouteGuide.
  3. Función add_RouteGuideServicer_to_server que se usa para registrar un RouteGuideServicer en un servidor de gRPC.

5. Cómo crear el servicio

Primero, veamos cómo crear un servidor RouteGuide. La creación y ejecución de un servidor RouteGuide se divide en dos elementos de trabajo:

  • Implementar la interfaz del servidor generada a partir de la definición del servicio con funciones que realizan el "trabajo" real del servicio
  • Ejecutar un servidor de gRPC en un puerto específico para escuchar las solicitudes de los clientes y transmitir respuestas

Puedes encontrar el servidor RouteGuide inicial en start_here/route_guide_server.py.

Implementa RouteGuide

route_guide_server.py tiene una clase RouteGuideServicer que es una subclase de la clase generada route_guide_pb2_grpc.RouteGuideServicer:

# RouteGuideServicer provides an implementation
# of the methods of the RouteGuide service.
class RouteGuideServicer(route_guide_pb2_grpc.RouteGuideServicer):

RouteGuideServicer implementa todos los métodos de servicio de RouteGuide.

Veamos en detalle una implementación simple de RPC. El método GetFeature obtiene un Point del cliente y devuelve la información del atributo correspondiente de su base de datos en Feature.

def GetFeature(self, request, context):
    feature = get_feature(self.db, request)
    if feature is None:
        return route_guide_pb2.Feature(name="", location=request)
    else:
        return feature

Al método se le pasa una solicitud route_guide_pb2.Point para la RPC y un objeto grpc.ServicerContext que proporciona información específica de la RPC, como los límites de tiempo de espera. Devuelve una respuesta route_guide_pb2.Feature.

Cómo iniciar el servidor

Una vez que implementaste todos los métodos de RouteGuide, el siguiente paso es iniciar un servidor de gRPC para que los clientes puedan usar tu servicio:

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    route_guide_pb2_grpc.add_RouteGuideServicer_to_server(
        RouteGuideServicer(),
        server,
    )
    listen_addr = "localhost:50051"
    server.add_insecure_port(listen_addr)
    print(f"Starting server on {listen_addr}")
    server.start()
    server.wait_for_termination()

El método start() del servidor no genera bloqueos. Se creará una instancia de un nuevo subproceso para controlar las solicitudes. El subproceso que llama a server.start() a menudo no tendrá ningún otro trabajo que hacer mientras tanto. En este caso, puedes llamar a server.wait_for_termination() para bloquear de forma limpia el subproceso de llamada hasta que finalice el servidor.

6. Crea el cliente

En esta sección, veremos cómo crear un cliente para nuestro servicio RouteGuide. Puedes ver el código del cliente inicial en start_here/route_guide_client.py.

Cómo crear un código auxiliar

Para llamar a los métodos del servicio, primero debemos crear un stub.

Creamos una instancia de la clase RouteGuideStub del módulo route_guide_pb2_grpc, que se genera a partir de nuestro .proto dentro del archivo route_guide_client.py.

channel = grpc.insecure_channel("localhost:50051")
stub = route_guide_pb2_grpc.RouteGuideStub(channel)

Cómo llamar a métodos de servicio

En el caso de los métodos de RPC que devuelven una sola respuesta (conocidos como métodos de respuesta unaria), gRPC Python admite la semántica de flujo de control síncrona (bloqueante) y asíncrona (no bloqueante).

RPC simple

Primero, definamos un Point para llamar al servicio. Esto debería ser tan simple como crear una instancia de un objeto del paquete route_guide_pb2 con algunas propiedades:

point = route_guide_pb2.Point(latitude=412346009, longitude=-744026814)

Una llamada síncrona a la RPC simple GetFeature es casi tan sencilla como llamar a un método local. La llamada a RPC espera la respuesta del servidor y mostrará una respuesta o generará una excepción. Podemos llamar al método y ver la respuesta de la siguiente manera:

feature = stub.GetFeature(point)
print(feature)

Puedes inspeccionar los campos del objeto Feature y generar el resultado de la solicitud:

if feature.name:
    print(f"Feature called '{feature.name}' at {format_point(feature.location)}")
else:
    print(f"Found no feature at at {format_point(feature.location)}")

7. Probar

Ejecuta el servidor:

python route_guide_server.py

Desde otra terminal, activa el entorno virtual de nuevo y, luego, ejecuta el cliente:

python route_guide_client.py

Verás un resultado como este, con marcas de tiempo omitidas para mayor claridad:

name: "16 Old Brook Lane, Warwick, NY 10990, USA"
location {
  latitude: 412346009
  longitude: -744026814
}

Feature called '16 Old Brook Lane, Warwick, NY 10990, USA' at latitude: 412346009, longitude: -744026814

8. ¿Qué sigue?