Skip to content

updating web gui to work with temoa v4#2

Open
ParticularlyPythonicBS wants to merge 2 commits intomainfrom
updating_for_temoa_v4
Open

updating web gui to work with temoa v4#2
ParticularlyPythonicBS wants to merge 2 commits intomainfrom
updating_for_temoa_v4

Conversation

@ParticularlyPythonicBS
Copy link
Copy Markdown
Member

@ParticularlyPythonicBS ParticularlyPythonicBS commented Apr 17, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • New scenario mode options: stochastic, SVMGA, method_of_morris, and database integrity check
  • Configuration Improvements

    • Added cycle detection limits and output filtering thresholds for finer simulation control
    • Introduced database performance tuning parameters for improved responsiveness
    • Enabled period-based time scaling via configurable days per period
  • Updates

    • Updated core solver library dependency to release version 4.0.0

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Warning

Rate limit exceeded

@ParticularlyPythonicBS has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 46 minutes and 15 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 46 minutes and 15 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ec369c2c-9af1-4fca-ad30-fd1263a7454e

📥 Commits

Reviewing files that changed from the base of the PR and between 25ac2bc and cb5a6d7.

📒 Files selected for processing (5)
  • backend/main.py
  • backend/tests/test_download.py
  • backend/tests/test_main.py
  • backend/utils.py
  • run.sh

Walkthrough

This pull request consolidates backend services by removing FastAPI's backend/main.py and migrating functionality to temoa_runner.py, adds a complete interactive network graph visualization system with HTML pages and supporting JavaScript/CSS, updates configuration with new performance tuning and filtering parameters, and adds new scenario modes to the frontend UI.

Changes

Cohort / File(s) Summary
Network Visualization Assets
assets/output_files/2026-04-17_125920/Network_Graph_utopia_*.html, graph_script.js, graph_styles.css
Added three standalone HTML pages for interactive network visualizations (1990, 2000, 2010) with embedded JSON graph datasets, view toggling between commodity/technology perspectives, sector/style filtering, and search functionality. Supporting JavaScript implements network initialization, UI controls, filtering logic, view switching with position preservation, and double-click neighborhood exploration. CSS provides responsive layout with collapsible config panel, filter UI, and legend styling.
Configuration & Testing Updates
assets/tutorial_config.toml, assets/output_files/2026-04-17_125920/unit_check_reports/units_check_2026-04-17_165929.txt
Extended configuration with cycle detection limits, output filtering thresholds, period-day scaling, SQLite performance tuning (WAL mode, memory mapping), and myopic mode step-size adjustment with evolution script support. Updated Monte Carlo settings path. Added units validation report confirming database integrity checks.
Backend Service Consolidation
backend/main.py, temoa_runner.py, run.sh
Removed standalone FastAPI application (backend/main.py) containing HTTP/WebSocket endpoints for configuration, file serving, solver detection, result streaming, and background task execution. Migrated core functionality to temoa_runner.py, which now handles asset generation (with Temoa CLI fallback to legacy GitHub downloads), Datasette pause/restart around simulation runs, and unified service startup. Updated launch script to start single consolidated process instead of separate FastAPI and Datasette services.
Backend Utilities & Dependencies
backend/utils.py, pyproject.toml
Removed SSL context creation utility function (create_secure_ssl_context) previously used for secure downloads. Updated temoa dependency from pre-release (>=4.0.0a1) to stable (>=4.0.0).
Test Updates
backend/tests/test_main.py, backend/tests/test_download.py
Updated test client initialization to use app from temoa_runner instead of backend.main. Restructured download tests to replace SSL context validation with CLI fallback simulation and legacy asset download failure scenarios.
Frontend UI Expansion
frontend/src/App.jsx
Added new scenario mode options (stochastic, SVMGA, method_of_morris, check) to the configuration UI's Scenario Mode selector.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the primary objective of the changeset, which is updating the web GUI to work with Temoa v4, as evidenced by dependency updates (temoa>=4.0.0), import refactoring, and integration with the new TemoaSequencer/TemoaConfig exports.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch updating_for_temoa_v4

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
run.sh (1)

4-5: ⚠️ Potential issue | 🟡 Minor

uv add mutates pyproject.toml on every invocation of run.sh.

uvicorn[standard] and websockets are already declared in pyproject.toml, so uv sync is sufficient. Running uv add on every launch re-writes the manifest (updating uv.lock / pinning) as a side effect of simply starting the app, which is surprising and can produce spurious diffs. Drop line 5:

Proposed fix
-# Sync dependencies
-uv sync
-uv add "uvicorn[standard]" websockets
+# Sync dependencies
+uv sync
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@run.sh` around lines 4 - 5, The script currently runs "uv add
\"uvicorn[standard]\" websockets" which mutates pyproject.toml/uv.lock on every
launch; remove the "uv add \"uvicorn[standard]\" websockets" invocation and keep
only "uv sync" so the app starts without re-writing the manifest—edit the run.sh
to delete the uv add line referencing uvicorn[standard] and websockets.
backend/tests/test_main.py (1)

21-27: 🧹 Nitpick | 🔵 Trivial

Stale fixture reference after removing backend/main.py.

backend/main.py was deleted in this PR, so the "main.py" in names branch of the assertion is dead weight. The new root-level entrypoint is temoa_runner.py. Tighten the test to reflect the actual layout:

Proposed fix
 def test_list_files():
     response = client.get("/api/files?path=.")
     assert response.status_code == 200
     assert isinstance(response.json(), list)
-    # Check if we see backend folder
-    names = [item["name"] for item in response.json()]
-    assert "backend" in names or "main.py" in names
+    # Check if we see expected top-level entries
+    names = [item["name"] for item in response.json()]
+    assert "backend" in names or "temoa_runner.py" in names
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/test_main.py` around lines 21 - 27, Update the test_list_files
assertion to stop checking for the deleted backend/main.py and instead assert
presence of the current root entrypoint; in the test_list_files function capture
names = [item["name"] for item in response.json()] and replace the final
assertion to assert "backend" in names or "temoa_runner.py" in names so it
reflects the new project layout.
temoa_runner.py (5)

483-513: ⚠️ Potential issue | 🟠 Major

Latent bug in SERVED_DATABASES bookkeeping.

Two problems here:

  1. SERVED_DATABASES.add(abs_new) on line 501 runs before the Popen on line 546. If the subprocess fails to launch (caught on line 569), the path is still recorded as "served", and a subsequent call with the same new_db will hit the early-return on line 500 and never retry — yet no Datasette process is running.
  2. The early-return on line 499–500 also skips the "Kill existing process if running" block. In the normal flow this is fine, but after run_temoa_task terminates Datasette and sets DATASETTE_PROCESS = None, if someone calls start_datasette(already_served_db) before the finally-block's start_datasette() runs, the early-return leaves the app with no Datasette at all.

Move the SERVED_DATABASES.add(...) call to after Popen succeeds, and gate the early-return on DATASETTE_PROCESS actually being alive:

Proposed fix
-    # If new_db is already served, no need to restart
-    if new_db:
-        abs_new = str(Path(new_db).resolve())
-        if abs_new in SERVED_DATABASES:
-            return
-        SERVED_DATABASES.add(abs_new)
+    # If new_db is already served AND Datasette is still running, no-op.
+    abs_new = None
+    if new_db:
+        abs_new = str(Path(new_db).resolve())
+        if (
+            abs_new in SERVED_DATABASES
+            and DATASETTE_PROCESS is not None
+            and DATASETTE_PROCESS.poll() is None
+        ):
+            return

…and after a successful Popen(...) add:

+        if abs_new is not None:
+            SERVED_DATABASES.add(abs_new)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@temoa_runner.py` around lines 483 - 513, The SERVED_DATABASES bookkeeping is
done too early and the early-return only checks new_db presence, so move the
SERVED_DATABASES.add(abs_new) to after the Datasette subprocess is successfully
started (i.e., after the subprocess.Popen(...) completes without raising and
after any restart logic), and change the early-return in start_datasette (the
check that currently returns when abs_new in SERVED_DATABASES) to also verify
that DATASETTE_PROCESS is non-None and alive (e.g., not terminated) before
returning; ensure that if Popen fails you do not add abs_new and that existing
process-kill logic still runs when DATASETTE_PROCESS is None or dead.

573-578: ⚠️ Potential issue | 🟠 Major

Datasette subprocess is orphaned when temoa_runner.py exits.

DATASETTE_PROCESS is a raw subprocess.Popen — it does not receive any signal when the parent uvicorn process is terminated (e.g. Ctrl+C, kill $BACKEND_PID from run.sh, SIGTERM from the container runtime). The result is a leaked Datasette on port 8001 every shutdown; run.sh papers over this with fuser -k 8001/tcp on next startup, which is a band-aid.

Register an atexit hook (and ideally SIGTERM/SIGINT handlers) to tear it down:

Proposed fix
 if __name__ == "__main__":
     import uvicorn
+    import atexit
+    import signal
+
+    def _shutdown_datasette(*_):
+        global DATASETTE_PROCESS
+        if DATASETTE_PROCESS and DATASETTE_PROCESS.poll() is None:
+            try:
+                DATASETTE_PROCESS.terminate()
+                DATASETTE_PROCESS.wait(timeout=5)
+            except Exception:
+                try:
+                    DATASETTE_PROCESS.kill()
+                except Exception:
+                    pass
+
+    atexit.register(_shutdown_datasette)
+    signal.signal(signal.SIGTERM, lambda *_: (_shutdown_datasette(), sys.exit(0)))

     start_datasette()  # Initial start

     uvicorn.run(app, host="0.0.0.0", port=8000)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@temoa_runner.py` around lines 573 - 578, The Datasette subprocess
(DATASETTE_PROCESS) is left running when temoa_runner.py exits; update the
module to register an atexit hook and SIGINT/SIGTERM handlers that call the
existing start/stop lifecycle functions (use start_datasette() context and add
or call a new stop_datasette()/terminate function) to ensure DATASETTE_PROCESS
is killed on shutdown; specifically, import atexit and signal near top-level,
add an atexit.register handler that checks DATASETTE_PROCESS and calls
terminate/kill and wait, and install signal.signal handlers for signal.SIGINT
and signal.SIGTERM that perform the same cleanup (then re-raise or exit) before
running uvicorn.run(app,...).

366-370: ⚠️ Potential issue | 🔴 Critical

Critical: hardcoded developer-specific absolute path.

"/media/Secondary/Projects/TemoaProject/temoa-web-gui/assets/tutorial_config.toml" is a local filesystem path from a contributor's machine. On any other machine this fallback branch is a silent no-op (the if template_path.exists(): check masks the bug), so runs that reach it will proceed with an empty run_toml, dropping all the SQLite/myopic/output‑threshold defaults you just added to tutorial_config.toml.

The primary relative path is Path("assets/tutorial_config.toml"), which is already correct when the runner is launched from the repo root. Drop the absolute fallback, or resolve it relative to __file__:

Proposed fix
         if input_path.suffix in [".sqlite", ".db"]:
             await manager.broadcast(f"Input is SQLite: {input_path.name}")
             # Use tutorial_config.toml as base if it exists
-            template_path = Path("assets/tutorial_config.toml")
-            if not template_path.exists():
-                template_path = Path(
-                    "/media/Secondary/Projects/TemoaProject/temoa-web-gui/assets/tutorial_config.toml"
-                )
+            template_path = Path("assets/tutorial_config.toml")
+            if not template_path.exists():
+                # Fall back to a path resolved relative to this script
+                template_path = Path(__file__).resolve().parent / "assets" / "tutorial_config.toml"
             if template_path.exists():
                 run_toml = tomlkit.parse(template_path.read_text(encoding="utf-8"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@temoa_runner.py` around lines 366 - 370, The code in temoa_runner.py sets
template_path to Path("assets/tutorial_config.toml") then falls back to a
developer-specific absolute path; remove that hardcoded absolute fallback and
instead resolve the assets path relative to the current module so it works from
any working directory: compute template_path from
Path(__file__).resolve().parent.joinpath("assets/tutorial_config.toml") (or
similar) before checking exists(), and keep references to template_path and
run_toml as-is so the file is loaded when present.

63-71: ⚠️ Potential issue | 🔴 Critical

Import TemoaSequencer from the internal module path, not the top-level package.

from temoa import TemoaSequencer, TemoaConfig will fail on the TemoaSequencer import. In temoa 4.0.0, TemoaSequencer is not part of the public API; it exists only in the internal submodule temoa.temoa_model.temoa_sequencer. TemoaConfig is available from the top-level import, but the statement fails as written. The current error handling silently masks this breakage, causing every run to log "Temoa not found" and fail downstream. Fix the import to use the correct internal path for TemoaSequencer, or refactor to use the documented public API (e.g., TemoaModel, TemoaConfig, TemoaMode from temoa.core).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@temoa_runner.py` around lines 63 - 71, The import of TemoaSequencer is wrong:
replace the top-level import "from temoa import TemoaSequencer, TemoaConfig"
with an explicit import of TemoaSequencer from its internal module
(temoa.temoa_model.temoa_sequencer) while keeping TemoaConfig from the public
API, or refactor to use the documented public API (e.g., TemoaModel/TemoaConfig
from temoa.core) so the import line references the correct symbols
(TemoaSequencer, TemoaConfig) and remove the broad except ImportError masking
real failures; ensure the code raises or logs the actual ImportError if the
specific import fails instead of silently continuing.

457-470: ⚠️ Potential issue | 🟠 Major

Use asyncio.get_running_loop() instead of the deprecated asyncio.get_event_loop().

In Python 3.10+, asyncio.get_event_loop() emits a DeprecationWarning when called from async code, and in 3.12+ it raises RuntimeError. Since start_run is an async def endpoint with a guaranteed running loop, use get_running_loop():

Proposed fix
-    loop = asyncio.get_event_loop()
+    loop = asyncio.get_running_loop()
     background_tasks.add_task(run_temoa_task, config, output_dir, loop)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@temoa_runner.py` around lines 457 - 470, The endpoint start_run currently
calls asyncio.get_event_loop() to obtain the event loop; replace that call with
asyncio.get_running_loop() so the running loop is used when scheduling the
background task (background_tasks.add_task(run_temoa_task, config, output_dir,
loop)). Update the reference in the start_run function where loop is passed into
run_temoa_task and ensure no other call sites in this block use
get_event_loop().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@assets/output_files/2026-04-17_125920/graph_script.js`:
- Line 89: The aria-expanded attribute is being set with a boolean; update the
call on configToggleButton so it explicitly passes a string per ARIA contract
(use String(!isCollapsed)) instead of a bare boolean, e.g., replace
setAttribute("aria-expanded", !isCollapsed) with setAttribute("aria-expanded",
String(!isCollapsed)) in the code that toggles the configToggleButton.
- Around line 174-222: The applyAllFilters function currently catches invalid
regexes and returns silently; update it to set a visible accessibility/state
indicator on the search input when a regex parse fails and clear it when the
regex is valid: inside applyAllFilters, keep capturing currentPositions as
before, but when the try/catch around new RegExp(searchQuery, "i") catches, set
searchInput.setAttribute("aria-invalid","true") (and/or add an error class)
before returning; when a regex is successfully created (or when searchQuery is
empty), ensure you remove the aria-invalid flag
(searchInput.removeAttribute("aria-invalid") and/or remove the error class) so
valid subsequent input clears the error; references: applyAllFilters,
searchInput, currentPositions, addWithCurrentFontSize, applyPositions.
- Around line 306-322: resetView currently double-renders the primary dataset:
the else branch manually calls addWithCurrentFontSize(allNodesPrimary,
allEdgesPrimary) → applyPositions(primaryViewPositions) → network.fit() and then
the function unconditionally calls applyAllFilters(), which re-adds the same
data. Fix by removing the manual reload in the else branch (keep
applyPositions(primaryViewPositions) and network.fit()) and let
applyAllFilters() handle loading, or alternatively set a boolean flag (e.g.,
alreadyLoaded) when you manually reload and skip applyAllFilters() if that flag
is true; update references in resetView to use the chosen approach so
addWithCurrentFontSize, applyPositions, network.fit, and applyAllFilters are not
invoked redundantly.
- Around line 85-99: The collapse and advanced-controls event listeners are
incorrectly nested inside the optionsObject?.configure?.enabled check; pull the
configHeader.addEventListener(...) and
advancedControlsToggle.addEventListener(...) blocks out of that if so the
collapse affordance and the "Show Advanced Physics Controls" toggle are always
wired, but keep optionsObject.configure.container = visConfigContainer inside
the conditional; when moving the listeners, guard against missing DOM nodes
(check configHeader, configWrapper, configToggleButton, advancedControlsToggle,
visConfigContainer are non-null before adding listeners) and retain the existing
toggling logic (configWrapper.classList.toggle("collapsed"),
configToggleButton.setAttribute("aria-expanded", ...), and
visConfigContainer.style.display/textContent toggle).
- Around line 342-348: Guard against missing DOM elements before accessing or
wiring them: check that viewToggleButton is non-null before setting
viewToggleButton.textContent and before calling
viewToggleButton.addEventListener("click", switchView), and check that
resetButton is non-null before calling resetButton.addEventListener("click",
resetView); retain the existing logic that hides "view-toggle-panel" when
allNodesSecondary is falsy but only perform text/content and event binding when
the corresponding elements (viewToggleButton, resetButton) are present to avoid
runtime errors if the template changes.

In `@assets/output_files/2026-04-17_125920/Network_Graph_utopia_1990.html`:
- Around line 15-20: Add an explicit type="button" attribute to every <button>
element in this HTML (including the one with class "config-toggle-btn" and the
other buttons flagged by the linter) so they do not default to type="submit";
locate buttons by their class names/aria attributes (e.g.,
class="config-toggle-btn", aria-label="Toggle configuration panel", and the
other similar buttons) and add type="button" to each element.
- Around line 7-8: The external vis-network script tag is loaded from unpkg.com
without Subresource Integrity or crossorigin, which risks executing tampered JS;
update the <script
src="https://unpkg.com/vis-network@9.1.6/standalone/umd/vis-network.min.js"></script>
inclusion to either (a) add a correct integrity attribute and
crossorigin="anonymous" for SRI, or (b) vendor the vis-network bundle locally
(place the minified file alongside graph_script.js and update the src to the
local path), and remove the external CDN reference; ensure the chosen approach
is reflected in the HTML and any deployment packaging so graph_script.js
continues to depend on the safe local/SRI-protected vis-network.
- Around line 1-940: The PR includes runtime-generated HTML artifacts (e.g.,
Network_Graph_utopia_1990.html) that should be ignored: add a .gitignore entry
blocking assets/output_files/ (or assets/output_files/**/) and update
build/runtime to write these files to a local cache or temp directory instead of
committing them; also update the HTML template referenced (contains <script
src="https://unpkg.com/vis-network@9.1.6/standalone/umd/vis-network.min.js"> and
buttons with IDs config-toggle-btn, view-toggle-btn, reset-view-btn) to include
Subresource Integrity (integrity and crossorigin) on the vis-network CDN script
tag and to set explicit button type="button" attributes for elements with IDs
config-toggle-btn, view-toggle-btn and reset-view-btn so they do not implicitly
submit forms.

In
`@assets/output_files/2026-04-17_125920/unit_check_reports/units_check_2026-04-17_165929.txt`:
- Around line 1-18: Remove the generated output files (e.g.
assets/output_files/2026-04-17_125920/* and sibling graph files) from the PR,
add the output root (assets/output_files/) to .gitignore to prevent future
commits, and if any of those files are intended as stable test fixtures move
them to a dedicated location like assets/fixtures/ and document their purpose;
also verify callers of ensure_assets() still behave correctly (no hardcoded
references to the removed timestamped files) so tests/CI continue to
pull/generate assets on demand.

In `@backend/tests/test_download.py`:
- Around line 25-37: Update the test_download_tutorial_legacy_fallback to also
assert that the legacy download path was executed by checking the urlopen mock;
after calling client.post("/api/download_tutorial") add an assertion on
mock_urlopen.called or mock_urlopen.call_count > 0 so the test fails if the
endpoint short-circuits before invoking temoa_runner.urllib.request.urlopen (the
mock named mock_urlopen in the test).
- Around line 9-18: The test helper create_exists_mock is fragile because it
counts Path.exists calls; change it to inspect the Path argument and return True
only when the path matches the specific files used by
ensure_assets()/_download_legacy_assets() (tutorial_database.sqlite or
tutorial_config.toml) and otherwise return False (or delegate to the real
Path.exists for non-matching paths); update create_exists_mock to accept the
Path parameter, check path.name or path.suffix to identify those two filenames,
and only short-circuit existence for them so other Path.exists calls won’t shift
behavior.

In `@frontend/src/App.jsx`:
- Around line 354-358: The dropdown in App.jsx includes invalid scenario_mode
options "stochastic" and "SVMGA" which are not accepted by
TemoaConfig.build_config(); remove those two <option> entries from the list
(leaving valid options such as "method_of_morris" and "check") so the UI only
emits values present in assets/tutorial_config.toml (e.g., perfect_foresight,
MGA, myopic, method_of_morris, build_only, check, monte_carlo).

In `@run.sh`:
- Around line 13-24: Register the EXIT trap before starting the foreground
frontend so the backend (temoa_runner.py) is always cleaned up if the script is
interrupted; move the trap "kill $BACKEND_PID" so it is defined immediately
after BACKEND_PID is set and change it to use single quotes (e.g. trap 'kill
"$BACKEND_PID"' EXIT) so $BACKEND_PID is expanded when the trap runs, not when
it is declared; optionally consider signaling the backend's process group or
ensuring temoa_runner.py forwards signals to its Datasette child so killing
BACKEND_PID also tears down Datasette.

In `@temoa_runner.py`:
- Around line 183-193: _in _download_legacy_assets(), after streaming into
temp_target (symbols: temp_target, target, base_url, f, ctx,
urllib.request.urlopen) add post-download integrity checks: first verify the
downloaded file is non-empty (size > 0) before calling
temp_target.replace(target); if the repository publishes checksums, attempt to
fetch base_url + f + ".sha256" (or similar) and compute the file's SHA256 to
compare, deleting temp_target and logging/raising on mismatch; only perform the
atomic replace when checks pass. Also consider replacing urllib.request.urlopen
with httpx (using a streamed GET with timeout and verify=True) to make requests
more robust and easier to fetch an adjacent checksum file.
- Around line 143-167: The ensure_assets function currently invokes
subprocess.run([sys.executable, "-m", "temoa", "tutorial", "-f"], ...) which
fails because the Temoa CLI has no -f flag; remove "-f" so the command is
[sys.executable, "-m", "temoa", "tutorial"]. Also stop swallowing useful failure
details by replacing the broad except Exception with targeted exception handling
around subprocess.run: catch subprocess.CalledProcessError and print/ log
e.stdout and e.stderr (decoded) or the CalledProcessError message before falling
back to _download_legacy_assets(), and optionally catch other exceptions
separately to surface unexpected errors.

---

Outside diff comments:
In `@backend/tests/test_main.py`:
- Around line 21-27: Update the test_list_files assertion to stop checking for
the deleted backend/main.py and instead assert presence of the current root
entrypoint; in the test_list_files function capture names = [item["name"] for
item in response.json()] and replace the final assertion to assert "backend" in
names or "temoa_runner.py" in names so it reflects the new project layout.

In `@run.sh`:
- Around line 4-5: The script currently runs "uv add \"uvicorn[standard]\"
websockets" which mutates pyproject.toml/uv.lock on every launch; remove the "uv
add \"uvicorn[standard]\" websockets" invocation and keep only "uv sync" so the
app starts without re-writing the manifest—edit the run.sh to delete the uv add
line referencing uvicorn[standard] and websockets.

In `@temoa_runner.py`:
- Around line 483-513: The SERVED_DATABASES bookkeeping is done too early and
the early-return only checks new_db presence, so move the
SERVED_DATABASES.add(abs_new) to after the Datasette subprocess is successfully
started (i.e., after the subprocess.Popen(...) completes without raising and
after any restart logic), and change the early-return in start_datasette (the
check that currently returns when abs_new in SERVED_DATABASES) to also verify
that DATASETTE_PROCESS is non-None and alive (e.g., not terminated) before
returning; ensure that if Popen fails you do not add abs_new and that existing
process-kill logic still runs when DATASETTE_PROCESS is None or dead.
- Around line 573-578: The Datasette subprocess (DATASETTE_PROCESS) is left
running when temoa_runner.py exits; update the module to register an atexit hook
and SIGINT/SIGTERM handlers that call the existing start/stop lifecycle
functions (use start_datasette() context and add or call a new
stop_datasette()/terminate function) to ensure DATASETTE_PROCESS is killed on
shutdown; specifically, import atexit and signal near top-level, add an
atexit.register handler that checks DATASETTE_PROCESS and calls terminate/kill
and wait, and install signal.signal handlers for signal.SIGINT and
signal.SIGTERM that perform the same cleanup (then re-raise or exit) before
running uvicorn.run(app,...).
- Around line 366-370: The code in temoa_runner.py sets template_path to
Path("assets/tutorial_config.toml") then falls back to a developer-specific
absolute path; remove that hardcoded absolute fallback and instead resolve the
assets path relative to the current module so it works from any working
directory: compute template_path from
Path(__file__).resolve().parent.joinpath("assets/tutorial_config.toml") (or
similar) before checking exists(), and keep references to template_path and
run_toml as-is so the file is loaded when present.
- Around line 63-71: The import of TemoaSequencer is wrong: replace the
top-level import "from temoa import TemoaSequencer, TemoaConfig" with an
explicit import of TemoaSequencer from its internal module
(temoa.temoa_model.temoa_sequencer) while keeping TemoaConfig from the public
API, or refactor to use the documented public API (e.g., TemoaModel/TemoaConfig
from temoa.core) so the import line references the correct symbols
(TemoaSequencer, TemoaConfig) and remove the broad except ImportError masking
real failures; ensure the code raises or logs the actual ImportError if the
specific import fails instead of silently continuing.
- Around line 457-470: The endpoint start_run currently calls
asyncio.get_event_loop() to obtain the event loop; replace that call with
asyncio.get_running_loop() so the running loop is used when scheduling the
background task (background_tasks.add_task(run_temoa_task, config, output_dir,
loop)). Update the reference in the start_run function where loop is passed into
run_temoa_task and ensure no other call sites in this block use
get_event_loop().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: eccacac0-6850-4ada-a7fc-d88dcce05973

📥 Commits

Reviewing files that changed from the base of the PR and between 3f54ea7 and 25ac2bc.

⛔ Files ignored due to path filters (2)
  • assets/output_files/2026-04-17_125920/temoa-run.log is excluded by !**/*.log
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • assets/output_files/2026-04-17_125920/Network_Graph_utopia_1990.html
  • assets/output_files/2026-04-17_125920/Network_Graph_utopia_2000.html
  • assets/output_files/2026-04-17_125920/Network_Graph_utopia_2010.html
  • assets/output_files/2026-04-17_125920/graph_script.js
  • assets/output_files/2026-04-17_125920/graph_styles.css
  • assets/output_files/2026-04-17_125920/unit_check_reports/units_check_2026-04-17_165929.txt
  • assets/tutorial_config.toml
  • assets/tutorial_database.sqlite
  • assets/tutorial_database.sqlite-shm
  • assets/tutorial_database.sqlite-wal
  • backend/main.py
  • backend/tests/test_download.py
  • backend/tests/test_main.py
  • backend/utils.py
  • frontend/src/App.jsx
  • pyproject.toml
  • run.sh
  • temoa_runner.py
💤 Files with no reviewable changes (2)
  • backend/utils.py
  • backend/main.py

Comment thread assets/output_files/2026-04-17_125920/graph_script.js Outdated
Comment thread assets/output_files/2026-04-17_125920/graph_script.js Outdated
Comment thread assets/output_files/2026-04-17_125920/graph_script.js Outdated
Comment thread assets/output_files/2026-04-17_125920/graph_script.js Outdated
Comment thread assets/output_files/2026-04-17_125920/graph_script.js Outdated
Comment on lines +25 to +37
with patch("temoa_runner.subprocess.run", side_effect=Exception("CLI fail")), patch(
"temoa_runner.urllib.request.urlopen"
) as mock_urlopen, patch("temoa_runner.shutil.copyfileobj"), patch(
"temoa_runner.open", new_callable=MagicMock
), patch("temoa_runner.Path.replace"), patch("temoa_runner.Path.unlink"), patch(
"temoa_runner.Path.exists", side_effect=create_exists_mock(success_after=1)
), patch("temoa_runner.start_datasette"):
mock_response = MagicMock()
mock_urlopen.return_value.__enter__.return_value = mock_response

response = client.post("/api/download_tutorial")
assert response.status_code == 200
assert "path" in response.json()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Also assert the legacy fallback path was actually exercised.

As written, test_download_tutorial_legacy_fallback only asserts 200 + "path" in json. With the count-based exists mock it can also pass if the endpoint short-circuits before ever calling urlopen. Add an assertion on mock_urlopen.called (or call_count) so a regression that silently skips the legacy download is caught.

🧪 Proposed fix
         response = client.post("/api/download_tutorial")
         assert response.status_code == 200
         assert "path" in response.json()
+        assert mock_urlopen.called, "legacy fallback urlopen was not invoked"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/test_download.py` around lines 25 - 37, Update the
test_download_tutorial_legacy_fallback to also assert that the legacy download
path was executed by checking the urlopen mock; after calling
client.post("/api/download_tutorial") add an assertion on mock_urlopen.called or
mock_urlopen.call_count > 0 so the test fails if the endpoint short-circuits
before invoking temoa_runner.urllib.request.urlopen (the mock named mock_urlopen
in the test).

Comment thread frontend/src/App.jsx
Comment on lines +354 to +358
<option value="stochastic">Stochastic</option>
<option value="SVMGA">SVMGA</option>
<option value="method_of_morris">Method of Morris</option>
<option value="build_only">Build Only</option>
<option value="check">Check Database</option>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether TEMOA v4 accepts 'stochastic' or 'SVMGA' as scenario_mode values.
rg -nP -C2 "scenario_mode|SVMGA|stochastic" --iglob '!**/node_modules/**'

Repository: TemoaProject/temoa-web-gui

Length of output: 2653


🏁 Script executed:

#!/bin/bash
# Search for where scenario_mode is validated or used
rg -n "scenario_mode" --type py -A 3 -B 1 | head -100

Repository: TemoaProject/temoa-web-gui

Length of output: 665


🏁 Script executed:

#!/bin/bash
# Search for stochastic and SVMGA usage patterns
rg -n "stochastic|SVMGA" --type py -B 2 -A 2

Repository: TemoaProject/temoa-web-gui

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Check if there's validation in TemoaConfig or similar classes
rg -n "class.*Config|def.*validate|scenario_mode.*=" --type py | head -50

Repository: TemoaProject/temoa-web-gui

Length of output: 257


🏁 Script executed:

#!/bin/bash
# Search for TEMOA imports and validation
rg -n "from temoa|import temoa|TemoaConfig" --type py -A 2 -B 1

Repository: TemoaProject/temoa-web-gui

Length of output: 1176


🏁 Script executed:

#!/bin/bash
# Check requirements/setup to understand TEMOA version
fd -e "requirements.txt" -o -e "setup.py" -o -e "pyproject.toml" | xargs cat 2>/dev/null | grep -i temoa

Repository: TemoaProject/temoa-web-gui

Length of output: 186


🏁 Script executed:

#!/bin/bash
# Look for any backend configuration documentation
find . -name "*.md" -o -name "*.rst" | xargs grep -l "scenario_mode" 2>/dev/null

Repository: TemoaProject/temoa-web-gui

Length of output: 54


🏁 Script executed:

#!/bin/bash
# Find and read requirements files
find . -maxdepth 3 \( -name "requirements.txt" -o -name "setup.py" -o -name "pyproject.toml" \) -type f 2>/dev/null

Repository: TemoaProject/temoa-web-gui

Length of output: 86


🏁 Script executed:

#!/bin/bash
# Check for any README or docs mentioning valid scenario modes
rg -i "scenario_mode.*=" --type toml

Repository: TemoaProject/temoa-web-gui

Length of output: 133


🏁 Script executed:

#!/bin/bash
# Read pyproject.toml to find TEMOA dependency
cat pyproject.toml | grep -A 20 "dependencies"

Repository: TemoaProject/temoa-web-gui

Length of output: 454


🏁 Script executed:

#!/bin/bash
# Read the tutorial_config.toml to confirm valid scenario modes
cat -n assets/tutorial_config.toml | head -25

Repository: TemoaProject/temoa-web-gui

Length of output: 1213


Remove unsupported scenario_mode options: stochastic and SVMGA are not valid.

Per assets/tutorial_config.toml (line 18), the allowed scenario_mode values are [perfect_foresight, MGA, myopic, method_of_morris, build_only, check, monte_carlo]. The added stochastic and SVMGA options are not in this list and will fail validation when passed to TemoaConfig.build_config() at runtime. (method_of_morris and check are fine.)

Remove the two unsupported options:

Proposed fix
                   <option value="monte_carlo">Monte Carlo</option>
-                  <option value="stochastic">Stochastic</option>
-                  <option value="SVMGA">SVMGA</option>
                   <option value="method_of_morris">Method of Morris</option>
                   <option value="build_only">Build Only</option>
                   <option value="check">Check Database</option>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<option value="stochastic">Stochastic</option>
<option value="SVMGA">SVMGA</option>
<option value="method_of_morris">Method of Morris</option>
<option value="build_only">Build Only</option>
<option value="check">Check Database</option>
<option value="method_of_morris">Method of Morris</option>
<option value="build_only">Build Only</option>
<option value="check">Check Database</option>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/App.jsx` around lines 354 - 358, The dropdown in App.jsx
includes invalid scenario_mode options "stochastic" and "SVMGA" which are not
accepted by TemoaConfig.build_config(); remove those two <option> entries from
the list (leaving valid options such as "method_of_morris" and "check") so the
UI only emits values present in assets/tutorial_config.toml (e.g.,
perfect_foresight, MGA, myopic, method_of_morris, build_only, check,
monte_carlo).

Comment thread run.sh
Comment on lines +13 to +24
# Start Temoa Web GUI Backend (includes Datasette)
echo "Starting backend and explorer..."
uv run python temoa_runner.py &
BACKEND_PID=$!

# Start Datasette in background to view results
echo "Starting Datasette..."
uv run datasette . --port 8001 --setting sql_time_limit_ms 5000 &
DATASETTE_PID=$!

# Start Vite frontend
echo "Starting frontend..."
cd frontend
npm run dev

# Cleanup
trap "kill $BACKEND_PID $DATASETTE_PID" EXIT
trap "kill $BACKEND_PID" EXIT
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Trap is registered too late — cleanup never runs.

npm run dev on line 21 blocks in the foreground, so execution only reaches trap "kill $BACKEND_PID" EXIT on line 24 after the frontend has already exited. If the user Ctrl+Cs (or the frontend crashes), the shell will unwind without the trap being registered, leaving temoa_runner.py (and its Datasette child) orphaned on port 8000/8001. That's exactly why the script has to fuser -k on the next startup.

Also, per shellcheck SC2064, use single quotes so $BACKEND_PID is evaluated when the trap fires, not at definition time (it matters if you later decide to respawn the backend).

Proposed fix
 # Start Temoa Web GUI Backend (includes Datasette)
 echo "Starting backend and explorer..."
 uv run python temoa_runner.py &
 BACKEND_PID=$!

+# Register cleanup BEFORE launching the blocking frontend
+trap 'kill "$BACKEND_PID" 2>/dev/null' EXIT INT TERM
+
 # Start Vite frontend
 echo "Starting frontend..."
-cd frontend
+cd frontend || { echo "frontend directory not found"; exit 1; }
 npm run dev
-
-# Cleanup
-trap "kill $BACKEND_PID" EXIT

Note: kill $BACKEND_PID only signals temoa_runner.py; the Datasette subprocess it spawns won't receive the signal automatically. See the related comment on temoa_runner.py re: ensuring Datasette is torn down when the parent exits (e.g. register an atexit / signal handler, or put both in a process group).

🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 20-20: Use 'cd ... || exit' or 'cd ... || return' in case cd fails.

(SC2164)


[warning] 24-24: Use single quotes, otherwise this expands now rather than when signalled.

(SC2064)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@run.sh` around lines 13 - 24, Register the EXIT trap before starting the
foreground frontend so the backend (temoa_runner.py) is always cleaned up if the
script is interrupted; move the trap "kill $BACKEND_PID" so it is defined
immediately after BACKEND_PID is set and change it to use single quotes (e.g.
trap 'kill "$BACKEND_PID"' EXIT) so $BACKEND_PID is expanded when the trap runs,
not when it is declared; optionally consider signaling the backend's process
group or ensuring temoa_runner.py forwards signals to its Datasette child so
killing BACKEND_PID also tears down Datasette.

Comment thread temoa_runner.py
Comment on lines 143 to +167
def ensure_assets():
"""Download tutorial assets if they are missing."""
"""Download tutorial assets if they are missing.
In Temoa 4.0+, we can use the built-in tutorial command to get the latest assets.
"""
assets_dir = Path("assets")
assets_dir.mkdir(parents=True, exist_ok=True)

tutorial_db = assets_dir / "tutorial_database.sqlite"
tutorial_config = assets_dir / "tutorial_config.toml"

if not tutorial_db.exists() or not tutorial_config.exists():
print("Tutorial assets missing or incomplete. Generating from Temoa...")
try:
# Run temoa tutorial in the assets directory
subprocess.run(
[sys.executable, "-m", "temoa", "tutorial", "-f"],
cwd=str(assets_dir.absolute()),
check=True,
capture_output=True,
)
print("Tutorial assets generated successfully.")
except Exception as e:
print(f"Failed to generate tutorial assets via temoa CLI: {e}")
# Fallback to legacy download if CLI fails
_download_legacy_assets()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

temoa 4.0.0 CLI tutorial subcommand generate sample database config

💡 Result:

Temoa is Tools for Energy Model Optimization and Analysis, an open-source energy systems optimization framework. Version 4.0.0 (alpha) features a CLI built with Typer. The temoa tutorial subcommand generates sample files for quick start, including a sample database and configuration: - temoa tutorial creates tutorial_config.toml (TOML config) and tutorial_database.sqlite (sample input database with energy model data like technologies, demands, costs). - temoa tutorial quick_start (or custom: temoa tutorial my_model) creates custom-named files like quick_start.toml and quick_start.sqlite. Example config snippet from tutorial: scenario = "tutorial" scenario_mode = "perfect_foresight" input_database = "tutorial_database.sqlite" Tutorial steps: 1. Install: pip install temoa (Python 3.12+ recommended, use venv). 2. temoa tutorial quick_start - generates sample database config and files. 3. temoa run quick_start.toml - runs the model. 4. Optional: temoa validate quick_start.toml; temoa --version. The sample database is pre-populated SQLite for Temoa v4 schema, suitable for learning/running perfect foresight mode. No separate "generate" subcommand for just database/config; tutorial handles it. For full CLI help: temoa tutorial --help (shows options like custom names). Official sources: PyPI temoa 4.0.0a2, docs.temoaproject.org (4.0.0a1), GitHub TemoaProject/temoa.

Citations:


🌐 Web query:

temoa CLI tutorial -f flag force option documentation

💡 Result:

No -f or --force flag exists in the Temoa CLI documentation or source code. The available CLI flags are -d/--debug (enables debug logging, including additional model checks like Missing Costs Check 0), -b/--build_only (build model without solving), -s/--silent (no prompts), -o/--output_path (custom output directory), --version, --how-to-cite, and subcommands like run, validate, migrate, tutorial, check-units. To view full help: temoa --help or temoa run --help. The -d flag on 'temoa run' enables debug mode for detailed logging and extra data quality checks during model execution.

Citations:


🏁 Script executed:

# Search for temoa GitHub repository and CLI implementation
git clone --depth 1 https://github.com/TemoaProject/temoa.git /tmp/temoa 2>/dev/null || echo "Clone failed, trying archive"
# Also look for any temoa source already in the current repo
find . -name "*temoa*" -type d 2>/dev/null | head -5

Repository: TemoaProject/temoa-web-gui

Length of output: 52


🌐 Web query:

temoa tutorial command syntax usage example generate tutorial_database.sqlite tutorial_config.toml

💡 Result:

Temoa is an open-source energy system optimization model. To generate tutorial_database.sqlite and tutorial_config.toml, use the 'temoa tutorial' command after installing Temoa via pip. Command syntax and usage: temoa --help # Shows all commands: run, validate, migrate, tutorial temoa tutorial # Generates default tutorial_config.toml and tutorial_database.sqlite in the current directory temoa tutorial my_first_model # Generates my_first_model.toml and my_first_model.sqlite (or similar) temoa tutorial quick_start # Example from docs, generates quick_start.toml and associated database After generation: temoa run tutorial_config.toml # Runs the tutorial model Example workflow: 1. pip install temoa 2. temoa tutorial tutorial # Creates tutorial_database.sqlite and tutorial_config.toml 3. temoa run tutorial_config.toml The tutorial command creates sample input database (SQLite) and configuration (TOML) files for learning and testing Temoa models. The database contains example energy system data (e.g., technologies, costs, demands), and the config specifies scenario settings like perfect_foresight mode, input/output databases, solver (e.g., HiGHS included), and output options (logs, Excel, LP files).

Citations:


Remove the non-existent -f flag and improve error visibility.

The temoa tutorial subcommand exists and generates the expected files (tutorial_database.sqlite and tutorial_config.toml), but the -f flag does not exist in the Temoa CLI and will cause the subprocess call to fail on every run. The error will be caught and silently masked by the fallback to _download_legacy_assets(), making the regression undiagnosable.

Fixes required:

  1. Remove -f from the command at line 158. Use [sys.executable, "-m", "temoa", "tutorial"] instead.
  2. Replace the broad except Exception with specific exception handling to surface the actual failure reason, since capture_output=True swallows stdout/stderr:
Proposed fix (command correction + error visibility)
             subprocess.run(
                 [sys.executable, "-m", "temoa", "tutorial"],
                 cwd=str(assets_dir.absolute()),
                 check=True,
                 capture_output=True,
             )
             print("Tutorial assets generated successfully.")
-        except Exception as e:
-            print(f"Failed to generate tutorial assets via temoa CLI: {e}")
-            # Fallback to legacy download if CLI fails
-            _download_legacy_assets()
+        except subprocess.CalledProcessError as e:
+            print(
+                f"Failed to generate tutorial assets via temoa CLI "
+                f"(exit {e.returncode}):\n"
+                f"stdout: {(e.stdout or b'').decode(errors='replace')}\n"
+                f"stderr: {(e.stderr or b'').decode(errors='replace')}"
+            )
+            _download_legacy_assets()
+        except FileNotFoundError as e:
+            print(f"Temoa CLI not found: {e}")
+            _download_legacy_assets()
🧰 Tools
🪛 Ruff (0.15.10)

[warning] 164-164: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@temoa_runner.py` around lines 143 - 167, The ensure_assets function currently
invokes subprocess.run([sys.executable, "-m", "temoa", "tutorial", "-f"], ...)
which fails because the Temoa CLI has no -f flag; remove "-f" so the command is
[sys.executable, "-m", "temoa", "tutorial"]. Also stop swallowing useful failure
details by replacing the broad except Exception with targeted exception handling
around subprocess.run: catch subprocess.CalledProcessError and print/ log
e.stdout and e.stderr (decoded) or the CalledProcessError message before falling
back to _download_legacy_assets(), and optionally catch other exceptions
separately to surface unexpected errors.

Comment thread temoa_runner.py
Comment on lines 183 to +193
temp_target = target.with_suffix(".part")
try:
url = base_url + f
with urllib.request.urlopen(url, context=ctx, timeout=10) as response:
with open(temp_target, "wb") as out_file:
shutil.copyfileobj(response, out_file)
# Atomic rename
temp_target.replace(target)
except Exception as e:
print(f"Failed to download {f}: {e}")
if temp_target.exists():
try:
temp_target.unlink()
except Exception:
pass
temp_target.unlink()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Post-download integrity is unchecked.

_download_legacy_assets() streams a remote SQLite file to .part and atomically replaces — good. But it does no size/hash validation, so a partially-delivered body that still closes cleanly (proxy/CDN hiccup) can become a corrupt "successful" asset that is then treated as present on every subsequent run (the early-exit on line 153 passes). At minimum, verify a non-empty file and, ideally, compare against a known checksum published alongside the repo assets.

Also note (per ruff S310) urllib.request.urlopen accepts any scheme; since base_url is a hardcoded https constant here it's fine, but worth switching to httpx (already in dev deps) or requests if this pathway is extended.

🧰 Tools
🪛 Ruff (0.15.10)

[warning] 186-187: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


[error] 186-186: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 190-190: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@temoa_runner.py` around lines 183 - 193, _in _download_legacy_assets(), after
streaming into temp_target (symbols: temp_target, target, base_url, f, ctx,
urllib.request.urlopen) add post-download integrity checks: first verify the
downloaded file is non-empty (size > 0) before calling
temp_target.replace(target); if the repository publishes checksums, attempt to
fetch base_url + f + ".sha256" (or similar) and compute the file's SHA256 to
compare, deleting temp_target and logging/raising on mismatch; only perform the
atomic replace when checks pass. Also consider replacing urllib.request.urlopen
with httpx (using a streamed GET with timeout and verify=True) to make requests
more robust and easier to fetch an adjacent checksum file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant