Package: powerful-agents
VeryPowerfulAgentsAgents are lightweight microservices with built-in interprocess communications infrastructure using ZeroMQDocumentationGithubreadthedocsFeaturesAgentsGraceful boot & shutdown with resource cleanup done correctlyUser setup/shutdown override methods for graceful boot & shutdownZeroMQ communications is done in a thread safe manner using queues (ZeroMQ is not threadsafe)Socket data is received through Observables using RxPyNicely formatted logs using self.logPowerful AgentsPub/sub notification facilitiesRouter/client facilitiesSimple Messaging protocol for standard facilities (notifications, client, etc ..)Elliptical curve encryption and authenticationProduction ready communication architecturesMesh networks (TODO)... (TODO)Very Powerful AgentsREST server routes (TODO)RPC endpoints (TODO)File sharing (TODO)... (TODO)# install from gitgitclonehttps://github.com/shirecoding/VeryPowerfulAgents.gitcdVeryPowerfulAgents
pip3install./# install from pypipipinstallpowerful-agentsExamplesSimple Echo Server & ClientimportzmqimporttimeimportthreadingfromagentsimportAgentclassEchoServer(Agent):defsetup(self,name=None,address=None):self.connection=self.bind_socket(zmq.REP,{},address)self.connection.observable.subscribe(self.echo)defecho(self,xs):self.connection.send(xs)classClient(Agent):defsetup(self,name=None,address=None):self.counter=0# receiveself.connection=self.connect_socket(zmq.REQ,{},address)self.connection.observable.subscribe(lambdax:self.log.info(f"received:{x}"))# begin sending forever, add to managed threads for graceful cleanupt=threading.Thread(target=self.send_forever)self.threads.append(t)t.start()defsend_forever(self):# use exit event to gracefully exit loop and graceful cleanupwhilenotself.exit_event.is_set():time.sleep(1)self.counter+=1multipart_message=[str(self.counter).encode()]self.log.info(f"sending:{multipart_message}")self.connection.send(multipart_message)if__name__=='__main__':echo_server=EchoServer(name='server',address='tcp://0.0.0.0:5000')client=Client(name='client',address='tcp://0.0.0.0:5000')INFO[agent=server]bootingup...
INFO[agent=server]runningusersetup...
INFO[agent=server]binding4socketontcp://0.0.0.0:5000...
INFO[agent=server]bootedin0.00168609619140625seconds...
INFO[agent=server]startprocessingsockets...
INFO[agent=client]bootingup...
INFO[agent=client]runningusersetup...
INFO[agent=client]connecting3sockettotcp://0.0.0.0:5000...
INFO[agent=client]bootedin0.0009851455688476562seconds...
INFO[agent=client]startprocessingsockets...
INFO[agent=client]sending:[b'1']INFO[agent=client]received:[b'1']INFO[agent=client]sending:[b'2']INFO[agent=client]received:[b'2']INFO[agent=client]sending:[b'3']INFO[agent=client]received:[b'3']INFO[agent=client]sending:[b'4']INFO[agent=client]received:[b'4']INFO[agent=client]sending:[b'5']INFO[agent=client]received:[b'5']Pub/sub notificationsimportzmqimporttimeimportthreadingfromagentsimportPowerfulAgent,MessageclassNotificationBroker(PowerfulAgent):defsetup(self,name=None,pub_address=None,sub_address=None):self.create_notification_broker(pub_address,sub_address)classSender(PowerfulAgent):defsetup(self,name=None,pub_address=None,sub_address=None):self.counter=0self.pub,self.sub=self.create_notification_client(pub_address,sub_address)# begin sending forever, add to managed threads for graceful cleanupt=threading.Thread(target=self.send_forever)self.threads.append(t)t.start()defsend_forever(self):# use exit event to gracefully exit loop and graceful cleanupwhilenotself.exit_event.is_set():time.sleep(1)self.counter+=1self.log.info(f"publishing:{self.counter}")self.pub.send(Message.notification(payload=self.counter))classListener(PowerfulAgent):defsetup(self,name=None,pub_address=None,sub_address=None):self.pub,self.sub=self.create_notification_client(pub_address,sub_address)self.sub.observable.subscribe(lambdax:self.log.info(f"received:{x['payload']}"))if__name__=='__main__':broker=NotificationBroker(name='broker',pub_address='tcp://0.0.0.0:5000',sub_address='tcp://0.0.0.0:5001')sender=Sender(name='sender',pub_address='tcp://0.0.0.0:5000',sub_address='tcp://0.0.0.0:5001')listener=Listener(name='listener',pub_address='tcp://0.0.0.0:5000',sub_address='tcp://0.0.0.0:5001')INFO[agent=broker]bootingup...
INFO[agent=broker]runningusersetup...
INFO[agent=broker]binding9socketontcp://0.0.0.0:5001...
INFO[agent=broker]binding10socketontcp://0.0.0.0:5000...
INFO[agent=broker]bootedin0.0032880306243896484seconds...
INFO[agent=broker]startprocessingsockets...
INFO[agent=sender]bootingup...
INFO[agent=sender]runningusersetup...
INFO[agent=sender]connecting1sockettotcp://0.0.0.0:5000...
INFO[agent=sender]connecting2sockettotcp://0.0.0.0:5001...
INFO[agent=sender]bootedin0.0016701221466064453seconds...
INFO[agent=sender]startprocessingsockets...
INFO[agent=listener]bootingup...
INFO[agent=listener]runningusersetup...
INFO[agent=listener]connecting1sockettotcp://0.0.0.0:5000...
INFO[agent=listener]connecting2sockettotcp://0.0.0.0:5001...
INFO[agent=listener]bootedin0.0013346672058105469seconds...
INFO[agent=listener]startprocessingsockets...
INFO[agent=sender]publishing:1INFO[agent=listener]received:1INFO[agent=sender]publishing:2INFO[agent=listener]received:2INFO[agent=sender]publishing:3INFO[agent=listener]received:3INFO[agent=sender]publishing:4INFO[agent=listener]received:4INFO[agent=sender]publishing:5INFO[agent=listener]received:5Router ClientimportzmqimporttimeimportthreadingfromagentsimportPowerfulAgent,MessageclassRouter(PowerfulAgent):defsetup(self,name=None,address=None):self.create_router(address)classClient1(PowerfulAgent):defsetup(self,name=None,address=None):self.counter=0self.client=self.create_client(address)# begin sending forever, add to managed threads for graceful cleanupt=threading.Thread(target=self.send_forever)self.threads.append(t)t.start()defsend_forever(self):# use exit event to gracefully exit loop and graceful cleanupwhilenotself.exit_event.is_set():time.sleep(1)self.counter+=1target='client2'self.log.info(f"send to{target}:{self.counter}")self.client.send(Message.client(name=target,payload=self.counter))classClient2(PowerfulAgent):defsetup(self,name=None,address=None):self.client=self.create_client(address)self.client.observable.subscribe(lambdax:self.log.info(f"received:{x['payload']}"))if__name__=='__main__':router=Router(name='router',address='tcp://0.0.0.0:5000')client1=Client1(name='client1',address='tcp://0.0.0.0:5000')client2=Client2(name='client2',address='tcp://0.0.0.0:5000')INFO[agent=router]bootingup...
INFO[agent=router]runningusersetup...
INFO[agent=router]binding6socketontcp://0.0.0.0:5000...
INFO[agent=router]bootedin0.0019252300262451172seconds...
INFO[agent=router]startprocessingsockets...
INFO[agent=client1]bootingup...
INFO[agent=client1]runningusersetup...
INFO[agent=client1]connecting5sockettotcp://0.0.0.0:5000...
INFO[agent=client1]bootedin0.0019159317016601562seconds...
INFO[agent=client1]startprocessingsockets...
INFO[agent=client2]bootingup...
INFO[agent=client2]runningusersetup...
INFO[agent=client2]connecting5sockettotcp://0.0.0.0:5000...
INFO[agent=client2]bootedin0.0008869171142578125seconds...
INFO[agent=client2]startprocessingsockets...
INFO[agent=client1]sendtoclient2:1INFO[agent=client2]received:1INFO[agent=client1]sendtoclient2:2INFO[agent=client2]received:2INFO[agent=client1]sendtoclient2:3INFO[agent=client2]received:3INFO[agent=client1]sendtoclient2:4INFO[agent=client2]received:4INFO[agent=client1]sendtoclient2:5INFO[agent=client2]received:5Elliptical Curve EncryptionThis example allows any client with the server's public key to connect and communicate over a secure channelimportzmqimporttimeimportthreadingfromagentsimportAgent,PowerfulAgent,Message# generate public and private keys for server and clientserver_public_key,server_private_key=Agent.curve_keypair()wrong_server_public_key,wrong_server_private_key=Agent.curve_keypair()client_public_key,client_private_key=Agent.curve_keypair()client2_public_key,client2_private_key=Agent.curve_keypair()classNotificationBroker(PowerfulAgent):defsetup(self,name=None,pub_address=None,sub_address=None):self.create_notification_broker(pub_address,sub_address,options=self.curve_server_config(server_private_key))classSender(PowerfulAgent):defsetup(self,name=None,pub_address=None,sub_address=None):self.counter=0self.pub,self.sub=self.create_notification_client(pub_address,sub_address,options=self.curve_client_config(server_public_key,client_public_key,client_private_key))# begin sending forever, add to managed threads for graceful cleanupt=threading.Thread(target=self.send_forever)self.threads.append(t)t.start()defsend_forever(self):# use exit event to gracefully exit loop and graceful cleanupwhilenotself.exit_event.is_set():time.sleep(1)self.counter+=1self.log.info(f"publishing:{self.counter}")self.pub.send(Message.notification(payload=self.counter))classListener(PowerfulAgent):defsetup(self,name=None,pub_address=None,sub_address=None):self.pub,self.sub=self.create_notification_client(pub_address,sub_address,options=self.curve_client_config(server_public_key,client_public_key,client_private_key))self.sub.observable.subscribe(lambdax:self.log.info(f"received:{x['payload']}"))classListenerInvalid(PowerfulAgent):defsetup(self,name=None,pub_address=None,sub_address=None):self.pub,self.sub=self.create_notification_client(pub_address,sub_address,options=self.curve_client_config(wrong_server_public_key,client2_public_key,client2_private_key))self.sub.observable.subscribe(lambdax:self.log.info(f"received:{x['payload']}"))if__name__=='__main__':broker=NotificationBroker(name='broker',pub_address='tcp://0.0.0.0:5000',sub_address='tcp://0.0.0.0:5001')sender=Sender(name='sender',pub_address='tcp://0.0.0.0:5000',sub_address='tcp://0.0.0.0:5001')listener=Listener(name='listener',pub_address='tcp://0.0.0.0:5000',sub_address='tcp://0.0.0.0:5001')listener_invalid=ListenerInvalid(name='listener_invalid',pub_address='tcp://0.0.0.0:5000',sub_address='tcp://0.0.0.0:5001')INFO[agent=broker]bootingup...
INFO[agent=broker]runningusersetup...
INFO[agent=broker]binding9socketontcp://0.0.0.0:5001...
INFO[agent=broker]binding10socketontcp://0.0.0.0:5000...
INFO[agent=broker]bootedin0.0027320384979248047seconds...
INFO[agent=broker]startprocessingsockets...
INFO[agent=sender]bootingup...
INFO[agent=sender]runningusersetup...
INFO[agent=sender]connecting1sockettotcp://0.0.0.0:5000...
INFO[agent=sender]connecting2sockettotcp://0.0.0.0:5001...
INFO[agent=sender]bootedin0.0042607784271240234seconds...
INFO[agent=sender]startprocessingsockets...
INFO[agent=listener]bootingup...
INFO[agent=listener]runningusersetup...
INFO[agent=listener]connecting1sockettotcp://0.0.0.0:5000...
INFO[agent=listener]connecting2sockettotcp://0.0.0.0:5001...
INFO[agent=listener]bootedin0.0016052722930908203seconds...
INFO[agent=listener]startprocessingsockets...
INFO[agent=listener_invalid]bootingup...
INFO[agent=listener_invalid]runningusersetup...
INFO[agent=listener_invalid]connecting1sockettotcp://0.0.0.0:5000...
INFO[agent=listener_invalid]connecting2sockettotcp://0.0.0.0:5001...
INFO[agent=listener_invalid]bootedin0.0014069080352783203seconds...
INFO[agent=listener_invalid]startprocessingsockets...
INFO[agent=sender]publishing:1INFO[agent=listener]received:1INFO[agent=sender]publishing:2INFO[agent=listener]received:2INFO[agent=sender]publishing:3INFO[agent=listener]received:3Full AuthenticationThis example adds another layer of protection for the server by allowing only trusted clients to connect.importosimportzmqimporttimeimportthreadingimporttempfilefromagentsimportAgent,PowerfulAgent,MessageclassNotificationBroker(PowerfulAgent):defsetup(self,name=None,pub_address=None,sub_address=None,private_key=None,client_certificates_path=None):# configure public key auth/encryption if private_key is providedoptions=self.curve_server_config(private_key)ifprivate_keyelse{}self.create_notification_broker(pub_address,sub_address,options=options)# start authenticator if client_certificates_path is providedifclient_certificates_path:self.auth=self.start_authenticator(domain='*',certificates_path=client_certificates_path)classSender(PowerfulAgent):defsetup(self,name=None,pub_address=None,sub_address=None,private_key=None,public_key=None,server_public_key=None):# configure public key auth/encryption if keys are providedifprivate_keyandpublic_keyandserver_public_key:options=self.curve_client_config(server_public_key,public_key,private_key)else:options={}self.counter=0self.pub,self.sub=self.create_notification_client(pub_address,sub_address,options=options)# begin sending forever, add to managed threads for graceful cleanupt=threading.Thread(target=self.send_forever)self.threads.append(t)t.start()defsend_forever(self):# use exit event to gracefully exit loop and graceful cleanupwhilenotself.exit_event.is_set():time.sleep(1)self.counter+=1self.log.info(f"publishing:{self.counter}")self.pub.send(Message.notification(payload=self.counter))classListener(PowerfulAgent):defsetup(self,name=None,pub_address=None,sub_address=None,private_key=None,public_key=None,server_public_key=None):# configure public key auth/encryption if keys are providedifprivate_keyandpublic_keyandserver_public_key:options=self.curve_client_config(server_public_key,public_key,private_key)else:options={}self.pub,self.sub=self.create_notification_client(pub_address,sub_address,options=options)self.sub.observable.subscribe(lambdax:self.log.info(f"received:{x['payload']}"))if__name__=='__main__':withtempfile.TemporaryDirectory()astrusted_keys_path,\tempfile.TemporaryDirectory()asuntrusted_keys_path:# create key pairs in corresponding directoriesAgent.create_curve_certificates(trusted_keys_path,'server')Agent.create_curve_certificates(trusted_keys_path,'listener')Agent.create_curve_certificates(untrusted_keys_path,'listener2')# load key pairsserver_public_key,server_private_key=Agent.load_curve_certificate(os.path.join(trusted_keys_path,"server.key_secret"))listener_public_key,listener_private_key=Agent.load_curve_certificate(os.path.join(trusted_keys_path,"listener.key_secret"))listener2_public_key,listener2_private_key=Agent.load_curve_certificate(os.path.join(untrusted_keys_path,"listener2.key_secret"))broker=NotificationBroker(name='broker',pub_address='tcp://127.0.0.1:5000',sub_address='tcp://127.0.0.1:5001',private_key=server_private_key,client_certificates_path=trusted_keys_path)sender=Sender(name='sender',pub_address='tcp://127.0.0.1:5000',sub_address='tcp://127.0.0.1:5001',private_key=server_private_key,public_key=server_public_key,server_public_key=server_public_key)listener=Listener(name='listener',pub_address='tcp://127.0.0.1:5000',sub_address='tcp://127.0.0.1:5001',private_key=listener_private_key,public_key=listener_public_key,server_public_key=server_public_key)listener2=Listener(name='listener2',pub_address='tcp://127.0.0.1:5000',sub_address='tcp://127.0.0.1:5001',private_key=listener2_private_key,public_key=listener2_public_key,server_public_key=server_public_key)INFO[agent=broker]bootingup...
INFO[agent=broker]runningusersetup...
INFO[agent=broker]binding9socketontcp://127.0.0.1:5001...
INFO[agent=broker]binding10socketontcp://127.0.0.1:5000...
INFO[agent=broker]trustedclients:{b'LJl(is$S!/A[3uj]lx}GosmI^28J+3TrR#N5L*C3':True,b'xHVFw-cZptp-.U=rMK)OdTM*p5iwnxX.6HEZWt9v':True}DEBUG[agent=broker]Starting
INFO[agent=broker]authenticatorstarted...
DEBUG[agent=broker]authreceivedAPIcommandb'ALLOW'DEBUG[agent=broker]Allowing
DEBUG[agent=broker]authreceivedAPIcommandb'CURVE'DEBUG[agent=broker]Configurecurve:*[/var/folders/v2/9dzql1f509n1rqnlgrxs9_180000gn/T/tmp6f67aogg]INFO[agent=broker]bootedin0.006651878356933594seconds...
INFO[agent=broker]startprocessingsockets...
INFO[agent=sender]bootingup...
INFO[agent=sender]runningusersetup...
INFO[agent=sender]connecting1sockettotcp://127.0.0.1:5000...
INFO[agent=sender]connecting2sockettotcp://127.0.0.1:5001...
INFO[agent=sender]bootedin0.003384113311767578seconds...
INFO[agent=sender]startprocessingsockets...
INFO[agent=listener]bootingup...
INFO[agent=listener]runningusersetup...
INFO[agent=listener]connecting1sockettotcp://127.0.0.1:5000...
INFO[agent=listener]connecting2sockettotcp://127.0.0.1:5001...
INFO[agent=listener]bootedin0.001445770263671875seconds...
INFO[agent=listener]startprocessingsockets...
INFO[agent=listener2]bootingup...
INFO[agent=listener2]runningusersetup...
INFO[agent=listener2]connecting1sockettotcp://127.0.0.1:5000...
INFO[agent=listener2]connecting2sockettotcp://127.0.0.1:5001...
INFO[agent=listener2]bootedin0.0016427040100097656seconds...
INFO[agent=listener2]startprocessingsockets...
DEBUG[agent=broker]version:b'1.0',request_id:b'1',domain:'',address:'127.0.0.1',identity:b'',mechanism:b'CURVE'DEBUG[agent=broker]ALLOWED(CURVE)domain=*client_key=b'LJl(is$S!/A[3uj]lx}GosmI^28J+3TrR#N5L*C3'DEBUG[agent=broker]ZAPreplycode=b'200'text=b'OK'DEBUG[agent=broker]version:b'1.0',request_id:b'1',domain:'',address:'127.0.0.1',identity:b'',mechanism:b'CURVE'DEBUG[agent=broker]ALLOWED(CURVE)domain=*client_key=b'LJl(is$S!/A[3uj]lx}GosmI^28J+3TrR#N5L*C3'DEBUG[agent=broker]ZAPreplycode=b'200'text=b'OK'DEBUG[agent=broker]version:b'1.0',request_id:b'1',domain:'',address:'127.0.0.1',identity:b'',mechanism:b'CURVE'DEBUG[agent=broker]ALLOWED(CURVE)domain=*client_key=b'xHVFw-cZptp-.U=rMK)OdTM*p5iwnxX.6HEZWt9v'DEBUG[agent=broker]ZAPreplycode=b'200'text=b'OK'DEBUG[agent=broker]version:b'1.0',request_id:b'1',domain:'',address:'127.0.0.1',identity:b'',mechanism:b'CURVE'DEBUG[agent=broker]DENIED(CURVE)domain=*client_key=b're69C5>LWfw%//*Qc^6Ei]>ntoYZCc6E]rZXA=F9'DEBUG[agent=broker]ZAPreplycode=b'400'text=b'Unknown key'DEBUG[agent=broker]version:b'1.0',request_id:b'1',domain:'',address:'127.0.0.1',identity:b'',mechanism:b'CURVE'DEBUG[agent=broker]ALLOWED(CURVE)domain=*client_key=b'xHVFw-cZptp-.U=rMK)OdTM*p5iwnxX.6HEZWt9v'DEBUG[agent=broker]ZAPreplycode=b'200'text=b'OK'DEBUG[agent=broker]version:b'1.0',request_id:b'1',domain:'',address:'127.0.0.1',identity:b'',mechanism:b'CURVE'DEBUG[agent=broker]DENIED(CURVE)domain=*client_key=b're69C5>LWfw%//*Qc^6Ei]>ntoYZCc6E]rZXA=F9'DEBUG[agent=broker]ZAPreplycode=b'400'text=b'Unknown key'INFO[agent=sender]publishing:1INFO[agent=listener]received:1INFO[agent=sender]publishing:2INFO[agent=listener]received:2