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.
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/PKG-INFO +1 -1
- trustgraph_base-1.3.1/trustgraph/base/publisher.py +123 -0
- trustgraph_base-1.3.1/trustgraph/base/subscriber.py +267 -0
- trustgraph_base-1.3.1/trustgraph/base_version.py +1 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph_base.egg-info/PKG-INFO +1 -1
- trustgraph_base-1.2.20/trustgraph/base/publisher.py +0 -79
- trustgraph_base-1.2.20/trustgraph/base/subscriber.py +0 -182
- trustgraph_base-1.2.20/trustgraph/base_version.py +0 -1
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/README.md +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/pyproject.toml +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/setup.cfg +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/__init__.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/api.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/config.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/exceptions.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/flow.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/knowledge.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/library.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/api/types.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/__init__.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/agent_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/agent_service.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/async_processor.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/consumer.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/consumer_spec.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/document_embeddings_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/document_embeddings_query_service.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/document_embeddings_store_service.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/embeddings_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/embeddings_service.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/flow.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/flow_processor.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/graph_embeddings_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/graph_embeddings_query_service.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/graph_embeddings_store_service.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/graph_rag_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/llm_service.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/metrics.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/producer.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/producer_spec.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/prompt_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/pubsub.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/request_response_spec.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/setting_spec.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/spec.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/subscriber_spec.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/text_completion_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/tool_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/tool_service.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/triples_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/triples_query_service.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/triples_store_service.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/__init__.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/agent_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/base.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/config_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/document_embeddings_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/document_rag_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/embeddings_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/graph_embeddings_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/graph_rag_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/llm_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/prompt_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/triples_query_client.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/exceptions.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/knowledge/__init__.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/knowledge/defs.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/knowledge/document.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/knowledge/identifier.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/knowledge/organization.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/knowledge/publication.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/log_level.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/__init__.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/registry.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/__init__.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/agent.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/base.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/config.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/document_loading.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/embeddings.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/embeddings_query.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/flow.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/knowledge.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/library.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/metadata.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/primitives.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/prompt.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/retrieval.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/text_completion.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/tool.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/triples.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/objects/__init__.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/objects/field.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/objects/object.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/rdf.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/__init__.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/core/__init__.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/core/metadata.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/core/primitives.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/core/topic.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/__init__.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/document.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/embeddings.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/graph.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/knowledge.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/nlp.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/object.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/rows.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/knowledge/structured.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/__init__.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/agent.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/config.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/flow.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/library.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/llm.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/lookup.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/nlp_query.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/prompt.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/query.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/retrieval.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/structured_query.py +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph_base.egg-info/SOURCES.txt +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph_base.egg-info/dependency_links.txt +0 -0
- {trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph_base.egg-info/requires.txt +0 -0
- {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.
|
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.
|
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"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/document_embeddings_client.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/graph_embeddings_query_service.py
RENAMED
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/base/graph_embeddings_store_service.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/document_embeddings_client.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/clients/graph_embeddings_client.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/embeddings.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/knowledge.py
RENAMED
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/library.py
RENAMED
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/metadata.py
RENAMED
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/primitives.py
RENAMED
File without changes
|
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/retrieval.py
RENAMED
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/text_completion.py
RENAMED
File without changes
|
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/messaging/translators/triples.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph/schema/services/structured_query.py
RENAMED
File without changes
|
File without changes
|
{trustgraph_base-1.2.20 → trustgraph_base-1.3.1}/trustgraph_base.egg-info/dependency_links.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|