nextpy-framework 3.6.1__tar.gz → 3.7.2__tar.gz

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 (144) hide show
  1. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/__init__.py +2 -2
  2. nextpy_framework-3.7.2/.nextpy_framework/nextpy/ast_parser.py +444 -0
  3. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/cli.py +1865 -1931
  4. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/debug/AutoDebug.py +269 -10
  5. nextpy_framework-3.7.2/.nextpy_framework/nextpy/components/debug/AutoDebug_v3.py +547 -0
  6. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/config.py +5 -1
  7. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/component_renderer.py +183 -21
  8. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/db.py +1 -1
  9. nextpy_framework-3.7.2/.nextpy_framework/nextpy/debug/core.py +264 -0
  10. nextpy_framework-3.7.2/.nextpy_framework/nextpy/debug/performance.py +343 -0
  11. nextpy_framework-3.7.2/.nextpy_framework/nextpy/debug/ui.py +757 -0
  12. nextpy_framework-3.7.2/.nextpy_framework/nextpy/debug/websocket.py +384 -0
  13. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/main.py +22 -3
  14. nextpy_framework-3.7.2/.nextpy_framework/nextpy/psx/compiler/actions.py +431 -0
  15. nextpy_framework-3.7.2/.nextpy_framework/nextpy/psx/compiler/handler_compiler.py +418 -0
  16. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/components/component.py +46 -4
  17. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/core/parser.py +21 -17
  18. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/core/runtime.py +20 -2
  19. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/hydration/decorators.py +307 -66
  20. nextpy_framework-3.7.2/.nextpy_framework/nextpy/psx/hydration/engine.py +345 -0
  21. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/hydration/integration.py +27 -15
  22. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/renderer/renderer.py +13 -2
  23. nextpy_framework-3.7.2/.nextpy_framework/nextpy/psx/runtime/actions_runtime.py +332 -0
  24. nextpy_framework-3.7.2/.nextpy_framework/nextpy/psx/runtime/js_actions_runtime.py +354 -0
  25. nextpy_framework-3.7.2/.nextpy_framework/nextpy/psx/tests/test_html_attribute_support.py +40 -0
  26. nextpy_framework-3.7.2/.nextpy_framework/nextpy/runtime/events.py +294 -0
  27. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/server/app.py +139 -10
  28. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/HANDLER_REGISTRATION_SYSTEM.py +149 -0
  29. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_autodebug_enhanced.py +272 -0
  30. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_cli_scaffolding.py +117 -0
  31. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_component_rendering.py +80 -0
  32. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_conversion.py +41 -0
  33. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_critical_fixes.py +412 -0
  34. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_enhanced_handlers.py +83 -0
  35. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_extraction.py +23 -0
  36. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_fixes.py +128 -0
  37. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_handler_system.py +102 -0
  38. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_hydration_engine.py +143 -0
  39. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_integration.py +33 -0
  40. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_interactive_bug.py +51 -0
  41. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_migration.py +265 -0
  42. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_modular_debug.py +453 -0
  43. nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_websocket_complete.py +351 -0
  44. nextpy_framework-3.7.2/.nextpy_framework/nextpy/websocket.py +238 -0
  45. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2/.nextpy_framework/nextpy_framework.egg-info}/PKG-INFO +1 -1
  46. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy_framework.egg-info/SOURCES.txt +28 -1
  47. {nextpy_framework-3.6.1/.nextpy_framework/nextpy_framework.egg-info → nextpy_framework-3.7.2}/PKG-INFO +1 -1
  48. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/pyproject.toml +1 -1
  49. nextpy_framework-3.6.1/.nextpy_framework/nextpy/psx/hydration/engine.py +0 -1381
  50. nextpy_framework-3.6.1/.nextpy_framework/nextpy/vscode-extension/node_modules/flatted/python/flatted.py +0 -144
  51. nextpy_framework-3.6.1/.nextpy_framework/nextpy/websocket.py +0 -76
  52. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/auth.py +0 -0
  53. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/builder.py +0 -0
  54. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/__init__.py +0 -0
  55. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/debug/DebugIcon.py +0 -0
  56. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/debug/DebugIconFixed.py +0 -0
  57. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/feedback.py +0 -0
  58. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/form.py +0 -0
  59. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/head.py +0 -0
  60. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/hooks_provider.py +0 -0
  61. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/image.py +0 -0
  62. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/layout.py +0 -0
  63. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/link.py +0 -0
  64. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/loader.py +0 -0
  65. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/navigation.py +0 -0
  66. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/toast.py +0 -0
  67. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/ui.py +0 -0
  68. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/visual.py +0 -0
  69. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components.py +0 -0
  70. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/conf.py +0 -0
  71. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/__init__.py +0 -0
  72. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/builder.py +0 -0
  73. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/component_router.py +0 -0
  74. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/data_fetching.py +0 -0
  75. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/demo_pages_simple.py +0 -0
  76. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/demo_router.py +0 -0
  77. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/renderer.py +0 -0
  78. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/router.py +0 -0
  79. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/sync.py +0 -0
  80. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/dev_server.py +0 -0
  81. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/dev_tools.py +0 -0
  82. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/errors.py +0 -0
  83. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/hooks.py +0 -0
  84. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/hooks_provider.py +0 -0
  85. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/hooks_provider_new.py +0 -0
  86. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/jsx.py +0 -0
  87. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/jsx_preprocessor.py +0 -0
  88. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/jsx_transformer.py +0 -0
  89. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/performance.py +0 -0
  90. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/plugins/__init__.py +0 -0
  91. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/plugins/base.py +0 -0
  92. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/plugins/builtin.py +0 -0
  93. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/plugins/config.py +0 -0
  94. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/plugins.py +0 -0
  95. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/__init__.py +0 -0
  96. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/core/__init__.py +0 -0
  97. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/core/ast_nodes.py +0 -0
  98. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/core/evaluator.py +0 -0
  99. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/devtools/language_server.py +0 -0
  100. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/devtools/language_server_lsp.py +0 -0
  101. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/devtools/psx_formatter.py +0 -0
  102. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/devtools/server_client_components.py +0 -0
  103. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/devtools/setup.py +0 -0
  104. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/hydration/__init__.py +0 -0
  105. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/tests/test_full_integration.py +0 -0
  106. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/utils/helpers.py +0 -0
  107. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/vdom/vnode.py +0 -0
  108. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/py.typed +0 -0
  109. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/security.py +0 -0
  110. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/server/__init__.py +0 -0
  111. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/server/debug.py +0 -0
  112. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/server/middleware.py +0 -0
  113. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/source/conf.py +0 -0
  114. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/setup_complete.py +0 -0
  115. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_colors.py +0 -0
  116. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_demo_mode.py +0 -0
  117. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_demo_server.py +0 -0
  118. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_final.py +0 -0
  119. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_jsx_edgecases.py +0 -0
  120. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_jsx_preprocessor.py +0 -0
  121. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_routing.py +0 -0
  122. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_server.py +0 -0
  123. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_server_features.py +0 -0
  124. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_server_working.py +0 -0
  125. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_tailwind.py +0 -0
  126. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_tailwind_full.py +0 -0
  127. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_tailwind_integration.py +0 -0
  128. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_tailwind_up_to_date.py +0 -0
  129. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/true_jsx.py +0 -0
  130. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/__init__.py +0 -0
  131. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/cache.py +0 -0
  132. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/email.py +0 -0
  133. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/file_upload.py +0 -0
  134. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/logging.py +0 -0
  135. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/search.py +0 -0
  136. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/seo.py +0 -0
  137. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/validators.py +0 -0
  138. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy_framework.egg-info/dependency_links.txt +0 -0
  139. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy_framework.egg-info/entry_points.txt +0 -0
  140. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy_framework.egg-info/requires.txt +0 -0
  141. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy_framework.egg-info/top_level.txt +0 -0
  142. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/LICENSE +0 -0
  143. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/README.md +0 -0
  144. {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/setup.cfg +0 -0
@@ -1,9 +1,9 @@
1
1
  """
2
- NextPy - A Python web framework inspired by Next.js
2
+ NextPy - The Python Web Framdwork
3
3
  File-based routing, SSR, SSG, and more with FastAPI + PSX (True JSX)
4
4
  """
5
5
 
6
- __version__ = "3.6.1"
6
+ __version__ = "3.7.2"
7
7
 
8
8
  from nextpy.core.router import Router, Route, DynamicRoute
9
9
  from nextpy.core.renderer import Renderer
@@ -0,0 +1,444 @@
1
+ """
2
+ NextPy AST Parser - Secure and reliable parsing using Abstract Syntax Trees
3
+ Replaces string-based evaluation with AST-based parsing for better security and performance
4
+ """
5
+
6
+ import ast
7
+ import operator
8
+ from typing import Any, Dict, List, Optional, Union
9
+ from dataclasses import dataclass
10
+
11
+
12
+ @dataclass
13
+ class ASTExpression:
14
+ """Represents a parsed AST expression in NextPy Intermediate Representation"""
15
+ type: str
16
+ operator: Optional[str] = None
17
+ left: Optional['ASTExpression'] = None
18
+ right: Optional['ASTExpression'] = None
19
+ value: Any = None
20
+ name: Optional[str] = None
21
+ args: Optional[List['ASTExpression']] = None
22
+ body: Optional[List['ASTExpression']] = None
23
+
24
+
25
+ class ASTParser:
26
+ """Converts Python AST to NextPy Intermediate Representation"""
27
+
28
+ # Safe operators mapping
29
+ OPERATORS = {
30
+ ast.Add: operator.add,
31
+ ast.Sub: operator.sub,
32
+ ast.Mult: operator.mul,
33
+ ast.Div: operator.truediv,
34
+ ast.FloorDiv: operator.floordiv,
35
+ ast.Mod: operator.mod,
36
+ ast.Pow: operator.pow,
37
+ ast.LShift: operator.lshift,
38
+ ast.RShift: operator.rshift,
39
+ ast.BitOr: operator.or_,
40
+ ast.BitXor: operator.xor,
41
+ ast.BitAnd: operator.and_,
42
+ ast.MatMult: operator.matmul,
43
+
44
+ ast.Eq: operator.eq,
45
+ ast.NotEq: operator.ne,
46
+ ast.Lt: operator.lt,
47
+ ast.LtE: operator.le,
48
+ ast.Gt: operator.gt,
49
+ ast.GtE: operator.ge,
50
+ ast.Is: operator.is_,
51
+ ast.IsNot: operator.is_not,
52
+ ast.In: lambda a, b: a in b,
53
+ ast.NotIn: lambda a, b: a not in b,
54
+
55
+ ast.And: lambda a, b: a and b,
56
+ ast.Or: lambda a, b: a or b,
57
+ ast.Not: operator.not_,
58
+ }
59
+
60
+ def __init__(self, allowed_names: Optional[Dict[str, Any]] = None):
61
+ """
62
+ Initialize AST parser with allowed names for security
63
+
64
+ Args:
65
+ allowed_names: Dictionary of allowed variable names and their values
66
+ """
67
+ self.allowed_names = allowed_names or {}
68
+
69
+ def parse_expression(self, expression: str) -> ASTExpression:
70
+ """
71
+ Parse a Python expression into NextPy IR
72
+
73
+ Args:
74
+ expression: Python expression string
75
+
76
+ Returns:
77
+ ASTExpression in NextPy IR format
78
+ """
79
+ try:
80
+ tree = ast.parse(expression, mode='eval')
81
+ return self._convert_node(tree.body)
82
+ except SyntaxError as e:
83
+ raise ValueError(f"Invalid Python syntax: {e}")
84
+ except Exception as e:
85
+ raise ValueError(f"Error parsing expression: {e}")
86
+
87
+ def parse_code(self, code: str) -> List[ASTExpression]:
88
+ """
89
+ Parse Python code into NextPy IR
90
+
91
+ Args:
92
+ code: Python code string
93
+
94
+ Returns:
95
+ List of ASTExpression in NextPy IR format
96
+ """
97
+ try:
98
+ tree = ast.parse(code, mode='exec')
99
+ return [self._convert_node(node) for node in tree.body]
100
+ except SyntaxError as e:
101
+ raise ValueError(f"Invalid Python syntax: {e}")
102
+ except Exception as e:
103
+ raise ValueError(f"Error parsing code: {e}")
104
+
105
+ def evaluate_expression(self, expression: str, context: Optional[Dict[str, Any]] = None) -> Any:
106
+ """
107
+ Safely evaluate a Python expression using AST
108
+
109
+ Args:
110
+ expression: Python expression string
111
+ context: Additional context variables
112
+
113
+ Returns:
114
+ Evaluated result
115
+ """
116
+ ast_expr = self.parse_expression(expression)
117
+ return self._evaluate_ast_expression(ast_expr, context)
118
+
119
+ def _convert_node(self, node: ast.AST) -> ASTExpression:
120
+ """Convert AST node to NextPy IR"""
121
+
122
+ if isinstance(node, ast.Constant):
123
+ return ASTExpression(
124
+ type="Literal",
125
+ value=node.value
126
+ )
127
+
128
+ elif isinstance(node, ast.Name):
129
+ # Allow all names during parsing, check during evaluation
130
+ return ASTExpression(
131
+ type="Identifier",
132
+ name=node.id
133
+ )
134
+
135
+ elif isinstance(node, ast.BinOp):
136
+ return ASTExpression(
137
+ type="BinaryExpression",
138
+ operator=self._get_operator_name(node.op),
139
+ left=self._convert_node(node.left),
140
+ right=self._convert_node(node.right)
141
+ )
142
+
143
+ elif isinstance(node, ast.UnaryOp):
144
+ return ASTExpression(
145
+ type="UnaryExpression",
146
+ operator=self._get_operator_name(node.op),
147
+ left=self._convert_node(node.operand),
148
+ right=None
149
+ )
150
+
151
+ elif isinstance(node, ast.BoolOp):
152
+ return ASTExpression(
153
+ type="LogicalExpression",
154
+ operator=self._get_operator_name(node.op),
155
+ left=self._convert_node(node.values[0]),
156
+ right=self._convert_node(node.values[1]) if len(node.values) > 1 else None
157
+ )
158
+
159
+ elif isinstance(node, ast.Compare):
160
+ if len(node.comparators) == 1:
161
+ return ASTExpression(
162
+ type="BinaryExpression",
163
+ operator=self._get_operator_name(node.ops[0]),
164
+ left=self._convert_node(node.left),
165
+ right=self._convert_node(node.comparators[0])
166
+ )
167
+ else:
168
+ # Handle chained comparisons (a < b < c)
169
+ raise ValueError("Chained comparisons not yet supported")
170
+
171
+ elif isinstance(node, ast.Call):
172
+ if isinstance(node.func, ast.Name):
173
+ func_name = node.func.id
174
+ if func_name not in self.allowed_names:
175
+ raise ValueError(f"Function '{func_name}' is not allowed")
176
+
177
+ return ASTExpression(
178
+ type="CallExpression",
179
+ name=func_name,
180
+ args=[self._convert_node(arg) for arg in node.args]
181
+ )
182
+ else:
183
+ raise ValueError("Only simple function calls are allowed")
184
+
185
+ elif isinstance(node, ast.Attribute):
186
+ return ASTExpression(
187
+ type="MemberExpression",
188
+ name=node.attr,
189
+ left=self._convert_node(node.value)
190
+ )
191
+
192
+ elif isinstance(node, ast.List):
193
+ return ASTExpression(
194
+ type="ArrayExpression",
195
+ args=[self._convert_node(elt) for elt in node.elts]
196
+ )
197
+
198
+ elif isinstance(node, ast.Dict):
199
+ keys = [self._convert_node(key) for key in node.keys]
200
+ values = [self._convert_node(value) for value in node.values]
201
+ return ASTExpression(
202
+ type="ObjectExpression",
203
+ args=[keys, values]
204
+ )
205
+
206
+ else:
207
+ raise ValueError(f"Unsupported AST node type: {type(node).__name__}")
208
+
209
+ def _get_operator_name(self, op: ast.AST) -> str:
210
+ """Get operator name from AST node"""
211
+ operator_map = {
212
+ ast.Add: "+",
213
+ ast.Sub: "-",
214
+ ast.Mult: "*",
215
+ ast.Div: "/",
216
+ ast.FloorDiv: "//",
217
+ ast.Mod: "%",
218
+ ast.Pow: "**",
219
+ ast.LShift: "<<",
220
+ ast.RShift: ">>",
221
+ ast.BitOr: "|",
222
+ ast.BitXor: "^",
223
+ ast.BitAnd: "&",
224
+
225
+ ast.Eq: "==",
226
+ ast.NotEq: "!=",
227
+ ast.Lt: "<",
228
+ ast.LtE: "<=",
229
+ ast.Gt: ">",
230
+ ast.GtE: ">=",
231
+ ast.Is: "is",
232
+ ast.IsNot: "is not",
233
+ ast.In: "in",
234
+ ast.NotIn: "not in",
235
+
236
+ ast.And: "and",
237
+ ast.Or: "or",
238
+ ast.Not: "not",
239
+
240
+ ast.UAdd: "+",
241
+ ast.USub: "-",
242
+ }
243
+
244
+ if type(op) in operator_map:
245
+ return operator_map[type(op)]
246
+ else:
247
+ raise ValueError(f"Unsupported operator: {type(op).__name__}")
248
+
249
+ def _evaluate_ast_expression(self, expr: ASTExpression, context: Optional[Dict[str, Any]] = None) -> Any:
250
+ """Evaluate ASTExpression safely"""
251
+ if context is None:
252
+ context = {}
253
+
254
+ # Merge allowed names with context
255
+ eval_context = {**self.allowed_names, **context}
256
+
257
+ if expr.type == "Literal":
258
+ return expr.value
259
+
260
+ elif expr.type == "Identifier":
261
+ if expr.name in eval_context:
262
+ return eval_context[expr.name]
263
+ else:
264
+ raise ValueError(f"Undefined variable: {expr.name}")
265
+
266
+ elif expr.type == "BinaryExpression":
267
+ left = self._evaluate_ast_expression(expr.left, context)
268
+ right = self._evaluate_ast_expression(expr.right, context)
269
+
270
+ op_func = self.OPERATORS.get(self._get_ast_operator(expr.operator))
271
+ if op_func:
272
+ return op_func(left, right)
273
+ else:
274
+ raise ValueError(f"Unsupported operator: {expr.operator}")
275
+
276
+ elif expr.type == "UnaryExpression":
277
+ operand = self._evaluate_ast_expression(expr.left, context)
278
+
279
+ if expr.operator == "not":
280
+ return not operand
281
+ elif expr.operator == "-":
282
+ return -operand
283
+ elif expr.operator == "+":
284
+ return +operand
285
+ else:
286
+ raise ValueError(f"Unsupported unary operator: {expr.operator}")
287
+
288
+ elif expr.type == "LogicalExpression":
289
+ left = self._evaluate_ast_expression(expr.left, context)
290
+
291
+ if expr.operator == "and":
292
+ # Short-circuit evaluation
293
+ if not left:
294
+ return False
295
+ right = self._evaluate_ast_expression(expr.right, context)
296
+ return bool(right)
297
+ elif expr.operator == "or":
298
+ # Short-circuit evaluation
299
+ if left:
300
+ return True
301
+ right = self._evaluate_ast_expression(expr.right, context)
302
+ return bool(right)
303
+ else:
304
+ raise ValueError(f"Unsupported logical operator: {expr.operator}")
305
+
306
+ elif expr.type == "CallExpression":
307
+ func = eval_context.get(expr.name)
308
+ if not callable(func):
309
+ raise ValueError(f"'{expr.name}' is not a function")
310
+
311
+ args = [self._evaluate_ast_expression(arg, context) for arg in expr.args]
312
+ return func(*args)
313
+
314
+ elif expr.type == "ArrayExpression":
315
+ return [self._evaluate_ast_expression(arg, context) for arg in expr.args]
316
+
317
+ elif expr.type == "ObjectExpression":
318
+ keys = expr.args[0]
319
+ values = expr.args[1]
320
+ result = {}
321
+ for i, (key_expr, value_expr) in enumerate(zip(keys, values)):
322
+ key = self._evaluate_ast_expression(key_expr, context)
323
+ value = self._evaluate_ast_expression(value_expr, context)
324
+ result[key] = value
325
+ return result
326
+
327
+ else:
328
+ raise ValueError(f"Cannot evaluate expression type: {expr.type}")
329
+
330
+ def _get_ast_operator(self, op_name: str) -> type:
331
+ """Get AST operator class from operator name"""
332
+ operator_map = {
333
+ "+": ast.Add,
334
+ "-": ast.Sub,
335
+ "*": ast.Mult,
336
+ "/": ast.Div,
337
+ "//": ast.FloorDiv,
338
+ "%": ast.Mod,
339
+ "**": ast.Pow,
340
+ "<<": ast.LShift,
341
+ ">>": ast.RShift,
342
+ "|": ast.BitOr,
343
+ "^": ast.BitXor,
344
+ "&": ast.BitAnd,
345
+
346
+ "==": ast.Eq,
347
+ "!=": ast.NotEq,
348
+ "<": ast.Lt,
349
+ "<=": ast.LtE,
350
+ ">": ast.Gt,
351
+ ">=": ast.GtE,
352
+ "is": ast.Is,
353
+ "is not": ast.IsNot,
354
+ "in": ast.In,
355
+ "not in": ast.NotIn,
356
+ }
357
+
358
+ return operator_map.get(op_name)
359
+
360
+
361
+ # Global AST parser instance with safe defaults
362
+ SAFE_PARSER = ASTParser(allowed_names={
363
+ # Built-in functions
364
+ 'len': len,
365
+ 'str': str,
366
+ 'int': int,
367
+ 'float': float,
368
+ 'bool': bool,
369
+ 'list': list,
370
+ 'dict': dict,
371
+ 'tuple': tuple,
372
+ 'set': set,
373
+ 'range': range,
374
+ 'enumerate': enumerate,
375
+ 'zip': zip,
376
+ 'map': map,
377
+ 'filter': filter,
378
+ 'sum': sum,
379
+ 'max': max,
380
+ 'min': min,
381
+ 'abs': abs,
382
+ 'round': round,
383
+ 'all': all,
384
+ 'any': any,
385
+
386
+ # Math functions
387
+ 'math': __import__('math'),
388
+
389
+ # Common constants
390
+ 'True': True,
391
+ 'False': False,
392
+ 'None': None,
393
+ })
394
+
395
+
396
+ def parse_expression_safe(expression: str, context: Optional[Dict[str, Any]] = None) -> Any:
397
+ """
398
+ Safely parse and evaluate a Python expression
399
+
400
+ Args:
401
+ expression: Python expression string
402
+ context: Additional context variables
403
+
404
+ Returns:
405
+ Evaluated result
406
+ """
407
+ return SAFE_PARSER.evaluate_expression(expression, context)
408
+
409
+
410
+ def convert_to_ir(expression: str) -> Dict[str, Any]:
411
+ """
412
+ Convert Python expression to NextPy Intermediate Representation
413
+
414
+ Args:
415
+ expression: Python expression string
416
+
417
+ Returns:
418
+ Dictionary representation of ASTExpression
419
+ """
420
+ ast_expr = SAFE_PARSER.parse_expression(expression)
421
+
422
+ def expr_to_dict(expr: ASTExpression) -> Dict[str, Any]:
423
+ result = {
424
+ 'type': expr.type
425
+ }
426
+
427
+ if expr.operator is not None:
428
+ result['operator'] = expr.operator
429
+ if expr.value is not None:
430
+ result['value'] = expr.value
431
+ if expr.name is not None:
432
+ result['name'] = expr.name
433
+ if expr.left is not None:
434
+ result['left'] = expr_to_dict(expr.left)
435
+ if expr.right is not None:
436
+ result['right'] = expr_to_dict(expr.right)
437
+ if expr.args is not None:
438
+ result['args'] = [expr_to_dict(arg) for arg in expr.args]
439
+ if expr.body is not None:
440
+ result['body'] = [expr_to_dict(node) for node in expr.body]
441
+
442
+ return result
443
+
444
+ return expr_to_dict(ast_expr)