Pārlūkot izejas kodu

spectrometer startup fix

spacexerq 1 nedēļu atpakaļ
vecāks
revīzija
6d3922aa2d

+ 33 - 0
apps/gui/src/clients/__init__.py

@@ -0,0 +1,33 @@
+"""Shared utilities for service HTTP clients."""
+
+
+def extract_error_text(response) -> str:
+    """
+    Extract a human-readable error message from an httpx Response.
+
+    Priority:
+      1. JSON body  -> 'detail' field  (FastAPI / DRF standard)
+      2. JSON body  -> 'message' field
+      3. JSON body  -> entire dict as string
+      4. Raw response text (truncated to 300 chars)
+    """
+    try:
+        body = response.json()
+        if isinstance(body, dict):
+            msg = body.get("detail") or body.get("message")
+            if msg:
+                # msg may itself be a list of validation errors
+                if isinstance(msg, list):
+                    parts = []
+                    for item in msg:
+                        if isinstance(item, dict):
+                            parts.append(item.get("msg") or str(item))
+                        else:
+                            parts.append(str(item))
+                    return "; ".join(parts)
+                return str(msg)
+            return str(body)
+    except Exception:
+        pass
+    text = response.text or ""
+    return text[:300] if len(text) > 300 else text

+ 5 - 3
apps/gui/src/clients/orchestrator_client.py

@@ -6,6 +6,8 @@ never from the Qt UI thread.
 """
 from __future__ import annotations
 
+from src.clients import extract_error_text
+
 
 class OrchestratorError(Exception):
     def __init__(self, message: str, status_code: int | None = None) -> None:
@@ -33,7 +35,7 @@ class OrchestratorClient:
         except httpx.TimeoutException as exc:
             raise OrchestratorError("Orchestrator request timed out") from exc
         if not r.is_success:
-            raise OrchestratorError(r.text, status_code=r.status_code)
+            raise OrchestratorError(extract_error_text(r), status_code=r.status_code)
         return r.json()
 
     def _post(self, path: str, json: dict | None = None) -> dict:
@@ -49,7 +51,7 @@ class OrchestratorClient:
         except httpx.TimeoutException as exc:
             raise OrchestratorError("Orchestrator request timed out") from exc
         if not r.is_success:
-            raise OrchestratorError(r.text, status_code=r.status_code)
+            raise OrchestratorError(extract_error_text(r), status_code=r.status_code)
         return r.json()
 
     # -- public API ---------------------------------------------------------
@@ -150,7 +152,7 @@ class OrchestratorClient:
             raise OrchestratorError("Orchestrator request timed out") from exc
 
         if not r.is_success:
-            raise OrchestratorError(r.text, status_code=r.status_code)
+            raise OrchestratorError(extract_error_text(r), status_code=r.status_code)
         return r.json()["job_id"]
 
     def get_task_registry(self) -> list[str]:

+ 4 - 2
apps/gui/src/clients/seq_interp_client.py

@@ -7,6 +7,8 @@ from __future__ import annotations
 
 import os
 
+from src.clients import extract_error_text
+
 
 class SeqInterpError(Exception):
     def __init__(self, message: str, status_code: int | None = None) -> None:
@@ -36,7 +38,7 @@ class SeqInterpClient:
         except httpx.TimeoutException as exc:
             raise SeqInterpError("seq_interp request timed out") from exc
         if not r.is_success:
-            raise SeqInterpError(r.text, status_code=r.status_code)
+            raise SeqInterpError(extract_error_text(r), status_code=r.status_code)
         return r.json()
 
     def _post_file(self, path: str, file_path: str) -> dict:
@@ -56,7 +58,7 @@ class SeqInterpClient:
         except httpx.TimeoutException as exc:
             raise SeqInterpError("seq_interp upload timed out") from exc
         if not r.is_success:
-            raise SeqInterpError(r.text, status_code=r.status_code)
+            raise SeqInterpError(extract_error_text(r), status_code=r.status_code)
         return r.json()
 
     # -- public API --------------------------------------------------------

+ 5 - 3
apps/gui/src/clients/spectroscopy_client.py

@@ -7,6 +7,8 @@ from __future__ import annotations
 
 import os
 
+from src.clients import extract_error_text
+
 
 class SpectroscopyError(Exception):
     def __init__(self, message: str, status_code: int | None = None) -> None:
@@ -36,7 +38,7 @@ class SpectroscopyClient:
         except httpx.TimeoutException as exc:
             raise SpectroscopyError("Spectroscopy request timed out") from exc
         if not r.is_success:
-            raise SpectroscopyError(r.text, status_code=r.status_code)
+            raise SpectroscopyError(extract_error_text(r), status_code=r.status_code)
         return r.json()
 
     def _post_form_file(self, path: str, file_path: str, fields: dict) -> dict:
@@ -58,7 +60,7 @@ class SpectroscopyClient:
         except httpx.TimeoutException as exc:
             raise SpectroscopyError("Spectroscopy upload timed out") from exc
         if not r.is_success:
-            raise SpectroscopyError(r.text, status_code=r.status_code)
+            raise SpectroscopyError(extract_error_text(r), status_code=r.status_code)
         return r.json()
 
     def _post_form(self, path: str, fields: dict) -> dict:
@@ -77,7 +79,7 @@ class SpectroscopyClient:
         except httpx.TimeoutException as exc:
             raise SpectroscopyError("Spectroscopy request timed out") from exc
         if not r.is_success:
-            raise SpectroscopyError(r.text, status_code=r.status_code)
+            raise SpectroscopyError(extract_error_text(r), status_code=r.status_code)
         return r.json()
 
     # -- public API --------------------------------------------------------

+ 31 - 9
start.ps1

@@ -140,20 +140,38 @@ if (-not $GuiOnly) {
             Push-Location $SpecDir
             & python -m venv mvenv
             if ($LASTEXITCODE -ne 0) { Write-Fail "Failed to create spectrometer venv" }
-            & $SpecVenv -m pip install -q --upgrade pip
-            & $SpecVenv -m pip install -q -r requirements.txt
-            if ($LASTEXITCODE -ne 0) { Write-Fail "Failed to install spectrometer packages" }
             Pop-Location
             Write-OK "Spectrometer venv created"
         } else {
             Write-OK "Spectrometer venv found"
         }
 
+        # Always sync packages (catches new deps after git pull)
+        Write-Host "    Installing/updating spectrometer packages..." -ForegroundColor DarkGray
         Push-Location $SpecDir
-        & $SpecVenv manage.py migrate --noinput 2>&1 | Out-Null
+        & $SpecVenv -m pip install -q --upgrade pip
+        $pipOut = & $SpecVenv -m pip install -q -r requirements.txt 2>&1
+        $pipFailed = ($LASTEXITCODE -ne 0)
         Pop-Location
-        Write-OK "Migrations applied"
+        if ($pipFailed) {
+            Write-Warn "pip install had issues:`n$pipOut"
+        } else {
+            Write-OK "Packages up to date"
+        }
+
+        # Migrations -- show output so errors are visible
+        Write-Host "    Running migrations..." -ForegroundColor DarkGray
+        Push-Location $SpecDir
+        $migrateOut = & $SpecVenv manage.py migrate --noinput 2>&1
+        $migrateFailed = ($LASTEXITCODE -ne 0)
+        Pop-Location
+        if ($migrateFailed) {
+            Write-Warn "Migration warnings:`n$migrateOut"
+        } else {
+            Write-OK "Migrations applied"
+        }
 
+        # pico-tcp.exe
         if (Test-Path $PicoExe) {
             Start-Process $PicoExe -WindowStyle Normal
             Write-OK "pico-tcp.exe started (visible window)"
@@ -161,7 +179,7 @@ if (-not $GuiOnly) {
             Write-Warn "bin\pico-tcp.exe not found -- ADC proxy not started"
         }
 
-        # Check if runserver is already up by probing port 8000
+        # Django runserver -- run via "cmd /k" so window stays open on error
         $specPort = Get-EnvPort "SPECTROMETER_PORT" "8000"
         $alreadyUp = $false
         try {
@@ -173,11 +191,15 @@ if (-not $GuiOnly) {
         if ($alreadyUp) {
             Write-OK "Spectrometer already responding on port $specPort"
         } else {
-            Start-Process $SpecVenv `
-                -ArgumentList "manage.py runserver 0.0.0.0:$specPort" `
+            # Title makes the window easy to find in the taskbar.
+            # /k keeps the window open even if Django crashes at startup.
+            # --noreload disables Django's file-watcher subprocess (cleaner in cmd).
+            $runCmd = "`"$SpecVenv`" manage.py runserver 0.0.0.0:$specPort --noreload"
+            Start-Process "cmd.exe" `
+                -ArgumentList "/k title LF-MRI Spectrometer && $runCmd" `
                 -WorkingDirectory $SpecDir `
                 -WindowStyle Normal
-            Write-OK "Spectrometer started in new window (port $specPort)"
+            Write-OK "Spectrometer started (window: 'LF-MRI Spectrometer', port $specPort)"
         }
     }