fed8b2eed7
Backend release / release (push) Waiting to run
Bandit Security Scan / bandit_scan (push) Waiting to run
Build and push multi-arch DocsGPT Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Waiting to run
Build and push multi-arch DocsGPT Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Waiting to run
Build and push multi-arch DocsGPT Docker image / manifest (push) Blocked by required conditions
Build and push DocsGPT FE Docker image for development / build (linux/amd64, ubuntu-latest, amd64) (push) Waiting to run
Build and push DocsGPT FE Docker image for development / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Waiting to run
Build and push DocsGPT FE Docker image for development / manifest (push) Blocked by required conditions
Python linting / ruff (push) Waiting to run
Run python tests with pytest / Run tests and count coverage (3.12) (push) Waiting to run
React Widget Build / build (push) Waiting to run
109 lines
4.1 KiB
Python
109 lines
4.1 KiB
Python
"""Build a Daytona snapshot preloaded with the artifact-render libraries.
|
|
|
|
The Daytona managed sandbox backend (``SANDBOX_BACKEND=daytona``) creates each
|
|
session from a snapshot. The default snapshot is a plain Python image, so the
|
|
``artifact`` tool's renderers — which ``import`` ``python-pptx`` / ``python-docx``
|
|
/ ``openpyxl`` / ``reportlab`` inside the sandbox — fail with
|
|
``render failed: ExecutionError``. This script bakes those libraries into a
|
|
snapshot once; point ``DAYTONA_SNAPSHOT`` at its name to fix rendering on Daytona.
|
|
|
|
Usage::
|
|
|
|
# Reads DAYTONA_API_KEY / DAYTONA_API_URL / DAYTONA_TARGET from .env (settings):
|
|
python scripts/build_daytona_snapshot.py
|
|
python scripts/build_daytona_snapshot.py --name docsgpt-artifacts-py312 --python 3.12
|
|
|
|
Then set in .env::
|
|
|
|
DAYTONA_SNAPSHOT=docsgpt-artifacts-py312
|
|
|
|
Keep the pins in sync with the backend venv (python-pptx / openpyxl / lxml /
|
|
pillow are in application/requirements.txt; python-docx and reportlab arrive
|
|
transitively) so the Daytona render output matches the Jupyter-backend output.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
|
|
# Render libraries imported by the artifact tool's renderers, pinned to the
|
|
# versions installed in the backend venv as of this writing.
|
|
RENDER_PINS = [
|
|
"python-pptx==1.0.2",
|
|
"python-docx==1.2.0",
|
|
"openpyxl==3.1.5",
|
|
"reportlab==4.5.1",
|
|
"lxml==6.0.2",
|
|
"pillow==11.3.0",
|
|
]
|
|
|
|
DEFAULT_NAME = "docsgpt-artifacts-py312"
|
|
DEFAULT_PYTHON = "3.12"
|
|
|
|
|
|
def _parse_args(argv: list[str]) -> argparse.Namespace:
|
|
"""Parse CLI arguments for the snapshot name and Python series."""
|
|
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
parser.add_argument("--name", default=DEFAULT_NAME, help="snapshot name (default: %(default)s)")
|
|
parser.add_argument("--python", default=DEFAULT_PYTHON, help="Python series, e.g. 3.12")
|
|
parser.add_argument(
|
|
"--force", action="store_true", help="rebuild even if a snapshot with that name exists"
|
|
)
|
|
return parser.parse_args(argv)
|
|
|
|
|
|
def main(argv: list[str]) -> int:
|
|
"""Build (or skip) the snapshot and print the value to set as DAYTONA_SNAPSHOT."""
|
|
args = _parse_args(argv)
|
|
|
|
from application.core.settings import settings
|
|
|
|
if not settings.DAYTONA_API_KEY:
|
|
print("DAYTONA_API_KEY is not set (check .env).", file=sys.stderr)
|
|
return 2
|
|
|
|
from daytona import (
|
|
CreateSnapshotParams,
|
|
Daytona,
|
|
DaytonaConfig,
|
|
DaytonaConflictError,
|
|
Image,
|
|
)
|
|
|
|
cfg: dict[str, object] = {"api_key": settings.DAYTONA_API_KEY}
|
|
if settings.DAYTONA_API_URL:
|
|
cfg["api_url"] = settings.DAYTONA_API_URL
|
|
if settings.DAYTONA_TARGET:
|
|
cfg["target"] = settings.DAYTONA_TARGET
|
|
client = Daytona(DaytonaConfig(**cfg))
|
|
|
|
if not args.force:
|
|
try:
|
|
existing = client.snapshot.get(args.name)
|
|
print(f"snapshot {args.name!r} already exists (state={getattr(existing, 'state', '?')}).")
|
|
print(f"set DAYTONA_SNAPSHOT={args.name}")
|
|
return 0
|
|
except Exception as exc: # noqa: BLE001 - "not found" is the happy path; build it
|
|
if type(exc).__name__ != "DaytonaNotFoundError" and "not found" not in str(exc).lower():
|
|
print(f"warning: get({args.name!r}) probe: {type(exc).__name__}: {exc}", file=sys.stderr)
|
|
|
|
image = Image.debian_slim(args.python).pip_install(RENDER_PINS)
|
|
print(f"building snapshot {args.name!r} (python {args.python}) with: {', '.join(RENDER_PINS)}")
|
|
print("--- build logs ---")
|
|
try:
|
|
snap = client.snapshot.create(
|
|
CreateSnapshotParams(name=args.name, image=image), on_logs=print, timeout=600
|
|
)
|
|
except DaytonaConflictError:
|
|
print(f"snapshot {args.name!r} already exists (conflict) — reuse it or pass --force a new name.")
|
|
return 0
|
|
print("--- created ---")
|
|
print(f"name={getattr(snap, 'name', args.name)} state={getattr(snap, 'state', '?')}")
|
|
print(f"\nNow set in .env:\n DAYTONA_SNAPSHOT={args.name}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main(sys.argv[1:]))
|