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
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""UMA implementacao de `q:param`, usada pelos dois lugares que validam.
|
|
2
|
+
|
|
3
|
+
Havia duas, e elas discordavam.
|
|
4
|
+
|
|
5
|
+
`ComponentRuntime._coerce_param` (quantum/runtime/component.py) aceitava
|
|
6
|
+
`integer|int|long` e `number|numeric|decimal|float|double`, deixava
|
|
7
|
+
string/file/array/object como estavam e aplicava min/max a qualquer valor
|
|
8
|
+
que virasse numero. `ActionHandler._validate_type`
|
|
9
|
+
(quantum/runtime/action_handler.py) conhecia so `integer`, `decimal` e
|
|
10
|
+
`float`, e tudo o mais caia num `return str(value)` final. O mesmo
|
|
11
|
+
|
|
12
|
+
<q:param name="idade" type="number" min="18" />
|
|
13
|
+
|
|
14
|
+
era um float com minimo checado dentro de um componente e a STRING "7" sem
|
|
15
|
+
checagem nenhuma dentro de um `q:action` — 32 arquivos .q do repositorio
|
|
16
|
+
usam `type="number"`, todos nessa situacao quando estao num action.
|
|
17
|
+
|
|
18
|
+
O caso pior era `type="file"`, que 5 arquivos entregues usam:
|
|
19
|
+
|
|
20
|
+
<q:param name="avatar" type="file" required="true" />
|
|
21
|
+
<q:file action="upload" file="{avatar}" destination="..." />
|
|
22
|
+
|
|
23
|
+
O `str(value)` final transformava o objeto de upload na sua repr —
|
|
24
|
+
`"<FileStorage: 'foto.png' ('image/png')>"` — e o `q:file` recebia essa
|
|
25
|
+
string no lugar do arquivo. Upload por formulario nao funcionava, e o
|
|
26
|
+
sintoma nao apontava para lugar nenhum.
|
|
27
|
+
|
|
28
|
+
Este modulo e a uniao das duas: a coercao do componente mais as validacoes
|
|
29
|
+
de `email`/`url` que so o action tinha, mais um tipo de arquivo que nunca e
|
|
30
|
+
convertido para texto.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import logging
|
|
34
|
+
import re
|
|
35
|
+
from typing import Any, List, Optional, Tuple
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger('quantum.param')
|
|
38
|
+
|
|
39
|
+
INT_TYPES = ('integer', 'int', 'long')
|
|
40
|
+
FLOAT_TYPES = ('number', 'numeric', 'decimal', 'float', 'double')
|
|
41
|
+
FILE_TYPES = ('file', 'binary', 'upload')
|
|
42
|
+
# Tipos que passam intactos: converter mudaria dado que o destino espera
|
|
43
|
+
# receber como veio.
|
|
44
|
+
PASSTHROUGH_TYPES = ('string', 'text', 'array', 'object', 'any', 'json')
|
|
45
|
+
|
|
46
|
+
_EMAIL = re.compile(r'^[^@\s]+@[^@\s]+\.[^@\s]+$')
|
|
47
|
+
_URL = re.compile(r'^https?://', re.IGNORECASE)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_uploaded_file(value: Any) -> bool:
|
|
51
|
+
"""Um upload (werkzeug FileStorage ou equivalente).
|
|
52
|
+
|
|
53
|
+
Checado por forma, nao por import: o werkzeug e opcional, e uma checagem
|
|
54
|
+
por isinstance obrigaria a importa-lo so para decidir se um valor pode
|
|
55
|
+
virar texto.
|
|
56
|
+
"""
|
|
57
|
+
return hasattr(value, 'filename') and hasattr(value, 'read')
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def coerce(param_def, value: Any) -> Tuple[Any, Optional[str]]:
|
|
61
|
+
"""Devolve (valor_convertido, erro). Erro None quando esta tudo bem."""
|
|
62
|
+
ptype = (getattr(param_def, 'type', None) or 'string').lower()
|
|
63
|
+
name = getattr(param_def, 'name', '?')
|
|
64
|
+
|
|
65
|
+
if ptype in FILE_TYPES:
|
|
66
|
+
if value is None or is_uploaded_file(value):
|
|
67
|
+
return value, None
|
|
68
|
+
return value, (
|
|
69
|
+
f"Parameter '{name}' must be an uploaded file, got "
|
|
70
|
+
f"{type(value).__name__}")
|
|
71
|
+
|
|
72
|
+
# Um arquivo nunca vira texto nem numero. Antes, `str(value)` produzia a
|
|
73
|
+
# repr do FileStorage e o resto do fluxo trabalhava com ela.
|
|
74
|
+
if is_uploaded_file(value):
|
|
75
|
+
return value, (
|
|
76
|
+
f"Parameter '{name}' received an uploaded file but is declared "
|
|
77
|
+
f"type=\"{ptype}\" — use type=\"file\"")
|
|
78
|
+
|
|
79
|
+
if ptype in INT_TYPES:
|
|
80
|
+
if isinstance(value, bool):
|
|
81
|
+
return value, f"Parameter '{name}' must be an integer"
|
|
82
|
+
if isinstance(value, int):
|
|
83
|
+
return value, None
|
|
84
|
+
try:
|
|
85
|
+
return int(str(value).strip()), None
|
|
86
|
+
except (TypeError, ValueError):
|
|
87
|
+
return value, f"Parameter '{name}' must be an integer, got {value!r}"
|
|
88
|
+
|
|
89
|
+
if ptype in FLOAT_TYPES:
|
|
90
|
+
if isinstance(value, bool):
|
|
91
|
+
return value, f"Parameter '{name}' must be a number"
|
|
92
|
+
if isinstance(value, (int, float)):
|
|
93
|
+
return value, None
|
|
94
|
+
try:
|
|
95
|
+
return float(str(value).strip()), None
|
|
96
|
+
except (TypeError, ValueError):
|
|
97
|
+
return value, f"Parameter '{name}' must be a number, got {value!r}"
|
|
98
|
+
|
|
99
|
+
if ptype == 'boolean':
|
|
100
|
+
if isinstance(value, bool):
|
|
101
|
+
return value, None
|
|
102
|
+
text = str(value).strip().lower()
|
|
103
|
+
if text in ('true', 'yes', '1', 'on'):
|
|
104
|
+
return True, None
|
|
105
|
+
if text in ('false', 'no', '0', 'off', ''):
|
|
106
|
+
return False, None
|
|
107
|
+
return value, f"Parameter '{name}' must be a boolean, got {value!r}"
|
|
108
|
+
|
|
109
|
+
if ptype == 'email':
|
|
110
|
+
text = str(value)
|
|
111
|
+
if not _EMAIL.match(text):
|
|
112
|
+
return value, f"Parameter '{name}' must be a valid email"
|
|
113
|
+
return text, None
|
|
114
|
+
|
|
115
|
+
if ptype == 'url':
|
|
116
|
+
text = str(value)
|
|
117
|
+
if not _URL.match(text):
|
|
118
|
+
return value, f"Parameter '{name}' must be a valid URL"
|
|
119
|
+
return text, None
|
|
120
|
+
|
|
121
|
+
return value, None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def check_rules(param_def, value: Any) -> List[str]:
|
|
125
|
+
"""min/max/minlength/maxlength/pattern/enum."""
|
|
126
|
+
errors: List[str] = []
|
|
127
|
+
name = getattr(param_def, 'name', '?')
|
|
128
|
+
|
|
129
|
+
# Nada de comprimento, faixa ou padrao faz sentido sobre um arquivo; as
|
|
130
|
+
# regras de upload sao maxsize/accept, que o q:file aplica.
|
|
131
|
+
if is_uploaded_file(value):
|
|
132
|
+
return errors
|
|
133
|
+
|
|
134
|
+
minimo = getattr(param_def, 'min', None)
|
|
135
|
+
maximo = getattr(param_def, 'max', None)
|
|
136
|
+
if minimo is not None or maximo is not None:
|
|
137
|
+
try:
|
|
138
|
+
numeric = float(value)
|
|
139
|
+
except (TypeError, ValueError):
|
|
140
|
+
numeric = None
|
|
141
|
+
if numeric is not None:
|
|
142
|
+
for bound, comparison, word in (
|
|
143
|
+
(minimo, lambda a, b: a < b, "at least"),
|
|
144
|
+
(maximo, lambda a, b: a > b, "at most"),
|
|
145
|
+
):
|
|
146
|
+
if bound is None:
|
|
147
|
+
continue
|
|
148
|
+
try:
|
|
149
|
+
limit = float(bound)
|
|
150
|
+
except (TypeError, ValueError):
|
|
151
|
+
continue
|
|
152
|
+
if comparison(numeric, limit):
|
|
153
|
+
errors.append(
|
|
154
|
+
f"Parameter '{name}' must be {word} {bound} "
|
|
155
|
+
f"(got {value})")
|
|
156
|
+
|
|
157
|
+
if isinstance(value, str):
|
|
158
|
+
minlength = getattr(param_def, 'minlength', None)
|
|
159
|
+
maxlength = getattr(param_def, 'maxlength', None)
|
|
160
|
+
if minlength is not None and len(value) < minlength:
|
|
161
|
+
errors.append(
|
|
162
|
+
f"Parameter '{name}' must be at least {minlength} characters")
|
|
163
|
+
if maxlength is not None and len(value) > maxlength:
|
|
164
|
+
errors.append(
|
|
165
|
+
f"Parameter '{name}' must be at most {maxlength} characters")
|
|
166
|
+
pattern = getattr(param_def, 'pattern', None)
|
|
167
|
+
if pattern:
|
|
168
|
+
try:
|
|
169
|
+
if not re.search(pattern, value):
|
|
170
|
+
errors.append(
|
|
171
|
+
f"Parameter '{name}' does not match {pattern!r}")
|
|
172
|
+
except re.error as exc:
|
|
173
|
+
logger.warning(
|
|
174
|
+
"q:param %s has an invalid pattern %r: %s",
|
|
175
|
+
name, pattern, exc)
|
|
176
|
+
|
|
177
|
+
enum = getattr(param_def, 'enum', None)
|
|
178
|
+
if enum:
|
|
179
|
+
allowed = [v.strip() for v in str(enum).split(',') if v.strip()]
|
|
180
|
+
if allowed and str(value) not in allowed:
|
|
181
|
+
errors.append(
|
|
182
|
+
f"Parameter '{name}' must be one of: {', '.join(allowed)}")
|
|
183
|
+
|
|
184
|
+
return errors
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyPy Compatibility Module - Phase 3 Performance Optimization
|
|
3
|
+
|
|
4
|
+
Ensures Quantum Framework runs correctly on both CPython and PyPy.
|
|
5
|
+
PyPy provides automatic JIT compilation for 5-10x speedup on hot paths.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Runtime detection of Python implementation
|
|
9
|
+
- Alternative implementations for incompatible code
|
|
10
|
+
- Performance recommendations for PyPy users
|
|
11
|
+
- Dependency compatibility checks
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
from quantum.runtime.pypy_compat import is_pypy, get_implementation_info
|
|
15
|
+
|
|
16
|
+
if is_pypy():
|
|
17
|
+
# PyPy-specific optimizations
|
|
18
|
+
pass
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import sys
|
|
22
|
+
import platform
|
|
23
|
+
from typing import Dict, Any, List, Tuple, Optional
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Runtime detection
|
|
28
|
+
def is_pypy() -> bool:
|
|
29
|
+
"""Check if running on PyPy"""
|
|
30
|
+
return platform.python_implementation() == 'PyPy'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def is_cpython() -> bool:
|
|
34
|
+
"""Check if running on CPython"""
|
|
35
|
+
return platform.python_implementation() == 'CPython'
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_python_version() -> Tuple[int, int, int]:
|
|
39
|
+
"""Get Python version as tuple"""
|
|
40
|
+
return sys.version_info[:3]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_implementation_info() -> Dict[str, Any]:
|
|
44
|
+
"""Get detailed information about the Python implementation"""
|
|
45
|
+
return {
|
|
46
|
+
'implementation': platform.python_implementation(),
|
|
47
|
+
'version': platform.python_version(),
|
|
48
|
+
'version_tuple': get_python_version(),
|
|
49
|
+
'compiler': platform.python_compiler(),
|
|
50
|
+
'is_pypy': is_pypy(),
|
|
51
|
+
'is_cpython': is_cpython(),
|
|
52
|
+
'is_64bit': sys.maxsize > 2**32,
|
|
53
|
+
'platform': sys.platform,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class CompatibilityResult:
|
|
59
|
+
"""Result of a compatibility check"""
|
|
60
|
+
name: str
|
|
61
|
+
compatible: bool
|
|
62
|
+
message: str
|
|
63
|
+
severity: str # 'info', 'warning', 'error'
|
|
64
|
+
workaround: Optional[str] = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class DependencyChecker:
|
|
68
|
+
"""
|
|
69
|
+
Check compatibility of dependencies with PyPy.
|
|
70
|
+
|
|
71
|
+
Some packages have C extensions that may not work with PyPy.
|
|
72
|
+
This checker identifies potential issues.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
# Known compatibility status of common dependencies
|
|
76
|
+
KNOWN_DEPS = {
|
|
77
|
+
# Fully compatible
|
|
78
|
+
'flask': ('compatible', 'Flask works well with PyPy'),
|
|
79
|
+
'jinja2': ('compatible', 'Jinja2 works well with PyPy'),
|
|
80
|
+
'click': ('compatible', 'Click works well with PyPy'),
|
|
81
|
+
'requests': ('compatible', 'Requests works well with PyPy'),
|
|
82
|
+
'pyyaml': ('compatible', 'PyYAML works with PyPy (pure Python mode)'),
|
|
83
|
+
'json': ('builtin', 'Standard library, fully compatible'),
|
|
84
|
+
'sqlite3': ('builtin', 'Standard library, fully compatible'),
|
|
85
|
+
'asyncio': ('builtin', 'Standard library, fully compatible'),
|
|
86
|
+
'pathlib': ('builtin', 'Standard library, fully compatible'),
|
|
87
|
+
're': ('builtin', 'Standard library, fully compatible'),
|
|
88
|
+
|
|
89
|
+
# Compatible with caveats
|
|
90
|
+
'lxml': ('caveat', 'lxml works but may need cffi version: pip install lxml-cffi'),
|
|
91
|
+
'numpy': ('caveat', 'NumPy works but performance may differ'),
|
|
92
|
+
'psutil': ('caveat', 'psutil requires cffi build'),
|
|
93
|
+
'watchdog': ('caveat', 'watchdog works but may need extra setup'),
|
|
94
|
+
|
|
95
|
+
# Potentially incompatible
|
|
96
|
+
'uvloop': ('incompatible', 'uvloop uses Cython, not compatible with PyPy'),
|
|
97
|
+
'cython': ('incompatible', 'Cython extensions not compatible with PyPy'),
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def check_dependency(cls, name: str) -> CompatibilityResult:
|
|
102
|
+
"""Check a single dependency for PyPy compatibility"""
|
|
103
|
+
name_lower = name.lower()
|
|
104
|
+
|
|
105
|
+
if name_lower in cls.KNOWN_DEPS:
|
|
106
|
+
status, message = cls.KNOWN_DEPS[name_lower]
|
|
107
|
+
compatible = status in ('compatible', 'builtin', 'caveat')
|
|
108
|
+
severity = 'info' if status in ('compatible', 'builtin') else \
|
|
109
|
+
'warning' if status == 'caveat' else 'error'
|
|
110
|
+
return CompatibilityResult(
|
|
111
|
+
name=name,
|
|
112
|
+
compatible=compatible,
|
|
113
|
+
message=message,
|
|
114
|
+
severity=severity,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Unknown - assume compatible but warn
|
|
118
|
+
return CompatibilityResult(
|
|
119
|
+
name=name,
|
|
120
|
+
compatible=True,
|
|
121
|
+
message=f'Unknown compatibility status for {name}',
|
|
122
|
+
severity='info',
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def check_installed_packages(cls) -> List[CompatibilityResult]:
|
|
127
|
+
"""Check all installed packages for PyPy compatibility"""
|
|
128
|
+
results = []
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
import pkg_resources
|
|
132
|
+
for pkg in pkg_resources.working_set:
|
|
133
|
+
result = cls.check_dependency(pkg.key)
|
|
134
|
+
if result.severity in ('warning', 'error'):
|
|
135
|
+
results.append(result)
|
|
136
|
+
except ImportError:
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
return results
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def check_quantum_dependencies(cls) -> List[CompatibilityResult]:
|
|
143
|
+
"""Check Quantum Framework's core dependencies"""
|
|
144
|
+
core_deps = [
|
|
145
|
+
'flask',
|
|
146
|
+
'jinja2',
|
|
147
|
+
'click',
|
|
148
|
+
'pyyaml',
|
|
149
|
+
'requests',
|
|
150
|
+
'watchdog',
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
return [cls.check_dependency(dep) for dep in core_deps]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class PyPyOptimizer:
|
|
157
|
+
"""
|
|
158
|
+
Provides PyPy-specific optimizations and recommendations.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def get_recommendations() -> List[str]:
|
|
163
|
+
"""Get performance recommendations for PyPy users"""
|
|
164
|
+
if not is_pypy():
|
|
165
|
+
return ['Not running on PyPy - no PyPy-specific recommendations']
|
|
166
|
+
|
|
167
|
+
return [
|
|
168
|
+
'PyPy JIT is warming up - first requests may be slower',
|
|
169
|
+
'Avoid using eval() in hot paths - use pre-compiled expressions',
|
|
170
|
+
'Large dictionaries perform well on PyPy',
|
|
171
|
+
'Use list comprehensions instead of map/filter for best JIT optimization',
|
|
172
|
+
'Avoid metaclasses in performance-critical code',
|
|
173
|
+
'PyPy startup is slower than CPython - use for long-running processes',
|
|
174
|
+
'Consider using PyPy 7.3+ for best compatibility',
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
@staticmethod
|
|
178
|
+
def optimize_for_jit():
|
|
179
|
+
"""
|
|
180
|
+
Apply PyPy-specific optimizations to help the JIT compiler.
|
|
181
|
+
Should be called during application startup.
|
|
182
|
+
"""
|
|
183
|
+
if not is_pypy():
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
# Hint to PyPy JIT about frequently used functions
|
|
187
|
+
# This is mostly documentation - PyPy will optimize automatically
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def warmup_jit(iterations: int = 1000):
|
|
192
|
+
"""
|
|
193
|
+
Warm up PyPy JIT by running common operations.
|
|
194
|
+
Call this during application startup for faster first requests.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
iterations: Number of warmup iterations
|
|
198
|
+
"""
|
|
199
|
+
if not is_pypy():
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
# Warm up common operations
|
|
203
|
+
for _ in range(iterations):
|
|
204
|
+
# Dictionary operations
|
|
205
|
+
d = {'key': 'value', 'num': 42}
|
|
206
|
+
_ = d.get('key')
|
|
207
|
+
d['new'] = 'item'
|
|
208
|
+
|
|
209
|
+
# List operations
|
|
210
|
+
lst = [1, 2, 3, 4, 5]
|
|
211
|
+
_ = [x * 2 for x in lst]
|
|
212
|
+
|
|
213
|
+
# String operations
|
|
214
|
+
s = "hello world"
|
|
215
|
+
_ = s.upper()
|
|
216
|
+
_ = s.split()
|
|
217
|
+
|
|
218
|
+
# Arithmetic
|
|
219
|
+
x = 42
|
|
220
|
+
y = x * 2 + 10 - 5
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class QuantumPyPyAdapter:
|
|
224
|
+
"""
|
|
225
|
+
Adapter that provides PyPy-compatible alternatives for
|
|
226
|
+
any CPython-specific code in Quantum Framework.
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
@staticmethod
|
|
230
|
+
def get_memory_usage() -> float:
|
|
231
|
+
"""
|
|
232
|
+
Get current memory usage in MB.
|
|
233
|
+
Uses different methods for PyPy vs CPython.
|
|
234
|
+
"""
|
|
235
|
+
try:
|
|
236
|
+
import psutil
|
|
237
|
+
process = psutil.Process()
|
|
238
|
+
return process.memory_info().rss / 1024 / 1024
|
|
239
|
+
except ImportError:
|
|
240
|
+
# Fallback for when psutil is not available
|
|
241
|
+
import resource
|
|
242
|
+
try:
|
|
243
|
+
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024
|
|
244
|
+
except:
|
|
245
|
+
return 0.0
|
|
246
|
+
|
|
247
|
+
@staticmethod
|
|
248
|
+
def gc_collect():
|
|
249
|
+
"""
|
|
250
|
+
Force garbage collection.
|
|
251
|
+
PyPy has different GC behavior than CPython.
|
|
252
|
+
"""
|
|
253
|
+
import gc
|
|
254
|
+
gc.collect()
|
|
255
|
+
|
|
256
|
+
if is_pypy():
|
|
257
|
+
# PyPy may need multiple collections
|
|
258
|
+
gc.collect()
|
|
259
|
+
gc.collect()
|
|
260
|
+
|
|
261
|
+
@staticmethod
|
|
262
|
+
def compile_expression(expr: str, mode: str = 'eval'):
|
|
263
|
+
"""
|
|
264
|
+
Compile a Python expression.
|
|
265
|
+
Both CPython and PyPy support this identically.
|
|
266
|
+
"""
|
|
267
|
+
return compile(expr, '<quantum-expr>', mode)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def print_compatibility_report():
|
|
271
|
+
"""Print a detailed compatibility report for the current environment"""
|
|
272
|
+
info = get_implementation_info()
|
|
273
|
+
|
|
274
|
+
print("\n" + "=" * 60)
|
|
275
|
+
print(" QUANTUM FRAMEWORK - PYTHON COMPATIBILITY REPORT")
|
|
276
|
+
print("=" * 60)
|
|
277
|
+
|
|
278
|
+
print(f"\n Implementation: {info['implementation']}")
|
|
279
|
+
print(f" Version: {info['version']}")
|
|
280
|
+
print(f" Platform: {info['platform']}")
|
|
281
|
+
print(f" 64-bit: {info['is_64bit']}")
|
|
282
|
+
|
|
283
|
+
if is_pypy():
|
|
284
|
+
print("\n Status: Running on PyPy (JIT enabled)")
|
|
285
|
+
print("\n Recommendations:")
|
|
286
|
+
for rec in PyPyOptimizer.get_recommendations():
|
|
287
|
+
print(f" - {rec}")
|
|
288
|
+
else:
|
|
289
|
+
print("\n Status: Running on CPython")
|
|
290
|
+
print("\n To use PyPy for better performance:")
|
|
291
|
+
print(" 1. Install PyPy 3.9+ from https://www.pypy.org/")
|
|
292
|
+
print(" 2. Create virtual environment: pypy3 -m venv venv-pypy")
|
|
293
|
+
print(" 3. Install dependencies: pip install -r requirements.txt")
|
|
294
|
+
print(" 4. Run Quantum: pypy3 src/cli/runner.py start")
|
|
295
|
+
|
|
296
|
+
print("\n Dependency Compatibility:")
|
|
297
|
+
results = DependencyChecker.check_quantum_dependencies()
|
|
298
|
+
for result in results:
|
|
299
|
+
status = "OK" if result.compatible else "ISSUE"
|
|
300
|
+
print(f" [{status}] {result.name}: {result.message}")
|
|
301
|
+
|
|
302
|
+
print("\n" + "=" * 60)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# Export commonly used functions
|
|
306
|
+
__all__ = [
|
|
307
|
+
'is_pypy',
|
|
308
|
+
'is_cpython',
|
|
309
|
+
'get_python_version',
|
|
310
|
+
'get_implementation_info',
|
|
311
|
+
'DependencyChecker',
|
|
312
|
+
'PyPyOptimizer',
|
|
313
|
+
'QuantumPyPyAdapter',
|
|
314
|
+
'print_compatibility_report',
|
|
315
|
+
]
|