quantum-framework 0.18.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/admin.py +131 -0
- quantum/cli/check.py +223 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +414 -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 +548 -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 +565 -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 +2619 -0
- quantum/core/config_env.py +54 -0
- quantum/core/docs_links.py +8 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +208 -0
- quantum/core/expressions.py +677 -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_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +292 -0
- quantum/core/features/data_import/src/runtime.py +533 -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 +147 -0
- quantum/core/features/invocation/src/runtime.py +332 -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/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +121 -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 +1913 -0
- quantum/core/features/ui_engine/src/parser.py +1192 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/guards.py +53 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1551 -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 +71 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +93 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +229 -0
- quantum/core/parsers/data/invoke_parser.py +133 -0
- quantum/core/parsers/data/query_parser.py +213 -0
- quantum/core/parsers/data/transaction_parser.py +86 -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 +79 -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 +98 -0
- quantum/core/parsers/functions/param_parser.py +73 -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/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 +164 -0
- quantum/core/xml_lines.py +81 -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 +25 -0
- quantum/runtime/action_handler.py +532 -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/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +1048 -0
- quantum/runtime/component_composer.py +133 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/conditions.py +54 -0
- quantum/runtime/database_service.py +595 -0
- quantum/runtime/db_schema.py +100 -0
- quantum/runtime/dev_panel.py +178 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +283 -0
- quantum/runtime/executor_registry.py +178 -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 +264 -0
- quantum/runtime/executors/control_flow/__init__.py +12 -0
- quantum/runtime/executors/control_flow/if_executor.py +98 -0
- quantum/runtime/executors/control_flow/loop_executor.py +308 -0
- quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
- quantum/runtime/executors/control_flow/set_executor.py +464 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +156 -0
- quantum/runtime/executors/data/invoke_executor.py +228 -0
- quantum/runtime/executors/data/query_executor.py +290 -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 +546 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/local_server.py +28 -0
- quantum/runtime/logging_setup.py +141 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +218 -0
- quantum/runtime/pypy_compat.py +317 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +734 -0
- quantum/runtime/service_container.py +470 -0
- quantum/runtime/table_params.py +163 -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 +222 -0
- quantum/runtime/ui_console.py +405 -0
- quantum/runtime/ui_desktop.py +56 -0
- quantum/runtime/ui_form_rules.py +193 -0
- quantum/runtime/ui_html_adapter.py +2567 -0
- quantum/runtime/ui_html_templates.py +2106 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_page_renderer.py +392 -0
- quantum/runtime/ui_pager.py +73 -0
- quantum/runtime/ui_table_edit.py +139 -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/ui_view.py +221 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +2131 -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/services.py +109 -0
- quantum/utils/__init__.py +1 -0
- quantum_admin/__init__.py +4 -0
- quantum_admin/assets/quantum-admin-connectors.js +123 -0
- quantum_admin/assets/quantum-admin.css +2058 -0
- quantum_admin/assets/quantum-source-highlight.js +106 -0
- quantum_admin/backend/__init__.py +4 -0
- quantum_admin/backend/audit_service.py +346 -0
- quantum_admin/backend/auth_service.py +394 -0
- quantum_admin/backend/component_discovery.py +766 -0
- quantum_admin/backend/config_generator.py +586 -0
- quantum_admin/backend/connector_service.py +1016 -0
- quantum_admin/backend/crud.py +557 -0
- quantum_admin/backend/database.py +226 -0
- quantum_admin/backend/db_setup_service.py +193 -0
- quantum_admin/backend/deploy_service.py +698 -0
- quantum_admin/backend/docker_service.py +422 -0
- quantum_admin/backend/environment_service.py +418 -0
- quantum_admin/backend/git_service.py +370 -0
- quantum_admin/backend/health_service.py +390 -0
- quantum_admin/backend/job_service.py +347 -0
- quantum_admin/backend/log_service.py +331 -0
- quantum_admin/backend/main.py +14426 -0
- quantum_admin/backend/models.py +986 -0
- quantum_admin/backend/pipeline_service.py +812 -0
- quantum_admin/backend/resource_manager.py +1118 -0
- quantum_admin/backend/schemas.py +419 -0
- quantum_admin/backend/secret_manager.py +277 -0
- quantum_admin/backend/settings_service.py +488 -0
- quantum_admin/backend/template_service.py +710 -0
- quantum_admin/backend/test_execution_service.py +364 -0
- quantum_admin/backend/test_generator.py +782 -0
- quantum_admin/backend/webhook_service.py +454 -0
- quantum_admin/backend/websocket_manager.py +360 -0
- quantum_admin/components/admin/_layout/AdminShell.q +121 -0
- quantum_admin/components/admin/agents.q +107 -0
- quantum_admin/components/admin/app/[name].q +558 -0
- quantum_admin/components/admin/applications.q +160 -0
- quantum_admin/components/admin/component/[...path].q +205 -0
- quantum_admin/components/admin/components.q +59 -0
- quantum_admin/components/admin/connectors.q +283 -0
- quantum_admin/components/admin/dashboard.q +86 -0
- quantum_admin/components/admin/database.q +95 -0
- quantum_admin/components/admin/features.q +69 -0
- quantum_admin/components/admin/index.q +12 -0
- quantum_admin/components/admin/jobs.q +75 -0
- quantum_admin/components/admin/login.q +65 -0
- quantum_admin/components/admin/logout.q +11 -0
- quantum_admin/components/admin/settings.q +174 -0
- quantum_admin/components/admin/source.q +45 -0
- quantum_admin/components/admin/tests.q +73 -0
- quantum_admin/run.py +13 -0
- quantum_admin/services/__init__.py +16 -0
- quantum_admin/services/_base.py +84 -0
- quantum_admin/services/_component_test_generator.py +205 -0
- quantum_admin/services/_yaml_editor.py +82 -0
- quantum_admin/services/apps.py +330 -0
- quantum_admin/services/auth.py +61 -0
- quantum_admin/services/components.py +210 -0
- quantum_admin/services/connectors.py +164 -0
- quantum_admin/services/projects.py +189 -0
- quantum_admin/services/repository.py +223 -0
- quantum_admin/services/settings.py +150 -0
- quantum_admin/services/yaml_import.py +118 -0
- quantum_framework-0.18.0.dist-info/METADATA +281 -0
- quantum_framework-0.18.0.dist-info/RECORD +333 -0
- quantum_framework-0.18.0.dist-info/WHEEL +5 -0
- quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""XML into an ElementTree whose elements know their line (M2, DEV-2).
|
|
2
|
+
|
|
3
|
+
`xml.etree` does not keep source positions, so an error found after parsing —
|
|
4
|
+
a statement inside markup, an unknown attribute, a failing expression — could
|
|
5
|
+
name the tag but not where it is. This builds the same tree with expat (the
|
|
6
|
+
parser ElementTree itself runs on) and sets `element.sourceline` on each
|
|
7
|
+
element.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from xml.etree import ElementTree as ET
|
|
11
|
+
from xml.parsers import expat
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _Element(ET.Element):
|
|
15
|
+
"""An Element that can carry `sourceline` (the C Element takes no new attributes)."""
|
|
16
|
+
sourceline = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def fromstring_with_lines(text: str) -> ET.Element:
|
|
20
|
+
"""Like ET.fromstring(text); every element gets `sourceline` (1-based)."""
|
|
21
|
+
builder = ET.TreeBuilder(element_factory=_Element)
|
|
22
|
+
parser = expat.ParserCreate(None, '}')
|
|
23
|
+
parser.buffer_text = True
|
|
24
|
+
parser.ordered_attributes = True
|
|
25
|
+
|
|
26
|
+
def nome(bruto: str) -> str:
|
|
27
|
+
# expat with a separator reports "uri}local"; ElementTree spells it "{uri}local".
|
|
28
|
+
return '{' + bruto if '}' in bruto else bruto
|
|
29
|
+
|
|
30
|
+
def inicio(tag, atributos):
|
|
31
|
+
pares = dict(zip(atributos[0::2], atributos[1::2]))
|
|
32
|
+
elemento = builder.start(nome(tag), {nome(k): v for k, v in pares.items()})
|
|
33
|
+
elemento.sourceline = parser.CurrentLineNumber
|
|
34
|
+
|
|
35
|
+
parser.StartElementHandler = inicio
|
|
36
|
+
parser.EndElementHandler = lambda tag: builder.end(nome(tag))
|
|
37
|
+
parser.CharacterDataHandler = builder.data
|
|
38
|
+
try:
|
|
39
|
+
parser.Parse(text, True)
|
|
40
|
+
except expat.ExpatError as exc:
|
|
41
|
+
erro = ET.ParseError(str(exc))
|
|
42
|
+
erro.code = exc.code
|
|
43
|
+
erro.position = (exc.lineno, exc.offset)
|
|
44
|
+
raise erro from None
|
|
45
|
+
return builder.close()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def line_of(item) -> int:
|
|
49
|
+
"""The source line of an element or an AST node, or None."""
|
|
50
|
+
return getattr(item, 'sourceline', None) or getattr(item, 'source_line', None)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def tag_error(exc: BaseException, node) -> None:
|
|
54
|
+
"""DEV-2: the innermost node an error passes through names its file and line.
|
|
55
|
+
|
|
56
|
+
Innermost across wrapping too: an error in a called component is re-raised
|
|
57
|
+
as a new exception by the page; the line inside the component, already in
|
|
58
|
+
the chain, wins over the line of the call."""
|
|
59
|
+
if error_location(exc)[1] is not None:
|
|
60
|
+
return
|
|
61
|
+
linha = getattr(node, 'source_line', None)
|
|
62
|
+
if not linha:
|
|
63
|
+
return
|
|
64
|
+
try:
|
|
65
|
+
exc.quantum_line = linha
|
|
66
|
+
exc.quantum_file = getattr(node, 'source_file', None)
|
|
67
|
+
except (AttributeError, TypeError):
|
|
68
|
+
pass # an exception type with __slots__
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def error_location(exc: BaseException):
|
|
72
|
+
"""(file, line) of an error or of any error it wraps; (None, None) if unknown."""
|
|
73
|
+
visto = set()
|
|
74
|
+
while exc is not None and id(exc) not in visto:
|
|
75
|
+
visto.add(id(exc))
|
|
76
|
+
if getattr(exc, 'quantum_line', None) is not None:
|
|
77
|
+
return getattr(exc, 'quantum_file', None), exc.quantum_line
|
|
78
|
+
if getattr(exc, 'line', None) is not None and type(exc).__name__ == 'QuantumParseError':
|
|
79
|
+
return getattr(exc, 'file', None), exc.line
|
|
80
|
+
exc = exc.__cause__ or exc.__context__
|
|
81
|
+
return None, None
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum Package Manager
|
|
3
|
+
|
|
4
|
+
Package management system for sharing Quantum components.
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- Package manifest handling (quantum.yaml)
|
|
8
|
+
- Local package registry (~/.quantum/packages/)
|
|
9
|
+
- Dependency resolution with version constraints
|
|
10
|
+
- Install, remove, list, search operations
|
|
11
|
+
- CLI integration (quantum pkg ...)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from .manifest import PackageManifest, ManifestError
|
|
15
|
+
from .registry import PackageRegistry, RegistryError
|
|
16
|
+
from .resolver import DependencyResolver, ResolutionError
|
|
17
|
+
from .manager import PackageManager, PackageError
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
'PackageManifest',
|
|
21
|
+
'ManifestError',
|
|
22
|
+
'PackageRegistry',
|
|
23
|
+
'RegistryError',
|
|
24
|
+
'DependencyResolver',
|
|
25
|
+
'ResolutionError',
|
|
26
|
+
'PackageManager',
|
|
27
|
+
'PackageError',
|
|
28
|
+
]
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum Package Manager
|
|
3
|
+
|
|
4
|
+
Main package manager that coordinates manifest, registry, and resolver.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from quantum.packages import PackageManager
|
|
8
|
+
|
|
9
|
+
pm = PackageManager()
|
|
10
|
+
pm.init('/path/to/new-package')
|
|
11
|
+
pm.install('/path/to/package-source')
|
|
12
|
+
pm.install_from_git('https://github.com/user/quantum-component.git')
|
|
13
|
+
pm.remove('my-component')
|
|
14
|
+
pm.list_packages()
|
|
15
|
+
pm.search('button')
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import shutil
|
|
20
|
+
import tempfile
|
|
21
|
+
import tarfile
|
|
22
|
+
import subprocess
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Dict, List, Optional, Any
|
|
25
|
+
|
|
26
|
+
from .manifest import PackageManifest, ManifestError, init_package
|
|
27
|
+
from .registry import PackageRegistry, RegistryError
|
|
28
|
+
from .resolver import DependencyResolver, ResolutionError, ResolvedPackage
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PackageError(Exception):
|
|
32
|
+
"""General package manager error."""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PackageManager:
|
|
37
|
+
"""
|
|
38
|
+
Main package manager for Quantum components.
|
|
39
|
+
|
|
40
|
+
Handles:
|
|
41
|
+
- Package initialization (quantum pkg init)
|
|
42
|
+
- Package publishing (quantum pkg publish)
|
|
43
|
+
- Package installation (quantum pkg install)
|
|
44
|
+
- Package removal (quantum pkg remove)
|
|
45
|
+
- Package listing (quantum pkg list)
|
|
46
|
+
- Package search (quantum pkg search)
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, registry_path: Optional[Path] = None):
|
|
50
|
+
"""
|
|
51
|
+
Initialize package manager.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
registry_path: Custom registry path (default: ~/.quantum)
|
|
55
|
+
"""
|
|
56
|
+
self.registry = PackageRegistry(registry_path)
|
|
57
|
+
self.resolver = DependencyResolver(self.registry)
|
|
58
|
+
|
|
59
|
+
def init(
|
|
60
|
+
self,
|
|
61
|
+
path: str,
|
|
62
|
+
name: Optional[str] = None,
|
|
63
|
+
description: str = "",
|
|
64
|
+
author: str = "",
|
|
65
|
+
license_id: str = "MIT"
|
|
66
|
+
) -> PackageManifest:
|
|
67
|
+
"""
|
|
68
|
+
Initialize a new package in the given directory.
|
|
69
|
+
|
|
70
|
+
Creates package structure with quantum.yaml, src/, examples/, README.md.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
path: Directory to initialize package in
|
|
74
|
+
name: Package name (default: directory name)
|
|
75
|
+
description: Package description
|
|
76
|
+
author: Package author
|
|
77
|
+
license_id: License identifier
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Created package manifest
|
|
81
|
+
"""
|
|
82
|
+
path = Path(path)
|
|
83
|
+
|
|
84
|
+
# Check if already initialized
|
|
85
|
+
manifest_path = path / 'quantum.yaml'
|
|
86
|
+
if manifest_path.exists():
|
|
87
|
+
raise PackageError(f"Package already initialized at {path}")
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
manifest = init_package(
|
|
91
|
+
path=path,
|
|
92
|
+
name=name,
|
|
93
|
+
description=description,
|
|
94
|
+
author=author,
|
|
95
|
+
license_id=license_id
|
|
96
|
+
)
|
|
97
|
+
return manifest
|
|
98
|
+
except ManifestError as e:
|
|
99
|
+
raise PackageError(f"Failed to initialize package: {e}")
|
|
100
|
+
|
|
101
|
+
def install(
|
|
102
|
+
self,
|
|
103
|
+
source: str,
|
|
104
|
+
version: Optional[str] = None,
|
|
105
|
+
resolve_deps: bool = True
|
|
106
|
+
) -> List[PackageManifest]:
|
|
107
|
+
"""
|
|
108
|
+
Install a package from local path or URL.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
source: Package source (local path, git URL, or package name)
|
|
112
|
+
version: Specific version to install (for registered packages)
|
|
113
|
+
resolve_deps: Whether to resolve and install dependencies
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
List of installed package manifests
|
|
117
|
+
"""
|
|
118
|
+
installed = []
|
|
119
|
+
source_path = None
|
|
120
|
+
temp_dir = None
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
# Determine source type
|
|
124
|
+
if source.startswith('http://') or source.startswith('https://'):
|
|
125
|
+
if '.git' in source or 'github.com' in source:
|
|
126
|
+
# Git URL
|
|
127
|
+
temp_dir = Path(tempfile.mkdtemp())
|
|
128
|
+
source_path = self._clone_git(source, temp_dir)
|
|
129
|
+
else:
|
|
130
|
+
raise PackageError(f"Unsupported URL: {source}")
|
|
131
|
+
elif os.path.exists(source):
|
|
132
|
+
# Local path
|
|
133
|
+
source_path = Path(source)
|
|
134
|
+
else:
|
|
135
|
+
# Check if it's already installed (for dependency resolution)
|
|
136
|
+
if self.registry.has_package(source, version):
|
|
137
|
+
manifest = self.registry.get_package(source, version)
|
|
138
|
+
if manifest:
|
|
139
|
+
return [manifest]
|
|
140
|
+
|
|
141
|
+
raise PackageError(
|
|
142
|
+
f"Package not found: {source}. "
|
|
143
|
+
"Provide a local path or git URL."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Load and validate manifest
|
|
147
|
+
manifest_path = source_path / 'quantum.yaml'
|
|
148
|
+
if not manifest_path.exists():
|
|
149
|
+
raise PackageError(f"No quantum.yaml found in {source_path}")
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
manifest = PackageManifest.load(manifest_path)
|
|
153
|
+
except ManifestError as e:
|
|
154
|
+
raise PackageError(f"Invalid manifest: {e}")
|
|
155
|
+
|
|
156
|
+
# Resolve and install dependencies first
|
|
157
|
+
if resolve_deps and manifest.dependencies:
|
|
158
|
+
for dep_name, dep_version in manifest.dependencies.items():
|
|
159
|
+
if not self.registry.has_package(dep_name):
|
|
160
|
+
raise PackageError(
|
|
161
|
+
f"Missing dependency: {dep_name}. "
|
|
162
|
+
f"Install it first: quantum pkg install <path-to-{dep_name}>"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Check version constraint
|
|
166
|
+
try:
|
|
167
|
+
resolved = self.resolver.resolve(dep_name, dep_version)
|
|
168
|
+
installed.append(resolved.manifest)
|
|
169
|
+
except ResolutionError as e:
|
|
170
|
+
raise PackageError(f"Dependency resolution failed: {e}")
|
|
171
|
+
|
|
172
|
+
# Install the main package
|
|
173
|
+
try:
|
|
174
|
+
installed_manifest = self.registry.install_package(source_path, manifest)
|
|
175
|
+
installed.append(installed_manifest)
|
|
176
|
+
except RegistryError as e:
|
|
177
|
+
raise PackageError(f"Installation failed: {e}")
|
|
178
|
+
|
|
179
|
+
return installed
|
|
180
|
+
|
|
181
|
+
finally:
|
|
182
|
+
# Clean up temp directory
|
|
183
|
+
if temp_dir and temp_dir.exists():
|
|
184
|
+
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
185
|
+
|
|
186
|
+
def install_from_git(self, git_url: str, ref: Optional[str] = None) -> List[PackageManifest]:
|
|
187
|
+
"""
|
|
188
|
+
Install a package from a git repository.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
git_url: Git repository URL
|
|
192
|
+
ref: Git ref (branch, tag, commit) to checkout
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
List of installed package manifests
|
|
196
|
+
"""
|
|
197
|
+
temp_dir = Path(tempfile.mkdtemp())
|
|
198
|
+
try:
|
|
199
|
+
source_path = self._clone_git(git_url, temp_dir, ref)
|
|
200
|
+
return self.install(str(source_path))
|
|
201
|
+
finally:
|
|
202
|
+
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
203
|
+
|
|
204
|
+
def remove(
|
|
205
|
+
self,
|
|
206
|
+
package_name: str,
|
|
207
|
+
version: Optional[str] = None
|
|
208
|
+
) -> bool:
|
|
209
|
+
"""
|
|
210
|
+
Remove an installed package.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
package_name: Name of package to remove
|
|
214
|
+
version: Specific version to remove (None = all versions)
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
True if removed, False if not found
|
|
218
|
+
"""
|
|
219
|
+
if not self.registry.has_package(package_name, version):
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
return self.registry.remove_package(package_name, version)
|
|
224
|
+
except RegistryError as e:
|
|
225
|
+
raise PackageError(f"Failed to remove package: {e}")
|
|
226
|
+
|
|
227
|
+
def list_packages(self) -> List[Dict[str, Any]]:
|
|
228
|
+
"""
|
|
229
|
+
List all installed packages.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
List of package info dictionaries
|
|
233
|
+
"""
|
|
234
|
+
return self.registry.list_packages()
|
|
235
|
+
|
|
236
|
+
def search(self, query: str) -> List[Dict[str, Any]]:
|
|
237
|
+
"""
|
|
238
|
+
Search installed packages.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
query: Search query (matches name, keywords, description)
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
List of matching package info dictionaries
|
|
245
|
+
"""
|
|
246
|
+
return self.registry.search_packages(query)
|
|
247
|
+
|
|
248
|
+
def get_package(
|
|
249
|
+
self,
|
|
250
|
+
package_name: str,
|
|
251
|
+
version: Optional[str] = None
|
|
252
|
+
) -> Optional[PackageManifest]:
|
|
253
|
+
"""
|
|
254
|
+
Get an installed package manifest.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
package_name: Package name
|
|
258
|
+
version: Specific version (None = latest)
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Package manifest or None
|
|
262
|
+
"""
|
|
263
|
+
return self.registry.get_package(package_name, version)
|
|
264
|
+
|
|
265
|
+
def get_package_path(
|
|
266
|
+
self,
|
|
267
|
+
package_name: str,
|
|
268
|
+
version: Optional[str] = None
|
|
269
|
+
) -> Optional[Path]:
|
|
270
|
+
"""
|
|
271
|
+
Get path to an installed package.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
package_name: Package name
|
|
275
|
+
version: Specific version (None = latest)
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Path to package directory or None
|
|
279
|
+
"""
|
|
280
|
+
return self.registry.get_package_path(package_name, version)
|
|
281
|
+
|
|
282
|
+
def publish(self, source_path: str) -> Path:
|
|
283
|
+
"""
|
|
284
|
+
Create a publishable package archive.
|
|
285
|
+
|
|
286
|
+
Creates a .tar.gz archive that can be shared or uploaded to a registry.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
source_path: Path to package directory
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Path to created archive
|
|
293
|
+
"""
|
|
294
|
+
source_path = Path(source_path)
|
|
295
|
+
|
|
296
|
+
# Load and validate manifest
|
|
297
|
+
manifest_path = source_path / 'quantum.yaml'
|
|
298
|
+
if not manifest_path.exists():
|
|
299
|
+
raise PackageError(f"No quantum.yaml found in {source_path}")
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
manifest = PackageManifest.load(manifest_path)
|
|
303
|
+
except ManifestError as e:
|
|
304
|
+
raise PackageError(f"Invalid manifest: {e}")
|
|
305
|
+
|
|
306
|
+
# Create archive
|
|
307
|
+
archive_name = f"{manifest.name}-{manifest.version}.tar.gz"
|
|
308
|
+
archive_path = source_path.parent / archive_name
|
|
309
|
+
|
|
310
|
+
with tarfile.open(archive_path, 'w:gz') as tar:
|
|
311
|
+
for item in source_path.iterdir():
|
|
312
|
+
if item.name.startswith('.'):
|
|
313
|
+
continue # Skip hidden files
|
|
314
|
+
if item.name.endswith('.tar.gz'):
|
|
315
|
+
continue # Skip existing archives
|
|
316
|
+
tar.add(item, arcname=item.name)
|
|
317
|
+
|
|
318
|
+
return archive_path
|
|
319
|
+
|
|
320
|
+
def resolve_dependencies(
|
|
321
|
+
self,
|
|
322
|
+
package_name: str,
|
|
323
|
+
version_constraint: str = '*'
|
|
324
|
+
) -> ResolvedPackage:
|
|
325
|
+
"""
|
|
326
|
+
Resolve package dependencies.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
package_name: Package to resolve
|
|
330
|
+
version_constraint: Version constraint
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
Resolved package with dependencies
|
|
334
|
+
"""
|
|
335
|
+
try:
|
|
336
|
+
return self.resolver.resolve(package_name, version_constraint)
|
|
337
|
+
except ResolutionError as e:
|
|
338
|
+
raise PackageError(f"Dependency resolution failed: {e}")
|
|
339
|
+
|
|
340
|
+
def get_component_path(
|
|
341
|
+
self,
|
|
342
|
+
package_name: str,
|
|
343
|
+
component_name: Optional[str] = None
|
|
344
|
+
) -> Optional[Path]:
|
|
345
|
+
"""
|
|
346
|
+
Get path to a component file in a package.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
package_name: Package name
|
|
350
|
+
component_name: Component name (None = main component)
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
Path to component file or None
|
|
354
|
+
"""
|
|
355
|
+
manifest = self.get_package(package_name)
|
|
356
|
+
if not manifest:
|
|
357
|
+
return None
|
|
358
|
+
|
|
359
|
+
if component_name is None:
|
|
360
|
+
# Return main component
|
|
361
|
+
return manifest.get_main_component_path()
|
|
362
|
+
|
|
363
|
+
# Search for component in package
|
|
364
|
+
pkg_path = self.get_package_path(package_name)
|
|
365
|
+
if not pkg_path:
|
|
366
|
+
return None
|
|
367
|
+
|
|
368
|
+
# Try src/{ComponentName}.q
|
|
369
|
+
component_path = pkg_path / 'src' / f'{component_name}.q'
|
|
370
|
+
if component_path.exists():
|
|
371
|
+
return component_path
|
|
372
|
+
|
|
373
|
+
# Try src/{component_name}.q (lowercase)
|
|
374
|
+
component_path = pkg_path / 'src' / f'{component_name.lower()}.q'
|
|
375
|
+
if component_path.exists():
|
|
376
|
+
return component_path
|
|
377
|
+
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
def _clone_git(
|
|
381
|
+
self,
|
|
382
|
+
git_url: str,
|
|
383
|
+
dest_dir: Path,
|
|
384
|
+
ref: Optional[str] = None
|
|
385
|
+
) -> Path:
|
|
386
|
+
"""Clone a git repository."""
|
|
387
|
+
try:
|
|
388
|
+
cmd = ['git', 'clone', '--depth', '1']
|
|
389
|
+
if ref:
|
|
390
|
+
cmd.extend(['--branch', ref])
|
|
391
|
+
cmd.extend([git_url, str(dest_dir / 'repo')])
|
|
392
|
+
|
|
393
|
+
result = subprocess.run(
|
|
394
|
+
cmd,
|
|
395
|
+
capture_output=True,
|
|
396
|
+
text=True,
|
|
397
|
+
timeout=60
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
if result.returncode != 0:
|
|
401
|
+
raise PackageError(f"Git clone failed: {result.stderr}")
|
|
402
|
+
|
|
403
|
+
return dest_dir / 'repo'
|
|
404
|
+
|
|
405
|
+
except subprocess.TimeoutExpired:
|
|
406
|
+
raise PackageError("Git clone timed out")
|
|
407
|
+
except FileNotFoundError:
|
|
408
|
+
raise PackageError("Git not found. Please install git.")
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def get_package_manager() -> PackageManager:
|
|
412
|
+
"""Get default package manager instance."""
|
|
413
|
+
return PackageManager()
|