Package: esdb
esdb-pyEventStoreDB Python gRPC clientNOTE: This project is still work in progressCompleted featuresInstallationDevelopmentUsageConnection stringDiscovery and node preferencesConnection configurationAppend, Read, Catch-up subscriptionsBatch appendCatch-up subscription to all events with filteringPersistent subscriptionsCompleted featuressecure connectionbasic authconnection string parsingstreamsappendbatch append (v21.10+)deleteread streamread all with stream/event type filters (v21.10+)catch-up subscriptionstombstonefilteringpersistent subscriptionscreateread streamread all with filter (v21.10+)updatedeletelistinforeply parked eventsCRUD for projectionsusersInstallationUsing pip:pipinstallesdbUsing poetry:poetryaddesdbDevelopmentInstallpoetryCreate virtualenv (i.e. using pyenv):pyenvinstall3.12.0
pyenvvirtualenv3.12.0esdb-py
pyenvlocalesdb-pyInstall deps withpoetry installStart eventstore in docker:make run-esdbRun the tests:pytest testsUsageHave a look attestsfor more examples.Connection string examplesDNS discovery with credentials, discovery configuration, node preference and ca file pathesdb+discover://admin:changeit@localhost:2111?discoveryInterval=0&maxDiscoverAttempts=3&tlsCafile=certs/ca/ca.crt&nodePreference=followerSingle-node insecure connectionesdb://localhost:2111?tls=falseSupported parameters:discoveryIntervalgossipTimeoutmaxDiscoverAttemptsnodePreferencekeepAliveIntervalkeepAliveTimeouttlstlsCafiletlsVerifyCertdefaultDeadlineConnection string can be generatedhere.Discovery and node preferencesfromesdbimportESClientclient=ESClient("esdb+discover://admin:changeit@localhost:2111?nodePreference=follower")Connection configurationfromesdbimportESClient# Connect without TLSclient=ESClient("esdb://localhost:2111?tls=false")# Secure connection with basic auth and keepaliveclient=ESClient("esdb://admin:changeit@localhost:2111?tlsCafile=certs/ca/ca.crt&keepAliveInterval=5&keepAliveTimeout=5")Append, Read, Catch-up subscriptionsimportasyncioimportdatetimeimportuuidfromesdbimportESClientclient=ESClient("esdb+discover://admin:changeit@localhost:2111")stream=f"test-{str(uuid.uuid4())}"asyncdefstreams():asyncwithclient.connect()asconn:# Appending to streamforiinrange(10):append_result=awaitconn.streams.append(stream=stream,event_type="test_event",data={"i":i,"ts":datetime.datetime.utcnow().isoformat()},)# Read up to 10 eventsasyncforresultinconn.streams.read(stream=stream,count=10):print(result.data)# Read up to 10 events, backwardsasyncforresultinconn.streams.read(stream=stream,count=10,backwards=True):print(result.data)# Read up to 10 events, starting from 5th eventasyncforresultinconn.streams.read(stream=stream,count=10,revision=5):print(result.data)# Read up to 10 events backwards, starting from 5th eventasyncforresultinconn.streams.read(stream=stream,count=10,backwards=True,revision=5):print(result.data)# Create a catch-up subscription to a streamasyncforresultinconn.streams.read(stream=stream,subscribe=True):print(result.data)asyncio.run(streams())Batch appendimportasyncioimportuuidfromesdbimportESClientfromesdb.streamsimportMessageasyncdefbatch_append():# Append multiple events in as a single batch# Batch append is not supported on EventStore < v21.10stream=str(uuid.uuid4())messages:list[Message]=[Message(event_type="one",data={"item":1}),Message(event_type="one",data={"item":2}),Message(event_type="one",data={"item":3}),Message(event_type="two",data={"item":1}),Message(event_type="two",data={"item":2}),Message(event_type="two",data={"item":3}),]asyncwithESClient("esdb+discover://admin:changeit@localhost:2111").connect()asconn:response=awaitconn.streams.batch_append(stream=stream,messages=messages)assertresponse.current_revision==5events=[easyncforeinconn.streams.read(stream=stream,count=50)]assertlen(events)==6asyncio.run(batch_append())Catch-up subscription to all events with filteringimportuuidimportasynciofromesdbimportESClientfromesdb.sharedimportFilterasyncdeffilters():asyncwithESClient("esdb+discover://admin:changeit@localhost:2111").connect()asconn:# Append 10 events with the same prefix to random streamsforiinrange(10):awaitconn.streams.append(stream=str(uuid.uuid4()),event_type=f"prefix-{i}",data=b"")# subscribe to events from all streams, filtering by event typeasyncforeventinconn.streams.read_all(subscribe=True,# subscribe will wait for events, use count=<n> to read <n> events and stopfilter_by=Filter(kind=Filter.Kind.EVENT_TYPE,regex="^prefix-",# Checkpoint only required when subscribe=True, it's not needed when using count=<int>checkpoint_interval_multiplier=1000,),):print(event)asyncio.run(filters())Persistent subscriptionsimportasynciofromesdbimportESClientfromesdb.sharedimportFilterfromesdb.subscriptionsimportSubscriptionSettings,NackActionclient=ESClient("esdb+discover://admin:changeit@localhost:2111")stream="stream-foo"group="group-bar"asyncdefpersistent():asyncwithclient.connect()asconn:# emit some events to the same streamforiinrange(50):awaitconn.streams.append(stream,"foobar",{"i":i})# create a stream subscriptionawaitconn.subscriptions.create_stream_subscription(stream=stream,group_name=group,settings=SubscriptionSettings(max_subscriber_count=50,read_batch_size=5,live_buffer_size=10,history_buffer_size=10,consumer_strategy=SubscriptionSettings.ConsumerStrategy.ROUND_ROBIN,checkpoint_ms=10000,),)# create subscription to all events with filtering# Only supported on EventStore v21.10+awaitconn.subscriptions.create_all_subscription(group_name="subscription_group",filter_by=Filter(kind=Filter.Kind.EVENT_TYPE,regex="^some_type$",checkpoint_interval_multiplier=200),settings=SubscriptionSettings(read_batch_size=50,live_buffer_size=100,history_buffer_size=100,max_retry_count=2,checkpoint_ms=20000,),)# read from a subscriptionasyncwithclient.connect()asconn:sub=conn.subscriptions.subscribe(stream=stream,group_name=group,buffer_size=5)asyncforeventinsub:try:# do work with eventprint(event)awaitsub.ack([event])exceptExceptionaserr:awaitsub.nack([event],NackAction.RETRY,reason=str(err))# get subscription infoinfo=awaitconn.subscriptions.get_info(group,stream)assertinfo.group_name==group# delete subscriptionawaitconn.subscriptions.delete(group,stream)# list subscriptionssubs=awaitconn.subscriptions.list()forsubinsubs:print(sub.total_items)asyncio.run(persistent())