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.
Files changed (333) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/admin.py +131 -0
  4. quantum/cli/check.py +223 -0
  5. quantum/cli/commands/__init__.py +15 -0
  6. quantum/cli/commands/build.py +414 -0
  7. quantum/cli/commands/dev.py +185 -0
  8. quantum/cli/commands/docs.py +329 -0
  9. quantum/cli/commands/lint.py +523 -0
  10. quantum/cli/commands/migrate.py +548 -0
  11. quantum/cli/commands/new.py +622 -0
  12. quantum/cli/commands/serve.py +190 -0
  13. quantum/cli/commands/test.py +193 -0
  14. quantum/cli/deploy.py +810 -0
  15. quantum/cli/hot_reload.py +951 -0
  16. quantum/cli/jobs.py +356 -0
  17. quantum/cli/mq.py +582 -0
  18. quantum/cli/pkg.py +390 -0
  19. quantum/cli/runner.py +565 -0
  20. quantum/cli/server_process.py +159 -0
  21. quantum/cli/utils.py +334 -0
  22. quantum/compiler/__init__.py +30 -0
  23. quantum/compiler/base_generator.py +367 -0
  24. quantum/compiler/cli.py +295 -0
  25. quantum/compiler/expression_transformer.py +444 -0
  26. quantum/compiler/javascript/__init__.py +10 -0
  27. quantum/compiler/javascript/generator.py +659 -0
  28. quantum/compiler/optimizer.py +270 -0
  29. quantum/compiler/python/__init__.py +10 -0
  30. quantum/compiler/python/generator.py +883 -0
  31. quantum/compiler/python/runtime.py +863 -0
  32. quantum/compiler/transpiler.py +330 -0
  33. quantum/core/__init__.py +3 -0
  34. quantum/core/ast_nodes.py +2619 -0
  35. quantum/core/config_env.py +54 -0
  36. quantum/core/docs_links.py +8 -0
  37. quantum/core/expression_diagnostics.py +87 -0
  38. quantum/core/expression_stdlib.py +208 -0
  39. quantum/core/expressions.py +677 -0
  40. quantum/core/features/agents/src/__init__.py +30 -0
  41. quantum/core/features/agents/src/ast_node.py +540 -0
  42. quantum/core/features/conditionals/src/__init__.py +8 -0
  43. quantum/core/features/conditionals/src/ast_node.py +68 -0
  44. quantum/core/features/data_import/src/__init__.py +0 -0
  45. quantum/core/features/data_import/src/ast_node.py +292 -0
  46. quantum/core/features/data_import/src/runtime.py +533 -0
  47. quantum/core/features/dump/src/__init__.py +12 -0
  48. quantum/core/features/dump/src/ast_node.py +106 -0
  49. quantum/core/features/dump/src/parser.py +61 -0
  50. quantum/core/features/dump/src/runtime.py +246 -0
  51. quantum/core/features/functions/src/__init__.py +8 -0
  52. quantum/core/features/functions/src/ast_node.py +149 -0
  53. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  54. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  55. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  56. quantum/core/features/invocation/src/__init__.py +0 -0
  57. quantum/core/features/invocation/src/ast_node.py +147 -0
  58. quantum/core/features/invocation/src/runtime.py +332 -0
  59. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  60. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  61. quantum/core/features/knowledge_base/src/parser.py +82 -0
  62. quantum/core/features/logging/src/__init__.py +12 -0
  63. quantum/core/features/logging/src/ast_node.py +111 -0
  64. quantum/core/features/logging/src/parser.py +50 -0
  65. quantum/core/features/logging/src/runtime.py +190 -0
  66. quantum/core/features/loops/src/__init__.py +8 -0
  67. quantum/core/features/loops/src/ast_node.py +60 -0
  68. quantum/core/features/query/src/__init__.py +0 -0
  69. quantum/core/features/query/src/query_validators.py +20 -0
  70. quantum/core/features/state_management/src/__init__.py +11 -0
  71. quantum/core/features/state_management/src/ast_node.py +121 -0
  72. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  73. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  74. quantum/core/features/terminal_engine/src/parser.py +361 -0
  75. quantum/core/features/testing_engine/src/__init__.py +41 -0
  76. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  77. quantum/core/features/testing_engine/src/parser.py +604 -0
  78. quantum/core/features/theming/src/__init__.py +48 -0
  79. quantum/core/features/theming/src/ast_node.py +137 -0
  80. quantum/core/features/theming/src/presets.py +405 -0
  81. quantum/core/features/ui_engine/src/__init__.py +20 -0
  82. quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
  83. quantum/core/features/ui_engine/src/parser.py +1192 -0
  84. quantum/core/features/websocket/src/__init__.py +24 -0
  85. quantum/core/features/websocket/src/ast_node.py +247 -0
  86. quantum/core/guards.py +53 -0
  87. quantum/core/html_compat.py +299 -0
  88. quantum/core/parser.py +1551 -0
  89. quantum/core/parser_registry.py +213 -0
  90. quantum/core/parsers/__init__.py +76 -0
  91. quantum/core/parsers/ai/__init__.py +12 -0
  92. quantum/core/parsers/ai/agent_parser.py +106 -0
  93. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  94. quantum/core/parsers/ai/llm_parser.py +78 -0
  95. quantum/core/parsers/ai/team_parser.py +88 -0
  96. quantum/core/parsers/base.py +322 -0
  97. quantum/core/parsers/composition/__init__.py +10 -0
  98. quantum/core/parsers/composition/import_parser.py +50 -0
  99. quantum/core/parsers/composition/slot_parser.py +48 -0
  100. quantum/core/parsers/control_flow/__init__.py +11 -0
  101. quantum/core/parsers/control_flow/if_parser.py +71 -0
  102. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  103. quantum/core/parsers/control_flow/set_parser.py +93 -0
  104. quantum/core/parsers/data/__init__.py +12 -0
  105. quantum/core/parsers/data/data_parser.py +229 -0
  106. quantum/core/parsers/data/invoke_parser.py +133 -0
  107. quantum/core/parsers/data/query_parser.py +213 -0
  108. quantum/core/parsers/data/transaction_parser.py +86 -0
  109. quantum/core/parsers/events/__init__.py +9 -0
  110. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  111. quantum/core/parsers/forms/__init__.py +11 -0
  112. quantum/core/parsers/forms/action_parser.py +79 -0
  113. quantum/core/parsers/forms/flash_parser.py +34 -0
  114. quantum/core/parsers/forms/redirect_parser.py +33 -0
  115. quantum/core/parsers/functions/__init__.py +11 -0
  116. quantum/core/parsers/functions/function_parser.py +98 -0
  117. quantum/core/parsers/functions/param_parser.py +73 -0
  118. quantum/core/parsers/functions/return_parser.py +31 -0
  119. quantum/core/parsers/html/__init__.py +10 -0
  120. quantum/core/parsers/html/component_call_parser.py +130 -0
  121. quantum/core/parsers/html/html_parser.py +115 -0
  122. quantum/core/parsers/jobs/__init__.py +11 -0
  123. quantum/core/parsers/jobs/job_parser.py +71 -0
  124. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  125. quantum/core/parsers/jobs/thread_parser.py +57 -0
  126. quantum/core/parsers/messaging/__init__.py +17 -0
  127. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  128. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_parser.py +114 -0
  130. quantum/core/parsers/messaging/queue_parser.py +61 -0
  131. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  132. quantum/core/parsers/routing/__init__.py +9 -0
  133. quantum/core/parsers/routing/route_parser.py +41 -0
  134. quantum/core/parsers/scripting/__init__.py +12 -0
  135. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  136. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  137. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  138. quantum/core/parsers/scripting/python_parser.py +49 -0
  139. quantum/core/parsers/services/__init__.py +12 -0
  140. quantum/core/parsers/services/dump_parser.py +52 -0
  141. quantum/core/parsers/services/file_parser.py +48 -0
  142. quantum/core/parsers/services/log_parser.py +46 -0
  143. quantum/core/parsers/services/mail_parser.py +65 -0
  144. quantum/core/tiers.py +164 -0
  145. quantum/core/xml_lines.py +81 -0
  146. quantum/packages/__init__.py +28 -0
  147. quantum/packages/manager.py +413 -0
  148. quantum/packages/manifest.py +351 -0
  149. quantum/packages/registry.py +399 -0
  150. quantum/packages/resolver.py +336 -0
  151. quantum/plugins/__init__.py +33 -0
  152. quantum/plugins/hooks.py +329 -0
  153. quantum/plugins/loader.py +479 -0
  154. quantum/plugins/manifest.py +336 -0
  155. quantum/plugins/registry.py +371 -0
  156. quantum/runtime/__init__.py +25 -0
  157. quantum/runtime/action_handler.py +532 -0
  158. quantum/runtime/adapters/__init__.py +88 -0
  159. quantum/runtime/adapters/memory_adapter.py +690 -0
  160. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  161. quantum/runtime/adapters/redis_adapter.py +582 -0
  162. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  163. quantum/runtime/agent_service.py +1133 -0
  164. quantum/runtime/ast_cache.py +506 -0
  165. quantum/runtime/auth_service.py +267 -0
  166. quantum/runtime/component.py +1048 -0
  167. quantum/runtime/component_composer.py +133 -0
  168. quantum/runtime/component_resolver.py +174 -0
  169. quantum/runtime/conditions.py +54 -0
  170. quantum/runtime/database_service.py +595 -0
  171. quantum/runtime/db_schema.py +100 -0
  172. quantum/runtime/dev_panel.py +178 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +283 -0
  176. quantum/runtime/executor_registry.py +178 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +264 -0
  184. quantum/runtime/executors/control_flow/__init__.py +12 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +98 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +308 -0
  187. quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
  188. quantum/runtime/executors/control_flow/set_executor.py +464 -0
  189. quantum/runtime/executors/data/__init__.py +12 -0
  190. quantum/runtime/executors/data/data_executor.py +156 -0
  191. quantum/runtime/executors/data/invoke_executor.py +228 -0
  192. quantum/runtime/executors/data/query_executor.py +290 -0
  193. quantum/runtime/executors/data/transaction_executor.py +91 -0
  194. quantum/runtime/executors/jobs/__init__.py +11 -0
  195. quantum/runtime/executors/jobs/job_executor.py +190 -0
  196. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  197. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  198. quantum/runtime/executors/messaging/__init__.py +17 -0
  199. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  200. quantum/runtime/executors/messaging/message_executor.py +174 -0
  201. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  202. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  203. quantum/runtime/executors/scripting/__init__.py +11 -0
  204. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  205. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  206. quantum/runtime/executors/scripting/python_executor.py +249 -0
  207. quantum/runtime/executors/services/__init__.py +12 -0
  208. quantum/runtime/executors/services/dump_executor.py +72 -0
  209. quantum/runtime/executors/services/file_executor.py +89 -0
  210. quantum/runtime/executors/services/log_executor.py +77 -0
  211. quantum/runtime/executors/services/mail_executor.py +81 -0
  212. quantum/runtime/expression_cache.py +498 -0
  213. quantum/runtime/file_upload_service.py +326 -0
  214. quantum/runtime/function_registry.py +118 -0
  215. quantum/runtime/game_builder.py +166 -0
  216. quantum/runtime/game_code_generator.py +2371 -0
  217. quantum/runtime/game_templates.py +2006 -0
  218. quantum/runtime/godot_code_generator.py +4681 -0
  219. quantum/runtime/godot_templates.py +1449 -0
  220. quantum/runtime/job_executor.py +1599 -0
  221. quantum/runtime/knowledge_service.py +546 -0
  222. quantum/runtime/llm_cache.py +100 -0
  223. quantum/runtime/llm_providers.py +704 -0
  224. quantum/runtime/llm_service.py +287 -0
  225. quantum/runtime/local_server.py +28 -0
  226. quantum/runtime/logging_setup.py +141 -0
  227. quantum/runtime/message_broker.py +364 -0
  228. quantum/runtime/message_queue_service.py +571 -0
  229. quantum/runtime/param_validation.py +218 -0
  230. quantum/runtime/pypy_compat.py +317 -0
  231. quantum/runtime/python_bridge.py +698 -0
  232. quantum/runtime/query_validators.py +304 -0
  233. quantum/runtime/renderer.py +734 -0
  234. quantum/runtime/service_container.py +470 -0
  235. quantum/runtime/table_params.py +163 -0
  236. quantum/runtime/terminal_builder.py +76 -0
  237. quantum/runtime/terminal_code_generator.py +607 -0
  238. quantum/runtime/terminal_templates.py +243 -0
  239. quantum/runtime/testing_builder.py +77 -0
  240. quantum/runtime/testing_code_generator.py +833 -0
  241. quantum/runtime/testing_templates.py +85 -0
  242. quantum/runtime/ui_builder.py +222 -0
  243. quantum/runtime/ui_console.py +405 -0
  244. quantum/runtime/ui_desktop.py +56 -0
  245. quantum/runtime/ui_form_rules.py +193 -0
  246. quantum/runtime/ui_html_adapter.py +2567 -0
  247. quantum/runtime/ui_html_templates.py +2106 -0
  248. quantum/runtime/ui_mobile_adapter.py +1832 -0
  249. quantum/runtime/ui_mobile_templates.py +1003 -0
  250. quantum/runtime/ui_page_renderer.py +392 -0
  251. quantum/runtime/ui_pager.py +73 -0
  252. quantum/runtime/ui_table_edit.py +139 -0
  253. quantum/runtime/ui_textual_adapter.py +1866 -0
  254. quantum/runtime/ui_textual_templates.py +45 -0
  255. quantum/runtime/ui_tokens.py +465 -0
  256. quantum/runtime/ui_validator.py +365 -0
  257. quantum/runtime/ui_view.py +221 -0
  258. quantum/runtime/validators.py +256 -0
  259. quantum/runtime/web_server.py +2131 -0
  260. quantum/runtime/websocket_adapter.py +501 -0
  261. quantum/runtime/websocket_service.py +585 -0
  262. quantum/runtime/websocket_transport.py +289 -0
  263. quantum/runtime/wsgi.py +101 -0
  264. quantum/services.py +109 -0
  265. quantum/utils/__init__.py +1 -0
  266. quantum_admin/__init__.py +4 -0
  267. quantum_admin/assets/quantum-admin-connectors.js +123 -0
  268. quantum_admin/assets/quantum-admin.css +2058 -0
  269. quantum_admin/assets/quantum-source-highlight.js +106 -0
  270. quantum_admin/backend/__init__.py +4 -0
  271. quantum_admin/backend/audit_service.py +346 -0
  272. quantum_admin/backend/auth_service.py +394 -0
  273. quantum_admin/backend/component_discovery.py +766 -0
  274. quantum_admin/backend/config_generator.py +586 -0
  275. quantum_admin/backend/connector_service.py +1016 -0
  276. quantum_admin/backend/crud.py +557 -0
  277. quantum_admin/backend/database.py +226 -0
  278. quantum_admin/backend/db_setup_service.py +193 -0
  279. quantum_admin/backend/deploy_service.py +698 -0
  280. quantum_admin/backend/docker_service.py +422 -0
  281. quantum_admin/backend/environment_service.py +418 -0
  282. quantum_admin/backend/git_service.py +370 -0
  283. quantum_admin/backend/health_service.py +390 -0
  284. quantum_admin/backend/job_service.py +347 -0
  285. quantum_admin/backend/log_service.py +331 -0
  286. quantum_admin/backend/main.py +14426 -0
  287. quantum_admin/backend/models.py +986 -0
  288. quantum_admin/backend/pipeline_service.py +812 -0
  289. quantum_admin/backend/resource_manager.py +1118 -0
  290. quantum_admin/backend/schemas.py +419 -0
  291. quantum_admin/backend/secret_manager.py +277 -0
  292. quantum_admin/backend/settings_service.py +488 -0
  293. quantum_admin/backend/template_service.py +710 -0
  294. quantum_admin/backend/test_execution_service.py +364 -0
  295. quantum_admin/backend/test_generator.py +782 -0
  296. quantum_admin/backend/webhook_service.py +454 -0
  297. quantum_admin/backend/websocket_manager.py +360 -0
  298. quantum_admin/components/admin/_layout/AdminShell.q +121 -0
  299. quantum_admin/components/admin/agents.q +107 -0
  300. quantum_admin/components/admin/app/[name].q +558 -0
  301. quantum_admin/components/admin/applications.q +160 -0
  302. quantum_admin/components/admin/component/[...path].q +205 -0
  303. quantum_admin/components/admin/components.q +59 -0
  304. quantum_admin/components/admin/connectors.q +283 -0
  305. quantum_admin/components/admin/dashboard.q +86 -0
  306. quantum_admin/components/admin/database.q +95 -0
  307. quantum_admin/components/admin/features.q +69 -0
  308. quantum_admin/components/admin/index.q +12 -0
  309. quantum_admin/components/admin/jobs.q +75 -0
  310. quantum_admin/components/admin/login.q +65 -0
  311. quantum_admin/components/admin/logout.q +11 -0
  312. quantum_admin/components/admin/settings.q +174 -0
  313. quantum_admin/components/admin/source.q +45 -0
  314. quantum_admin/components/admin/tests.q +73 -0
  315. quantum_admin/run.py +13 -0
  316. quantum_admin/services/__init__.py +16 -0
  317. quantum_admin/services/_base.py +84 -0
  318. quantum_admin/services/_component_test_generator.py +205 -0
  319. quantum_admin/services/_yaml_editor.py +82 -0
  320. quantum_admin/services/apps.py +330 -0
  321. quantum_admin/services/auth.py +61 -0
  322. quantum_admin/services/components.py +210 -0
  323. quantum_admin/services/connectors.py +164 -0
  324. quantum_admin/services/projects.py +189 -0
  325. quantum_admin/services/repository.py +223 -0
  326. quantum_admin/services/settings.py +150 -0
  327. quantum_admin/services/yaml_import.py +118 -0
  328. quantum_framework-0.18.0.dist-info/METADATA +281 -0
  329. quantum_framework-0.18.0.dist-info/RECORD +333 -0
  330. quantum_framework-0.18.0.dist-info/WHEEL +5 -0
  331. quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
  332. quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
  333. quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,218 @@
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
+ from quantum.core.expressions import coerce_number
38
+
39
+ logger = logging.getLogger('quantum.param')
40
+
41
+ INT_TYPES = ('integer', 'int', 'long')
42
+ FLOAT_TYPES = ('number', 'numeric', 'decimal', 'float', 'double')
43
+ FILE_TYPES = ('file', 'binary', 'upload')
44
+ # Tipos que passam intactos: converter mudaria dado que o destino espera
45
+ # receber como veio.
46
+ PASSTHROUGH_TYPES = ('string', 'text', 'array', 'object', 'any', 'json')
47
+
48
+ # FN-4: what q:function returnType= accepts ('void': returns nothing).
49
+ RETURN_TYPES = frozenset(INT_TYPES + FLOAT_TYPES + PASSTHROUGH_TYPES + ('boolean', 'email', 'url', 'void'))
50
+
51
+ _EMAIL = re.compile(r'^[^@\s]+@[^@\s]+\.[^@\s]+$')
52
+ _URL = re.compile(r'^https?://', re.IGNORECASE)
53
+
54
+
55
+ def is_uploaded_file(value: Any) -> bool:
56
+ """Um upload (werkzeug FileStorage ou equivalente).
57
+
58
+ Checado por forma, nao por import: o werkzeug e opcional, e uma checagem
59
+ por isinstance obrigaria a importa-lo so para decidir se um valor pode
60
+ virar texto.
61
+ """
62
+ return hasattr(value, 'filename') and hasattr(value, 'read')
63
+
64
+
65
+ def coerce(param_def, value: Any) -> Tuple[Any, Optional[str]]:
66
+ """Devolve (valor_convertido, erro). Erro None quando esta tudo bem."""
67
+ ptype = (getattr(param_def, 'type', None) or 'string').lower()
68
+ name = getattr(param_def, 'name', '?')
69
+
70
+ if ptype in FILE_TYPES:
71
+ if value is None or is_uploaded_file(value):
72
+ return value, None
73
+ return value, (
74
+ f"Parameter '{name}' must be an uploaded file, got "
75
+ f"{type(value).__name__}")
76
+
77
+ # Um arquivo nunca vira texto nem numero. Antes, `str(value)` produzia a
78
+ # repr do FileStorage e o resto do fluxo trabalhava com ela.
79
+ if is_uploaded_file(value):
80
+ return value, (
81
+ f"Parameter '{name}' received an uploaded file but is declared "
82
+ f"type=\"{ptype}\" — use type=\"file\"")
83
+
84
+ if ptype in INT_TYPES:
85
+ if isinstance(value, bool):
86
+ return value, f"Parameter '{name}' must be an integer"
87
+ if isinstance(value, int):
88
+ return value, None
89
+ try:
90
+ return int(str(value).strip()), None
91
+ except (TypeError, ValueError):
92
+ return value, f"Parameter '{name}' must be an integer, got {value!r}"
93
+
94
+ if ptype in FLOAT_TYPES:
95
+ if isinstance(value, bool):
96
+ return value, f"Parameter '{name}' must be a number"
97
+ # Same rule as q:set type="number" (ERR-1): "30" is 30, "2.5" is 2.5.
98
+ # float() made every form number a float, so "30" displayed as 30.0.
99
+ numero = coerce_number(value) if isinstance(value, str) else value
100
+ if isinstance(numero, (int, float)) and not isinstance(numero, bool):
101
+ return (float(numero) if ptype in ('decimal', 'float', 'double') else numero), None
102
+ return value, f"Parameter '{name}' must be a number, got {value!r}"
103
+
104
+ if ptype == 'boolean':
105
+ if isinstance(value, bool):
106
+ return value, None
107
+ text = str(value).strip().lower()
108
+ if text in ('true', 'yes', '1', 'on'):
109
+ return True, None
110
+ if text in ('false', 'no', '0', 'off', ''):
111
+ return False, None
112
+ return value, f"Parameter '{name}' must be a boolean, got {value!r}"
113
+
114
+ if ptype == 'email':
115
+ text = str(value)
116
+ if not _EMAIL.match(text):
117
+ return value, f"Parameter '{name}' must be a valid email"
118
+ return text, None
119
+
120
+ if ptype == 'url':
121
+ text = str(value)
122
+ if not _URL.match(text):
123
+ return value, f"Parameter '{name}' must be a valid URL"
124
+ return text, None
125
+
126
+ return value, None
127
+
128
+
129
+ def check_rules(param_def, value: Any) -> List[str]:
130
+ """min/max/minlength/maxlength/pattern/enum."""
131
+ errors: List[str] = []
132
+ name = getattr(param_def, 'name', '?')
133
+
134
+ # Nada de comprimento, faixa ou padrao faz sentido sobre um arquivo. O que
135
+ # vale para um upload e accept= (ACT-11).
136
+ if is_uploaded_file(value):
137
+ accept = getattr(param_def, 'accept', None)
138
+ if accept and not accepts_file(value, accept):
139
+ errors.append(f"Parameter '{name}' accepts {accept}; got "
140
+ f"'{getattr(value, 'filename', '')}'")
141
+ return errors
142
+
143
+ minimo = getattr(param_def, 'min', None)
144
+ maximo = getattr(param_def, 'max', None)
145
+ if minimo is not None or maximo is not None:
146
+ try:
147
+ numeric = float(value)
148
+ except (TypeError, ValueError):
149
+ numeric = None
150
+ if numeric is not None:
151
+ for bound, comparison, word in (
152
+ (minimo, lambda a, b: a < b, "at least"),
153
+ (maximo, lambda a, b: a > b, "at most"),
154
+ ):
155
+ if bound is None:
156
+ continue
157
+ try:
158
+ limit = float(bound)
159
+ except (TypeError, ValueError):
160
+ continue
161
+ if comparison(numeric, limit):
162
+ errors.append(
163
+ f"Parameter '{name}' must be {word} {bound} "
164
+ f"(got {value})")
165
+
166
+ if isinstance(value, str):
167
+ minlength = getattr(param_def, 'minlength', None)
168
+ maxlength = getattr(param_def, 'maxlength', None)
169
+ if minlength is not None and len(value) < minlength:
170
+ errors.append(
171
+ f"Parameter '{name}' must be at least {minlength} characters")
172
+ if maxlength is not None and len(value) > maxlength:
173
+ errors.append(
174
+ f"Parameter '{name}' must be at most {maxlength} characters")
175
+ pattern = getattr(param_def, 'pattern', None)
176
+ if pattern:
177
+ try:
178
+ if not re.search(pattern, value):
179
+ errors.append(
180
+ f"Parameter '{name}' does not match {pattern!r}")
181
+ except re.error as exc:
182
+ logger.warning(
183
+ "q:param %s has an invalid pattern %r: %s",
184
+ name, pattern, exc)
185
+
186
+ enum = getattr(param_def, 'enum', None)
187
+ if enum:
188
+ allowed = [v.strip() for v in str(enum).split(',') if v.strip()]
189
+ if allowed and str(value) not in allowed:
190
+ errors.append(
191
+ f"Parameter '{name}' must be one of: {', '.join(allowed)}")
192
+
193
+ return errors
194
+
195
+
196
+ def accepts_file(upload, accept: str) -> bool:
197
+ """ACT-11: does an upload match an HTML-style accept list (".pdf", "image/*")?
198
+
199
+ The Content-Type an upload declares comes from the browser, so a MIME
200
+ pattern is matched against the type the FILE NAME implies as well: an
201
+ .exe sent as image/png does not pass image/*. An extension pattern is
202
+ matched against the file name.
203
+ """
204
+ import mimetypes
205
+ nome = (getattr(upload, 'filename', '') or '').lower()
206
+ pelo_nome = (mimetypes.guess_type(nome)[0] or '').lower()
207
+ declarado = (getattr(upload, 'content_type', '') or '').split(';')[0].strip().lower()
208
+ for item in (a.strip().lower() for a in accept.split(',') if a.strip()):
209
+ if item.startswith('.'):
210
+ if nome.endswith(item):
211
+ return True
212
+ elif item.endswith('/*'):
213
+ base = item[:-1]
214
+ if pelo_nome.startswith(base) and (not declarado or declarado.startswith(base)):
215
+ return True
216
+ elif pelo_nome == item and (not declarado or declarado == item):
217
+ return True
218
+ return False
@@ -0,0 +1,317 @@
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. `resource` is
241
+ # POSIX-only: importing it outside the try crashed this on Windows
242
+ # instead of falling back.
243
+ try:
244
+ import resource
245
+ return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024
246
+ except (ImportError, OSError, ValueError):
247
+ return 0.0
248
+
249
+ @staticmethod
250
+ def gc_collect():
251
+ """
252
+ Force garbage collection.
253
+ PyPy has different GC behavior than CPython.
254
+ """
255
+ import gc
256
+ gc.collect()
257
+
258
+ if is_pypy():
259
+ # PyPy may need multiple collections
260
+ gc.collect()
261
+ gc.collect()
262
+
263
+ @staticmethod
264
+ def compile_expression(expr: str, mode: str = 'eval'):
265
+ """
266
+ Compile a Python expression.
267
+ Both CPython and PyPy support this identically.
268
+ """
269
+ return compile(expr, '<quantum-expr>', mode)
270
+
271
+
272
+ def print_compatibility_report():
273
+ """Print a detailed compatibility report for the current environment"""
274
+ info = get_implementation_info()
275
+
276
+ print("\n" + "=" * 60)
277
+ print(" QUANTUM FRAMEWORK - PYTHON COMPATIBILITY REPORT")
278
+ print("=" * 60)
279
+
280
+ print(f"\n Implementation: {info['implementation']}")
281
+ print(f" Version: {info['version']}")
282
+ print(f" Platform: {info['platform']}")
283
+ print(f" 64-bit: {info['is_64bit']}")
284
+
285
+ if is_pypy():
286
+ print("\n Status: Running on PyPy (JIT enabled)")
287
+ print("\n Recommendations:")
288
+ for rec in PyPyOptimizer.get_recommendations():
289
+ print(f" - {rec}")
290
+ else:
291
+ print("\n Status: Running on CPython")
292
+ print("\n To use PyPy for better performance:")
293
+ print(" 1. Install PyPy 3.9+ from https://www.pypy.org/")
294
+ print(" 2. Create virtual environment: pypy3 -m venv venv-pypy")
295
+ print(" 3. Install dependencies: pip install -r requirements.txt")
296
+ print(" 4. Run Quantum: pypy3 src/cli/runner.py start")
297
+
298
+ print("\n Dependency Compatibility:")
299
+ results = DependencyChecker.check_quantum_dependencies()
300
+ for result in results:
301
+ status = "OK" if result.compatible else "ISSUE"
302
+ print(f" [{status}] {result.name}: {result.message}")
303
+
304
+ print("\n" + "=" * 60)
305
+
306
+
307
+ # Export commonly used functions
308
+ __all__ = [
309
+ 'is_pypy',
310
+ 'is_cpython',
311
+ 'get_python_version',
312
+ 'get_implementation_info',
313
+ 'DependencyChecker',
314
+ 'PyPyOptimizer',
315
+ 'QuantumPyPyAdapter',
316
+ 'print_compatibility_report',
317
+ ]