m8flow 1.1.6 → 1.1.8
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.
- package/bundled/backend/api/routes/nodes.py +54 -0
- package/bundled/backend/core/code_validator.py +11 -5
- package/bundled/backend/templates.py +54 -0
- package/bundled/frontend-dist/assets/index-7OXDs57z.js +47 -0
- package/bundled/frontend-dist/assets/index-BdX84Y93.css +1 -0
- package/bundled/frontend-dist/index.html +2 -2
- package/package.json +1 -1
- package/bundled/frontend-dist/assets/index-CwaU33vI.js +0 -45
- package/bundled/frontend-dist/assets/index-D9h1Krrv.css +0 -1
|
@@ -2,6 +2,8 @@ from fastapi import APIRouter, HTTPException, Request, UploadFile, File
|
|
|
2
2
|
from pydantic import BaseModel
|
|
3
3
|
import os
|
|
4
4
|
import shutil
|
|
5
|
+
import sys
|
|
6
|
+
import subprocess
|
|
5
7
|
|
|
6
8
|
from core.parser import parse_node_code
|
|
7
9
|
from templates import TEMPLATES
|
|
@@ -13,6 +15,42 @@ class ParseRequest(BaseModel):
|
|
|
13
15
|
code: str
|
|
14
16
|
|
|
15
17
|
|
|
18
|
+
class InstallRequest(BaseModel):
|
|
19
|
+
package: str # e.g. "lightgbm" or "xgboost==2.0.3"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@router.post("/install")
|
|
23
|
+
def install_package(req: InstallRequest):
|
|
24
|
+
"""
|
|
25
|
+
Install a Python package into the currently running venv using pip.
|
|
26
|
+
Returns stdout/stderr so the frontend can display real output.
|
|
27
|
+
"""
|
|
28
|
+
package = req.package.strip()
|
|
29
|
+
|
|
30
|
+
# Basic safety: reject obviously dangerous strings
|
|
31
|
+
forbidden = (";", "&", "|", "`", "$", ">", "<", "\n", "\r")
|
|
32
|
+
if not package or any(c in package for c in forbidden):
|
|
33
|
+
raise HTTPException(status_code=422, detail="Invalid package name.")
|
|
34
|
+
|
|
35
|
+
result = subprocess.run(
|
|
36
|
+
[sys.executable, "-m", "pip", "install", package,
|
|
37
|
+
"--disable-pip-version-check"],
|
|
38
|
+
capture_output=True,
|
|
39
|
+
text=True,
|
|
40
|
+
timeout=180,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
output = (result.stdout + result.stderr).strip()
|
|
44
|
+
|
|
45
|
+
if result.returncode != 0:
|
|
46
|
+
raise HTTPException(
|
|
47
|
+
status_code=400,
|
|
48
|
+
detail=output or f"pip install failed (exit {result.returncode})",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return {"ok": True, "output": output, "package": package}
|
|
52
|
+
|
|
53
|
+
|
|
16
54
|
@router.post("/parse")
|
|
17
55
|
def parse(req: ParseRequest):
|
|
18
56
|
"""Parse Python code and return the derived NodeSchema (inputs, outputs, errors)."""
|
|
@@ -92,6 +130,22 @@ async def generate_node_code_route(http_request: Request, req: GenerateCodeReque
|
|
|
92
130
|
raise HTTPException(status_code=500, detail=f"{type(exc).__name__}: {exc}")
|
|
93
131
|
|
|
94
132
|
|
|
133
|
+
@router.get("/packages")
|
|
134
|
+
def list_packages():
|
|
135
|
+
"""Return all packages currently installed in the running Python environment."""
|
|
136
|
+
result = subprocess.run(
|
|
137
|
+
[sys.executable, "-m", "pip", "list", "--format=json", "--disable-pip-version-check"],
|
|
138
|
+
capture_output=True,
|
|
139
|
+
text=True,
|
|
140
|
+
timeout=30,
|
|
141
|
+
)
|
|
142
|
+
if result.returncode != 0:
|
|
143
|
+
raise HTTPException(status_code=500, detail="Failed to list packages.")
|
|
144
|
+
import json as _json
|
|
145
|
+
packages = _json.loads(result.stdout or "[]")
|
|
146
|
+
return {"packages": packages}
|
|
147
|
+
|
|
148
|
+
|
|
95
149
|
@router.get("/templates")
|
|
96
150
|
def list_templates():
|
|
97
151
|
"""Return every prebuilt template with its pre-parsed schema attached."""
|
|
@@ -17,14 +17,14 @@ from typing import NamedTuple
|
|
|
17
17
|
# ── Blocklists ────────────────────────────────────────────────────────────────
|
|
18
18
|
|
|
19
19
|
BLOCKED_IMPORTS = frozenset({
|
|
20
|
-
# System / process execution
|
|
21
|
-
"
|
|
20
|
+
# System / process execution (os is allowed with restrictions below)
|
|
21
|
+
"sys", "subprocess", "shutil", "signal",
|
|
22
22
|
# Networking
|
|
23
23
|
"socket", "http", "urllib", "requests", "httpx", "ftplib", "smtplib",
|
|
24
24
|
# Code injection
|
|
25
25
|
"importlib", "builtins", "ctypes", "cffi",
|
|
26
|
-
# Filesystem
|
|
27
|
-
"
|
|
26
|
+
# Filesystem wildcards
|
|
27
|
+
"glob",
|
|
28
28
|
# Threading / multiprocessing
|
|
29
29
|
"threading", "multiprocessing", "concurrent",
|
|
30
30
|
# Package management
|
|
@@ -32,19 +32,25 @@ BLOCKED_IMPORTS = frozenset({
|
|
|
32
32
|
})
|
|
33
33
|
|
|
34
34
|
BLOCKED_BUILTINS = frozenset({
|
|
35
|
-
"eval", "exec", "compile", "__import__", "
|
|
35
|
+
"eval", "exec", "compile", "__import__", "input",
|
|
36
36
|
"breakpoint", "vars", "dir",
|
|
37
|
+
# os-level execution even if os is imported
|
|
38
|
+
"system", "popen", "execv", "execve", "execvp",
|
|
37
39
|
})
|
|
38
40
|
|
|
39
41
|
ALLOWED_IMPORTS = frozenset({
|
|
40
42
|
# Scientific computing
|
|
41
43
|
"numpy", "pandas", "scipy", "sklearn", "xgboost", "lightgbm",
|
|
42
44
|
"statsmodels", "imblearn",
|
|
45
|
+
# Model persistence
|
|
46
|
+
"joblib", "pickle",
|
|
43
47
|
# Plotting
|
|
44
48
|
"matplotlib", "seaborn", "plotly", "mpl_toolkits",
|
|
45
49
|
# Standard safe libs
|
|
46
50
|
"math", "statistics", "itertools", "functools", "collections",
|
|
47
51
|
"json", "re", "datetime", "typing",
|
|
52
|
+
# Filesystem (path operations, needed for model saving)
|
|
53
|
+
"os", "io", "pathlib", "tempfile",
|
|
48
54
|
})
|
|
49
55
|
|
|
50
56
|
|
|
@@ -2769,6 +2769,56 @@ def run(
|
|
|
2769
2769
|
'''
|
|
2770
2770
|
|
|
2771
2771
|
|
|
2772
|
+
MODEL_SAVER = '''
|
|
2773
|
+
def run(model, file_path: str = "saved_model.pkl") -> dict:
|
|
2774
|
+
"""Save a trained model to disk using joblib (faster + handles numpy arrays better than pickle).
|
|
2775
|
+
|
|
2776
|
+
file_path : path where the model file will be written.
|
|
2777
|
+
Supports any extension (.pkl / .joblib / .model).
|
|
2778
|
+
Returns the absolute path so downstream nodes can reference it.
|
|
2779
|
+
"""
|
|
2780
|
+
import joblib
|
|
2781
|
+
import os
|
|
2782
|
+
|
|
2783
|
+
abs_path = os.path.abspath(file_path)
|
|
2784
|
+
parent = os.path.dirname(abs_path)
|
|
2785
|
+
if parent:
|
|
2786
|
+
os.makedirs(parent, exist_ok=True)
|
|
2787
|
+
|
|
2788
|
+
joblib.dump(model, abs_path)
|
|
2789
|
+
size_kb = round(os.path.getsize(abs_path) / 1024, 1)
|
|
2790
|
+
|
|
2791
|
+
return {
|
|
2792
|
+
"path": abs_path,
|
|
2793
|
+
"size_kb": size_kb,
|
|
2794
|
+
"status": "saved",
|
|
2795
|
+
}
|
|
2796
|
+
'''
|
|
2797
|
+
|
|
2798
|
+
MODEL_LOADER = '''
|
|
2799
|
+
def run(file_path: str = "saved_model.pkl") -> dict:
|
|
2800
|
+
"""Load a previously saved model from disk using joblib.
|
|
2801
|
+
|
|
2802
|
+
file_path : path to the model file produced by Model Saver.
|
|
2803
|
+
Returns the model object ready for inference or further training.
|
|
2804
|
+
"""
|
|
2805
|
+
import joblib
|
|
2806
|
+
import os
|
|
2807
|
+
|
|
2808
|
+
abs_path = os.path.abspath(file_path)
|
|
2809
|
+
if not os.path.exists(abs_path):
|
|
2810
|
+
raise FileNotFoundError(f"Model file not found: {abs_path}")
|
|
2811
|
+
|
|
2812
|
+
model = joblib.load(abs_path)
|
|
2813
|
+
|
|
2814
|
+
return {
|
|
2815
|
+
"model": model,
|
|
2816
|
+
"path": abs_path,
|
|
2817
|
+
"status": "loaded",
|
|
2818
|
+
}
|
|
2819
|
+
'''
|
|
2820
|
+
|
|
2821
|
+
|
|
2772
2822
|
TEMPLATES: list[dict] = [
|
|
2773
2823
|
# Data
|
|
2774
2824
|
{"id": "csv_loader", "label": "CSV Loader", "category": "Data", "code": CSV_LOADER},
|
|
@@ -2892,6 +2942,10 @@ TEMPLATES: list[dict] = [
|
|
|
2892
2942
|
{"id": "calibration_curve_data", "label": "Calibration Curve", "category": "Evaluation", "code": CALIBRATION_CURVE_DATA},
|
|
2893
2943
|
{"id": "learning_curve_analyzer", "label": "Learning Curve Analyzer", "category": "Evaluation", "code": LEARNING_CURVE_ANALYZER},
|
|
2894
2944
|
{"id": "cost_benefit_matrix", "label": "Cost-Benefit Matrix", "category": "Evaluation", "code": COST_BENEFIT_MATRIX},
|
|
2945
|
+
|
|
2946
|
+
# ── Model Persistence ────────────────────────────────────────────────────
|
|
2947
|
+
{"id": "model_saver", "label": "Model Saver", "category": "Data", "code": MODEL_SAVER},
|
|
2948
|
+
{"id": "model_loader", "label": "Model Loader", "category": "Data", "code": MODEL_LOADER},
|
|
2895
2949
|
]
|
|
2896
2950
|
|
|
2897
2951
|
|