Python gRPC Client Beta

The Python gRPC Client has released its first Beta build!

The “Production/Stable” version will be released in a couple of months, after the beta testing phase.
The client has been developed by John Bywater in collaboration with the EventStoreDB team and has been tested to work with EventStoreDB LTS versions 21.10 and 22.10, without and without SSL/TLS, and with Python versions 3.7 to 3.11.
If you want to try it out, check out the documentation in the README file to get started.

We’re looking for folks to test out the client during the Beta. If you’re interested, comment here or reach out via a direct message!

I’m attempting to use the client using EventStoreDB v24.6.0. First, I created multi-cluster using the following documentation:

Next, I’m trying to connect to the cluster using the following Python code:

event_store = EventStoreDBClient(
  uri=os.getenv('EVENTSTORE_URL'),
  root_certificates=os.getenv('EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH')
)

The .env file contains the following:

EVENTSTORE_URL=esdb+discover://admin:changeit@localhost:2111,localhost:2112,localhost:2113?tls=true&tlsVerifyCert=false
EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/certs/ca

When I attempt to run the Python code, I’m getting the following error:

 Traceback (most recent call last):
   File "/usr/local/lib/python3.9/site-packages/esdbclient/client.py", line 258, in _discover_preferred_node
     cluster_members = connection.gossip.read(
   File "/usr/local/lib/python3.9/site-packages/esdbclient/gossip.py", line 101, in read
     raise handle_rpc_error(e) from None
 esdbclient.exceptions.ServiceUnavailable: <_InactiveRpcError of RPC that terminated with:
 	status = StatusCode.UNAVAILABLE
 	details = "failed to connect to all addresses; last error: UNKNOWN: ipv4:127.0.0.1:2113: Failed to connect to remote host: 
 	debug_error_string = "UNKNOWN:Error received from peer  {created_time:"2024-09-04T05:26:50.463803999+00:00", grpc_status:14
d to connect to remote host: Connection refused"}"
 >
 
 The above exception was the direct cause of the following exception:
 
 Traceback (most recent call last):
   File "/app/main.py", line 17, in <module>
     event_store = EventStoreDBClient(
   File "/usr/local/lib/python3.9/site-packages/esdbclient/client.py", line 190, in __init__
     self._esdb = self._connect_to_preferred_node()
   File "/usr/local/lib/python3.9/site-packages/esdbclient/client.py", line 230, in _connect_to_preferred_node
     raise e
   File "/usr/local/lib/python3.9/site-packages/esdbclient/client.py", line 224, in _connect_to_preferred_node
     preferred, cluster_members, connection = self._discover_preferred_node(
   File "/usr/local/lib/python3.9/site-packages/esdbclient/client.py", line 270, in _discover_preferred_node
     raise DiscoveryFailed(msg) from last_exception
 esdbclient.exceptions.DiscoveryFailed: Failed to read from gossip seed: ['localhost:2111', 'localhost:2112', 'localhost:2113']

Finally, the EventStoreDB appears to be up and operational because the following command generates favorable results:

curl -v --insecure --fail -X GET "https://admin:changeit@[::1]:2113/gossip"

Hi,

There are a few things to note about your setup.

  • Given that all nodes are localhost mapped to a specific port and present in your connection string, you can remove the +discover from esdb+discover://....
  • TlsVerifyCert=false|true has no effect (see GitHub - pyeventsourcing/esdbclient: Python gRPC client for EventStoreDB)
  • Tls=true is the default
  • root_certificates needs to be the content of a PEM file (ca.crt would be the default).

The following worked for me

certificate = open('/certs/ca/ca.crt', 'rt')
client = EventStoreDBClient(
  uri='esdb://admin:changeit@localhost:2111,localhost:2112,localhost:2113?Tls=true',
  root_certificates=certificate.read()
)

Hope this helps.

1 Like

@yves.reynhout Hey, thanks for the information. It looks like the documentation may be out of date because I obtained the TlsVerifyCert from the following page:

Next, I ended up implementing the following function:

def get_ca_certificate() -> str:
  current_dir = os.getcwd()

  ca_cert_path = os.path.join(current_dir, 'certs/ca/ca.crt')

  with open(ca_cert_path, "r") as f:
    return f.read()

It’s working as expected now and thanks again for your assistance.

It looks like the documentation may be out of date because I obtained the TlsVerifyCert from the following page

Not really. Some of our clients (e.g., NET) support this parameter, which is why it is mentioned there. Of course, there’s no harm in using it with the Python client.

It’s good to hear it now works as expected :slight_smile:

@yves.reynhout Running a standalone Python client application works as expected. When attempting to run the client also as a service within the docker-compose.yaml, I’m getting the following error message:

eventstoredb:

esdbclient.exceptions.ServiceUnavailable: failed to connect to all addresses; last error: UNKNOWN: ipv4:127.0.0.1:2112: Failed to connect to remote host: Connection refused
curl "localhost:8080/hello-world?visitor=Ouro"
<!doctype html>
<html lang=en>
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and could not complete your request. Either the server is overloaded or there is an error in the application.</p>

My esdb connection string looks like the following:

EVENTSTORE_URL=esdb://admin:[email protected]:2111,node2.eventstore:2112,node3.eventstore:2113?tls=true&tlsVerifyCert=false

However, all the EventStoredb services (i.e. node1.eventstore, node2.eventstore, and node2.eventstore) are mapped to the container port 2113. Thus, I was thinking that the connection string should be something like the following:

EVENTSTORE_URL=esdb://admin:[email protected]:2113,node2.eventstore:2113,node3.eventstore:2113?tls=true&tlsVerifyCert=false

However, this also generates the same issue.

Finally, I’m setting up my client service (i.e. app) as follows:

  app:
    image: esdb-sample-python
    build:
      context: .
      dockerfile: Dockerfile
    env_file:
      - development.env
    ports:
      - '8080:8080'
    depends_on:
      node1.eventstore:
        condition: service_healthy
      node2.eventstore:
        condition: service_healthy
      node3.eventstore:
        condition: service_healthy
    networks:
      clusternetwork:
        ipv4_address: 172.30.240.14
    volumes:
      - .:/app
      - certs:/certs

The complete docker-compose.yaml can be found here:

Because your app runs inside the docker-composed network, it can resolve IP addresses (172.30.240.11|2|3) and hostnames (node1|2|3.eventstore) as they are known in the docker network. The gRPC (and thus HTTP) port is 2113 on each node inside the network, so you correctly identified the change to your connection string. However, you’ve made the nodes advertise themselves as 127.0.0.1 (in your vars.env it says EVENTSTORE_ADVERTISE_HOST_TO_CLIENT_AS=127.0.0.1) which is why you are getting that error. Remove this setting and you should be good to go (as you can see below)

1 Like