trustgraph-base 0.22.5__tar.gz → 0.22.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/PKG-INFO +2 -2
  2. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/api/api.py +230 -0
  3. trustgraph-base-0.22.7/trustgraph/base/__init__.py +31 -0
  4. trustgraph-base-0.22.7/trustgraph/base/agent_client.py +39 -0
  5. trustgraph-base-0.22.7/trustgraph/base/agent_service.py +100 -0
  6. trustgraph-base-0.22.7/trustgraph/base/async_processor.py +257 -0
  7. trustgraph-base-0.22.7/trustgraph/base/consumer.py +197 -0
  8. trustgraph-base-0.22.7/trustgraph/base/consumer_spec.py +36 -0
  9. trustgraph-base-0.22.7/trustgraph/base/document_embeddings_client.py +38 -0
  10. trustgraph-base-0.22.7/trustgraph/base/document_embeddings_query_service.py +84 -0
  11. trustgraph-base-0.22.7/trustgraph/base/document_embeddings_store_service.py +50 -0
  12. trustgraph-base-0.22.7/trustgraph/base/embeddings_client.py +31 -0
  13. trustgraph-base-0.22.7/trustgraph/base/embeddings_service.py +90 -0
  14. trustgraph-base-0.22.7/trustgraph/base/flow.py +32 -0
  15. trustgraph-base-0.22.7/trustgraph/base/flow_processor.py +115 -0
  16. trustgraph-base-0.22.7/trustgraph/base/graph_embeddings_client.py +45 -0
  17. trustgraph-base-0.22.7/trustgraph/base/graph_embeddings_query_service.py +84 -0
  18. trustgraph-base-0.22.7/trustgraph/base/graph_embeddings_store_service.py +50 -0
  19. trustgraph-base-0.22.7/trustgraph/base/graph_rag_client.py +33 -0
  20. trustgraph-base-0.22.7/trustgraph/base/llm_service.py +114 -0
  21. trustgraph-base-0.22.7/trustgraph/base/metrics.py +136 -0
  22. trustgraph-base-0.22.7/trustgraph/base/producer.py +69 -0
  23. trustgraph-base-0.22.7/trustgraph/base/producer_spec.py +25 -0
  24. trustgraph-base-0.22.7/trustgraph/base/prompt_client.py +93 -0
  25. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/base/publisher.py +23 -19
  26. trustgraph-base-0.22.7/trustgraph/base/pubsub.py +80 -0
  27. trustgraph-base-0.22.7/trustgraph/base/request_response_spec.py +141 -0
  28. trustgraph-base-0.22.7/trustgraph/base/setting_spec.py +19 -0
  29. trustgraph-base-0.22.7/trustgraph/base/spec.py +4 -0
  30. trustgraph-base-0.22.7/trustgraph/base/subscriber.py +156 -0
  31. trustgraph-base-0.22.7/trustgraph/base/subscriber_spec.py +30 -0
  32. trustgraph-base-0.22.7/trustgraph/base/text_completion_client.py +30 -0
  33. trustgraph-base-0.22.7/trustgraph/base/triples_client.py +61 -0
  34. trustgraph-base-0.22.7/trustgraph/base/triples_query_service.py +82 -0
  35. trustgraph-base-0.22.7/trustgraph/base/triples_store_service.py +47 -0
  36. trustgraph-base-0.22.7/trustgraph/base_version.py +1 -0
  37. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/schema/__init__.py +1 -1
  38. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/schema/agent.py +0 -7
  39. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/schema/config.py +1 -1
  40. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/schema/documents.py +0 -15
  41. trustgraph-base-0.22.7/trustgraph/schema/flows.py +66 -0
  42. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/schema/graph.py +0 -19
  43. trustgraph-base-0.22.7/trustgraph/schema/lookup.py +21 -0
  44. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/schema/models.py +0 -13
  45. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/schema/object.py +1 -3
  46. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/schema/prompt.py +0 -7
  47. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/schema/retrieval.py +0 -13
  48. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph_base.egg-info/PKG-INFO +2 -2
  49. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph_base.egg-info/SOURCES.txt +29 -2
  50. trustgraph-base-0.22.5/trustgraph/base/__init__.py +0 -8
  51. trustgraph-base-0.22.5/trustgraph/base/base_processor.py +0 -210
  52. trustgraph-base-0.22.5/trustgraph/base/consumer.py +0 -173
  53. trustgraph-base-0.22.5/trustgraph/base/consumer_producer.py +0 -62
  54. trustgraph-base-0.22.5/trustgraph/base/producer.py +0 -56
  55. trustgraph-base-0.22.5/trustgraph/base/subscriber.py +0 -114
  56. trustgraph-base-0.22.5/trustgraph/base_version.py +0 -1
  57. trustgraph-base-0.22.5/trustgraph/schema/lookup.py +0 -42
  58. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/README.md +0 -0
  59. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/setup.cfg +0 -0
  60. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/setup.py +0 -0
  61. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/api/__init__.py +0 -0
  62. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/clients/__init__.py +0 -0
  63. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/clients/agent_client.py +0 -0
  64. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/clients/base.py +0 -0
  65. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/clients/config_client.py +0 -0
  66. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/clients/document_embeddings_client.py +0 -0
  67. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/clients/document_rag_client.py +0 -0
  68. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/clients/embeddings_client.py +0 -0
  69. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/clients/graph_embeddings_client.py +0 -0
  70. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/clients/graph_rag_client.py +0 -0
  71. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/clients/llm_client.py +0 -0
  72. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/clients/prompt_client.py +0 -0
  73. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/clients/triples_query_client.py +0 -0
  74. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/exceptions.py +0 -0
  75. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/knowledge/__init__.py +0 -0
  76. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/knowledge/defs.py +0 -0
  77. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/knowledge/document.py +0 -0
  78. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/knowledge/identifier.py +0 -0
  79. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/knowledge/organization.py +0 -0
  80. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/knowledge/publication.py +0 -0
  81. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/log_level.py +0 -0
  82. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/objects/__init__.py +0 -0
  83. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/objects/field.py +0 -0
  84. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/objects/object.py +0 -0
  85. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/rdf.py +0 -0
  86. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/schema/library.py +0 -0
  87. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/schema/metadata.py +0 -0
  88. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/schema/topic.py +0 -0
  89. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph/schema/types.py +0 -0
  90. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph_base.egg-info/dependency_links.txt +0 -0
  91. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph_base.egg-info/requires.txt +0 -0
  92. {trustgraph-base-0.22.5 → trustgraph-base-0.22.7}/trustgraph_base.egg-info/top_level.txt +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: trustgraph-base
3
- Version: 0.22.5
3
+ Version: 0.22.7
4
4
  Summary: TrustGraph provides a means to run a pipeline of flexible AI processing components in a flexible means to achieve a processing pipeline.
5
5
  Home-page: https://github.com/trustgraph-ai/trustgraph
6
- Download-URL: https://github.com/trustgraph-ai/trustgraph/archive/refs/tags/v0.22.5.tar.gz
6
+ Download-URL: https://github.com/trustgraph-ai/trustgraph/archive/refs/tags/v0.22.7.tar.gz
7
7
  Author: trustgraph.ai
8
8
  Author-email: security@trustgraph.ai
9
9
  Classifier: Programming Language :: Python :: 3
@@ -562,3 +562,233 @@ class Api:
562
562
  except:
563
563
  raise ProtocolException(f"Response not formatted correctly")
564
564
 
565
+ def flow_list_classes(self):
566
+
567
+ # The input consists of system and prompt strings
568
+ input = {
569
+ "operation": "list-classes",
570
+ }
571
+
572
+ url = f"{self.url}flow"
573
+
574
+ # Invoke the API, input is passed as JSON
575
+ resp = requests.post(url, json=input)
576
+
577
+ # Should be a 200 status code
578
+ if resp.status_code != 200:
579
+ raise ProtocolException(f"Status code {resp.status_code}")
580
+
581
+ try:
582
+ # Parse the response as JSON
583
+ object = resp.json()
584
+ except:
585
+ raise ProtocolException(f"Expected JSON response")
586
+
587
+ self.check_error(object)
588
+
589
+ try:
590
+ return object["class-names"]
591
+ except:
592
+ raise ProtocolException(f"Response not formatted correctly")
593
+
594
+ def flow_get_class(self, class_name):
595
+
596
+ # The input consists of system and prompt strings
597
+ input = {
598
+ "operation": "get-class",
599
+ "class-name": class_name,
600
+ }
601
+
602
+ url = f"{self.url}flow"
603
+
604
+ # Invoke the API, input is passed as JSON
605
+ resp = requests.post(url, json=input)
606
+
607
+ # Should be a 200 status code
608
+ if resp.status_code != 200:
609
+ raise ProtocolException(f"Status code {resp.status_code}")
610
+
611
+ try:
612
+ # Parse the response as JSON
613
+ object = resp.json()
614
+ except:
615
+ raise ProtocolException(f"Expected JSON response")
616
+
617
+ self.check_error(object)
618
+
619
+ try:
620
+ return json.loads(object["class-definition"])
621
+ except Exception as e:
622
+ print(e)
623
+ raise ProtocolException(f"Response not formatted correctly")
624
+
625
+ def flow_put_class(self, class_name, definition):
626
+
627
+ # The input consists of system and prompt strings
628
+ input = {
629
+ "operation": "put-class",
630
+ "class-name": class_name,
631
+ "class-definition": json.dumps(definition),
632
+ }
633
+
634
+ url = f"{self.url}flow"
635
+
636
+ # Invoke the API, input is passed as JSON
637
+ resp = requests.post(url, json=input)
638
+
639
+ # Should be a 200 status code
640
+ if resp.status_code != 200:
641
+ raise ProtocolException(f"Status code {resp.status_code}")
642
+
643
+ try:
644
+ # Parse the response as JSON
645
+ object = resp.json()
646
+ except:
647
+ raise ProtocolException(f"Expected JSON response")
648
+
649
+ self.check_error(object)
650
+
651
+ return
652
+
653
+ def flow_delete_class(self, class_name):
654
+
655
+ # The input consists of system and prompt strings
656
+ input = {
657
+ "operation": "delete-class",
658
+ "class-name": class_name,
659
+ }
660
+
661
+ url = f"{self.url}flow"
662
+
663
+ # Invoke the API, input is passed as JSON
664
+ resp = requests.post(url, json=input)
665
+
666
+ # Should be a 200 status code
667
+ if resp.status_code != 200:
668
+ raise ProtocolException(f"Status code {resp.status_code}")
669
+
670
+ try:
671
+ # Parse the response as JSON
672
+ object = resp.json()
673
+ except:
674
+ raise ProtocolException(f"Expected JSON response")
675
+
676
+ self.check_error(object)
677
+
678
+ return
679
+
680
+ def flow_list(self):
681
+
682
+ # The input consists of system and prompt strings
683
+ input = {
684
+ "operation": "list-flows",
685
+ }
686
+
687
+ url = f"{self.url}flow"
688
+
689
+ # Invoke the API, input is passed as JSON
690
+ resp = requests.post(url, json=input)
691
+
692
+ # Should be a 200 status code
693
+ if resp.status_code != 200:
694
+ raise ProtocolException(f"Status code {resp.status_code}")
695
+
696
+ try:
697
+ # Parse the response as JSON
698
+ object = resp.json()
699
+ except:
700
+ raise ProtocolException(f"Expected JSON response")
701
+
702
+ self.check_error(object)
703
+
704
+ try:
705
+ return object["flow-ids"]
706
+ except:
707
+ raise ProtocolException(f"Response not formatted correctly")
708
+
709
+ def flow_get(self, id):
710
+
711
+ # The input consists of system and prompt strings
712
+ input = {
713
+ "operation": "get-flow",
714
+ "flow-id": id,
715
+ }
716
+
717
+ url = f"{self.url}flow"
718
+
719
+ # Invoke the API, input is passed as JSON
720
+ resp = requests.post(url, json=input)
721
+
722
+ # Should be a 200 status code
723
+ if resp.status_code != 200:
724
+ raise ProtocolException(f"Status code {resp.status_code}")
725
+
726
+ try:
727
+ # Parse the response as JSON
728
+ object = resp.json()
729
+ except:
730
+ raise ProtocolException(f"Expected JSON response")
731
+
732
+ self.check_error(object)
733
+
734
+ try:
735
+ return json.loads(object["flow"])
736
+ except:
737
+ raise ProtocolException(f"Response not formatted correctly")
738
+
739
+ def flow_start(self, class_name, id, description):
740
+
741
+ # The input consists of system and prompt strings
742
+ input = {
743
+ "operation": "start-flow",
744
+ "flow-id": id,
745
+ "class-name": class_name,
746
+ "description": description,
747
+ }
748
+
749
+ url = f"{self.url}flow"
750
+
751
+ # Invoke the API, input is passed as JSON
752
+ resp = requests.post(url, json=input)
753
+
754
+ # Should be a 200 status code
755
+ if resp.status_code != 200:
756
+ raise ProtocolException(f"Status code {resp.status_code}")
757
+
758
+ try:
759
+ # Parse the response as JSON
760
+ object = resp.json()
761
+ except:
762
+ raise ProtocolException(f"Expected JSON response")
763
+
764
+ self.check_error(object)
765
+
766
+ return
767
+
768
+ def flow_stop(self, id):
769
+
770
+ # The input consists of system and prompt strings
771
+ input = {
772
+ "operation": "stop-flow",
773
+ "flow-id": id,
774
+ }
775
+
776
+ url = f"{self.url}flow"
777
+
778
+ # Invoke the API, input is passed as JSON
779
+ resp = requests.post(url, json=input)
780
+
781
+ # Should be a 200 status code
782
+ if resp.status_code != 200:
783
+ raise ProtocolException(f"Status code {resp.status_code}")
784
+
785
+ try:
786
+ # Parse the response as JSON
787
+ object = resp.json()
788
+ except:
789
+ raise ProtocolException(f"Expected JSON response")
790
+
791
+ self.check_error(object)
792
+
793
+ return
794
+
@@ -0,0 +1,31 @@
1
+
2
+ from . pubsub import PulsarClient
3
+ from . async_processor import AsyncProcessor
4
+ from . consumer import Consumer
5
+ from . producer import Producer
6
+ from . publisher import Publisher
7
+ from . subscriber import Subscriber
8
+ from . metrics import ProcessorMetrics, ConsumerMetrics, ProducerMetrics
9
+ from . flow_processor import FlowProcessor
10
+ from . consumer_spec import ConsumerSpec
11
+ from . setting_spec import SettingSpec
12
+ from . producer_spec import ProducerSpec
13
+ from . subscriber_spec import SubscriberSpec
14
+ from . request_response_spec import RequestResponseSpec
15
+ from . llm_service import LlmService, LlmResult
16
+ from . embeddings_service import EmbeddingsService
17
+ from . embeddings_client import EmbeddingsClientSpec
18
+ from . text_completion_client import TextCompletionClientSpec
19
+ from . prompt_client import PromptClientSpec
20
+ from . triples_store_service import TriplesStoreService
21
+ from . graph_embeddings_store_service import GraphEmbeddingsStoreService
22
+ from . document_embeddings_store_service import DocumentEmbeddingsStoreService
23
+ from . triples_query_service import TriplesQueryService
24
+ from . graph_embeddings_query_service import GraphEmbeddingsQueryService
25
+ from . document_embeddings_query_service import DocumentEmbeddingsQueryService
26
+ from . graph_embeddings_client import GraphEmbeddingsClientSpec
27
+ from . triples_client import TriplesClientSpec
28
+ from . document_embeddings_client import DocumentEmbeddingsClientSpec
29
+ from . agent_service import AgentService
30
+ from . graph_rag_client import GraphRagClientSpec
31
+
@@ -0,0 +1,39 @@
1
+
2
+ from . request_response_spec import RequestResponse, RequestResponseSpec
3
+ from .. schema import AgentRequest, AgentResponse
4
+ from .. knowledge import Uri, Literal
5
+
6
+ class AgentClient(RequestResponse):
7
+ async def request(self, recipient, question, plan=None, state=None,
8
+ history=[], timeout=300):
9
+
10
+ resp = await self.request(
11
+ AgentRequest(
12
+ question = question,
13
+ plan = plan,
14
+ state = state,
15
+ history = history,
16
+ ),
17
+ recipient=recipient,
18
+ timeout=timeout,
19
+ )
20
+
21
+ print(resp, flush=True)
22
+
23
+ if resp.error:
24
+ raise RuntimeError(resp.error.message)
25
+
26
+ return resp
27
+
28
+ class GraphEmbeddingsClientSpec(RequestResponseSpec):
29
+ def __init__(
30
+ self, request_name, response_name,
31
+ ):
32
+ super(GraphEmbeddingsClientSpec, self).__init__(
33
+ request_name = request_name,
34
+ request_schema = GraphEmbeddingsRequest,
35
+ response_name = response_name,
36
+ response_schema = GraphEmbeddingsResponse,
37
+ impl = GraphEmbeddingsClient,
38
+ )
39
+
@@ -0,0 +1,100 @@
1
+
2
+ """
3
+ Agent manager service completion base class
4
+ """
5
+
6
+ import time
7
+ from prometheus_client import Histogram
8
+
9
+ from .. schema import AgentRequest, AgentResponse, Error
10
+ from .. exceptions import TooManyRequests
11
+ from .. base import FlowProcessor, ConsumerSpec, ProducerSpec
12
+
13
+ default_ident = "agent-manager"
14
+
15
+ class AgentService(FlowProcessor):
16
+
17
+ def __init__(self, **params):
18
+
19
+ id = params.get("id")
20
+
21
+ super(AgentService, self).__init__(**params | { "id": id })
22
+
23
+ self.register_specification(
24
+ ConsumerSpec(
25
+ name = "request",
26
+ schema = AgentRequest,
27
+ handler = self.on_request
28
+ )
29
+ )
30
+
31
+ self.register_specification(
32
+ ProducerSpec(
33
+ name = "next",
34
+ schema = AgentRequest
35
+ )
36
+ )
37
+
38
+ self.register_specification(
39
+ ProducerSpec(
40
+ name = "response",
41
+ schema = AgentResponse
42
+ )
43
+ )
44
+
45
+ async def on_request(self, msg, consumer, flow):
46
+
47
+ try:
48
+
49
+ request = msg.value()
50
+
51
+ # Sender-produced ID
52
+ id = msg.properties()["id"]
53
+
54
+ async def respond(resp):
55
+
56
+ await flow("response").send(
57
+ resp,
58
+ properties={"id": id}
59
+ )
60
+
61
+ async def next(resp):
62
+
63
+ await flow("next").send(
64
+ resp,
65
+ properties={"id": id}
66
+ )
67
+
68
+ await self.agent_request(
69
+ request = request, respond = respond, next = next,
70
+ flow = flow
71
+ )
72
+
73
+ except TooManyRequests as e:
74
+ raise e
75
+
76
+ except Exception as e:
77
+
78
+ # Apart from rate limits, treat all exceptions as unrecoverable
79
+ print(f"on_request Exception: {e}")
80
+
81
+ print("Send error response...", flush=True)
82
+
83
+ await flow.producer["response"].send(
84
+ AgentResponse(
85
+ error=Error(
86
+ type = "agent-error",
87
+ message = str(e),
88
+ ),
89
+ thought = None,
90
+ observation = None,
91
+ answer = None,
92
+ ),
93
+ properties={"id": id}
94
+ )
95
+
96
+ @staticmethod
97
+ def add_args(parser):
98
+
99
+ FlowProcessor.add_args(parser)
100
+
@@ -0,0 +1,257 @@
1
+
2
+ # Base class for processors. Implements:
3
+ # - Pulsar client, subscribe and consume basic
4
+ # - the async startup logic
5
+ # - Initialising metrics
6
+
7
+ import asyncio
8
+ import argparse
9
+ import _pulsar
10
+ import time
11
+ import uuid
12
+ from prometheus_client import start_http_server, Info
13
+
14
+ from .. schema import ConfigPush, config_push_queue
15
+ from .. log_level import LogLevel
16
+ from .. exceptions import TooManyRequests
17
+ from . pubsub import PulsarClient
18
+ from . producer import Producer
19
+ from . consumer import Consumer
20
+ from . metrics import ProcessorMetrics, ConsumerMetrics
21
+
22
+ default_config_queue = config_push_queue
23
+
24
+ # Async processor
25
+ class AsyncProcessor:
26
+
27
+ def __init__(self, **params):
28
+
29
+ # Store the identity
30
+ self.id = params.get("id")
31
+
32
+ # Register a pulsar client
33
+ self.pulsar_client_object = PulsarClient(**params)
34
+
35
+ # Initialise metrics, records the parameters
36
+ ProcessorMetrics(processor = self.id).info({
37
+ k: str(params[k])
38
+ for k in params
39
+ if k != "id"
40
+ })
41
+
42
+ # The processor runs all activity in a taskgroup, it's mandatory
43
+ # that this is provded
44
+ self.taskgroup = params.get("taskgroup")
45
+ if self.taskgroup is None:
46
+ raise RuntimeError("Essential taskgroup missing")
47
+
48
+ # Get the configuration topic
49
+ self.config_push_queue = params.get(
50
+ "config_push_queue", default_config_queue
51
+ )
52
+
53
+ # This records registered configuration handlers
54
+ self.config_handlers = []
55
+
56
+ # Create a random ID for this subscription to the configuration
57
+ # service
58
+ config_subscriber_id = str(uuid.uuid4())
59
+
60
+ config_consumer_metrics = ConsumerMetrics(
61
+ processor = self.id, flow = None, name = "config",
62
+ )
63
+
64
+ # Subscribe to config queue
65
+ self.config_sub_task = Consumer(
66
+
67
+ taskgroup = self.taskgroup,
68
+ client = self.pulsar_client,
69
+ subscriber = config_subscriber_id,
70
+ flow = None,
71
+
72
+ topic = self.config_push_queue,
73
+ schema = ConfigPush,
74
+
75
+ handler = self.on_config_change,
76
+
77
+ metrics = config_consumer_metrics,
78
+
79
+ # This causes new subscriptions to view the entire history of
80
+ # configuration
81
+ start_of_messages = True
82
+ )
83
+
84
+ self.running = True
85
+
86
+ # This is called to start dynamic behaviour. An over-ride point for
87
+ # extra functionality
88
+ async def start(self):
89
+ await self.config_sub_task.start()
90
+
91
+ # This is called to stop all threads. An over-ride point for extra
92
+ # functionality
93
+ def stop(self):
94
+ self.pulsar_client.close()
95
+ self.running = False
96
+
97
+ # Returns the pulsar host
98
+ @property
99
+ def pulsar_host(self): return self.pulsar_client_object.pulsar_host
100
+
101
+ # Returns the pulsar client
102
+ @property
103
+ def pulsar_client(self): return self.pulsar_client_object.client
104
+
105
+ # Register a new event handler for configuration change
106
+ def register_config_handler(self, handler):
107
+ self.config_handlers.append(handler)
108
+
109
+ # Called when a new configuration message push occurs
110
+ async def on_config_change(self, message, consumer, flow):
111
+
112
+ # Get configuration data and version number
113
+ config = message.value().config
114
+ version = message.value().version
115
+
116
+ # Invoke message handlers
117
+ print("Config change event", config, version, flush=True)
118
+ for ch in self.config_handlers:
119
+ await ch(config, version)
120
+
121
+ # This is the 'main' body of the handler. It is a point to override
122
+ # if needed. By default does nothing. Processors are implemented
123
+ # by adding consumer/producer functionality so maybe nothing is needed
124
+ # in the run() body
125
+ async def run(self):
126
+ while self.running:
127
+ await asyncio.sleep(2)
128
+
129
+ # Startup fabric. This runs in 'async' mode, creates a taskgroup and
130
+ # runs the producer.
131
+ @classmethod
132
+ async def launch_async(cls, args):
133
+
134
+ try:
135
+
136
+ # Create a taskgroup. This seems complicated, when an exception
137
+ # occurs, unhandled it looks like it cancels all threads in the
138
+ # taskgroup. Needs the exception to be caught in the right
139
+ # place.
140
+ async with asyncio.TaskGroup() as tg:
141
+
142
+
143
+ # Create a processor instance, and include the taskgroup
144
+ # as a paramter. A processor identity ident is used as
145
+ # - subscriber name
146
+ # - an identifier for flow configuration
147
+ p = cls(**args | { "taskgroup": tg })
148
+
149
+ # Start the processor
150
+ await p.start()
151
+
152
+ # Run the processor
153
+ task = tg.create_task(p.run())
154
+
155
+ # The taskgroup causes everything to wait until
156
+ # all threads have stopped
157
+
158
+ # This is here to output a debug message, shouldn't be needed.
159
+ except Exception as e:
160
+ print("Exception, closing taskgroup", flush=True)
161
+ raise e
162
+
163
+ # Startup fabric. launch calls launch_async in async mode.
164
+ @classmethod
165
+ def launch(cls, ident, doc):
166
+
167
+ # Start assembling CLI arguments
168
+ parser = argparse.ArgumentParser(
169
+ prog=ident,
170
+ description=doc
171
+ )
172
+
173
+ parser.add_argument(
174
+ '--id',
175
+ default=ident,
176
+ help=f'Configuration identity (default: {ident})',
177
+ )
178
+
179
+ # Invoke the class-specific add_args, which manages adding all the
180
+ # command-line arguments
181
+ cls.add_args(parser)
182
+
183
+ # Parse arguments
184
+ args = parser.parse_args()
185
+ args = vars(args)
186
+
187
+ # Debug
188
+ print(args, flush=True)
189
+
190
+ # Start the Prometheus metrics service if needed
191
+ if args["metrics"]:
192
+ start_http_server(args["metrics_port"])
193
+
194
+ # Loop forever, exception handler
195
+ while True:
196
+
197
+ print("Starting...", flush=True)
198
+
199
+ try:
200
+
201
+ # Launch the processor in an asyncio handler
202
+ asyncio.run(cls.launch_async(
203
+ args
204
+ ))
205
+
206
+ except KeyboardInterrupt:
207
+ print("Keyboard interrupt.", flush=True)
208
+ return
209
+
210
+ except _pulsar.Interrupted:
211
+ print("Pulsar Interrupted.", flush=True)
212
+ return
213
+
214
+ # Exceptions from a taskgroup come in as an exception group
215
+ except ExceptionGroup as e:
216
+
217
+ print("Exception group:", flush=True)
218
+
219
+ for se in e.exceptions:
220
+ print(" Type:", type(se), flush=True)
221
+ print(f" Exception: {se}", flush=True)
222
+
223
+ except Exception as e:
224
+ print("Type:", type(e), flush=True)
225
+ print("Exception:", e, flush=True)
226
+
227
+ # Retry occurs here
228
+ print("Will retry...", flush=True)
229
+ time.sleep(4)
230
+ print("Retrying...", flush=True)
231
+
232
+ # The command-line arguments are built using a stack of add_args
233
+ # invocations
234
+ @staticmethod
235
+ def add_args(parser):
236
+
237
+ PulsarClient.add_args(parser)
238
+
239
+ parser.add_argument(
240
+ '--config-queue',
241
+ default=default_config_queue,
242
+ help=f'Config push queue {default_config_queue}',
243
+ )
244
+
245
+ parser.add_argument(
246
+ '--metrics',
247
+ action=argparse.BooleanOptionalAction,
248
+ default=True,
249
+ help=f'Metrics enabled (default: true)',
250
+ )
251
+
252
+ parser.add_argument(
253
+ '-P', '--metrics-port',
254
+ type=int,
255
+ default=8000,
256
+ help=f'Pulsar host (default: 8000)',
257
+ )