trustgraph-base 1.2.20__tar.gz → 1.3.1__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 (125) hide show
  1. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/PKG-INFO +1 -1
  2. trustgraph_base-1.3.1/trustgraph/base/publisher.py +123 -0
  3. trustgraph_base-1.3.1/trustgraph/base/subscriber.py +267 -0
  4. trustgraph_base-1.3.1/trustgraph/base_version.py +1 -0
  5. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph_base.egg-info/PKG-INFO +1 -1
  6. trustgraph_base-1.2.20/trustgraph/base/publisher.py +0 -79
  7. trustgraph_base-1.2.20/trustgraph/base/subscriber.py +0 -182
  8. trustgraph_base-1.2.20/trustgraph/base_version.py +0 -1
  9. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/README.md +0 -0
  10. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/pyproject.toml +0 -0
  11. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/setup.cfg +0 -0
  12. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/__init__.py +0 -0
  13. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/api.py +0 -0
  14. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/config.py +0 -0
  15. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/exceptions.py +0 -0
  16. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/flow.py +0 -0
  17. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/knowledge.py +0 -0
  18. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/library.py +0 -0
  19. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/types.py +0 -0
  20. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/__init__.py +0 -0
  21. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/agent_client.py +0 -0
  22. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/agent_service.py +0 -0
  23. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/async_processor.py +0 -0
  24. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/consumer.py +0 -0
  25. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/consumer_spec.py +0 -0
  26. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/document_embeddings_client.py +0 -0
  27. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/document_embeddings_query_service.py +0 -0
  28. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/document_embeddings_store_service.py +0 -0
  29. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/embeddings_client.py +0 -0
  30. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/embeddings_service.py +0 -0
  31. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/flow.py +0 -0
  32. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/flow_processor.py +0 -0
  33. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/graph_embeddings_client.py +0 -0
  34. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/graph_embeddings_query_service.py +0 -0
  35. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/graph_embeddings_store_service.py +0 -0
  36. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/graph_rag_client.py +0 -0
  37. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/llm_service.py +0 -0
  38. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/metrics.py +0 -0
  39. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/producer.py +0 -0
  40. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/producer_spec.py +0 -0
  41. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/prompt_client.py +0 -0
  42. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/pubsub.py +0 -0
  43. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/request_response_spec.py +0 -0
  44. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/setting_spec.py +0 -0
  45. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/spec.py +0 -0
  46. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/subscriber_spec.py +0 -0
  47. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/text_completion_client.py +0 -0
  48. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/tool_client.py +0 -0
  49. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/tool_service.py +0 -0
  50. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/triples_client.py +0 -0
  51. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/triples_query_service.py +0 -0
  52. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/triples_store_service.py +0 -0
  53. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/__init__.py +0 -0
  54. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/agent_client.py +0 -0
  55. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/base.py +0 -0
  56. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/config_client.py +0 -0
  57. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/document_embeddings_client.py +0 -0
  58. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/document_rag_client.py +0 -0
  59. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/embeddings_client.py +0 -0
  60. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/graph_embeddings_client.py +0 -0
  61. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/graph_rag_client.py +0 -0
  62. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/llm_client.py +0 -0
  63. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/prompt_client.py +0 -0
  64. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/triples_query_client.py +0 -0
  65. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/exceptions.py +0 -0
  66. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/knowledge/__init__.py +0 -0
  67. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/knowledge/defs.py +0 -0
  68. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/knowledge/document.py +0 -0
  69. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/knowledge/identifier.py +0 -0
  70. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/knowledge/organization.py +0 -0
  71. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/knowledge/publication.py +0 -0
  72. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/log_level.py +0 -0
  73. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/__init__.py +0 -0
  74. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/registry.py +0 -0
  75. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/__init__.py +0 -0
  76. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/agent.py +0 -0
  77. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/base.py +0 -0
  78. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/config.py +0 -0
  79. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/document_loading.py +0 -0
  80. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/embeddings.py +0 -0
  81. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/embeddings_query.py +0 -0
  82. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/flow.py +0 -0
  83. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/knowledge.py +0 -0
  84. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/library.py +0 -0
  85. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/metadata.py +0 -0
  86. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/primitives.py +0 -0
  87. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/prompt.py +0 -0
  88. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/retrieval.py +0 -0
  89. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/text_completion.py +0 -0
  90. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/tool.py +0 -0
  91. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/triples.py +0 -0
  92. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/objects/__init__.py +0 -0
  93. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/objects/field.py +0 -0
  94. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/objects/object.py +0 -0
  95. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/rdf.py +0 -0
  96. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/__init__.py +0 -0
  97. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/core/__init__.py +0 -0
  98. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/core/metadata.py +0 -0
  99. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/core/primitives.py +0 -0
  100. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/core/topic.py +0 -0
  101. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/__init__.py +0 -0
  102. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/document.py +0 -0
  103. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/embeddings.py +0 -0
  104. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/graph.py +0 -0
  105. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/knowledge.py +0 -0
  106. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/nlp.py +0 -0
  107. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/object.py +0 -0
  108. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/rows.py +0 -0
  109. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/structured.py +0 -0
  110. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/__init__.py +0 -0
  111. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/agent.py +0 -0
  112. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/config.py +0 -0
  113. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/flow.py +0 -0
  114. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/library.py +0 -0
  115. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/llm.py +0 -0
  116. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/lookup.py +0 -0
  117. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/nlp_query.py +0 -0
  118. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/prompt.py +0 -0
  119. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/query.py +0 -0
  120. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/retrieval.py +0 -0
  121. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/structured_query.py +0 -0
  122. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph_base.egg-info/SOURCES.txt +0 -0
  123. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph_base.egg-info/dependency_links.txt +0 -0
  124. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph_base.egg-info/requires.txt +0 -0
  125. {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph_base.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trustgraph-base
3
- Version: 1.2.20
3
+ Version: 1.3.1
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
  Author-email: "trustgraph.ai" <security@trustgraph.ai>
6
6
  Project-URL: Homepage, https://github.com/trustgraph-ai/trustgraph
@@ -0,0 +1,123 @@
1
+
2
+ from pulsar.schema import JsonSchema
3
+
4
+ import asyncio
5
+ import time
6
+ import pulsar
7
+ import logging
8
+
9
+ # Module logger
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class Publisher:
13
+
14
+ def __init__(self, client, topic, schema=None, max_size=10,
15
+ chunking_enabled=True, drain_timeout=5.0):
16
+ self.client = client
17
+ self.topic = topic
18
+ self.schema = schema
19
+ self.q = asyncio.Queue(maxsize=max_size)
20
+ self.chunking_enabled = chunking_enabled
21
+ self.running = True
22
+ self.draining = False # New state for graceful shutdown
23
+ self.task = None
24
+ self.drain_timeout = drain_timeout
25
+
26
+ async def start(self):
27
+ self.task = asyncio.create_task(self.run())
28
+
29
+ async def stop(self):
30
+ """Initiate graceful shutdown with draining"""
31
+ self.running = False
32
+ self.draining = True
33
+
34
+ if self.task:
35
+ # Wait for run() to complete draining
36
+ await self.task
37
+
38
+ async def join(self):
39
+ await self.stop()
40
+
41
+ if self.task:
42
+ await self.task
43
+
44
+ async def run(self):
45
+
46
+ while self.running or self.draining:
47
+
48
+ try:
49
+
50
+ producer = self.client.create_producer(
51
+ topic=self.topic,
52
+ schema=JsonSchema(self.schema),
53
+ chunking_enabled=self.chunking_enabled,
54
+ )
55
+
56
+ drain_end_time = None
57
+
58
+ while self.running or self.draining:
59
+
60
+ try:
61
+ # Start drain timeout when entering drain mode
62
+ if self.draining and drain_end_time is None:
63
+ drain_end_time = time.time() + self.drain_timeout
64
+ logger.info(f"Publisher entering drain mode, timeout={self.drain_timeout}s")
65
+
66
+ # Check drain timeout
67
+ if self.draining and drain_end_time and time.time() > drain_end_time:
68
+ if not self.q.empty():
69
+ logger.warning(f"Drain timeout reached with {self.q.qsize()} messages remaining")
70
+ self.draining = False
71
+ break
72
+
73
+ # Calculate wait timeout based on mode
74
+ if self.draining:
75
+ # Shorter timeout during draining to exit quickly when empty
76
+ timeout = min(0.1, drain_end_time - time.time()) if drain_end_time else 0.1
77
+ else:
78
+ # Normal operation timeout
79
+ timeout = 0.25
80
+
81
+ id, item = await asyncio.wait_for(
82
+ self.q.get(),
83
+ timeout=timeout
84
+ )
85
+ except asyncio.TimeoutError:
86
+ # If draining and queue is empty, we're done
87
+ if self.draining and self.q.empty():
88
+ logger.info("Publisher queue drained successfully")
89
+ self.draining = False
90
+ break
91
+ continue
92
+ except asyncio.QueueEmpty:
93
+ # If draining and queue is empty, we're done
94
+ if self.draining and self.q.empty():
95
+ logger.info("Publisher queue drained successfully")
96
+ self.draining = False
97
+ break
98
+ continue
99
+
100
+ if id:
101
+ producer.send(item, { "id": id })
102
+ else:
103
+ producer.send(item)
104
+
105
+ # Flush producer before closing
106
+ producer.flush()
107
+ producer.close()
108
+
109
+ except Exception as e:
110
+ logger.error(f"Exception in publisher: {e}", exc_info=True)
111
+
112
+ if not self.running and not self.draining:
113
+ return
114
+
115
+ # If handler drops out, sleep a retry
116
+ await asyncio.sleep(1)
117
+
118
+ async def send(self, id, item):
119
+ if self.draining:
120
+ # Optionally reject new messages during drain
121
+ raise RuntimeError("Publisher is shutting down, not accepting new messages")
122
+ await self.q.put((id, item))
123
+
@@ -0,0 +1,267 @@
1
+
2
+ # Subscriber is similar to consumer: It provides a service to take stuff
3
+ # off of a queue and make it available using an internal broker system,
4
+ # so suitable for when multiple recipients are reading from the same queue
5
+
6
+ from pulsar.schema import JsonSchema
7
+ import asyncio
8
+ import _pulsar
9
+ import time
10
+ import logging
11
+ import uuid
12
+
13
+ # Module logger
14
+ logger = logging.getLogger(__name__)
15
+
16
+ class Subscriber:
17
+
18
+ def __init__(self, client, topic, subscription, consumer_name,
19
+ schema=None, max_size=100, metrics=None,
20
+ backpressure_strategy="block", drain_timeout=5.0):
21
+ self.client = client
22
+ self.topic = topic
23
+ self.subscription = subscription
24
+ self.consumer_name = consumer_name
25
+ self.schema = schema
26
+ self.q = {}
27
+ self.full = {}
28
+ self.max_size = max_size
29
+ self.lock = asyncio.Lock()
30
+ self.running = True
31
+ self.draining = False # New state for graceful shutdown
32
+ self.metrics = metrics
33
+ self.task = None
34
+ self.backpressure_strategy = backpressure_strategy
35
+ self.drain_timeout = drain_timeout
36
+ self.pending_acks = {} # Track messages awaiting delivery
37
+
38
+ self.consumer = None
39
+
40
+ def __del__(self):
41
+
42
+ self.running = False
43
+
44
+ async def start(self):
45
+
46
+ self.consumer = self.client.subscribe(
47
+ topic = self.topic,
48
+ subscription_name = self.subscription,
49
+ consumer_name = self.consumer_name,
50
+ schema = JsonSchema(self.schema),
51
+ )
52
+
53
+ self.task = asyncio.create_task(self.run())
54
+
55
+ async def stop(self):
56
+ """Initiate graceful shutdown with draining"""
57
+ self.running = False
58
+ self.draining = True
59
+
60
+ if self.task:
61
+ # Wait for run() to complete draining
62
+ await self.task
63
+
64
+ async def join(self):
65
+ await self.stop()
66
+
67
+ if self.task:
68
+ await self.task
69
+
70
+ async def run(self):
71
+ """Enhanced run method with integrated draining logic"""
72
+ while self.running or self.draining:
73
+
74
+ if self.metrics:
75
+ self.metrics.state("stopped")
76
+
77
+ try:
78
+
79
+ if self.metrics:
80
+ self.metrics.state("running")
81
+
82
+ logger.info("Subscriber running...")
83
+ drain_end_time = None
84
+
85
+ while self.running or self.draining:
86
+ # Start drain timeout when entering drain mode
87
+ if self.draining and drain_end_time is None:
88
+ drain_end_time = time.time() + self.drain_timeout
89
+ logger.info(f"Subscriber entering drain mode, timeout={self.drain_timeout}s")
90
+
91
+ # Stop accepting new messages from Pulsar during drain
92
+ if self.consumer:
93
+ self.consumer.pause_message_listener()
94
+
95
+ # Check drain timeout
96
+ if self.draining and drain_end_time and time.time() > drain_end_time:
97
+ async with self.lock:
98
+ total_pending = sum(
99
+ q.qsize() for q in
100
+ list(self.q.values()) + list(self.full.values())
101
+ )
102
+ if total_pending > 0:
103
+ logger.warning(f"Drain timeout reached with {total_pending} messages in queues")
104
+ self.draining = False
105
+ break
106
+
107
+ # Check if we can exit drain mode
108
+ if self.draining:
109
+ async with self.lock:
110
+ all_empty = all(
111
+ q.empty() for q in
112
+ list(self.q.values()) + list(self.full.values())
113
+ )
114
+ if all_empty and len(self.pending_acks) == 0:
115
+ logger.info("Subscriber queues drained successfully")
116
+ self.draining = False
117
+ break
118
+
119
+ # Process messages only if not draining
120
+ if not self.draining:
121
+ try:
122
+ msg = await asyncio.to_thread(
123
+ self.consumer.receive,
124
+ timeout_millis=250
125
+ )
126
+ except _pulsar.Timeout:
127
+ continue
128
+ except Exception as e:
129
+ logger.error(f"Exception in subscriber receive: {e}", exc_info=True)
130
+ raise e
131
+
132
+ if self.metrics:
133
+ self.metrics.received()
134
+
135
+ # Process the message with deferred acknowledgment
136
+ await self._process_message(msg)
137
+ else:
138
+ # During draining, just wait for queues to empty
139
+ await asyncio.sleep(0.1)
140
+
141
+
142
+ except Exception as e:
143
+ logger.error(f"Subscriber exception: {e}", exc_info=True)
144
+
145
+ finally:
146
+ # Negative acknowledge any pending messages
147
+ for msg in self.pending_acks.values():
148
+ self.consumer.negative_acknowledge(msg)
149
+ self.pending_acks.clear()
150
+
151
+ if self.consumer:
152
+ self.consumer.unsubscribe()
153
+ self.consumer.close()
154
+ self.consumer = None
155
+
156
+
157
+ if self.metrics:
158
+ self.metrics.state("stopped")
159
+
160
+ if not self.running and not self.draining:
161
+ return
162
+
163
+ # If handler drops out, sleep a retry
164
+ await asyncio.sleep(1)
165
+
166
+ async def subscribe(self, id):
167
+
168
+ async with self.lock:
169
+
170
+ q = asyncio.Queue(maxsize=self.max_size)
171
+ self.q[id] = q
172
+
173
+ return q
174
+
175
+ async def unsubscribe(self, id):
176
+
177
+ async with self.lock:
178
+
179
+ if id in self.q:
180
+ # self.q[id].shutdown(immediate=True)
181
+ del self.q[id]
182
+
183
+ async def subscribe_all(self, id):
184
+
185
+ async with self.lock:
186
+
187
+ q = asyncio.Queue(maxsize=self.max_size)
188
+ self.full[id] = q
189
+
190
+ return q
191
+
192
+ async def unsubscribe_all(self, id):
193
+
194
+ async with self.lock:
195
+
196
+ if id in self.full:
197
+ # self.full[id].shutdown(immediate=True)
198
+ del self.full[id]
199
+
200
+ async def _process_message(self, msg):
201
+ """Process a single message with deferred acknowledgment"""
202
+ # Store message for later acknowledgment
203
+ msg_id = str(uuid.uuid4())
204
+ self.pending_acks[msg_id] = msg
205
+
206
+ try:
207
+ id = msg.properties()["id"]
208
+ except:
209
+ id = None
210
+
211
+ value = msg.value()
212
+ delivery_success = False
213
+
214
+ async with self.lock:
215
+ # Deliver to specific subscribers
216
+ if id in self.q:
217
+ delivery_success = await self._deliver_to_queue(
218
+ self.q[id], value
219
+ )
220
+
221
+ # Deliver to all subscribers
222
+ for q in self.full.values():
223
+ if await self._deliver_to_queue(q, value):
224
+ delivery_success = True
225
+
226
+ # Acknowledge only on successful delivery
227
+ if delivery_success:
228
+ self.consumer.acknowledge(msg)
229
+ del self.pending_acks[msg_id]
230
+ else:
231
+ # Negative acknowledge for retry
232
+ self.consumer.negative_acknowledge(msg)
233
+ del self.pending_acks[msg_id]
234
+
235
+ async def _deliver_to_queue(self, queue, value):
236
+ """Deliver message to queue with backpressure handling"""
237
+ try:
238
+ if self.backpressure_strategy == "block":
239
+ # Block until space available (no timeout)
240
+ await queue.put(value)
241
+ return True
242
+
243
+ elif self.backpressure_strategy == "drop_oldest":
244
+ # Drop oldest message if queue full
245
+ if queue.full():
246
+ try:
247
+ queue.get_nowait()
248
+ if self.metrics:
249
+ self.metrics.dropped()
250
+ except asyncio.QueueEmpty:
251
+ pass
252
+ await queue.put(value)
253
+ return True
254
+
255
+ elif self.backpressure_strategy == "drop_new":
256
+ # Drop new message if queue full
257
+ if queue.full():
258
+ if self.metrics:
259
+ self.metrics.dropped()
260
+ return False
261
+ await queue.put(value)
262
+ return True
263
+
264
+ except Exception as e:
265
+ logger.error(f"Failed to deliver message: {e}")
266
+ return False
267
+
@@ -0,0 +1 @@
1
+ __version__ = "1.3.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trustgraph-base
3
- Version: 1.2.20
3
+ Version: 1.3.1
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
  Author-email: "trustgraph.ai" <security@trustgraph.ai>
6
6
  Project-URL: Homepage, https://github.com/trustgraph-ai/trustgraph
@@ -1,79 +0,0 @@
1
-
2
- from pulsar.schema import JsonSchema
3
-
4
- import asyncio
5
- import time
6
- import pulsar
7
- import logging
8
-
9
- # Module logger
10
- logger = logging.getLogger(__name__)
11
-
12
- class Publisher:
13
-
14
- def __init__(self, client, topic, schema=None, max_size=10,
15
- chunking_enabled=True):
16
- self.client = client
17
- self.topic = topic
18
- self.schema = schema
19
- self.q = asyncio.Queue(maxsize=max_size)
20
- self.chunking_enabled = chunking_enabled
21
- self.running = True
22
- self.task = None
23
-
24
- async def start(self):
25
- self.task = asyncio.create_task(self.run())
26
-
27
- async def stop(self):
28
- self.running = False
29
-
30
- if self.task:
31
- await self.task
32
-
33
- async def join(self):
34
- await self.stop()
35
-
36
- if self.task:
37
- await self.task
38
-
39
- async def run(self):
40
-
41
- while self.running:
42
-
43
- try:
44
-
45
- producer = self.client.create_producer(
46
- topic=self.topic,
47
- schema=JsonSchema(self.schema),
48
- chunking_enabled=self.chunking_enabled,
49
- )
50
-
51
- while self.running:
52
-
53
- try:
54
- id, item = await asyncio.wait_for(
55
- self.q.get(),
56
- timeout=0.25
57
- )
58
- except asyncio.TimeoutError:
59
- continue
60
- except asyncio.QueueEmpty:
61
- continue
62
-
63
- if id:
64
- producer.send(item, { "id": id })
65
- else:
66
- producer.send(item)
67
-
68
- except Exception as e:
69
- logger.error(f"Exception in publisher: {e}", exc_info=True)
70
-
71
- if not self.running:
72
- return
73
-
74
- # If handler drops out, sleep a retry
75
- await asyncio.sleep(1)
76
-
77
- async def send(self, id, item):
78
- await self.q.put((id, item))
79
-
@@ -1,182 +0,0 @@
1
-
2
- # Subscriber is similar to consumer: It provides a service to take stuff
3
- # off of a queue and make it available using an internal broker system,
4
- # so suitable for when multiple recipients are reading from the same queue
5
-
6
- from pulsar.schema import JsonSchema
7
- import asyncio
8
- import _pulsar
9
- import time
10
- import logging
11
-
12
- # Module logger
13
- logger = logging.getLogger(__name__)
14
-
15
- class Subscriber:
16
-
17
- def __init__(self, client, topic, subscription, consumer_name,
18
- schema=None, max_size=100, metrics=None):
19
- self.client = client
20
- self.topic = topic
21
- self.subscription = subscription
22
- self.consumer_name = consumer_name
23
- self.schema = schema
24
- self.q = {}
25
- self.full = {}
26
- self.max_size = max_size
27
- self.lock = asyncio.Lock()
28
- self.running = True
29
- self.metrics = metrics
30
- self.task = None
31
-
32
- self.consumer = None
33
-
34
- def __del__(self):
35
-
36
- self.running = False
37
-
38
- async def start(self):
39
-
40
- self.consumer = self.client.subscribe(
41
- topic = self.topic,
42
- subscription_name = self.subscription,
43
- consumer_name = self.consumer_name,
44
- schema = JsonSchema(self.schema),
45
- )
46
-
47
- self.task = asyncio.create_task(self.run())
48
-
49
- async def stop(self):
50
- self.running = False
51
-
52
- if self.task:
53
- await self.task
54
-
55
- async def join(self):
56
- await self.stop()
57
-
58
- if self.task:
59
- await self.task
60
-
61
- async def run(self):
62
-
63
- while self.running:
64
-
65
- if self.metrics:
66
- self.metrics.state("stopped")
67
-
68
- try:
69
-
70
- if self.metrics:
71
- self.metrics.state("running")
72
-
73
- logger.info("Subscriber running...")
74
-
75
- while self.running:
76
-
77
- try:
78
- msg = await asyncio.to_thread(
79
- self.consumer.receive,
80
- timeout_millis=250
81
- )
82
- except _pulsar.Timeout:
83
- continue
84
- except Exception as e:
85
- logger.error(f"Exception in subscriber receive: {e}", exc_info=True)
86
- raise e
87
-
88
- if self.metrics:
89
- self.metrics.received()
90
-
91
- # Acknowledge successful reception of the message
92
- self.consumer.acknowledge(msg)
93
-
94
- try:
95
- id = msg.properties()["id"]
96
- except:
97
- id = None
98
-
99
- value = msg.value()
100
-
101
- async with self.lock:
102
-
103
- # FIXME: Hard-coded timeouts
104
-
105
- if id in self.q:
106
-
107
- try:
108
- # FIXME: Timeout means data goes missing
109
- await asyncio.wait_for(
110
- self.q[id].put(value),
111
- timeout=1
112
- )
113
-
114
- except Exception as e:
115
- self.metrics.dropped()
116
- logger.warning(f"Failed to put message in queue: {e}")
117
-
118
- for q in self.full.values():
119
- try:
120
- # FIXME: Timeout means data goes missing
121
- await asyncio.wait_for(
122
- q.put(value),
123
- timeout=1
124
- )
125
- except Exception as e:
126
- self.metrics.dropped()
127
- logger.warning(f"Failed to put message in full queue: {e}")
128
-
129
- except Exception as e:
130
- logger.error(f"Subscriber exception: {e}", exc_info=True)
131
-
132
- finally:
133
-
134
- if self.consumer:
135
- self.consumer.unsubscribe()
136
- self.consumer.close()
137
- self.consumer = None
138
-
139
-
140
- if self.metrics:
141
- self.metrics.state("stopped")
142
-
143
- if not self.running:
144
- return
145
-
146
- # If handler drops out, sleep a retry
147
- await asyncio.sleep(1)
148
-
149
- async def subscribe(self, id):
150
-
151
- async with self.lock:
152
-
153
- q = asyncio.Queue(maxsize=self.max_size)
154
- self.q[id] = q
155
-
156
- return q
157
-
158
- async def unsubscribe(self, id):
159
-
160
- async with self.lock:
161
-
162
- if id in self.q:
163
- # self.q[id].shutdown(immediate=True)
164
- del self.q[id]
165
-
166
- async def subscribe_all(self, id):
167
-
168
- async with self.lock:
169
-
170
- q = asyncio.Queue(maxsize=self.max_size)
171
- self.full[id] = q
172
-
173
- return q
174
-
175
- async def unsubscribe_all(self, id):
176
-
177
- async with self.lock:
178
-
179
- if id in self.full:
180
- # self.full[id].shutdown(immediate=True)
181
- del self.full[id]
182
-
@@ -1 +0,0 @@
1
- __version__ = "1.2.20"