scistack-gui 0.1.28__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 (65) hide show
  1. scistack_gui-0.1.28/.gitignore +32 -0
  2. scistack_gui-0.1.28/PKG-INFO +17 -0
  3. scistack_gui-0.1.28/pyproject.toml +52 -0
  4. scistack_gui-0.1.28/scistack_gui/__init__.py +0 -0
  5. scistack_gui-0.1.28/scistack_gui/__main__.py +149 -0
  6. scistack_gui-0.1.28/scistack_gui/api/__init__.py +0 -0
  7. scistack_gui-0.1.28/scistack_gui/api/artifacts.py +52 -0
  8. scistack_gui-0.1.28/scistack_gui/api/bootstrap.py +161 -0
  9. scistack_gui-0.1.28/scistack_gui/api/builtin_functions.py +31 -0
  10. scistack_gui-0.1.28/scistack_gui/api/glue.py +108 -0
  11. scistack_gui-0.1.28/scistack_gui/api/layout.py +294 -0
  12. scistack_gui-0.1.28/scistack_gui/api/matlab_command.py +1564 -0
  13. scistack_gui-0.1.28/scistack_gui/api/pipeline.py +1335 -0
  14. scistack_gui-0.1.28/scistack_gui/api/plot.py +212 -0
  15. scistack_gui-0.1.28/scistack_gui/api/project.py +468 -0
  16. scistack_gui-0.1.28/scistack_gui/api/registry.py +33 -0
  17. scistack_gui-0.1.28/scistack_gui/api/run.py +1723 -0
  18. scistack_gui-0.1.28/scistack_gui/api/schema.py +37 -0
  19. scistack_gui-0.1.28/scistack_gui/api/scopes.py +303 -0
  20. scistack_gui-0.1.28/scistack_gui/api/variables.py +361 -0
  21. scistack_gui-0.1.28/scistack_gui/api/ws.py +392 -0
  22. scistack_gui-0.1.28/scistack_gui/app.py +75 -0
  23. scistack_gui-0.1.28/scistack_gui/bootstrap.py +266 -0
  24. scistack_gui-0.1.28/scistack_gui/config.py +1651 -0
  25. scistack_gui-0.1.28/scistack_gui/db.py +563 -0
  26. scistack_gui-0.1.28/scistack_gui/domain/__init__.py +0 -0
  27. scistack_gui-0.1.28/scistack_gui/domain/edge_resolver.py +583 -0
  28. scistack_gui-0.1.28/scistack_gui/domain/graph_builder.py +2286 -0
  29. scistack_gui-0.1.28/scistack_gui/domain/run_state.py +193 -0
  30. scistack_gui-0.1.28/scistack_gui/domain/scope_filter.py +249 -0
  31. scistack_gui-0.1.28/scistack_gui/domain/variant_resolver.py +638 -0
  32. scistack_gui-0.1.28/scistack_gui/layout.py +634 -0
  33. scistack_gui-0.1.28/scistack_gui/library_functions.py +346 -0
  34. scistack_gui-0.1.28/scistack_gui/matlab_parser.py +1028 -0
  35. scistack_gui-0.1.28/scistack_gui/matlab_registry.py +815 -0
  36. scistack_gui-0.1.28/scistack_gui/matlab_sidecar.py +369 -0
  37. scistack_gui-0.1.28/scistack_gui/notify.py +65 -0
  38. scistack_gui-0.1.28/scistack_gui/pipeline_discovery.py +354 -0
  39. scistack_gui-0.1.28/scistack_gui/pipeline_store.py +1585 -0
  40. scistack_gui-0.1.28/scistack_gui/registry.py +1129 -0
  41. scistack_gui-0.1.28/scistack_gui/server.py +1759 -0
  42. scistack_gui-0.1.28/scistack_gui/services/__init__.py +0 -0
  43. scistack_gui-0.1.28/scistack_gui/services/builtin_function_service.py +266 -0
  44. scistack_gui-0.1.28/scistack_gui/services/code_export_service.py +489 -0
  45. scistack_gui-0.1.28/scistack_gui/services/endpoint_service.py +73 -0
  46. scistack_gui-0.1.28/scistack_gui/services/execution_service.py +1491 -0
  47. scistack_gui-0.1.28/scistack_gui/services/glue_service.py +475 -0
  48. scistack_gui-0.1.28/scistack_gui/services/layout_service.py +666 -0
  49. scistack_gui-0.1.28/scistack_gui/services/matlab_command_service.py +759 -0
  50. scistack_gui-0.1.28/scistack_gui/services/parameter_service.py +246 -0
  51. scistack_gui-0.1.28/scistack_gui/services/path_input_service.py +164 -0
  52. scistack_gui-0.1.28/scistack_gui/services/pipeline_service.py +296 -0
  53. scistack_gui-0.1.28/scistack_gui/services/plot_service.py +1195 -0
  54. scistack_gui-0.1.28/scistack_gui/services/portability_service.py +714 -0
  55. scistack_gui-0.1.28/scistack_gui/services/project_init_service.py +325 -0
  56. scistack_gui-0.1.28/scistack_gui/services/project_service.py +47 -0
  57. scistack_gui-0.1.28/scistack_gui/services/registry_reload_service.py +39 -0
  58. scistack_gui-0.1.28/scistack_gui/services/run_service.py +36 -0
  59. scistack_gui-0.1.28/scistack_gui/services/scope_service.py +710 -0
  60. scistack_gui-0.1.28/scistack_gui/services/target_file_service.py +781 -0
  61. scistack_gui-0.1.28/scistack_gui/services/variable_service.py +256 -0
  62. scistack_gui-0.1.28/scistack_gui/startup.py +295 -0
  63. scistack_gui-0.1.28/scistack_gui/static/assets/index-BnuhLJ6X.css +1 -0
  64. scistack_gui-0.1.28/scistack_gui/static/assets/index-DPD2EHIC.js +254 -0
  65. scistack_gui-0.1.28/scistack_gui/static/index.html +18 -0
@@ -0,0 +1,32 @@
1
+ .venv
2
+ .DS_Store
3
+ */*/__pycache__
4
+ others_projects/sciforge
5
+ scistack-gui/extension/node_modules/
6
+ scistack-gui/frontend/node_modules/__pycache__/
7
+ *.pyc
8
+ __pycache__/
9
+ *.pyc
10
+ *.pyo
11
+ *.egg-info/
12
+ # Python package build output (rebuilt fresh by CI on every publish; a
13
+ # committed dist/ gets deleted by `rm -rf dist` mid-build and dirties the
14
+ # tree for hatch-vcs). Excludes scistack-gui/extension/dist/, which is an
15
+ # intentionally committed compiled bundle, not a Python build artifact.
16
+ /dist/
17
+ /*/dist/
18
+ # Generated database artifacts (DuckDB data/lineage + write-ahead logs)
19
+ *.duckdb
20
+ *.duckdb.wal
21
+ *.wal
22
+ scistack-gui/frontend/node_modules/
23
+ # Compiled output of the frontend's React-free unit tests (npm test in
24
+ # scistack-gui/frontend). Regenerated by `tsc -p tsconfig.test.json`; unlike
25
+ # extension/dist/, nothing loads it at runtime.
26
+ scistack-gui/frontend/dist/
27
+ # mkdocs build output (regenerated by `mkdocs build`)
28
+ /site/
29
+ # Runtime output of code_export_service (pipeline-to-code export) — timestamped
30
+ # per-run files, not source.
31
+ /exports/
32
+ *.log
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.5
2
+ Name: scistack-gui
3
+ Version: 0.1.28
4
+ Summary: GUI for SciStack pipelines
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: duckdb>=0.9.0
8
+ Requires-Dist: fastapi>=0.100.0
9
+ Requires-Dist: matplotlib>=3.8
10
+ Requires-Dist: pydantic>=2.0
11
+ Requires-Dist: scifor
12
+ Requires-Dist: scimatlab
13
+ Requires-Dist: scistack-db
14
+ Requires-Dist: scistackplot[mpl]>=0.1.23
15
+ Requires-Dist: scistackplotdb>=0.1.23
16
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
17
+ Requires-Dist: uvicorn[standard]>=0.20.0
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["hatchling", "hatch-vcs"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [tool.hatch.version]
6
+ source = "vcs"
7
+ raw-options = { search_parent_directories = true, local_scheme = "no-local-version" }
8
+
9
+ [project]
10
+ name = "scistack-gui"
11
+ dynamic = ["version"]
12
+ description = "GUI for SciStack pipelines"
13
+ license = {text = "MIT"}
14
+ requires-python = ">=3.10"
15
+ dependencies = [
16
+ "fastapi>=0.100.0",
17
+ # [standard] pulls in a WebSocket protocol library — bare uvicorn
18
+ # rejects /ws upgrades ("Unsupported upgrade request") and the GUI
19
+ # silently never receives run_output/run_done/dag_updated pushes.
20
+ "uvicorn[standard]>=0.20.0",
21
+ # Top-level import in 9 × api/*.py. Arrives via fastapi today; declared
22
+ # because metadata states intent, not coincidence.
23
+ "pydantic>=2.0",
24
+ "scistack-db",
25
+ # Top-level import in config.py and registry.py (scifor.discovery).
26
+ "scifor",
27
+ # matlab_registry.py, config.py, api/pipeline.py.
28
+ "scimatlab",
29
+ # Plot Studio. [mpl] is required, not optional: the save/export path
30
+ # calls render_matplotlib(). Both packages first shipped in v0.1.23.
31
+ "scistackplot[mpl]>=0.1.23",
32
+ "scistackplotdb>=0.1.23",
33
+ # plot_service.py imports matplotlib directly for PNG export, on top of
34
+ # whatever scistackplot[mpl] pulls in.
35
+ "matplotlib>=3.8",
36
+ "duckdb>=0.9.0",
37
+ # config.py falls back to tomli when tomllib is unavailable (<3.11).
38
+ "tomli>=2.0 ; python_version < '3.11'",
39
+ ]
40
+
41
+ [project.scripts]
42
+ scistack-gui = "scistack_gui.__main__:main"
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["scistack_gui"]
46
+
47
+ # Without this, hatchling's default file selection sweeps the whole directory
48
+ # into the sdist — frontend/ and extension/ TypeScript sources plus committed
49
+ # sourcemaps, ~11MB. The wheel only ever needs the Python package and the
50
+ # built standalone bundle under scistack_gui/static/.
51
+ [tool.hatch.build.targets.sdist]
52
+ include = ["/scistack_gui"]
File without changes
@@ -0,0 +1,149 @@
1
+ """
2
+ CLI entry point: scistack-gui [path/to/experiment.duckdb]
3
+
4
+ What happens:
5
+ 1. If a db_path is given: import pipeline code, open (or create, with
6
+ --schema-keys) the database via scistack_gui.bootstrap.
7
+ 2. If no db_path is given: skip straight to step 3 with no project loaded
8
+ — the browser opens onto the project-creation wizard
9
+ (POST /api/bootstrap/create, /api/bootstrap/open), which runs the same
10
+ bootstrap sequence from a running server.
11
+ 3. Start uvicorn on localhost:8765
12
+ 4. Open the browser
13
+ """
14
+
15
+ import argparse
16
+ import sys
17
+ import webbrowser
18
+ from pathlib import Path
19
+
20
+ import uvicorn
21
+
22
+
23
+ def main():
24
+ parser = argparse.ArgumentParser(
25
+ prog="scistack-gui",
26
+ description="Launch the SciStack GUI for a pipeline database.",
27
+ )
28
+ parser.add_argument(
29
+ "db_path",
30
+ type=Path,
31
+ nargs="?",
32
+ default=None,
33
+ help="Path to the SciStack .duckdb file (e.g. experiment.duckdb). "
34
+ "If omitted, the GUI opens onto a wizard to create or open one.",
35
+ )
36
+ parser.add_argument(
37
+ "--port",
38
+ type=int,
39
+ default=8765,
40
+ help="Port to serve on (default: 8765)",
41
+ )
42
+ parser.add_argument(
43
+ "--module",
44
+ "-m",
45
+ type=Path,
46
+ default=None,
47
+ help="Path to your pipeline .py file (single-file mode).",
48
+ )
49
+ parser.add_argument(
50
+ "--project",
51
+ "-p",
52
+ type=Path,
53
+ default=None,
54
+ help="Path to pyproject.toml or directory containing one "
55
+ "(project mode — reads [tool.scistack] config).",
56
+ )
57
+ parser.add_argument(
58
+ "--schema-keys",
59
+ type=str,
60
+ default=None,
61
+ help="Comma-separated schema keys; if provided and db_path "
62
+ "does not exist, a new database is created.",
63
+ )
64
+ parser.add_argument(
65
+ "--project-root",
66
+ type=Path,
67
+ default=None,
68
+ help="Directory to treat as the project root when no "
69
+ "pyproject.toml/scistack.toml exists yet. Determines where a new "
70
+ "scistack.toml and entities file are written; defaults to the "
71
+ "working directory.",
72
+ )
73
+ parser.add_argument(
74
+ "--no-browser",
75
+ action="store_true",
76
+ help="Don't open the browser automatically",
77
+ )
78
+ args = parser.parse_args()
79
+
80
+ if args.project_root is not None:
81
+ from scistack_gui.config import set_project_root_hint
82
+
83
+ set_project_root_hint(args.project_root)
84
+
85
+ if args.module and args.project:
86
+ print("Error: --module and --project are mutually exclusive.", file=sys.stderr)
87
+ sys.exit(1)
88
+
89
+ if args.db_path is not None:
90
+ db_path = args.db_path.resolve()
91
+ schema_keys = None
92
+ if args.schema_keys:
93
+ schema_keys = [k.strip() for k in args.schema_keys.split(",") if k.strip()]
94
+
95
+ if not db_path.exists() and not schema_keys:
96
+ print(f"Error: database file not found: {db_path}", file=sys.stderr)
97
+ sys.exit(1)
98
+
99
+ from scistack_gui.bootstrap import open_or_create_project
100
+
101
+ try:
102
+ result = open_or_create_project(
103
+ db_path,
104
+ schema_keys=schema_keys,
105
+ module=args.module,
106
+ project=args.project,
107
+ )
108
+ except (FileNotFoundError, ValueError, FileExistsError) as e:
109
+ print(f"Error: {e}", file=sys.stderr)
110
+ sys.exit(1)
111
+ except Exception as e:
112
+ print(f"Error opening database: {e}", file=sys.stderr)
113
+ sys.exit(1)
114
+
115
+ print(f"Opened database: {db_path}")
116
+ print(f"Schema keys: {result.schema_keys}")
117
+ print(
118
+ f"Loaded: {result.functions_loaded} functions, "
119
+ f"{result.variables_loaded} variables"
120
+ )
121
+ if result.matlab_functions_loaded or result.matlab_variables_loaded:
122
+ print(
123
+ f"MATLAB: {result.matlab_functions_loaded} functions, "
124
+ f"{result.matlab_variables_loaded} variables"
125
+ )
126
+ for w in result.warnings:
127
+ print(f"Warning: {w}", file=sys.stderr)
128
+ else:
129
+ print("No database given — open the browser to create or open one.")
130
+
131
+ url = f"http://localhost:{args.port}"
132
+ print(f"SciStack GUI running at {url}")
133
+
134
+ if not args.no_browser:
135
+ # Open after a short delay to let uvicorn bind the port
136
+ import threading
137
+
138
+ threading.Timer(1.0, lambda: webbrowser.open(url)).start()
139
+
140
+ uvicorn.run(
141
+ "scistack_gui.app:app",
142
+ host="localhost",
143
+ port=args.port,
144
+ log_level="warning", # suppress uvicorn's per-request logs
145
+ )
146
+
147
+
148
+ if __name__ == "__main__":
149
+ main()
File without changes
@@ -0,0 +1,52 @@
1
+ """
2
+ Endpoint-presentation routes (plan-endpoint-presentation.md).
3
+
4
+ GET /api/endpoints/{fn_name}/artifacts — finalized figures/stats manifest
5
+ GET /api/artifacts/file?path= — serve one artifact (project-dir
6
+ guarded; 403 outside, 404 missing)
7
+ POST /api/report — write the endpoint report,
8
+ return its index.html path
9
+ """
10
+
11
+ import logging
12
+
13
+ from fastapi import APIRouter, Depends, HTTPException
14
+ from fastapi.responses import FileResponse
15
+ from scidb.database import DatabaseManager
16
+
17
+ from scistack_gui.db import get_db
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ router = APIRouter()
22
+
23
+
24
+ @router.get("/endpoints/{fn_name}/artifacts")
25
+ def get_endpoint_artifacts(fn_name: str, db: DatabaseManager = Depends(get_db)) -> dict:
26
+ from scistack_gui.services.endpoint_service import endpoint_artifacts
27
+
28
+ return endpoint_artifacts(db, fn_name)
29
+
30
+
31
+ @router.get("/artifacts/file")
32
+ def get_artifact_file(path: str, db: DatabaseManager = Depends(get_db)):
33
+ from scistack_gui.services.endpoint_service import artifact_file_path
34
+
35
+ try:
36
+ resolved = artifact_file_path(db, path)
37
+ except ValueError as exc:
38
+ raise HTTPException(status_code=403, detail=str(exc))
39
+ except FileNotFoundError as exc:
40
+ raise HTTPException(status_code=404, detail=str(exc))
41
+ return FileResponse(resolved)
42
+
43
+
44
+ @router.post("/report")
45
+ def post_report(db: DatabaseManager = Depends(get_db)) -> dict:
46
+ from scistack_gui.services.endpoint_service import write_report
47
+
48
+ try:
49
+ return write_report(db)
50
+ except Exception as exc:
51
+ logger.exception("[api/artifacts] report generation failed")
52
+ raise HTTPException(status_code=500, detail=str(exc))
@@ -0,0 +1,161 @@
1
+ """
2
+ Browser-frontend project-creation wizard endpoints.
3
+
4
+ POST /api/bootstrap/create — create a new .duckdb (folder + filename +
5
+ schema keys) and load pipeline code into it.
6
+ Also gives loose-script projects a scistack.toml
7
+ + an entities file (default
8
+ src/scistack_entities.toml) if they have none,
9
+ so a freshly-created project never sits in the
10
+ pure-folder-scan config state that
11
+ pre-existing projects opened without ever
12
+ running this endpoint still can (see
13
+ docs/claude/code-discovery-categories.md).
14
+ Create-only: a project that already declares
15
+ an entities file keeps it, whatever the
16
+ wizard's field says -- see
17
+ services.project_init_service.
18
+ POST /api/bootstrap/open — open an existing .duckdb and load pipeline code
19
+
20
+ These exist so the standalone browser frontend can bootstrap a project the
21
+ same way the VS Code extension's "SciStack: Open Pipeline" wizard does
22
+ (extension/src/extension.ts), without requiring a --db path to already
23
+ exist when the server process starts. Both endpoints run the same sequence
24
+ __main__.py runs at CLI startup — see scistack_gui.bootstrap.
25
+
26
+ VS Code never hits these: server.py's JSON-RPC entry point always opens or
27
+ creates the database before the webview is shown, so its React bundle never
28
+ observes ``db_loaded: false`` from GET /api/info.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import logging
34
+ from pathlib import Path
35
+
36
+ from fastapi import APIRouter, HTTPException
37
+ from pydantic import BaseModel
38
+
39
+ from scistack_gui.bootstrap import open_or_create_project
40
+ from scistack_gui.services.pipeline_service import get_info
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+ router = APIRouter(prefix="/bootstrap", tags=["bootstrap"])
45
+
46
+
47
+ class CreateProjectRequest(BaseModel):
48
+ folder: str
49
+ filename: str
50
+ schema_keys: list[str]
51
+ module: str | None = None
52
+ project: str | None = None
53
+ entities_file: str | None = "src/scistack_entities.toml"
54
+ """Where to create an entities file *if this project has none*.
55
+
56
+ Relative to the *project root*, not to ``folder`` -- ``folder`` is where
57
+ the database goes, which is typically a datasets directory. See
58
+ ``config.resolve_project_root``. An explicit ``null`` opts out of project
59
+ initialization entirely."""
60
+
61
+
62
+ class OpenProjectRequest(BaseModel):
63
+ db_path: str
64
+ module: str | None = None
65
+ project: str | None = None
66
+
67
+
68
+ def _resolve_db_path(folder: str, filename: str) -> Path:
69
+ filename = filename.strip()
70
+ if not filename:
71
+ raise HTTPException(status_code=400, detail="Filename must not be empty.")
72
+ if "/" in filename or "\\" in filename:
73
+ raise HTTPException(
74
+ status_code=400, detail="Filename must not contain path separators."
75
+ )
76
+ if not filename.endswith(".duckdb"):
77
+ filename += ".duckdb"
78
+ return Path(folder).expanduser() / filename
79
+
80
+
81
+ @router.post("/create")
82
+ def create_project(req: CreateProjectRequest) -> dict:
83
+ """Create a new database (folder must already exist) and load pipeline code."""
84
+ logger.info(
85
+ "[api.bootstrap] create request: folder=%s filename=%s schema_keys=%s "
86
+ "module=%s project=%s",
87
+ req.folder,
88
+ req.filename,
89
+ req.schema_keys,
90
+ req.module,
91
+ req.project,
92
+ )
93
+ folder = Path(req.folder).expanduser()
94
+ if not folder.is_dir():
95
+ raise HTTPException(
96
+ status_code=404, detail=f"Folder does not exist: {folder}"
97
+ )
98
+
99
+ db_path = _resolve_db_path(req.folder, req.filename)
100
+ if db_path.exists():
101
+ # Checked here as well as inside open_or_create_project so the
102
+ # frontend gets a 409 with this message rather than a bare
103
+ # FileExistsError string.
104
+ raise HTTPException(
105
+ status_code=409, detail=f"Database already exists: {db_path}"
106
+ )
107
+ module = Path(req.module).expanduser() if req.module else None
108
+ project = Path(req.project).expanduser() if req.project else None
109
+
110
+ try:
111
+ open_or_create_project(
112
+ db_path,
113
+ schema_keys=req.schema_keys,
114
+ module=module,
115
+ project=project,
116
+ # An explicit null entities_file is an opt-out, so bootstrap's
117
+ # own initialization must not put the files back.
118
+ init_project_files=bool(req.entities_file),
119
+ # Where to put one IF the project has none. This used to be a
120
+ # separate eager config_mod.set_entities_file call right here,
121
+ # which unconditionally re-pointed the key -- so creating a
122
+ # database in a project that already declared its entities
123
+ # somewhere else silently swapped in a new empty file and the
124
+ # existing declarations vanished from the GUI. Routing it
125
+ # through project_init_service makes it create-only, like every
126
+ # other step there.
127
+ entities_file=req.entities_file,
128
+ )
129
+ except ValueError as exc:
130
+ raise HTTPException(status_code=400, detail=str(exc))
131
+ except FileExistsError as exc:
132
+ raise HTTPException(status_code=409, detail=str(exc))
133
+ except FileNotFoundError as exc:
134
+ raise HTTPException(status_code=404, detail=str(exc))
135
+
136
+ logger.info("[api.bootstrap] created database at %s", db_path)
137
+ return get_info()
138
+
139
+
140
+ @router.post("/open")
141
+ def open_project(req: OpenProjectRequest) -> dict:
142
+ """Open an existing database and load pipeline code."""
143
+ logger.info(
144
+ "[api.bootstrap] open request: db_path=%s module=%s project=%s",
145
+ req.db_path,
146
+ req.module,
147
+ req.project,
148
+ )
149
+ db_path = Path(req.db_path).expanduser()
150
+ module = Path(req.module).expanduser() if req.module else None
151
+ project = Path(req.project).expanduser() if req.project else None
152
+
153
+ try:
154
+ open_or_create_project(db_path, module=module, project=project)
155
+ except ValueError as exc:
156
+ raise HTTPException(status_code=400, detail=str(exc))
157
+ except FileNotFoundError as exc:
158
+ raise HTTPException(status_code=404, detail=str(exc))
159
+
160
+ logger.info("[api.bootstrap] opened database at %s", db_path)
161
+ return get_info()
@@ -0,0 +1,31 @@
1
+ """
2
+ POST /api/functions/builtin — manual built-in/library function references.
3
+
4
+ Thin FastAPI wrapper; the actual validation/registration logic lives in
5
+ ``services/builtin_function_service.py`` so it can be shared with the
6
+ JSON-RPC handler used by the VS Code extension (``server.py``,
7
+ ``create_builtin_function`` method).
8
+ """
9
+
10
+ import logging
11
+
12
+ from fastapi import APIRouter
13
+ from pydantic import BaseModel
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ router = APIRouter()
18
+
19
+
20
+ class CreateBuiltinFunctionRequest(BaseModel):
21
+ language: str # "python" | "matlab"
22
+ reference: str # e.g. "numpy.mean", "len", "mean"
23
+
24
+
25
+ @router.post("/functions/builtin")
26
+ def create_builtin_function(req: CreateBuiltinFunctionRequest) -> dict:
27
+ from scistack_gui.services.builtin_function_service import (
28
+ create_builtin_function as _create,
29
+ )
30
+
31
+ return _create(req.language, req.reference)
@@ -0,0 +1,108 @@
1
+ """
2
+ Glue-node endpoints — list, create, read, save, remove, and the live column
3
+ list the code panel shows beside the editor.
4
+
5
+ A glue node has **no run endpoint**, by design (D5). It is transient by
6
+ construction, so a standalone run would produce nothing and a state badge
7
+ would describe nothing; it executes only as part of a consuming function's
8
+ run. See ``docs/claude/free-code-glue-nodes.md`` §5, and ``api/run.py``'s
9
+ refusal of a glue node id.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+
16
+ from fastapi import APIRouter
17
+ from pydantic import BaseModel
18
+
19
+ from scistack_gui.api import ws
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ router = APIRouter()
24
+
25
+
26
+ class CreateGlueRequest(BaseModel):
27
+ name: str
28
+ param: str = "value"
29
+ language: str = "python"
30
+
31
+
32
+ class SaveGlueRequest(BaseModel):
33
+ name: str
34
+ source: str
35
+
36
+
37
+ @router.get("/glue")
38
+ def list_glue() -> dict:
39
+ from scistack_gui.services import glue_service
40
+
41
+ return {"nodes": glue_service.list_glue_nodes()}
42
+
43
+
44
+ @router.get("/glue/{name}")
45
+ def get_glue(name: str) -> dict:
46
+ from scistack_gui.services import glue_service
47
+
48
+ return glue_service.read_glue_source(name)
49
+
50
+
51
+ @router.get("/glue/{name}/columns")
52
+ def get_glue_columns(name: str, variable_type: str = "") -> dict:
53
+ """The columns a glue on ``variable_type`` actually receives.
54
+
55
+ Read live on every panel open rather than scaffolded into the file as a
56
+ comment: a comment goes stale the moment the node is rewired.
57
+ """
58
+ from scistack_gui.services import glue_service
59
+
60
+ if not variable_type:
61
+ return {
62
+ "ok": False,
63
+ "error": (
64
+ "This glue node is not wired to a variable yet, so there are "
65
+ "no columns to show."
66
+ ),
67
+ }
68
+ return glue_service.input_columns(variable_type)
69
+
70
+
71
+ @router.post("/glue")
72
+ async def create_glue(req: CreateGlueRequest) -> dict:
73
+ from scistack_gui.services import glue_service
74
+
75
+ result = glue_service.create_glue_node(
76
+ req.name, param=req.param, language=req.language
77
+ )
78
+ if result.get("ok"):
79
+ await ws.broadcast({"type": "dag_updated"})
80
+ return result
81
+
82
+
83
+ @router.put("/glue")
84
+ async def save_glue(req: SaveGlueRequest) -> dict:
85
+ """Write the edited body, then refresh the registry.
86
+
87
+ The refresh is the whole point of the round-trip: the new body has a new
88
+ hash, so the consuming function's glue chain hash changes, so its next
89
+ run recomputes instead of skipping. Saving without refreshing would look
90
+ identical in the panel and silently keep running the old body.
91
+ """
92
+ from scistack_gui.services import glue_service
93
+
94
+ result = glue_service.update_glue_source(req.name, req.source)
95
+ if result.get("ok"):
96
+ await ws.broadcast({"type": "dag_updated"})
97
+ return result
98
+
99
+
100
+ @router.delete("/glue/{name}")
101
+ async def delete_glue(name: str) -> dict:
102
+ """Remove the node from the canvas. The source file is never unlinked."""
103
+ from scistack_gui.services import glue_service
104
+
105
+ result = glue_service.delete_glue_node(name)
106
+ if result.get("ok"):
107
+ await ws.broadcast({"type": "dag_updated"})
108
+ return result