trustgraph-base 0.22.8__tar.gz → 0.22.10__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.8 → trustgraph-base-0.22.10}/PKG-INFO +2 -2
  2. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/api/api.py +0 -230
  3. trustgraph-base-0.22.10/trustgraph/base/__init__.py +8 -0
  4. trustgraph-base-0.22.10/trustgraph/base/base_processor.py +210 -0
  5. trustgraph-base-0.22.10/trustgraph/base/consumer.py +173 -0
  6. trustgraph-base-0.22.10/trustgraph/base/consumer_producer.py +62 -0
  7. trustgraph-base-0.22.10/trustgraph/base/producer.py +56 -0
  8. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/base/publisher.py +19 -23
  9. trustgraph-base-0.22.10/trustgraph/base/subscriber.py +114 -0
  10. trustgraph-base-0.22.10/trustgraph/base_version.py +1 -0
  11. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/schema/__init__.py +1 -1
  12. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/schema/agent.py +7 -0
  13. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/schema/config.py +1 -1
  14. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/schema/documents.py +15 -0
  15. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/schema/graph.py +19 -0
  16. trustgraph-base-0.22.10/trustgraph/schema/lookup.py +42 -0
  17. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/schema/models.py +13 -0
  18. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/schema/object.py +3 -1
  19. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/schema/prompt.py +7 -0
  20. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/schema/retrieval.py +13 -0
  21. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph_base.egg-info/PKG-INFO +2 -2
  22. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph_base.egg-info/SOURCES.txt +2 -29
  23. trustgraph-base-0.22.8/trustgraph/base/__init__.py +0 -31
  24. trustgraph-base-0.22.8/trustgraph/base/agent_client.py +0 -39
  25. trustgraph-base-0.22.8/trustgraph/base/agent_service.py +0 -100
  26. trustgraph-base-0.22.8/trustgraph/base/async_processor.py +0 -257
  27. trustgraph-base-0.22.8/trustgraph/base/consumer.py +0 -197
  28. trustgraph-base-0.22.8/trustgraph/base/consumer_spec.py +0 -36
  29. trustgraph-base-0.22.8/trustgraph/base/document_embeddings_client.py +0 -38
  30. trustgraph-base-0.22.8/trustgraph/base/document_embeddings_query_service.py +0 -84
  31. trustgraph-base-0.22.8/trustgraph/base/document_embeddings_store_service.py +0 -50
  32. trustgraph-base-0.22.8/trustgraph/base/embeddings_client.py +0 -31
  33. trustgraph-base-0.22.8/trustgraph/base/embeddings_service.py +0 -90
  34. trustgraph-base-0.22.8/trustgraph/base/flow.py +0 -32
  35. trustgraph-base-0.22.8/trustgraph/base/flow_processor.py +0 -115
  36. trustgraph-base-0.22.8/trustgraph/base/graph_embeddings_client.py +0 -45
  37. trustgraph-base-0.22.8/trustgraph/base/graph_embeddings_query_service.py +0 -84
  38. trustgraph-base-0.22.8/trustgraph/base/graph_embeddings_store_service.py +0 -50
  39. trustgraph-base-0.22.8/trustgraph/base/graph_rag_client.py +0 -33
  40. trustgraph-base-0.22.8/trustgraph/base/llm_service.py +0 -114
  41. trustgraph-base-0.22.8/trustgraph/base/metrics.py +0 -136
  42. trustgraph-base-0.22.8/trustgraph/base/producer.py +0 -69
  43. trustgraph-base-0.22.8/trustgraph/base/producer_spec.py +0 -25
  44. trustgraph-base-0.22.8/trustgraph/base/prompt_client.py +0 -93
  45. trustgraph-base-0.22.8/trustgraph/base/pubsub.py +0 -80
  46. trustgraph-base-0.22.8/trustgraph/base/request_response_spec.py +0 -141
  47. trustgraph-base-0.22.8/trustgraph/base/setting_spec.py +0 -19
  48. trustgraph-base-0.22.8/trustgraph/base/spec.py +0 -4
  49. trustgraph-base-0.22.8/trustgraph/base/subscriber.py +0 -156
  50. trustgraph-base-0.22.8/trustgraph/base/subscriber_spec.py +0 -30
  51. trustgraph-base-0.22.8/trustgraph/base/text_completion_client.py +0 -30
  52. trustgraph-base-0.22.8/trustgraph/base/triples_client.py +0 -61
  53. trustgraph-base-0.22.8/trustgraph/base/triples_query_service.py +0 -82
  54. trustgraph-base-0.22.8/trustgraph/base/triples_store_service.py +0 -47
  55. trustgraph-base-0.22.8/trustgraph/base_version.py +0 -1
  56. trustgraph-base-0.22.8/trustgraph/schema/flows.py +0 -66
  57. trustgraph-base-0.22.8/trustgraph/schema/lookup.py +0 -21
  58. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/README.md +0 -0
  59. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/setup.cfg +0 -0
  60. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/setup.py +0 -0
  61. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/api/__init__.py +0 -0
  62. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/clients/__init__.py +0 -0
  63. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/clients/agent_client.py +0 -0
  64. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/clients/base.py +0 -0
  65. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/clients/config_client.py +0 -0
  66. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/clients/document_embeddings_client.py +0 -0
  67. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/clients/document_rag_client.py +0 -0
  68. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/clients/embeddings_client.py +0 -0
  69. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/clients/graph_embeddings_client.py +0 -0
  70. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/clients/graph_rag_client.py +0 -0
  71. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/clients/llm_client.py +0 -0
  72. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/clients/prompt_client.py +0 -0
  73. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/clients/triples_query_client.py +0 -0
  74. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/exceptions.py +0 -0
  75. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/knowledge/__init__.py +0 -0
  76. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/knowledge/defs.py +0 -0
  77. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/knowledge/document.py +0 -0
  78. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/knowledge/identifier.py +0 -0
  79. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/knowledge/organization.py +0 -0
  80. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/knowledge/publication.py +0 -0
  81. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/log_level.py +0 -0
  82. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/objects/__init__.py +0 -0
  83. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/objects/field.py +0 -0
  84. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/objects/object.py +0 -0
  85. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/rdf.py +0 -0
  86. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/schema/library.py +0 -0
  87. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/schema/metadata.py +0 -0
  88. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/schema/topic.py +0 -0
  89. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph/schema/types.py +0 -0
  90. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph_base.egg-info/dependency_links.txt +0 -0
  91. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/trustgraph_base.egg-info/requires.txt +0 -0
  92. {trustgraph-base-0.22.8 → trustgraph-base-0.22.10}/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.8
3
+ Version: 0.22.10
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.8.tar.gz
6
+ Download-URL: https://github.com/trustgraph-ai/trustgraph/archive/refs/tags/v0.22.10.tar.gz
7
7
  Author: trustgraph.ai
8
8
  Author-email: security@trustgraph.ai
9
9
  Classifier: Programming Language :: Python :: 3
@@ -562,233 +562,3 @@ 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,8 @@
1
+
2
+ from . base_processor import BaseProcessor
3
+ from . consumer import Consumer
4
+ from . producer import Producer
5
+ from . consumer_producer import ConsumerProducer
6
+ from . publisher import Publisher
7
+ from . subscriber import Subscriber
8
+
@@ -0,0 +1,210 @@
1
+
2
+ import asyncio
3
+ import os
4
+ import argparse
5
+ import pulsar
6
+ from pulsar.schema import JsonSchema
7
+ import _pulsar
8
+ import time
9
+ import uuid
10
+ from prometheus_client import start_http_server, Info
11
+
12
+ from .. schema import ConfigPush, config_push_queue
13
+ from .. log_level import LogLevel
14
+
15
+ default_config_queue = config_push_queue
16
+ config_subscriber_id = str(uuid.uuid4())
17
+
18
+ class BaseProcessor:
19
+
20
+ default_pulsar_host = os.getenv("PULSAR_HOST", 'pulsar://pulsar:6650')
21
+ default_pulsar_api_key = os.getenv("PULSAR_API_KEY", None)
22
+
23
+ def __init__(self, **params):
24
+
25
+ self.client = None
26
+
27
+ if not hasattr(__class__, "params_metric"):
28
+ __class__.params_metric = Info(
29
+ 'params', 'Parameters configuration'
30
+ )
31
+
32
+ # FIXME: Maybe outputs information it should not
33
+ __class__.params_metric.info({
34
+ k: str(params[k])
35
+ for k in params
36
+ })
37
+
38
+ pulsar_host = params.get("pulsar_host", self.default_pulsar_host)
39
+ pulsar_listener = params.get("pulsar_listener", None)
40
+ pulsar_api_key = params.get("pulsar_api_key", None)
41
+ log_level = params.get("log_level", LogLevel.INFO)
42
+
43
+ self.config_push_queue = params.get(
44
+ "config_push_queue",
45
+ default_config_queue
46
+ )
47
+
48
+ self.pulsar_host = pulsar_host
49
+ self.pulsar_api_key = pulsar_api_key
50
+
51
+ if pulsar_api_key:
52
+ auth = pulsar.AuthenticationToken(pulsar_api_key)
53
+ self.client = pulsar.Client(
54
+ pulsar_host,
55
+ authentication=auth,
56
+ logger=pulsar.ConsoleLogger(log_level.to_pulsar())
57
+ )
58
+ else:
59
+ self.client = pulsar.Client(
60
+ pulsar_host,
61
+ listener_name=pulsar_listener,
62
+ logger=pulsar.ConsoleLogger(log_level.to_pulsar())
63
+ )
64
+
65
+ self.pulsar_listener = pulsar_listener
66
+
67
+ self.config_subscriber = self.client.subscribe(
68
+ self.config_push_queue, config_subscriber_id,
69
+ consumer_type=pulsar.ConsumerType.Shared,
70
+ initial_position=pulsar.InitialPosition.Earliest,
71
+ schema=JsonSchema(ConfigPush),
72
+ )
73
+
74
+ def __del__(self):
75
+
76
+ if hasattr(self, "client"):
77
+ if self.client:
78
+ self.client.close()
79
+
80
+ @staticmethod
81
+ def add_args(parser):
82
+
83
+ parser.add_argument(
84
+ '-p', '--pulsar-host',
85
+ default=__class__.default_pulsar_host,
86
+ help=f'Pulsar host (default: {__class__.default_pulsar_host})',
87
+ )
88
+
89
+ parser.add_argument(
90
+ '--pulsar-api-key',
91
+ default=__class__.default_pulsar_api_key,
92
+ help=f'Pulsar API key',
93
+ )
94
+
95
+ parser.add_argument(
96
+ '--config-push-queue',
97
+ default=default_config_queue,
98
+ help=f'Config push queue {default_config_queue}',
99
+ )
100
+
101
+ parser.add_argument(
102
+ '--pulsar-listener',
103
+ help=f'Pulsar listener (default: none)',
104
+ )
105
+
106
+ parser.add_argument(
107
+ '-l', '--log-level',
108
+ type=LogLevel,
109
+ default=LogLevel.INFO,
110
+ choices=list(LogLevel),
111
+ help=f'Output queue (default: info)'
112
+ )
113
+
114
+ parser.add_argument(
115
+ '--metrics',
116
+ action=argparse.BooleanOptionalAction,
117
+ default=True,
118
+ help=f'Metrics enabled (default: true)',
119
+ )
120
+
121
+ parser.add_argument(
122
+ '-P', '--metrics-port',
123
+ type=int,
124
+ default=8000,
125
+ help=f'Pulsar host (default: 8000)',
126
+ )
127
+
128
+ async def start(self):
129
+ pass
130
+
131
+ async def run_config_queue(self):
132
+
133
+ if self.module == "config.service":
134
+ print("I am config-svc, not looking at config queue", flush=True)
135
+ return
136
+
137
+ print("Config thread running", flush=True)
138
+
139
+ while True:
140
+
141
+ try:
142
+ msg = await asyncio.to_thread(
143
+ self.config_subscriber.receive, timeout_millis=2000
144
+ )
145
+ except pulsar.Timeout:
146
+ continue
147
+
148
+ v = msg.value()
149
+ print("Got config version", v.version, flush=True)
150
+
151
+ await self.on_config(v.version, v.config)
152
+
153
+ async def on_config(self, version, config):
154
+ pass
155
+
156
+ async def run(self):
157
+ raise RuntimeError("Something should have implemented the run method")
158
+
159
+ @classmethod
160
+ async def launch_async(cls, args, prog):
161
+ p = cls(**args)
162
+ p.module = prog
163
+ await p.start()
164
+
165
+ task1 = asyncio.create_task(p.run_config_queue())
166
+ task2 = asyncio.create_task(p.run())
167
+
168
+ await asyncio.gather(task1, task2)
169
+
170
+ @classmethod
171
+ def launch(cls, prog, doc):
172
+
173
+ parser = argparse.ArgumentParser(
174
+ prog=prog,
175
+ description=doc
176
+ )
177
+
178
+ cls.add_args(parser)
179
+
180
+ args = parser.parse_args()
181
+ args = vars(args)
182
+
183
+ print(args)
184
+
185
+ if args["metrics"]:
186
+ start_http_server(args["metrics_port"])
187
+
188
+ while True:
189
+
190
+ try:
191
+
192
+ asyncio.run(cls.launch_async(args, prog))
193
+
194
+ except KeyboardInterrupt:
195
+ print("Keyboard interrupt.")
196
+ return
197
+
198
+ except _pulsar.Interrupted:
199
+ print("Pulsar Interrupted.")
200
+ return
201
+
202
+ except Exception as e:
203
+
204
+ print(type(e))
205
+
206
+ print("Exception:", e, flush=True)
207
+ print("Will retry...", flush=True)
208
+
209
+ time.sleep(4)
210
+
@@ -0,0 +1,173 @@
1
+
2
+ import asyncio
3
+ from pulsar.schema import JsonSchema
4
+ import pulsar
5
+ from prometheus_client import Histogram, Info, Counter, Enum
6
+ import time
7
+
8
+ from . base_processor import BaseProcessor
9
+ from .. exceptions import TooManyRequests
10
+
11
+ default_rate_limit_retry = 10
12
+ default_rate_limit_timeout = 7200
13
+
14
+ class Consumer(BaseProcessor):
15
+
16
+ def __init__(self, **params):
17
+
18
+ if not hasattr(__class__, "state_metric"):
19
+ __class__.state_metric = Enum(
20
+ 'processor_state', 'Processor state',
21
+ states=['starting', 'running', 'stopped']
22
+ )
23
+ __class__.state_metric.state('starting')
24
+
25
+ __class__.state_metric.state('starting')
26
+
27
+ super(Consumer, self).__init__(**params)
28
+
29
+ self.input_queue = params.get("input_queue")
30
+ self.subscriber = params.get("subscriber")
31
+ self.input_schema = params.get("input_schema")
32
+
33
+ self.rate_limit_retry = params.get(
34
+ "rate_limit_retry", default_rate_limit_retry
35
+ )
36
+ self.rate_limit_timeout = params.get(
37
+ "rate_limit_timeout", default_rate_limit_timeout
38
+ )
39
+
40
+ if self.input_schema == None:
41
+ raise RuntimeError("input_schema must be specified")
42
+
43
+ if not hasattr(__class__, "request_metric"):
44
+ __class__.request_metric = Histogram(
45
+ 'request_latency', 'Request latency (seconds)'
46
+ )
47
+
48
+ if not hasattr(__class__, "pubsub_metric"):
49
+ __class__.pubsub_metric = Info(
50
+ 'pubsub', 'Pub/sub configuration'
51
+ )
52
+
53
+ if not hasattr(__class__, "processing_metric"):
54
+ __class__.processing_metric = Counter(
55
+ 'processing_count', 'Processing count', ["status"]
56
+ )
57
+
58
+ if not hasattr(__class__, "rate_limit_metric"):
59
+ __class__.rate_limit_metric = Counter(
60
+ 'rate_limit_count', 'Rate limit event count',
61
+ )
62
+
63
+ __class__.pubsub_metric.info({
64
+ "input_queue": self.input_queue,
65
+ "subscriber": self.subscriber,
66
+ "input_schema": self.input_schema.__name__,
67
+ "rate_limit_retry": str(self.rate_limit_retry),
68
+ "rate_limit_timeout": str(self.rate_limit_timeout),
69
+ })
70
+
71
+ self.consumer = self.client.subscribe(
72
+ self.input_queue, self.subscriber,
73
+ consumer_type=pulsar.ConsumerType.Shared,
74
+ schema=JsonSchema(self.input_schema),
75
+ )
76
+
77
+ print("Initialised consumer.", flush=True)
78
+
79
+ async def run(self):
80
+
81
+ __class__.state_metric.state('running')
82
+
83
+ while True:
84
+
85
+ msg = await asyncio.to_thread(self.consumer.receive)
86
+
87
+ expiry = time.time() + self.rate_limit_timeout
88
+
89
+ # This loop is for retry on rate-limit / resource limits
90
+ while True:
91
+
92
+ if time.time() > expiry:
93
+
94
+ print("Gave up waiting for rate-limit retry", flush=True)
95
+
96
+ # Message failed to be processed, this causes it to
97
+ # be retried
98
+ self.consumer.negative_acknowledge(msg)
99
+
100
+ __class__.processing_metric.labels(status="error").inc()
101
+
102
+ # Break out of retry loop, processes next message
103
+ break
104
+
105
+ try:
106
+
107
+ with __class__.request_metric.time():
108
+ await self.handle(msg)
109
+
110
+ # Acknowledge successful processing of the message
111
+ self.consumer.acknowledge(msg)
112
+
113
+ __class__.processing_metric.labels(status="success").inc()
114
+
115
+ # Break out of retry loop
116
+ break
117
+
118
+ except TooManyRequests:
119
+
120
+ print("TooManyRequests: will retry...", flush=True)
121
+
122
+ __class__.rate_limit_metric.inc()
123
+
124
+ # Sleep
125
+ time.sleep(self.rate_limit_retry)
126
+
127
+ # Contine from retry loop, just causes a reprocessing
128
+ continue
129
+
130
+ except Exception as e:
131
+
132
+ print("Exception:", e, flush=True)
133
+
134
+ # Message failed to be processed, this causes it to
135
+ # be retried
136
+ self.consumer.negative_acknowledge(msg)
137
+
138
+ __class__.processing_metric.labels(status="error").inc()
139
+
140
+ # Break out of retry loop, processes next message
141
+ break
142
+
143
+ @staticmethod
144
+ def add_args(parser, default_input_queue, default_subscriber):
145
+
146
+ BaseProcessor.add_args(parser)
147
+
148
+ parser.add_argument(
149
+ '-i', '--input-queue',
150
+ default=default_input_queue,
151
+ help=f'Input queue (default: {default_input_queue})'
152
+ )
153
+
154
+ parser.add_argument(
155
+ '-s', '--subscriber',
156
+ default=default_subscriber,
157
+ help=f'Queue subscriber name (default: {default_subscriber})'
158
+ )
159
+
160
+ parser.add_argument(
161
+ '--rate-limit-retry',
162
+ type=int,
163
+ default=default_rate_limit_retry,
164
+ help=f'Rate limit retry (default: {default_rate_limit_retry})'
165
+ )
166
+
167
+ parser.add_argument(
168
+ '--rate-limit-timeout',
169
+ type=int,
170
+ default=default_rate_limit_timeout,
171
+ help=f'Rate limit timeout (default: {default_rate_limit_timeout})'
172
+ )
173
+
@@ -0,0 +1,62 @@
1
+
2
+ from pulsar.schema import JsonSchema
3
+ import pulsar
4
+ from prometheus_client import Histogram, Info, Counter, Enum
5
+ import time
6
+
7
+ from . consumer import Consumer
8
+ from .. exceptions import TooManyRequests
9
+
10
+ class ConsumerProducer(Consumer):
11
+
12
+ def __init__(self, **params):
13
+
14
+ super(ConsumerProducer, self).__init__(**params)
15
+
16
+ self.output_queue = params.get("output_queue")
17
+ self.output_schema = params.get("output_schema")
18
+
19
+ if not hasattr(__class__, "output_metric"):
20
+ __class__.output_metric = Counter(
21
+ 'output_count', 'Output items created'
22
+ )
23
+
24
+ __class__.pubsub_metric.info({
25
+ "input_queue": self.input_queue,
26
+ "output_queue": self.output_queue,
27
+ "subscriber": self.subscriber,
28
+ "input_schema": self.input_schema.__name__,
29
+ "output_schema": self.output_schema.__name__,
30
+ "rate_limit_retry": str(self.rate_limit_retry),
31
+ "rate_limit_timeout": str(self.rate_limit_timeout),
32
+ })
33
+
34
+ if self.output_schema == None:
35
+ raise RuntimeError("output_schema must be specified")
36
+
37
+ self.producer = self.client.create_producer(
38
+ topic=self.output_queue,
39
+ schema=JsonSchema(self.output_schema),
40
+ chunking_enabled=True,
41
+ )
42
+
43
+ print("Initialised consumer/producer.")
44
+
45
+ async def send(self, msg, properties={}):
46
+ self.producer.send(msg, properties)
47
+ __class__.output_metric.inc()
48
+
49
+ @staticmethod
50
+ def add_args(
51
+ parser, default_input_queue, default_subscriber,
52
+ default_output_queue,
53
+ ):
54
+
55
+ Consumer.add_args(parser, default_input_queue, default_subscriber)
56
+
57
+ parser.add_argument(
58
+ '-o', '--output-queue',
59
+ default=default_output_queue,
60
+ help=f'Output queue (default: {default_output_queue})'
61
+ )
62
+