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.
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/__init__.py +2 -2
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/ast_parser.py +444 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/cli.py +1865 -1931
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/debug/AutoDebug.py +269 -10
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/components/debug/AutoDebug_v3.py +547 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/config.py +5 -1
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/component_renderer.py +183 -21
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/db.py +1 -1
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/debug/core.py +264 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/debug/performance.py +343 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/debug/ui.py +757 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/debug/websocket.py +384 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/main.py +22 -3
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/psx/compiler/actions.py +431 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/psx/compiler/handler_compiler.py +418 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/components/component.py +46 -4
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/core/parser.py +21 -17
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/core/runtime.py +20 -2
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/hydration/decorators.py +307 -66
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/psx/hydration/engine.py +345 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/hydration/integration.py +27 -15
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/renderer/renderer.py +13 -2
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/psx/runtime/actions_runtime.py +332 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/psx/runtime/js_actions_runtime.py +354 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/psx/tests/test_html_attribute_support.py +40 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/runtime/events.py +294 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/server/app.py +139 -10
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/HANDLER_REGISTRATION_SYSTEM.py +149 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_autodebug_enhanced.py +272 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_cli_scaffolding.py +117 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_component_rendering.py +80 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_conversion.py +41 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_critical_fixes.py +412 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_enhanced_handlers.py +83 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_extraction.py +23 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_fixes.py +128 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_handler_system.py +102 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_hydration_engine.py +143 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_integration.py +33 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_interactive_bug.py +51 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_migration.py +265 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_modular_debug.py +453 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/tests/test_websocket_complete.py +351 -0
- nextpy_framework-3.7.2/.nextpy_framework/nextpy/websocket.py +238 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2/.nextpy_framework/nextpy_framework.egg-info}/PKG-INFO +1 -1
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy_framework.egg-info/SOURCES.txt +28 -1
- {nextpy_framework-3.6.1/.nextpy_framework/nextpy_framework.egg-info → nextpy_framework-3.7.2}/PKG-INFO +1 -1
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/pyproject.toml +1 -1
- nextpy_framework-3.6.1/.nextpy_framework/nextpy/psx/hydration/engine.py +0 -1381
- nextpy_framework-3.6.1/.nextpy_framework/nextpy/vscode-extension/node_modules/flatted/python/flatted.py +0 -144
- nextpy_framework-3.6.1/.nextpy_framework/nextpy/websocket.py +0 -76
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/auth.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/builder.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/__init__.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/debug/DebugIcon.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/debug/DebugIconFixed.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/feedback.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/form.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/head.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/hooks_provider.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/image.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/layout.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/link.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/loader.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/navigation.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/toast.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/ui.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components/visual.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/components.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/conf.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/__init__.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/builder.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/component_router.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/data_fetching.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/demo_pages_simple.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/demo_router.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/renderer.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/router.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/core/sync.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/dev_server.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/dev_tools.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/errors.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/hooks.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/hooks_provider.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/hooks_provider_new.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/jsx.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/jsx_preprocessor.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/jsx_transformer.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/performance.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/plugins/__init__.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/plugins/base.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/plugins/builtin.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/plugins/config.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/plugins.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/__init__.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/core/__init__.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/core/ast_nodes.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/core/evaluator.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/devtools/language_server.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/devtools/language_server_lsp.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/devtools/psx_formatter.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/devtools/server_client_components.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/devtools/setup.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/hydration/__init__.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/tests/test_full_integration.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/utils/helpers.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/psx/vdom/vnode.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/py.typed +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/security.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/server/__init__.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/server/debug.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/server/middleware.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/source/conf.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/setup_complete.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_colors.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_demo_mode.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_demo_server.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_final.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_jsx_edgecases.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_jsx_preprocessor.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_routing.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_server.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_server_features.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_server_working.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_tailwind.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_tailwind_full.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_tailwind_integration.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/tests/test_tailwind_up_to_date.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/true_jsx.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/__init__.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/cache.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/email.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/file_upload.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/logging.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/search.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/seo.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy/utils/validators.py +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy_framework.egg-info/dependency_links.txt +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy_framework.egg-info/entry_points.txt +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy_framework.egg-info/requires.txt +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/.nextpy_framework/nextpy_framework.egg-info/top_level.txt +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/LICENSE +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/README.md +0 -0
- {nextpy_framework-3.6.1 → nextpy_framework-3.7.2}/setup.cfg +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
|
-
NextPy -
|
|
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
|
+
__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)
|