quantum-framework 0.9.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.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +417 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +527 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +547 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2611 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +148 -0
- quantum/core/expressions.py +591 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_fetching/src/__init__.py +21 -0
- quantum/core/features/data_fetching/src/ast_node.py +312 -0
- quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
- quantum/core/features/data_fetching/src/html_adapter.py +474 -0
- quantum/core/features/data_fetching/src/parser.py +225 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +291 -0
- quantum/core/features/data_import/src/runtime.py +538 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +146 -0
- quantum/core/features/invocation/src/runtime.py +327 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/database_service.py +322 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +228 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
- quantum/core/features/ui_engine/src/parser.py +1106 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1235 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +68 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +89 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +217 -0
- quantum/core/parsers/data/invoke_parser.py +116 -0
- quantum/core/parsers/data/query_parser.py +188 -0
- quantum/core/parsers/data/transaction_parser.py +77 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +54 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +137 -0
- quantum/core/parsers/functions/param_parser.py +66 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/persistence/__init__.py +9 -0
- quantum/core/parsers/persistence/persist_parser.py +64 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +82 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +28 -0
- quantum/runtime/action_handler.py +443 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/api_server.py +86 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +990 -0
- quantum/runtime/component_composer.py +319 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/database_service.py +598 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +286 -0
- quantum/runtime/executor_registry.py +171 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +262 -0
- quantum/runtime/executors/control_flow/__init__.py +11 -0
- quantum/runtime/executors/control_flow/if_executor.py +93 -0
- quantum/runtime/executors/control_flow/loop_executor.py +307 -0
- quantum/runtime/executors/control_flow/set_executor.py +412 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +145 -0
- quantum/runtime/executors/data/invoke_executor.py +176 -0
- quantum/runtime/executors/data/query_executor.py +256 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +500 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/logging_setup.py +140 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +184 -0
- quantum/runtime/pypy_compat.py +315 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +733 -0
- quantum/runtime/service_container.py +444 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +188 -0
- quantum/runtime/ui_desktop_adapter.py +1730 -0
- quantum/runtime/ui_desktop_templates.py +307 -0
- quantum/runtime/ui_html_adapter.py +2691 -0
- quantum/runtime/ui_html_templates.py +2297 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +1766 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/utils/__init__.py +1 -0
- quantum_framework-0.9.0.dist-info/METADATA +244 -0
- quantum_framework-0.9.0.dist-info/RECORD +262 -0
- quantum_framework-0.9.0.dist-info/WHEEL +5 -0
- quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
quantum/cli/mq.py
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Quantum CLI - Message Queue Commands
|
|
5
|
+
|
|
6
|
+
Commands for managing message queues, publishing messages, and running consumers.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import sys
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
# Add parent to path for imports
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_mq_parser(subparsers):
|
|
20
|
+
"""Add mq subcommand to CLI parser"""
|
|
21
|
+
mq_parser = subparsers.add_parser(
|
|
22
|
+
'mq',
|
|
23
|
+
help='Message queue operations',
|
|
24
|
+
description='Commands for managing message queues, topics, and consumers'
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
mq_sub = mq_parser.add_subparsers(dest='mq_action', help='MQ action')
|
|
28
|
+
|
|
29
|
+
# Queues management
|
|
30
|
+
queues_parser = mq_sub.add_parser('queues', help='Manage message queues')
|
|
31
|
+
queues_sub = queues_parser.add_subparsers(dest='queues_action', help='Queue action')
|
|
32
|
+
|
|
33
|
+
list_queues = queues_sub.add_parser('list', help='List all queues')
|
|
34
|
+
list_queues.add_argument('--format', choices=['table', 'json'], default='table')
|
|
35
|
+
|
|
36
|
+
create_queue = queues_sub.add_parser('create', help='Create a queue')
|
|
37
|
+
create_queue.add_argument('name', help='Queue name')
|
|
38
|
+
create_queue.add_argument('--durable', action='store_true', default=True, help='Durable queue')
|
|
39
|
+
create_queue.add_argument('--dlq', help='Dead letter queue name')
|
|
40
|
+
create_queue.add_argument('--ttl', type=int, help='Message TTL in milliseconds')
|
|
41
|
+
|
|
42
|
+
delete_queue = queues_sub.add_parser('delete', help='Delete a queue')
|
|
43
|
+
delete_queue.add_argument('name', help='Queue name')
|
|
44
|
+
delete_queue.add_argument('--force', action='store_true', help='Force delete even if not empty')
|
|
45
|
+
|
|
46
|
+
purge_queue = queues_sub.add_parser('purge', help='Purge all messages from a queue')
|
|
47
|
+
purge_queue.add_argument('name', help='Queue name')
|
|
48
|
+
|
|
49
|
+
peek_queue = queues_sub.add_parser('peek', help='Peek messages without consuming')
|
|
50
|
+
peek_queue.add_argument('name', help='Queue name')
|
|
51
|
+
peek_queue.add_argument('--limit', type=int, default=10, help='Number of messages to peek')
|
|
52
|
+
peek_queue.add_argument('--format', choices=['table', 'json'], default='table')
|
|
53
|
+
|
|
54
|
+
# Topics management
|
|
55
|
+
topics_parser = mq_sub.add_parser('topics', help='Manage topics')
|
|
56
|
+
topics_sub = topics_parser.add_subparsers(dest='topics_action', help='Topic action')
|
|
57
|
+
|
|
58
|
+
list_topics = topics_sub.add_parser('list', help='List all topics')
|
|
59
|
+
list_topics.add_argument('--format', choices=['table', 'json'], default='table')
|
|
60
|
+
|
|
61
|
+
# Publish message
|
|
62
|
+
publish_parser = mq_sub.add_parser('publish', help='Publish a message to a topic')
|
|
63
|
+
publish_parser.add_argument('topic', help='Topic name')
|
|
64
|
+
publish_parser.add_argument('message', nargs='?', help='Message body (JSON string or plain text)')
|
|
65
|
+
publish_parser.add_argument('--file', help='Read message from file')
|
|
66
|
+
publish_parser.add_argument('--header', action='append', dest='headers',
|
|
67
|
+
help='Add header (format: key=value)')
|
|
68
|
+
|
|
69
|
+
# Send to queue
|
|
70
|
+
send_parser = mq_sub.add_parser('send', help='Send a message to a queue')
|
|
71
|
+
send_parser.add_argument('queue', help='Queue name')
|
|
72
|
+
send_parser.add_argument('message', nargs='?', help='Message body (JSON string or plain text)')
|
|
73
|
+
send_parser.add_argument('--file', help='Read message from file')
|
|
74
|
+
send_parser.add_argument('--header', action='append', dest='headers',
|
|
75
|
+
help='Add header (format: key=value)')
|
|
76
|
+
send_parser.add_argument('--delay', type=int, help='Delay in seconds before delivery')
|
|
77
|
+
|
|
78
|
+
# Consume messages
|
|
79
|
+
consume_parser = mq_sub.add_parser('consume', help='Consume messages from a queue')
|
|
80
|
+
consume_parser.add_argument('queue', help='Queue name')
|
|
81
|
+
consume_parser.add_argument('--limit', type=int, default=1, help='Number of messages to consume')
|
|
82
|
+
consume_parser.add_argument('--timeout', type=int, default=5, help='Timeout in seconds')
|
|
83
|
+
consume_parser.add_argument('--ack', action='store_true', help='Auto-acknowledge messages')
|
|
84
|
+
consume_parser.add_argument('--format', choices=['table', 'json'], default='json')
|
|
85
|
+
|
|
86
|
+
# Subscribe to topic
|
|
87
|
+
subscribe_parser = mq_sub.add_parser('subscribe', help='Subscribe to a topic (real-time)')
|
|
88
|
+
subscribe_parser.add_argument('topic', help='Topic pattern (supports wildcards)')
|
|
89
|
+
subscribe_parser.add_argument('--format', choices=['raw', 'json'], default='json')
|
|
90
|
+
|
|
91
|
+
# Worker mode
|
|
92
|
+
worker_parser = mq_sub.add_parser('worker', help='Start a message worker')
|
|
93
|
+
worker_parser.add_argument('--queues', required=True, help='Queues to consume (comma-separated)')
|
|
94
|
+
worker_parser.add_argument('--handler', help='Handler script (.q file)')
|
|
95
|
+
worker_parser.add_argument('--concurrency', type=int, default=4, help='Number of concurrent consumers')
|
|
96
|
+
worker_parser.add_argument('--prefetch', type=int, default=1, help='Prefetch count')
|
|
97
|
+
|
|
98
|
+
# Stats
|
|
99
|
+
stats_parser = mq_sub.add_parser('stats', help='Show message broker statistics')
|
|
100
|
+
stats_parser.add_argument('--format', choices=['table', 'json'], default='table')
|
|
101
|
+
|
|
102
|
+
# Connection info
|
|
103
|
+
info_parser = mq_sub.add_parser('info', help='Show connection information')
|
|
104
|
+
|
|
105
|
+
return mq_parser
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def handle_mq(args) -> int:
|
|
109
|
+
"""Handle mq command"""
|
|
110
|
+
from quantum.runtime.message_broker import MessageBroker, Message
|
|
111
|
+
from quantum.runtime.adapters.memory_adapter import InMemoryAdapter
|
|
112
|
+
|
|
113
|
+
if not args.mq_action:
|
|
114
|
+
print("Usage: quantum mq <action>")
|
|
115
|
+
print("Actions: queues, topics, publish, send, consume, subscribe, worker, stats, info")
|
|
116
|
+
return 1
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
# Initialize broker (use in-memory by default, can be configured)
|
|
120
|
+
broker = _get_broker()
|
|
121
|
+
|
|
122
|
+
# Actions that only make sense ACROSS processes. With the default
|
|
123
|
+
# in-memory broker they silently do nothing, so say so.
|
|
124
|
+
if args.mq_action in ('publish', 'send', 'consume', 'subscribe',
|
|
125
|
+
'worker', 'stats'):
|
|
126
|
+
_warn_if_ephemeral(broker, f"mq {args.mq_action}")
|
|
127
|
+
|
|
128
|
+
if args.mq_action == 'queues':
|
|
129
|
+
return _handle_mq_queues(broker, args)
|
|
130
|
+
elif args.mq_action == 'topics':
|
|
131
|
+
return _handle_mq_topics(broker, args)
|
|
132
|
+
elif args.mq_action == 'publish':
|
|
133
|
+
return _handle_mq_publish(broker, args)
|
|
134
|
+
elif args.mq_action == 'send':
|
|
135
|
+
return _handle_mq_send(broker, args)
|
|
136
|
+
elif args.mq_action == 'consume':
|
|
137
|
+
return _handle_mq_consume(broker, args)
|
|
138
|
+
elif args.mq_action == 'subscribe':
|
|
139
|
+
return _handle_mq_subscribe(broker, args)
|
|
140
|
+
elif args.mq_action == 'worker':
|
|
141
|
+
return _handle_mq_worker(broker, args)
|
|
142
|
+
elif args.mq_action == 'stats':
|
|
143
|
+
return _handle_mq_stats(broker, args)
|
|
144
|
+
elif args.mq_action == 'info':
|
|
145
|
+
return _handle_mq_info(broker, args)
|
|
146
|
+
else:
|
|
147
|
+
print(f"Unknown action: {args.mq_action}")
|
|
148
|
+
return 1
|
|
149
|
+
|
|
150
|
+
except Exception as e:
|
|
151
|
+
print(f"[ERROR] {e}")
|
|
152
|
+
import traceback
|
|
153
|
+
traceback.print_exc()
|
|
154
|
+
return 1
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _get_broker():
|
|
158
|
+
"""Get message broker instance based on configuration"""
|
|
159
|
+
import os
|
|
160
|
+
|
|
161
|
+
broker_type = os.environ.get('QUANTUM_MQ_BROKER', 'memory')
|
|
162
|
+
|
|
163
|
+
if broker_type == 'rabbitmq':
|
|
164
|
+
try:
|
|
165
|
+
from quantum.runtime.adapters.rabbitmq_adapter import RabbitMQAdapter
|
|
166
|
+
broker = RabbitMQAdapter()
|
|
167
|
+
broker.connect({
|
|
168
|
+
'host': os.environ.get('RABBITMQ_HOST', 'localhost'),
|
|
169
|
+
'port': int(os.environ.get('RABBITMQ_PORT', 5672)),
|
|
170
|
+
'username': os.environ.get('RABBITMQ_USER', 'guest'),
|
|
171
|
+
'password': os.environ.get('RABBITMQ_PASS', 'guest'),
|
|
172
|
+
'vhost': os.environ.get('RABBITMQ_VHOST', '/'),
|
|
173
|
+
})
|
|
174
|
+
return broker
|
|
175
|
+
except ImportError:
|
|
176
|
+
print("[WARN] RabbitMQ adapter not available, using in-memory")
|
|
177
|
+
|
|
178
|
+
elif broker_type == 'redis':
|
|
179
|
+
try:
|
|
180
|
+
from quantum.runtime.adapters.redis_adapter import RedisAdapter
|
|
181
|
+
broker = RedisAdapter()
|
|
182
|
+
broker.connect({
|
|
183
|
+
'host': os.environ.get('REDIS_HOST', 'localhost'),
|
|
184
|
+
'port': int(os.environ.get('REDIS_PORT', 6379)),
|
|
185
|
+
'db': int(os.environ.get('REDIS_DB', 0)),
|
|
186
|
+
'password': os.environ.get('REDIS_PASSWORD'),
|
|
187
|
+
})
|
|
188
|
+
return broker
|
|
189
|
+
except ImportError:
|
|
190
|
+
print("[WARN] Redis adapter not available, using in-memory")
|
|
191
|
+
|
|
192
|
+
if broker_type == 'sqlite':
|
|
193
|
+
from quantum.runtime.adapters.sqlite_adapter import SqliteAdapter
|
|
194
|
+
broker = SqliteAdapter()
|
|
195
|
+
broker.connect({'path': os.environ.get('QUANTUM_MQ_PATH', 'quantum_messages.db')})
|
|
196
|
+
return broker
|
|
197
|
+
|
|
198
|
+
# Default: in-memory.
|
|
199
|
+
#
|
|
200
|
+
# This broker lives inside THIS PROCESS and dies with it. Every `quantum
|
|
201
|
+
# mq` invocation is its own process, so `mq publish` wrote into a broker
|
|
202
|
+
# that was discarded a moment later and `mq consume` created a fresh empty
|
|
203
|
+
# one — every message silently lost, with the CLI reporting success both
|
|
204
|
+
# times. Nothing said the messages were going nowhere.
|
|
205
|
+
#
|
|
206
|
+
# It still exists because it is genuinely useful in-process (a .q file
|
|
207
|
+
# that publishes and subscribes in one run), so this warns rather than
|
|
208
|
+
# refusing; the caller decides whether the operation is cross-process.
|
|
209
|
+
from quantum.runtime.adapters.memory_adapter import InMemoryAdapter
|
|
210
|
+
broker = InMemoryAdapter()
|
|
211
|
+
broker.connect({})
|
|
212
|
+
broker._is_ephemeral_cli_broker = True
|
|
213
|
+
return broker
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _warn_if_ephemeral(broker, action: str) -> None:
|
|
217
|
+
"""Say plainly that a cross-process mq action cannot work in memory."""
|
|
218
|
+
if not getattr(broker, '_is_ephemeral_cli_broker', False):
|
|
219
|
+
return
|
|
220
|
+
print(
|
|
221
|
+
f"[WARN] QUANTUM_MQ_BROKER is 'memory' (the default). That broker lives\n"
|
|
222
|
+
f" inside this process and is discarded when it exits, so "
|
|
223
|
+
f"`{action}` cannot\n"
|
|
224
|
+
f" reach any other command. Messages published this way are lost.\n"
|
|
225
|
+
f" Set QUANTUM_MQ_BROKER=rabbitmq or =redis for anything that has to\n"
|
|
226
|
+
f" cross process boundaries."
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _parse_headers(headers_list):
|
|
231
|
+
"""Parse headers from command line format"""
|
|
232
|
+
if not headers_list:
|
|
233
|
+
return {}
|
|
234
|
+
|
|
235
|
+
result = {}
|
|
236
|
+
for h in headers_list:
|
|
237
|
+
if '=' in h:
|
|
238
|
+
key, value = h.split('=', 1)
|
|
239
|
+
result[key.strip()] = value.strip()
|
|
240
|
+
return result
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _handle_mq_queues(broker, args) -> int:
|
|
244
|
+
"""Handle queue management commands"""
|
|
245
|
+
if not args.queues_action:
|
|
246
|
+
print("Usage: quantum mq queues <list|create|delete|purge|peek>")
|
|
247
|
+
return 1
|
|
248
|
+
|
|
249
|
+
if args.queues_action == 'list':
|
|
250
|
+
queues = broker.list_queues() if hasattr(broker, 'list_queues') else []
|
|
251
|
+
|
|
252
|
+
if args.format == 'json':
|
|
253
|
+
print(json.dumps(queues, indent=2))
|
|
254
|
+
else:
|
|
255
|
+
if not queues:
|
|
256
|
+
print("No queues found.")
|
|
257
|
+
return 0
|
|
258
|
+
|
|
259
|
+
print(f"{'Queue':<30} {'Messages':<10} {'Consumers':<10}")
|
|
260
|
+
print("-" * 55)
|
|
261
|
+
for q in queues:
|
|
262
|
+
name = q if isinstance(q, str) else q.get('name', 'unknown')
|
|
263
|
+
messages = q.get('messages', 0) if isinstance(q, dict) else 0
|
|
264
|
+
consumers = q.get('consumers', 0) if isinstance(q, dict) else 0
|
|
265
|
+
print(f"{name:<30} {messages:<10} {consumers:<10}")
|
|
266
|
+
|
|
267
|
+
return 0
|
|
268
|
+
|
|
269
|
+
elif args.queues_action == 'create':
|
|
270
|
+
options = {
|
|
271
|
+
'durable': args.durable,
|
|
272
|
+
}
|
|
273
|
+
if args.dlq:
|
|
274
|
+
options['dead_letter_queue'] = args.dlq
|
|
275
|
+
if args.ttl:
|
|
276
|
+
options['ttl'] = args.ttl
|
|
277
|
+
|
|
278
|
+
broker.declare_queue(args.name, **options)
|
|
279
|
+
print(f"Queue created: {args.name}")
|
|
280
|
+
return 0
|
|
281
|
+
|
|
282
|
+
elif args.queues_action == 'delete':
|
|
283
|
+
if hasattr(broker, 'delete_queue'):
|
|
284
|
+
broker.delete_queue(args.name, force=args.force)
|
|
285
|
+
print(f"Queue deleted: {args.name}")
|
|
286
|
+
else:
|
|
287
|
+
print("Delete queue not supported by this broker")
|
|
288
|
+
return 1
|
|
289
|
+
return 0
|
|
290
|
+
|
|
291
|
+
elif args.queues_action == 'purge':
|
|
292
|
+
if hasattr(broker, 'purge_queue'):
|
|
293
|
+
count = broker.purge_queue(args.name)
|
|
294
|
+
print(f"Purged {count} message(s) from queue: {args.name}")
|
|
295
|
+
else:
|
|
296
|
+
print("Purge queue not supported by this broker")
|
|
297
|
+
return 1
|
|
298
|
+
return 0
|
|
299
|
+
|
|
300
|
+
elif args.queues_action == 'peek':
|
|
301
|
+
if hasattr(broker, 'peek_queue'):
|
|
302
|
+
messages = broker.peek_queue(args.name, limit=args.limit)
|
|
303
|
+
else:
|
|
304
|
+
print("Peek not supported by this broker")
|
|
305
|
+
return 1
|
|
306
|
+
|
|
307
|
+
if args.format == 'json':
|
|
308
|
+
print(json.dumps([m.__dict__ for m in messages], indent=2, default=str))
|
|
309
|
+
else:
|
|
310
|
+
if not messages:
|
|
311
|
+
print("No messages in queue.")
|
|
312
|
+
return 0
|
|
313
|
+
|
|
314
|
+
print(f"{'ID':<36} {'Body (truncated)':<50}")
|
|
315
|
+
print("-" * 90)
|
|
316
|
+
for msg in messages:
|
|
317
|
+
body_str = str(msg.body)[:50]
|
|
318
|
+
print(f"{msg.id:<36} {body_str:<50}")
|
|
319
|
+
|
|
320
|
+
return 0
|
|
321
|
+
|
|
322
|
+
return 1
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _handle_mq_topics(broker, args) -> int:
|
|
326
|
+
"""Handle topics management"""
|
|
327
|
+
if not args.topics_action:
|
|
328
|
+
print("Usage: quantum mq topics <list>")
|
|
329
|
+
return 1
|
|
330
|
+
|
|
331
|
+
if args.topics_action == 'list':
|
|
332
|
+
topics = broker.list_topics() if hasattr(broker, 'list_topics') else []
|
|
333
|
+
|
|
334
|
+
if args.format == 'json':
|
|
335
|
+
print(json.dumps(topics, indent=2))
|
|
336
|
+
else:
|
|
337
|
+
if not topics:
|
|
338
|
+
print("No topics found.")
|
|
339
|
+
return 0
|
|
340
|
+
|
|
341
|
+
print("Topics:")
|
|
342
|
+
for t in topics:
|
|
343
|
+
print(f" - {t}")
|
|
344
|
+
|
|
345
|
+
return 0
|
|
346
|
+
|
|
347
|
+
return 1
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _handle_mq_publish(broker, args) -> int:
|
|
351
|
+
"""Publish message to topic"""
|
|
352
|
+
from quantum.runtime.message_broker import Message
|
|
353
|
+
|
|
354
|
+
# Get message body
|
|
355
|
+
if args.file:
|
|
356
|
+
with open(args.file, 'r') as f:
|
|
357
|
+
body = f.read()
|
|
358
|
+
elif args.message:
|
|
359
|
+
body = args.message
|
|
360
|
+
else:
|
|
361
|
+
print("Error: Message body required (use positional argument or --file)")
|
|
362
|
+
return 1
|
|
363
|
+
|
|
364
|
+
# Try to parse as JSON
|
|
365
|
+
try:
|
|
366
|
+
body = json.loads(body)
|
|
367
|
+
except json.JSONDecodeError:
|
|
368
|
+
pass # Keep as string
|
|
369
|
+
|
|
370
|
+
# Create message
|
|
371
|
+
message = Message(
|
|
372
|
+
topic=args.topic,
|
|
373
|
+
body=body,
|
|
374
|
+
headers=_parse_headers(args.headers)
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Publish
|
|
378
|
+
broker.publish(args.topic, message)
|
|
379
|
+
print(f"Published to topic: {args.topic}")
|
|
380
|
+
print(f"Message ID: {message.id}")
|
|
381
|
+
|
|
382
|
+
return 0
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _handle_mq_send(broker, args) -> int:
|
|
386
|
+
"""Send message to queue"""
|
|
387
|
+
from quantum.runtime.message_broker import Message
|
|
388
|
+
|
|
389
|
+
# Get message body
|
|
390
|
+
if args.file:
|
|
391
|
+
with open(args.file, 'r') as f:
|
|
392
|
+
body = f.read()
|
|
393
|
+
elif args.message:
|
|
394
|
+
body = args.message
|
|
395
|
+
else:
|
|
396
|
+
print("Error: Message body required (use positional argument or --file)")
|
|
397
|
+
return 1
|
|
398
|
+
|
|
399
|
+
# Try to parse as JSON
|
|
400
|
+
try:
|
|
401
|
+
body = json.loads(body)
|
|
402
|
+
except json.JSONDecodeError:
|
|
403
|
+
pass # Keep as string
|
|
404
|
+
|
|
405
|
+
# Create message
|
|
406
|
+
message = Message(
|
|
407
|
+
queue=args.queue,
|
|
408
|
+
body=body,
|
|
409
|
+
headers=_parse_headers(args.headers)
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
# Send (with delay if specified)
|
|
413
|
+
if args.delay:
|
|
414
|
+
if hasattr(broker, 'send_delayed'):
|
|
415
|
+
broker.send_delayed(args.queue, message, delay=args.delay)
|
|
416
|
+
else:
|
|
417
|
+
print("[WARN] Delayed send not supported, sending immediately")
|
|
418
|
+
broker.send(args.queue, message)
|
|
419
|
+
else:
|
|
420
|
+
broker.send(args.queue, message)
|
|
421
|
+
|
|
422
|
+
print(f"Sent to queue: {args.queue}")
|
|
423
|
+
print(f"Message ID: {message.id}")
|
|
424
|
+
|
|
425
|
+
return 0
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _handle_mq_consume(broker, args) -> int:
|
|
429
|
+
"""Consume messages from queue"""
|
|
430
|
+
import time
|
|
431
|
+
|
|
432
|
+
messages = []
|
|
433
|
+
start_time = time.time()
|
|
434
|
+
|
|
435
|
+
def handler(msg):
|
|
436
|
+
messages.append(msg)
|
|
437
|
+
if args.ack:
|
|
438
|
+
broker.ack(msg)
|
|
439
|
+
|
|
440
|
+
# Start consuming
|
|
441
|
+
consumer_id = broker.consume(args.queue, handler)
|
|
442
|
+
|
|
443
|
+
# Wait for messages
|
|
444
|
+
while len(messages) < args.limit and (time.time() - start_time) < args.timeout:
|
|
445
|
+
time.sleep(0.1)
|
|
446
|
+
|
|
447
|
+
# Stop consumer if supported
|
|
448
|
+
if hasattr(broker, 'cancel_consumer'):
|
|
449
|
+
broker.cancel_consumer(consumer_id)
|
|
450
|
+
|
|
451
|
+
# Output
|
|
452
|
+
if args.format == 'json':
|
|
453
|
+
print(json.dumps([{
|
|
454
|
+
'id': m.id,
|
|
455
|
+
'body': m.body,
|
|
456
|
+
'headers': m.headers,
|
|
457
|
+
'timestamp': m.timestamp
|
|
458
|
+
} for m in messages], indent=2, default=str))
|
|
459
|
+
else:
|
|
460
|
+
for msg in messages:
|
|
461
|
+
print(f"--- Message {msg.id} ---")
|
|
462
|
+
print(json.dumps(msg.body, indent=2) if isinstance(msg.body, dict) else msg.body)
|
|
463
|
+
print()
|
|
464
|
+
|
|
465
|
+
if not messages:
|
|
466
|
+
print("No messages received within timeout.")
|
|
467
|
+
|
|
468
|
+
return 0
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _handle_mq_subscribe(broker, args) -> int:
|
|
472
|
+
"""Subscribe to topic (real-time)"""
|
|
473
|
+
print(f"Subscribing to topic: {args.topic}")
|
|
474
|
+
print("Press Ctrl+C to stop.\n")
|
|
475
|
+
|
|
476
|
+
def handler(msg):
|
|
477
|
+
if args.format == 'json':
|
|
478
|
+
print(json.dumps({
|
|
479
|
+
'topic': msg.topic,
|
|
480
|
+
'body': msg.body,
|
|
481
|
+
'headers': msg.headers,
|
|
482
|
+
'timestamp': msg.timestamp
|
|
483
|
+
}, indent=2, default=str))
|
|
484
|
+
else:
|
|
485
|
+
print(f"[{msg.topic}] {msg.body}")
|
|
486
|
+
|
|
487
|
+
try:
|
|
488
|
+
consumer_id = broker.subscribe(args.topic, handler)
|
|
489
|
+
|
|
490
|
+
# Keep running
|
|
491
|
+
import time
|
|
492
|
+
while True:
|
|
493
|
+
time.sleep(1)
|
|
494
|
+
|
|
495
|
+
except KeyboardInterrupt:
|
|
496
|
+
print("\nUnsubscribing...")
|
|
497
|
+
if hasattr(broker, 'unsubscribe'):
|
|
498
|
+
broker.unsubscribe(consumer_id)
|
|
499
|
+
|
|
500
|
+
return 0
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _handle_mq_worker(broker, args) -> int:
|
|
504
|
+
"""Start message worker"""
|
|
505
|
+
queues = [q.strip() for q in args.queues.split(',')]
|
|
506
|
+
|
|
507
|
+
print(f"Starting message worker...")
|
|
508
|
+
print(f" Queues: {', '.join(queues)}")
|
|
509
|
+
print(f" Concurrency: {args.concurrency}")
|
|
510
|
+
print(f" Prefetch: {args.prefetch}")
|
|
511
|
+
if args.handler:
|
|
512
|
+
print(f" Handler: {args.handler}")
|
|
513
|
+
print("")
|
|
514
|
+
print("Press Ctrl+C to stop.\n")
|
|
515
|
+
|
|
516
|
+
# Load handler if specified
|
|
517
|
+
handler_func = None
|
|
518
|
+
if args.handler:
|
|
519
|
+
from quantum.core.parser import QuantumParser
|
|
520
|
+
from quantum.runtime.component import ComponentRuntime
|
|
521
|
+
|
|
522
|
+
parser = QuantumParser()
|
|
523
|
+
runtime = ComponentRuntime()
|
|
524
|
+
ast = parser.parse_file(args.handler)
|
|
525
|
+
|
|
526
|
+
def handler_func(msg):
|
|
527
|
+
context = {'message': msg}
|
|
528
|
+
runtime.execute(ast, context)
|
|
529
|
+
else:
|
|
530
|
+
def handler_func(msg):
|
|
531
|
+
print(f"[{msg.queue}] {msg.id}: {msg.body}")
|
|
532
|
+
|
|
533
|
+
consumers = []
|
|
534
|
+
|
|
535
|
+
try:
|
|
536
|
+
for queue in queues:
|
|
537
|
+
for i in range(args.concurrency):
|
|
538
|
+
consumer_id = broker.consume(queue, handler_func)
|
|
539
|
+
consumers.append(consumer_id)
|
|
540
|
+
print(f"Started consumer {i+1} for queue: {queue}")
|
|
541
|
+
|
|
542
|
+
# Keep running
|
|
543
|
+
import time
|
|
544
|
+
while True:
|
|
545
|
+
time.sleep(1)
|
|
546
|
+
|
|
547
|
+
except KeyboardInterrupt:
|
|
548
|
+
print("\nStopping workers...")
|
|
549
|
+
for consumer_id in consumers:
|
|
550
|
+
if hasattr(broker, 'cancel_consumer'):
|
|
551
|
+
broker.cancel_consumer(consumer_id)
|
|
552
|
+
print("Workers stopped.")
|
|
553
|
+
|
|
554
|
+
return 0
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def _handle_mq_stats(broker, args) -> int:
|
|
558
|
+
"""Show broker statistics"""
|
|
559
|
+
stats = broker.get_stats() if hasattr(broker, 'get_stats') else {}
|
|
560
|
+
|
|
561
|
+
if args.format == 'json':
|
|
562
|
+
print(json.dumps(stats, indent=2, default=str))
|
|
563
|
+
else:
|
|
564
|
+
print("Message Broker Statistics")
|
|
565
|
+
print("-" * 40)
|
|
566
|
+
for key, value in stats.items():
|
|
567
|
+
print(f" {key}: {value}")
|
|
568
|
+
|
|
569
|
+
return 0
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def _handle_mq_info(broker, args) -> int:
|
|
573
|
+
"""Show connection info"""
|
|
574
|
+
info = broker.get_info() if hasattr(broker, 'get_info') else {}
|
|
575
|
+
|
|
576
|
+
print("Message Broker Connection")
|
|
577
|
+
print("-" * 40)
|
|
578
|
+
print(f" Type: {type(broker).__name__}")
|
|
579
|
+
for key, value in info.items():
|
|
580
|
+
print(f" {key}: {value}")
|
|
581
|
+
|
|
582
|
+
return 0
|