updating web gui to work with temoa v4#2
updating web gui to work with temoa v4#2ParticularlyPythonicBS wants to merge 2 commits intomainfrom
Conversation
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (5)
WalkthroughThis pull request consolidates backend services by removing FastAPI's Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 addmutatespyproject.tomlon every invocation ofrun.sh.
uvicorn[standard]andwebsocketsare already declared inpyproject.toml, souv syncis sufficient. Runninguv addon every launch re-writes the manifest (updatinguv.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 | 🔵 TrivialStale fixture reference after removing
backend/main.py.
backend/main.pywas deleted in this PR, so the"main.py" in namesbranch of the assertion is dead weight. The new root-level entrypoint istemoa_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 | 🟠 MajorLatent bug in
SERVED_DATABASESbookkeeping.Two problems here:
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 samenew_dbwill hit the early-return on line 500 and never retry — yet no Datasette process is running.- 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_taskterminates Datasette and setsDATASETTE_PROCESS = None, if someone callsstart_datasette(already_served_db)before thefinally-block'sstart_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 onDATASETTE_PROCESSactually 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 | 🟠 MajorDatasette subprocess is orphaned when
temoa_runner.pyexits.
DATASETTE_PROCESSis a rawsubprocess.Popen— it does not receive any signal when the parent uvicorn process is terminated (e.g. Ctrl+C,kill $BACKEND_PIDfromrun.sh, SIGTERM from the container runtime). The result is a leaked Datasette on port 8001 every shutdown;run.shpapers over this withfuser -k 8001/tcpon next startup, which is a band-aid.Register an
atexithook (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 | 🔴 CriticalCritical: 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 (theif template_path.exists():check masks the bug), so runs that reach it will proceed with an emptyrun_toml, dropping all the SQLite/myopic/output‑threshold defaults you just added totutorial_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 | 🔴 CriticalImport
TemoaSequencerfrom the internal module path, not the top-level package.
from temoa import TemoaSequencer, TemoaConfigwill fail on theTemoaSequencerimport. In temoa 4.0.0,TemoaSequenceris not part of the public API; it exists only in the internal submoduletemoa.temoa_model.temoa_sequencer.TemoaConfigis 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 forTemoaSequencer, or refactor to use the documented public API (e.g.,TemoaModel,TemoaConfig,TemoaModefromtemoa.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 | 🟠 MajorUse
asyncio.get_running_loop()instead of the deprecatedasyncio.get_event_loop().In Python 3.10+,
asyncio.get_event_loop()emits aDeprecationWarningwhen called from async code, and in 3.12+ it raisesRuntimeError. Sincestart_runis anasync defendpoint with a guaranteed running loop, useget_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
⛔ Files ignored due to path filters (2)
assets/output_files/2026-04-17_125920/temoa-run.logis excluded by!**/*.loguv.lockis excluded by!**/*.lock
📒 Files selected for processing (18)
assets/output_files/2026-04-17_125920/Network_Graph_utopia_1990.htmlassets/output_files/2026-04-17_125920/Network_Graph_utopia_2000.htmlassets/output_files/2026-04-17_125920/Network_Graph_utopia_2010.htmlassets/output_files/2026-04-17_125920/graph_script.jsassets/output_files/2026-04-17_125920/graph_styles.cssassets/output_files/2026-04-17_125920/unit_check_reports/units_check_2026-04-17_165929.txtassets/tutorial_config.tomlassets/tutorial_database.sqliteassets/tutorial_database.sqlite-shmassets/tutorial_database.sqlite-walbackend/main.pybackend/tests/test_download.pybackend/tests/test_main.pybackend/utils.pyfrontend/src/App.jsxpyproject.tomlrun.shtemoa_runner.py
💤 Files with no reviewable changes (2)
- backend/utils.py
- backend/main.py
| 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() |
There was a problem hiding this comment.
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).
| <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> |
There was a problem hiding this comment.
🧩 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 -100Repository: 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 2Repository: 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 -50Repository: 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 1Repository: 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 temoaRepository: 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/nullRepository: 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/nullRepository: 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 tomlRepository: 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 -25Repository: 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.
| <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).
| # 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 |
There was a problem hiding this comment.
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" EXITNote: 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.
| 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() |
There was a problem hiding this comment.
🧩 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:
- 1: https://docs.temoaproject.org/
- 2: https://pypi.org/project/temoa/4.0.0a2/
- 3: https://docs.temoaproject.org/en/latest/Documentation.html
- 4: https://github.com/TemoaProject/temoa
🌐 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:
- 1: https://docs.temoaproject.org/en/latest/Documentation.html
- 2: https://github.com/TemoaProject/temoa/blob/main/main.py
- 3: https://github.com/TemoaProject/temoa/blob/main/docs/source/Documentation.rst
- 4: https://github.com/TemoaProject/temoa/issues
🏁 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 -5Repository: 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:
- 1: https://docs.temoaproject.org/en/latest/Documentation.html
- 2: https://pypi.org/project/temoa/4.0.0a2/
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:
- Remove
-ffrom the command at line 158. Use[sys.executable, "-m", "temoa", "tutorial"]instead. - Replace the broad
except Exceptionwith specific exception handling to surface the actual failure reason, sincecapture_output=Trueswallows 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.
| 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() |
There was a problem hiding this comment.
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.
25ac2bc to
cb5a6d7
Compare
Summary by CodeRabbit
Release Notes
New Features
Configuration Improvements
Updates