solace-agent-mesh 1.5.1__py3-none-any.whl → 1.6.0__py3-none-any.whl

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.

Potentially problematic release.


This version of solace-agent-mesh might be problematic. Click here for more details.

Files changed (180) hide show
  1. solace_agent_mesh/agent/adk/callbacks.py +0 -5
  2. solace_agent_mesh/agent/adk/models/lite_llm.py +123 -8
  3. solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +245 -0
  4. solace_agent_mesh/agent/protocol/event_handlers.py +40 -1
  5. solace_agent_mesh/agent/proxies/__init__.py +0 -0
  6. solace_agent_mesh/agent/proxies/a2a/__init__.py +3 -0
  7. solace_agent_mesh/agent/proxies/a2a/app.py +55 -0
  8. solace_agent_mesh/agent/proxies/a2a/component.py +1115 -0
  9. solace_agent_mesh/agent/proxies/a2a/config.py +140 -0
  10. solace_agent_mesh/agent/proxies/a2a/oauth_token_cache.py +104 -0
  11. solace_agent_mesh/agent/proxies/base/__init__.py +3 -0
  12. solace_agent_mesh/agent/proxies/base/app.py +99 -0
  13. solace_agent_mesh/agent/proxies/base/component.py +619 -0
  14. solace_agent_mesh/agent/proxies/base/config.py +85 -0
  15. solace_agent_mesh/agent/proxies/base/proxy_task_context.py +17 -0
  16. solace_agent_mesh/agent/sac/app.py +9 -3
  17. solace_agent_mesh/agent/sac/component.py +160 -8
  18. solace_agent_mesh/agent/tools/audio_tools.py +125 -8
  19. solace_agent_mesh/agent/tools/web_tools.py +10 -5
  20. solace_agent_mesh/agent/utils/artifact_helpers.py +141 -3
  21. solace_agent_mesh/assets/docs/404.html +3 -3
  22. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +1 -0
  23. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +1 -0
  24. solace_agent_mesh/assets/docs/assets/js/71da7b71.38583438.js +1 -0
  25. solace_agent_mesh/assets/docs/assets/js/77cf947d.48cb18a2.js +1 -0
  26. solace_agent_mesh/assets/docs/assets/js/924ffdeb.8095e148.js +1 -0
  27. solace_agent_mesh/assets/docs/assets/js/9e9d0a82.570c057b.js +1 -0
  28. solace_agent_mesh/assets/docs/assets/js/{ad71b5ed.60668e9e.js → ad71b5ed.af3ecfd1.js} +1 -1
  29. solace_agent_mesh/assets/docs/assets/js/ceb2a7a6.5d92d7d0.js +1 -0
  30. solace_agent_mesh/assets/docs/assets/js/{da0b5bad.9d369087.js → da0b5bad.d08a9466.js} +1 -1
  31. solace_agent_mesh/assets/docs/assets/js/db924877.e98d12a1.js +1 -0
  32. solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +1 -0
  33. solace_agent_mesh/assets/docs/assets/js/e6f9706b.e74a984d.js +1 -0
  34. solace_agent_mesh/assets/docs/assets/js/f284c35a.42f59cdd.js +1 -0
  35. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +1 -0
  36. solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js → main.20feee82.js} +2 -2
  37. solace_agent_mesh/assets/docs/assets/js/runtime~main.0d198646.js +1 -0
  38. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +15 -4
  39. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +4 -4
  40. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +4 -4
  43. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +4 -4
  44. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +4 -4
  45. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +4 -4
  49. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +262 -0
  50. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +3 -3
  51. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +31 -3
  52. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +3 -3
  53. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +3 -3
  54. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +4 -4
  55. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +5 -5
  56. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +4 -4
  57. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +4 -4
  58. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +135 -0
  59. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +6 -4
  60. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +4 -4
  61. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +4 -4
  62. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +4 -4
  63. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +5 -5
  64. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +4 -4
  65. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +4 -4
  66. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +4 -4
  67. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +4 -4
  68. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +4 -4
  69. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +4 -4
  70. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
  71. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +3 -3
  72. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +3 -3
  73. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +3 -3
  74. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +3 -3
  75. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +3 -3
  76. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  77. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
  78. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +6 -5
  79. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +3 -3
  80. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +3 -3
  81. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +100 -3
  82. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +3 -3
  83. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
  84. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
  85. solace_agent_mesh/assets/docs/lunr-index-1761165361160.json +1 -0
  86. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  87. solace_agent_mesh/assets/docs/search-doc-1761165361160.json +1 -0
  88. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  89. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  90. solace_agent_mesh/cli/__init__.py +1 -1
  91. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +2 -69
  92. solace_agent_mesh/cli/commands/eval_cmd.py +11 -49
  93. solace_agent_mesh/cli/commands/init_cmd/__init__.py +0 -5
  94. solace_agent_mesh/cli/commands/init_cmd/env_step.py +10 -12
  95. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +9 -61
  96. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +9 -49
  97. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +1 -2
  98. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-DwrxZE0E.js → authCallback-BTf6dqwp.js} +1 -1
  99. solace_agent_mesh/client/webui/frontend/static/assets/{client-DarGQzyw.js → client-CaY59VuC.js} +1 -1
  100. solace_agent_mesh/client/webui/frontend/static/assets/main-BGTaW0uv.js +342 -0
  101. solace_agent_mesh/client/webui/frontend/static/assets/main-DHJKSW1S.css +1 -0
  102. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-BKIeiHj_.js → vendor-BEmvJSYz.js} +1 -1
  103. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  104. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  105. solace_agent_mesh/common/a2a/__init__.py +24 -0
  106. solace_agent_mesh/common/a2a/artifact.py +39 -0
  107. solace_agent_mesh/common/a2a/events.py +29 -0
  108. solace_agent_mesh/common/a2a/message.py +68 -0
  109. solace_agent_mesh/common/a2a/protocol.py +73 -1
  110. solace_agent_mesh/common/agent_registry.py +83 -3
  111. solace_agent_mesh/common/constants.py +3 -1
  112. solace_agent_mesh/common/utils/pydantic_utils.py +12 -0
  113. solace_agent_mesh/config_portal/backend/common.py +1 -1
  114. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-ByU1X1HD.js +98 -0
  115. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-44d62be6.js → manifest-61038fc6.js} +1 -1
  116. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  117. solace_agent_mesh/evaluation/evaluator.py +128 -104
  118. solace_agent_mesh/evaluation/message_organizer.py +116 -110
  119. solace_agent_mesh/evaluation/report_data_processor.py +84 -86
  120. solace_agent_mesh/evaluation/report_generator.py +73 -79
  121. solace_agent_mesh/evaluation/run.py +421 -235
  122. solace_agent_mesh/evaluation/shared/__init__.py +92 -0
  123. solace_agent_mesh/evaluation/shared/constants.py +47 -0
  124. solace_agent_mesh/evaluation/shared/exceptions.py +50 -0
  125. solace_agent_mesh/evaluation/shared/helpers.py +35 -0
  126. solace_agent_mesh/evaluation/shared/test_case_loader.py +167 -0
  127. solace_agent_mesh/evaluation/shared/test_suite_loader.py +280 -0
  128. solace_agent_mesh/evaluation/subscriber.py +111 -232
  129. solace_agent_mesh/evaluation/summary_builder.py +227 -117
  130. solace_agent_mesh/gateway/base/app.py +1 -1
  131. solace_agent_mesh/gateway/base/component.py +8 -1
  132. solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py +70 -0
  133. solace_agent_mesh/gateway/http_sse/component.py +98 -2
  134. solace_agent_mesh/gateway/http_sse/dependencies.py +4 -4
  135. solace_agent_mesh/gateway/http_sse/main.py +2 -1
  136. solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +12 -13
  137. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +15 -18
  138. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +25 -18
  139. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +30 -26
  140. solace_agent_mesh/gateway/http_sse/repository/task_repository.py +35 -44
  141. solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +4 -3
  142. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +95 -203
  143. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +4 -3
  144. solace_agent_mesh/gateway/http_sse/routers/sessions.py +2 -2
  145. solace_agent_mesh/gateway/http_sse/routers/tasks.py +33 -41
  146. solace_agent_mesh/gateway/http_sse/routers/visualization.py +17 -11
  147. solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +4 -4
  148. solace_agent_mesh/gateway/http_sse/services/feedback_service.py +51 -43
  149. solace_agent_mesh/gateway/http_sse/services/session_service.py +20 -20
  150. solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +8 -8
  151. solace_agent_mesh/gateway/http_sse/shared/base_repository.py +45 -71
  152. solace_agent_mesh/gateway/http_sse/shared/types.py +0 -18
  153. solace_agent_mesh/templates/gateway_config_template.yaml +0 -5
  154. solace_agent_mesh/templates/logging_config_template.ini +10 -6
  155. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +0 -3
  156. solace_agent_mesh/templates/shared_config.yaml +40 -0
  157. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/METADATA +47 -21
  158. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/RECORD +162 -141
  159. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.e49689dd.js +0 -1
  160. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.39d5851d.js +0 -1
  161. solace_agent_mesh/assets/docs/assets/js/71da7b71.804d6567.js +0 -1
  162. solace_agent_mesh/assets/docs/assets/js/77cf947d.64c9bd6c.js +0 -1
  163. solace_agent_mesh/assets/docs/assets/js/9e9d0a82.dd810042.js +0 -1
  164. solace_agent_mesh/assets/docs/assets/js/db924877.cbc66f02.js +0 -1
  165. solace_agent_mesh/assets/docs/assets/js/de915948.139b4b9c.js +0 -1
  166. solace_agent_mesh/assets/docs/assets/js/e6f9706b.582a78ca.js +0 -1
  167. solace_agent_mesh/assets/docs/assets/js/f284c35a.5766a13d.js +0 -1
  168. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.9c0297a6.js +0 -1
  169. solace_agent_mesh/assets/docs/assets/js/runtime~main.18dc45dd.js +0 -1
  170. solace_agent_mesh/assets/docs/lunr-index-1760121512891.json +0 -1
  171. solace_agent_mesh/assets/docs/search-doc-1760121512891.json +0 -1
  172. solace_agent_mesh/client/webui/frontend/static/assets/main-2nd1gbaH.js +0 -339
  173. solace_agent_mesh/client/webui/frontend/static/assets/main-DoKXctCM.css +0 -1
  174. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-BNuqpWDc.js +0 -98
  175. solace_agent_mesh/evaluation/config_loader.py +0 -657
  176. solace_agent_mesh/evaluation/test_case_loader.py +0 -714
  177. /solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js.LICENSE.txt → main.20feee82.js.LICENSE.txt} +0 -0
  178. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/WHEEL +0 -0
  179. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/entry_points.txt +0 -0
  180. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,129 +3,47 @@ Refactored message subscriber with improved structure and readability.
3
3
  This module handles Solace message subscription and processing for evaluation.
4
4
  """
5
5
 
6
- import os
7
6
  import json
7
+ import logging
8
+ import os
8
9
  import threading
9
10
  import time
10
- from typing import Dict, List, Optional, Any, Set, Callable
11
11
  from dataclasses import dataclass, field
12
- from enum import Enum
13
- import logging
14
- from dotenv import load_dotenv
12
+ from pathlib import Path
15
13
 
14
+ from dotenv import load_dotenv
16
15
  from solace.messaging.messaging_service import MessagingService
17
16
  from solace.messaging.resources.topic_subscription import TopicSubscription
18
- from solace.messaging.config.solace_properties import (
19
- transport_layer_properties,
20
- service_properties,
21
- authentication_properties,
22
- transport_layer_security_properties,
17
+
18
+ from .shared import (
19
+ ALLOWED_TOPIC_INFIXES,
20
+ BLOCKED_TOPIC_INFIXES,
21
+ MESSAGE_TIMEOUT,
22
+ BrokerConfig,
23
+ BrokerConnectionError,
24
+ ConfigurationError,
25
+ ConnectionState,
26
+ MessageProcessingError,
23
27
  )
24
28
 
25
- # Set up logging
26
- logging.basicConfig(level=logging.INFO)
27
- logger = logging.getLogger(__name__)
29
+ log = logging.getLogger(__name__)
28
30
 
29
31
  # Load environment variables
30
32
  load_dotenv()
31
33
 
32
34
 
33
- class ConnectionState(Enum):
34
- """Represents the connection state of the subscriber."""
35
-
36
- DISCONNECTED = "disconnected"
37
- CONNECTING = "connecting"
38
- CONNECTED = "connected"
39
- DISCONNECTING = "disconnecting"
40
- ERROR = "error"
41
-
42
-
43
- class SubscriberError(Exception):
44
- """Base exception for subscriber-related errors."""
45
-
46
- pass
47
-
48
-
49
- class BrokerConnectionError(SubscriberError):
50
- """Raised when broker connection fails."""
51
-
52
- pass
53
-
54
-
55
- class MessageProcessingError(SubscriberError):
56
- """Raised when message processing fails."""
57
-
58
- pass
59
-
60
-
61
- class ConfigurationError(SubscriberError):
62
- """Raised when configuration is invalid."""
63
-
64
- pass
65
-
66
-
67
- @dataclass
68
- class BrokerConfig:
69
- """Broker connection configuration with validation."""
70
-
71
- host: str
72
- vpn_name: str
73
- username: str
74
- password: str
75
- cert_validated: bool = False
76
- connection_timeout: int = 30
77
- reconnect_attempts: int = 3
78
- reconnect_delay: float = 1.0
79
-
80
- def __post_init__(self):
81
- """Validate broker configuration after initialization."""
82
- if not self.host or not self.host.strip():
83
- raise ConfigurationError("Broker host cannot be empty")
84
-
85
- if not self.vpn_name or not self.vpn_name.strip():
86
- raise ConfigurationError("VPN name cannot be empty")
87
-
88
- if not self.username or not self.username.strip():
89
- raise ConfigurationError("Username cannot be empty")
90
-
91
- if not self.password or not self.password.strip():
92
- raise ConfigurationError("Password cannot be empty")
93
-
94
- if self.connection_timeout <= 0:
95
- raise ConfigurationError("Connection timeout must be positive")
96
-
97
- if self.reconnect_attempts < 0:
98
- raise ConfigurationError("Reconnect attempts cannot be negative")
99
-
100
- def to_solace_properties(self) -> Dict[str, Any]:
101
- """Convert to Solace messaging properties."""
102
- return {
103
- transport_layer_properties.HOST: self.host,
104
- service_properties.VPN_NAME: self.vpn_name,
105
- authentication_properties.SCHEME_BASIC_USER_NAME: self.username,
106
- authentication_properties.SCHEME_BASIC_PASSWORD: self.password,
107
- transport_layer_security_properties.CERT_VALIDATED: self.cert_validated,
108
- }
109
-
110
-
111
35
  @dataclass
112
36
  class SubscriptionConfig:
113
37
  """Subscription configuration and topic filters."""
114
38
 
115
39
  namespace: str
116
- allowed_topic_infixes: List[str] = field(
117
- default_factory=lambda: [
118
- "/agent/request/",
119
- "/gateway/status/",
120
- "/gateway/response/",
121
- ]
40
+ allowed_topic_infixes: list[str] = field(
41
+ default_factory=lambda: ALLOWED_TOPIC_INFIXES
122
42
  )
123
- blocked_topic_infixes: List[str] = field(
124
- default_factory=lambda: [
125
- "/discovery/"
126
- ]
43
+ blocked_topic_infixes: list[str] = field(
44
+ default_factory=lambda: BLOCKED_TOPIC_INFIXES
127
45
  )
128
- message_timeout: int = 1000
46
+ message_timeout: int = MESSAGE_TIMEOUT
129
47
  filter_non_final_status: bool = True
130
48
  remove_config_keys: bool = False
131
49
 
@@ -148,9 +66,7 @@ class SubscriptionConfig:
148
66
  def is_topic_allowed(self, topic: str) -> bool:
149
67
  """Check if a topic is allowed based on configured infixes."""
150
68
  # return any(infix in topic for infix in self.allowed_topic_infixes)
151
- if any(infix in topic for infix in self.blocked_topic_infixes):
152
- return False
153
- return True
69
+ return not any(infix in topic for infix in self.blocked_topic_infixes)
154
70
 
155
71
 
156
72
  @dataclass
@@ -158,11 +74,11 @@ class ProcessedMessage:
158
74
  """Structured representation of a processed message."""
159
75
 
160
76
  topic: str
161
- payload: Any
77
+ payload: any
162
78
  timestamp: float = field(default_factory=time.time)
163
- message_type: Optional[str] = None
79
+ message_type: str | None = None
164
80
 
165
- def to_dict(self) -> Dict[str, Any]:
81
+ def to_dict(self) -> dict[str, any]:
166
82
  """Convert to dictionary for JSON serialization."""
167
83
  return {
168
84
  "topic": self.topic,
@@ -185,7 +101,7 @@ class MessageSanitizer:
185
101
  """Handles message sanitization and cleaning."""
186
102
 
187
103
  @staticmethod
188
- def remove_key_recursive(obj: Any, key_to_remove: str) -> None:
104
+ def remove_key_recursive(obj: any, key_to_remove: str) -> None:
189
105
  """
190
106
  Recursively remove a key from nested dictionaries and lists.
191
107
 
@@ -206,10 +122,10 @@ class MessageSanitizer:
206
122
  for item in obj:
207
123
  MessageSanitizer.remove_key_recursive(item, key_to_remove)
208
124
  except Exception as e:
209
- logger.warning(f"Error during key removal: {e}")
125
+ log.warning(f"Error during key removal: {e}")
210
126
 
211
127
  @staticmethod
212
- def sanitize_message(payload: Any, remove_config: bool = True) -> Any:
128
+ def sanitize_message(payload: any, remove_config: bool = True) -> any:
213
129
  """
214
130
  Sanitize message payload by removing unwanted keys.
215
131
 
@@ -239,7 +155,7 @@ class MessageProcessor:
239
155
  self.processed_count = 0
240
156
  self.error_count = 0
241
157
 
242
- def process_message(self, inbound_message) -> Optional[ProcessedMessage]:
158
+ def process_message(self, inbound_message) -> ProcessedMessage | None:
243
159
  """
244
160
  Process an inbound message and return a ProcessedMessage if valid.
245
161
 
@@ -280,10 +196,10 @@ class MessageProcessor:
280
196
 
281
197
  except Exception as e:
282
198
  self.error_count += 1
283
- logger.warning(f"Error processing message: {e}")
199
+ log.warning(f"Error processing message: {e}")
284
200
  return None
285
201
 
286
- def _extract_payload(self, inbound_message) -> Optional[Any]:
202
+ def _extract_payload(self, inbound_message):
287
203
  """Extract and parse payload from inbound message."""
288
204
  try:
289
205
  payload_bytes = inbound_message.get_payload_as_bytes()
@@ -300,10 +216,10 @@ class MessageProcessor:
300
216
  return payload_str
301
217
 
302
218
  except Exception as e:
303
- logger.warning(f"Error extracting payload: {e}")
219
+ log.warning(f"Error extracting payload: {e}")
304
220
  return None
305
221
 
306
- def _should_filter_status_message(self, topic: str, payload: Any) -> bool:
222
+ def _should_filter_status_message(self, topic: str, payload: any) -> bool:
307
223
  """Check if a status message should be filtered out."""
308
224
  if not self.config.filter_non_final_status:
309
225
  return False
@@ -325,12 +241,12 @@ class MessageProcessor:
325
241
 
326
242
  return False
327
243
 
328
- def _find_part_type(self, data: Any, type_to_find: str) -> bool:
244
+ def _find_part_type(self, data: any, type_to_find: str) -> bool:
329
245
  """Recursively search for a part with a specific type."""
330
246
  if isinstance(data, dict):
331
247
  if data.get("type") == type_to_find:
332
248
  return True
333
- for key, value in data.items():
249
+ for _key, value in data.items():
334
250
  if self._find_part_type(value, type_to_find):
335
251
  return True
336
252
  elif isinstance(data, list):
@@ -350,7 +266,7 @@ class MessageProcessor:
350
266
  else:
351
267
  return "unknown"
352
268
 
353
- def get_stats(self) -> Dict[str, int]:
269
+ def get_stats(self) -> dict[str, int]:
354
270
  """Get processing statistics."""
355
271
  return {
356
272
  "processed_count": self.processed_count,
@@ -362,14 +278,14 @@ class TaskTracker:
362
278
  """Tracks task completion and manages active tasks."""
363
279
 
364
280
  def __init__(
365
- self, active_tasks: Set[str], wave_complete_event: Optional[threading.Event]
281
+ self, active_tasks: set[str], wave_complete_event: threading.Event | None
366
282
  ):
367
283
  self.active_tasks = active_tasks
368
284
  self.wave_complete_event = wave_complete_event
369
- self.completed_tasks: List[TaskCompletionEvent] = []
285
+ self.completed_tasks: list[TaskCompletionEvent] = []
370
286
  self._lock = threading.Lock()
371
287
 
372
- def handle_task_completion(self, topic: str) -> Optional[TaskCompletionEvent]:
288
+ def handle_task_completion(self, topic: str) -> TaskCompletionEvent | None:
373
289
  """
374
290
  Handle task completion based on topic.
375
291
 
@@ -389,7 +305,7 @@ class TaskTracker:
389
305
 
390
306
  with self._lock:
391
307
  if task_id in self.active_tasks:
392
- logger.info(f"Task {task_id} completed")
308
+ log.info(f"Task {task_id} completed")
393
309
  self.active_tasks.remove(task_id)
394
310
 
395
311
  completion_event = TaskCompletionEvent(task_id=task_id, topic=topic)
@@ -397,17 +313,17 @@ class TaskTracker:
397
313
 
398
314
  # Check if all tasks are complete
399
315
  if not self.active_tasks and self.wave_complete_event:
400
- logger.info("All tasks completed, setting wave complete event")
316
+ log.info("All tasks completed, setting wave complete event")
401
317
  self.wave_complete_event.set()
402
318
 
403
319
  return completion_event
404
320
 
405
321
  except Exception as e:
406
- logger.error(f"Error handling task completion: {e}")
322
+ log.error(f"Error handling task completion: {e}")
407
323
 
408
324
  return None
409
325
 
410
- def _extract_task_id(self, topic: str) -> Optional[str]:
326
+ def _extract_task_id(self, topic: str) -> str | None:
411
327
  """Extract task ID from topic."""
412
328
  try:
413
329
  return topic.split("/")[-1]
@@ -428,9 +344,9 @@ class TaskTracker:
428
344
  class MessageStorage:
429
345
  """Handles message storage and file operations."""
430
346
 
431
- def __init__(self, results_path: str):
432
- self.results_path = results_path
433
- self.messages: List[ProcessedMessage] = []
347
+ def __init__(self, results_path: str | Path):
348
+ self.results_path = Path(results_path)
349
+ self.messages: list[ProcessedMessage] = []
434
350
  self._lock = threading.Lock()
435
351
 
436
352
  def add_message(self, message: ProcessedMessage) -> None:
@@ -453,25 +369,25 @@ class MessageStorage:
453
369
  Returns:
454
370
  The full path to the saved file
455
371
  """
456
- output_file = os.path.join(self.results_path, filename)
372
+ output_file = self.results_path / filename
457
373
 
458
374
  try:
459
375
  # Ensure directory exists
460
- os.makedirs(os.path.dirname(output_file), exist_ok=True)
376
+ output_file.parent.mkdir(parents=True, exist_ok=True)
461
377
 
462
378
  with self._lock:
463
379
  # Convert messages to dictionaries for JSON serialization
464
380
  message_dicts = [msg.to_dict() for msg in self.messages]
465
381
 
466
- with open(output_file, "w") as f:
382
+ with output_file.open("w") as f:
467
383
  json.dump(message_dicts, f, indent=4)
468
384
 
469
- logger.info(f"Saved {len(message_dicts)} messages to {output_file}")
470
- return output_file
385
+ log.info(f"Saved {len(message_dicts)} messages to {output_file}")
386
+ return str(output_file)
471
387
 
472
388
  except Exception as e:
473
- logger.error(f"Error saving messages: {e}")
474
- raise MessageProcessingError(f"Failed to save messages: {e}")
389
+ log.error(f"Error saving messages: {e}")
390
+ raise MessageProcessingError(f"Failed to save messages: {e}") from e
475
391
 
476
392
  def clear_messages(self) -> None:
477
393
  """Clear all stored messages."""
@@ -484,7 +400,7 @@ class BrokerConnectionService:
484
400
 
485
401
  def __init__(self, config: BrokerConfig):
486
402
  self.config = config
487
- self.messaging_service: Optional[MessagingService] = None
403
+ self.messaging_service: MessagingService | None = None
488
404
  self.connection_state = ConnectionState.DISCONNECTED
489
405
  self._connection_lock = threading.Lock()
490
406
 
@@ -492,13 +408,13 @@ class BrokerConnectionService:
492
408
  """Connect to the Solace broker."""
493
409
  with self._connection_lock:
494
410
  if self.connection_state == ConnectionState.CONNECTED:
495
- logger.warning("Already connected to broker")
411
+ log.warning("Already connected to broker")
496
412
  return
497
413
 
498
414
  self.connection_state = ConnectionState.CONNECTING
499
415
 
500
416
  try:
501
- logger.info("Connecting to Solace PubSub+ Broker...")
417
+ log.info("Connecting to Solace PubSub+ Broker...")
502
418
 
503
419
  broker_props = self.config.to_solace_properties()
504
420
  self.messaging_service = (
@@ -507,37 +423,37 @@ class BrokerConnectionService:
507
423
  self.messaging_service.connect()
508
424
 
509
425
  self.connection_state = ConnectionState.CONNECTED
510
- logger.info("Successfully connected to broker")
426
+ log.info("Successfully connected to broker")
511
427
 
512
428
  except Exception as e:
513
429
  self.connection_state = ConnectionState.ERROR
514
- logger.error(f"Failed to connect to broker: {e}")
515
- raise BrokerConnectionError(f"Connection failed: {e}")
430
+ log.error(f"Failed to connect to broker: {e}")
431
+ raise BrokerConnectionError(f"Connection failed: {e}") from e
516
432
 
517
433
  def disconnect(self) -> None:
518
434
  """Disconnect from the Solace broker."""
519
435
  with self._connection_lock:
520
436
  if self.connection_state == ConnectionState.DISCONNECTED:
521
- logger.warning("Already disconnected from broker")
437
+ log.warning("Already disconnected from broker")
522
438
  return
523
439
 
524
440
  self.connection_state = ConnectionState.DISCONNECTING
525
441
 
526
442
  try:
527
443
  if self.messaging_service:
528
- logger.info("Disconnecting from broker...")
444
+ log.info("Disconnecting from broker...")
529
445
  self.messaging_service.disconnect()
530
446
  self.messaging_service = None
531
447
 
532
448
  self.connection_state = ConnectionState.DISCONNECTED
533
- logger.info("Successfully disconnected from broker")
449
+ log.info("Successfully disconnected from broker")
534
450
 
535
451
  except Exception as e:
536
452
  self.connection_state = ConnectionState.ERROR
537
- logger.error(f"Error during disconnect: {e}")
538
- raise BrokerConnectionError(f"Disconnect failed: {e}")
453
+ log.error(f"Error during disconnect: {e}")
454
+ raise BrokerConnectionError(f"Disconnect failed: {e}") from e
539
455
 
540
- def get_messaging_service(self) -> Optional[MessagingService]:
456
+ def get_messaging_service(self) -> MessagingService | None:
541
457
  """Get the messaging service instance."""
542
458
  return self.messaging_service
543
459
 
@@ -563,12 +479,12 @@ class SubscriptionManager:
563
479
  self._receiver_lock = threading.Lock()
564
480
 
565
481
  def start_subscription(
566
- self, subscription_ready_event: Optional[threading.Event] = None
482
+ self, subscription_ready_event: threading.Event | None = None
567
483
  ) -> None:
568
484
  """Start message subscription."""
569
485
  with self._receiver_lock:
570
486
  if self.subscription_active:
571
- logger.warning("Subscription already active")
487
+ log.warning("Subscription already active")
572
488
  return
573
489
 
574
490
  if not self.connection_service.is_connected():
@@ -592,17 +508,17 @@ class SubscriptionManager:
592
508
  self.message_receiver.add_subscription(subscription)
593
509
 
594
510
  self.subscription_active = True
595
- logger.info(f"Started subscription to: {self.config.topic_pattern}")
511
+ log.info(f"Started subscription to: {self.config.topic_pattern}")
596
512
 
597
513
  # Signal that subscription is ready
598
514
  if subscription_ready_event:
599
515
  subscription_ready_event.set()
600
516
 
601
517
  except Exception as e:
602
- logger.error(f"Failed to start subscription: {e}")
603
- raise BrokerConnectionError(f"Subscription failed: {e}")
518
+ log.error(f"Failed to start subscription: {e}")
519
+ raise BrokerConnectionError(f"Subscription failed: {e}") from e
604
520
 
605
- def receive_message(self, timeout: Optional[int] = None):
521
+ def receive_message(self, timeout: int | None = None):
606
522
  """
607
523
  Receive a message from the subscription.
608
524
 
@@ -620,7 +536,7 @@ class SubscriptionManager:
620
536
  try:
621
537
  return self.message_receiver.receive_message(timeout=timeout_ms)
622
538
  except Exception as e:
623
- logger.warning(f"Error receiving message: {e}")
539
+ log.warning(f"Error receiving message: {e}")
624
540
  return None
625
541
 
626
542
  def stop_subscription(self) -> None:
@@ -631,51 +547,51 @@ class SubscriptionManager:
631
547
 
632
548
  try:
633
549
  if self.message_receiver:
634
- logger.info("Stopping message receiver...")
550
+ log.info("Stopping message receiver...")
635
551
  self.message_receiver.terminate()
636
552
  self.message_receiver = None
637
553
 
638
554
  self.subscription_active = False
639
- logger.info("Subscription stopped")
555
+ log.info("Subscription stopped")
640
556
 
641
557
  except Exception as e:
642
- logger.error(f"Error stopping subscription: {e}")
558
+ log.error(f"Error stopping subscription: {e}")
643
559
 
644
560
  def is_active(self) -> bool:
645
561
  """Check if subscription is active."""
646
562
  return self.subscription_active
647
563
 
648
564
 
649
- class MessageSubscriber(threading.Thread):
565
+ class Subscriber(threading.Thread):
650
566
  """
651
567
  Main message subscriber class that orchestrates all components.
652
-
653
568
  This is the refactored version of the original Subscriber class,
654
569
  maintaining the same interface while providing better structure.
655
570
  """
656
571
 
657
572
  def __init__(
658
573
  self,
574
+ broker_config: BrokerConfig,
659
575
  namespace: str,
660
- active_tasks: Set[str],
661
- wave_complete_event: Optional[threading.Event],
662
- subscription_ready_event: Optional[threading.Event],
576
+ active_tasks: set[str],
577
+ wave_complete_event: threading.Event | None,
578
+ subscription_ready_event: threading.Event | None,
663
579
  results_path: str,
664
580
  ):
665
581
  """
666
582
  Initialize the message subscriber.
667
-
668
583
  Args:
584
+ broker_config: The broker configuration object
669
585
  namespace: The namespace for topic subscription
670
586
  active_tasks: Set of active task IDs to track
671
587
  wave_complete_event: Event to set when all tasks complete
672
588
  subscription_ready_event: Event to set when subscription is ready
673
589
  results_path: Path to save results
674
590
  """
675
- super().__init__(name="MessageSubscriber")
591
+ super().__init__(name="Subscriber")
676
592
 
677
593
  # Initialize configuration
678
- self.broker_config = self._create_broker_config()
594
+ self.broker_config = broker_config
679
595
  self.subscription_config = SubscriptionConfig(namespace=namespace)
680
596
 
681
597
  # Initialize services
@@ -696,24 +612,11 @@ class MessageSubscriber(threading.Thread):
696
612
  self.messages_received = 0
697
613
  self.messages_processed = 0
698
614
 
699
- def _create_broker_config(self) -> BrokerConfig:
700
- """Create broker configuration from environment variables."""
701
- try:
702
- return BrokerConfig(
703
- host=os.environ.get("SOLACE_BROKER_URL", ""),
704
- vpn_name=os.environ.get("SOLACE_BROKER_VPN", ""),
705
- username=os.environ.get("SOLACE_BROKER_USERNAME", ""),
706
- password=os.environ.get("SOLACE_BROKER_PASSWORD", ""),
707
- )
708
- except ConfigurationError as e:
709
- logger.error(f"Invalid broker configuration: {e}")
710
- raise
711
-
712
615
  def run(self) -> None:
713
616
  """Main thread execution method."""
714
617
  try:
715
618
  self._running = True
716
- logger.info("Starting message subscriber...")
619
+ log.info("Starting message subscriber...")
717
620
 
718
621
  # Connect to broker
719
622
  self.connection_service.connect()
@@ -725,13 +628,13 @@ class MessageSubscriber(threading.Thread):
725
628
  self._message_processing_loop()
726
629
 
727
630
  except Exception as e:
728
- logger.error(f"Error in subscriber thread: {e}")
631
+ log.error(f"Error in subscriber thread: {e}")
729
632
  finally:
730
633
  self._cleanup()
731
634
 
732
635
  def _message_processing_loop(self) -> None:
733
636
  """Main message processing loop."""
734
- logger.info("Starting message processing loop...")
637
+ log.info("Starting message processing loop...")
735
638
 
736
639
  while self._running:
737
640
  try:
@@ -744,7 +647,7 @@ class MessageSubscriber(threading.Thread):
744
647
 
745
648
  except Exception as e:
746
649
  if self._running:
747
- logger.error(f"Error in message processing loop: {e}")
650
+ log.error(f"Error in message processing loop: {e}")
748
651
  # Continue processing other messages
749
652
  continue
750
653
 
@@ -764,11 +667,11 @@ class MessageSubscriber(threading.Thread):
764
667
  self.task_tracker.handle_task_completion(processed_message.topic)
765
668
 
766
669
  except Exception as e:
767
- logger.warning(f"Error handling message: {e}")
670
+ log.warning(f"Error handling message: {e}")
768
671
 
769
672
  def stop(self) -> None:
770
673
  """Stop the subscriber and clean up resources."""
771
- logger.info("Stopping message subscriber...")
674
+ log.info("Stopping message subscriber...")
772
675
  self._running = False
773
676
 
774
677
  def _cleanup(self) -> None:
@@ -787,68 +690,37 @@ class MessageSubscriber(threading.Thread):
787
690
  self._log_final_statistics()
788
691
 
789
692
  except Exception as e:
790
- logger.error(f"Error during cleanup: {e}")
693
+ log.error(f"Error during cleanup: {e}")
791
694
 
792
695
  def _log_final_statistics(self) -> None:
793
696
  """Log final processing statistics."""
794
697
  runtime = time.time() - self.start_time
795
698
  processor_stats = self.message_processor.get_stats()
796
699
 
797
- logger.info("=== SUBSCRIBER STATISTICS ===")
798
- logger.info(f"Runtime: {runtime:.2f} seconds")
799
- logger.info(f"Messages received: {self.messages_received}")
800
- logger.info(f"Messages processed: {self.messages_processed}")
801
- logger.info(f"Messages stored: {self.message_storage.get_message_count()}")
802
- logger.info(f"Processing errors: {processor_stats['error_count']}")
803
- logger.info(
700
+ log.info("=== SUBSCRIBER STATISTICS ===")
701
+ log.info(f"Runtime: {runtime:.2f} seconds")
702
+ log.info(f"Messages received: {self.messages_received}")
703
+ log.info(f"Messages processed: {self.messages_processed}")
704
+ log.info(f"Messages stored: {self.message_storage.get_message_count()}")
705
+ log.info(f"Processing errors: {processor_stats['error_count']}")
706
+ log.info(
804
707
  f"Active tasks remaining: {self.task_tracker.get_active_task_count()}"
805
708
  )
806
- logger.info(f"Tasks completed: {self.task_tracker.get_completed_task_count()}")
807
- logger.info("=============================")
709
+ log.info(f"Tasks completed: {self.task_tracker.get_completed_task_count()}")
710
+ log.info("=============================")
808
711
 
809
712
  # Backward compatibility properties
810
713
  @property
811
- def active_tasks(self) -> Set[str]:
714
+ def active_tasks(self) -> set[str]:
812
715
  """Get active tasks set for backward compatibility."""
813
716
  return self.task_tracker.active_tasks
814
717
 
815
718
  @property
816
- def messages(self) -> List[Dict[str, Any]]:
719
+ def messages(self) -> list[dict[str, any]]:
817
720
  """Get messages list for backward compatibility."""
818
721
  return [msg.to_dict() for msg in self.message_storage.messages]
819
722
 
820
723
 
821
- # Backward compatibility alias
822
- Subscriber = MessageSubscriber
823
-
824
-
825
- def create_subscriber_from_env(
826
- namespace: str,
827
- active_tasks: Set[str],
828
- wave_complete_event: Optional[threading.Event] = None,
829
- subscription_ready_event: Optional[threading.Event] = None,
830
- results_path: str = ".",
831
- ) -> MessageSubscriber:
832
- """
833
- Factory function to create a subscriber with environment-based configuration.
834
-
835
- Args:
836
- namespace: The namespace for topic subscription
837
- active_tasks: Set of active task IDs to track
838
- wave_complete_event: Event to set when all tasks complete
839
- subscription_ready_event: Event to set when subscription is ready
840
- results_path: Path to save results
841
-
842
- Returns:
843
- Configured MessageSubscriber instance
844
- """
845
- return MessageSubscriber(
846
- namespace=namespace,
847
- active_tasks=active_tasks,
848
- wave_complete_event=wave_complete_event,
849
- subscription_ready_event=subscription_ready_event,
850
- results_path=results_path,
851
- )
852
724
 
853
725
 
854
726
  def main():
@@ -858,7 +730,7 @@ def main():
858
730
 
859
731
  # Set up signal handling for graceful shutdown
860
732
  def signal_handler(signum, frame):
861
- print("\nShutting down subscriber...")
733
+ log.info("\nShutting down subscriber...")
862
734
  if "subscriber" in locals():
863
735
  subscriber.stop()
864
736
  subscriber.join()
@@ -872,7 +744,14 @@ def main():
872
744
  subscription_ready = threading.Event()
873
745
 
874
746
  try:
875
- subscriber = create_subscriber_from_env(
747
+ broker_config = BrokerConfig(
748
+ host=os.environ.get("SOLACE_BROKER_URL", ""),
749
+ vpn_name=os.environ.get("SOLACE_BROKER_VPN", ""),
750
+ username=os.environ.get("SOLACE_BROKER_USERNAME", ""),
751
+ password=os.environ.get("SOLACE_BROKER_PASSWORD", ""),
752
+ )
753
+ subscriber = Subscriber(
754
+ broker_config=broker_config,
876
755
  namespace="test",
877
756
  active_tasks=active_tasks,
878
757
  subscription_ready_event=subscription_ready,
@@ -883,13 +762,13 @@ def main():
883
762
 
884
763
  # Wait for subscription to be ready
885
764
  subscription_ready.wait(timeout=30)
886
- print("Subscriber is ready and running...")
765
+ log.info("Subscriber is ready and running...")
887
766
 
888
767
  # Keep running until interrupted
889
768
  subscriber.join()
890
769
 
891
770
  except Exception as e:
892
- print(f"Error running subscriber: {e}")
771
+ log.error(f"Error running subscriber: {e}")
893
772
  sys.exit(1)
894
773
 
895
774