trustgraph-base 0.22.9__tar.gz → 0.22.11__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.
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/PKG-INFO +2 -2
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/api/api.py +0 -230
- trustgraph-base-0.22.11/trustgraph/base/__init__.py +8 -0
- trustgraph-base-0.22.11/trustgraph/base/base_processor.py +210 -0
- trustgraph-base-0.22.11/trustgraph/base/consumer.py +173 -0
- trustgraph-base-0.22.11/trustgraph/base/consumer_producer.py +62 -0
- trustgraph-base-0.22.11/trustgraph/base/producer.py +56 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/base/publisher.py +19 -23
- trustgraph-base-0.22.11/trustgraph/base/subscriber.py +114 -0
- trustgraph-base-0.22.11/trustgraph/base_version.py +1 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/schema/__init__.py +1 -1
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/schema/agent.py +7 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/schema/config.py +1 -1
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/schema/documents.py +15 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/schema/graph.py +19 -0
- trustgraph-base-0.22.11/trustgraph/schema/lookup.py +42 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/schema/models.py +13 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/schema/object.py +3 -1
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/schema/prompt.py +7 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/schema/retrieval.py +13 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph_base.egg-info/PKG-INFO +2 -2
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph_base.egg-info/SOURCES.txt +2 -29
- trustgraph-base-0.22.9/trustgraph/base/__init__.py +0 -31
- trustgraph-base-0.22.9/trustgraph/base/agent_client.py +0 -39
- trustgraph-base-0.22.9/trustgraph/base/agent_service.py +0 -100
- trustgraph-base-0.22.9/trustgraph/base/async_processor.py +0 -257
- trustgraph-base-0.22.9/trustgraph/base/consumer.py +0 -197
- trustgraph-base-0.22.9/trustgraph/base/consumer_spec.py +0 -36
- trustgraph-base-0.22.9/trustgraph/base/document_embeddings_client.py +0 -38
- trustgraph-base-0.22.9/trustgraph/base/document_embeddings_query_service.py +0 -84
- trustgraph-base-0.22.9/trustgraph/base/document_embeddings_store_service.py +0 -50
- trustgraph-base-0.22.9/trustgraph/base/embeddings_client.py +0 -31
- trustgraph-base-0.22.9/trustgraph/base/embeddings_service.py +0 -90
- trustgraph-base-0.22.9/trustgraph/base/flow.py +0 -32
- trustgraph-base-0.22.9/trustgraph/base/flow_processor.py +0 -115
- trustgraph-base-0.22.9/trustgraph/base/graph_embeddings_client.py +0 -45
- trustgraph-base-0.22.9/trustgraph/base/graph_embeddings_query_service.py +0 -84
- trustgraph-base-0.22.9/trustgraph/base/graph_embeddings_store_service.py +0 -50
- trustgraph-base-0.22.9/trustgraph/base/graph_rag_client.py +0 -33
- trustgraph-base-0.22.9/trustgraph/base/llm_service.py +0 -114
- trustgraph-base-0.22.9/trustgraph/base/metrics.py +0 -136
- trustgraph-base-0.22.9/trustgraph/base/producer.py +0 -69
- trustgraph-base-0.22.9/trustgraph/base/producer_spec.py +0 -25
- trustgraph-base-0.22.9/trustgraph/base/prompt_client.py +0 -93
- trustgraph-base-0.22.9/trustgraph/base/pubsub.py +0 -80
- trustgraph-base-0.22.9/trustgraph/base/request_response_spec.py +0 -141
- trustgraph-base-0.22.9/trustgraph/base/setting_spec.py +0 -19
- trustgraph-base-0.22.9/trustgraph/base/spec.py +0 -4
- trustgraph-base-0.22.9/trustgraph/base/subscriber.py +0 -156
- trustgraph-base-0.22.9/trustgraph/base/subscriber_spec.py +0 -30
- trustgraph-base-0.22.9/trustgraph/base/text_completion_client.py +0 -30
- trustgraph-base-0.22.9/trustgraph/base/triples_client.py +0 -61
- trustgraph-base-0.22.9/trustgraph/base/triples_query_service.py +0 -82
- trustgraph-base-0.22.9/trustgraph/base/triples_store_service.py +0 -47
- trustgraph-base-0.22.9/trustgraph/base_version.py +0 -1
- trustgraph-base-0.22.9/trustgraph/schema/flows.py +0 -66
- trustgraph-base-0.22.9/trustgraph/schema/lookup.py +0 -21
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/README.md +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/setup.cfg +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/setup.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/api/__init__.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/clients/__init__.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/clients/agent_client.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/clients/base.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/clients/config_client.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/clients/document_embeddings_client.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/clients/document_rag_client.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/clients/embeddings_client.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/clients/graph_embeddings_client.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/clients/graph_rag_client.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/clients/llm_client.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/clients/prompt_client.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/clients/triples_query_client.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/exceptions.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/knowledge/__init__.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/knowledge/defs.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/knowledge/document.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/knowledge/identifier.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/knowledge/organization.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/knowledge/publication.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/log_level.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/objects/__init__.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/objects/field.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/objects/object.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/rdf.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/schema/library.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/schema/metadata.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/schema/topic.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph/schema/types.py +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph_base.egg-info/dependency_links.txt +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/trustgraph_base.egg-info/requires.txt +0 -0
- {trustgraph-base-0.22.9 → trustgraph-base-0.22.11}/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.
|
3
|
+
Version: 0.22.11
|
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.
|
6
|
+
Download-URL: https://github.com/trustgraph-ai/trustgraph/archive/refs/tags/v0.22.11.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,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
|
+
|