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.
- scistack_gui-0.1.28/.gitignore +32 -0
- scistack_gui-0.1.28/PKG-INFO +17 -0
- scistack_gui-0.1.28/pyproject.toml +52 -0
- scistack_gui-0.1.28/scistack_gui/__init__.py +0 -0
- scistack_gui-0.1.28/scistack_gui/__main__.py +149 -0
- scistack_gui-0.1.28/scistack_gui/api/__init__.py +0 -0
- scistack_gui-0.1.28/scistack_gui/api/artifacts.py +52 -0
- scistack_gui-0.1.28/scistack_gui/api/bootstrap.py +161 -0
- scistack_gui-0.1.28/scistack_gui/api/builtin_functions.py +31 -0
- scistack_gui-0.1.28/scistack_gui/api/glue.py +108 -0
- scistack_gui-0.1.28/scistack_gui/api/layout.py +294 -0
- scistack_gui-0.1.28/scistack_gui/api/matlab_command.py +1564 -0
- scistack_gui-0.1.28/scistack_gui/api/pipeline.py +1335 -0
- scistack_gui-0.1.28/scistack_gui/api/plot.py +212 -0
- scistack_gui-0.1.28/scistack_gui/api/project.py +468 -0
- scistack_gui-0.1.28/scistack_gui/api/registry.py +33 -0
- scistack_gui-0.1.28/scistack_gui/api/run.py +1723 -0
- scistack_gui-0.1.28/scistack_gui/api/schema.py +37 -0
- scistack_gui-0.1.28/scistack_gui/api/scopes.py +303 -0
- scistack_gui-0.1.28/scistack_gui/api/variables.py +361 -0
- scistack_gui-0.1.28/scistack_gui/api/ws.py +392 -0
- scistack_gui-0.1.28/scistack_gui/app.py +75 -0
- scistack_gui-0.1.28/scistack_gui/bootstrap.py +266 -0
- scistack_gui-0.1.28/scistack_gui/config.py +1651 -0
- scistack_gui-0.1.28/scistack_gui/db.py +563 -0
- scistack_gui-0.1.28/scistack_gui/domain/__init__.py +0 -0
- scistack_gui-0.1.28/scistack_gui/domain/edge_resolver.py +583 -0
- scistack_gui-0.1.28/scistack_gui/domain/graph_builder.py +2286 -0
- scistack_gui-0.1.28/scistack_gui/domain/run_state.py +193 -0
- scistack_gui-0.1.28/scistack_gui/domain/scope_filter.py +249 -0
- scistack_gui-0.1.28/scistack_gui/domain/variant_resolver.py +638 -0
- scistack_gui-0.1.28/scistack_gui/layout.py +634 -0
- scistack_gui-0.1.28/scistack_gui/library_functions.py +346 -0
- scistack_gui-0.1.28/scistack_gui/matlab_parser.py +1028 -0
- scistack_gui-0.1.28/scistack_gui/matlab_registry.py +815 -0
- scistack_gui-0.1.28/scistack_gui/matlab_sidecar.py +369 -0
- scistack_gui-0.1.28/scistack_gui/notify.py +65 -0
- scistack_gui-0.1.28/scistack_gui/pipeline_discovery.py +354 -0
- scistack_gui-0.1.28/scistack_gui/pipeline_store.py +1585 -0
- scistack_gui-0.1.28/scistack_gui/registry.py +1129 -0
- scistack_gui-0.1.28/scistack_gui/server.py +1759 -0
- scistack_gui-0.1.28/scistack_gui/services/__init__.py +0 -0
- scistack_gui-0.1.28/scistack_gui/services/builtin_function_service.py +266 -0
- scistack_gui-0.1.28/scistack_gui/services/code_export_service.py +489 -0
- scistack_gui-0.1.28/scistack_gui/services/endpoint_service.py +73 -0
- scistack_gui-0.1.28/scistack_gui/services/execution_service.py +1491 -0
- scistack_gui-0.1.28/scistack_gui/services/glue_service.py +475 -0
- scistack_gui-0.1.28/scistack_gui/services/layout_service.py +666 -0
- scistack_gui-0.1.28/scistack_gui/services/matlab_command_service.py +759 -0
- scistack_gui-0.1.28/scistack_gui/services/parameter_service.py +246 -0
- scistack_gui-0.1.28/scistack_gui/services/path_input_service.py +164 -0
- scistack_gui-0.1.28/scistack_gui/services/pipeline_service.py +296 -0
- scistack_gui-0.1.28/scistack_gui/services/plot_service.py +1195 -0
- scistack_gui-0.1.28/scistack_gui/services/portability_service.py +714 -0
- scistack_gui-0.1.28/scistack_gui/services/project_init_service.py +325 -0
- scistack_gui-0.1.28/scistack_gui/services/project_service.py +47 -0
- scistack_gui-0.1.28/scistack_gui/services/registry_reload_service.py +39 -0
- scistack_gui-0.1.28/scistack_gui/services/run_service.py +36 -0
- scistack_gui-0.1.28/scistack_gui/services/scope_service.py +710 -0
- scistack_gui-0.1.28/scistack_gui/services/target_file_service.py +781 -0
- scistack_gui-0.1.28/scistack_gui/services/variable_service.py +256 -0
- scistack_gui-0.1.28/scistack_gui/startup.py +295 -0
- scistack_gui-0.1.28/scistack_gui/static/assets/index-BnuhLJ6X.css +1 -0
- scistack_gui-0.1.28/scistack_gui/static/assets/index-DPD2EHIC.js +254 -0
- 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
|