feat: Modernize CLI and Refactor Core Sequencer#186
feat: Modernize CLI and Refactor Core Sequencer#186ParticularlyPythonicBS merged 14 commits intotemoa_davey_codefrom
Conversation
WalkthroughReplaces the legacy Python CLI entrypoint with a Typer-based CLI, moves CLI/output/logging into Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI as temoa/cli.py
participant Config as TemoaConfig
participant Sequencer as TemoaSequencer
participant DB as Database
rect rgb(220,230,250)
Note over CLI: User invokes CLI (run/validate)
User->>CLI: temoa run/validate config.toml
end
rect rgb(230,250,220)
Note over CLI: CLI prepares environment
CLI->>CLI: _create_output_folder()
CLI->>CLI: _setup_logging()
CLI->>Config: TemoaConfig.build_config(config_file, output_path, silent)
Config-->>CLI: TemoaConfig
end
rect rgb(250,240,220)
Note over CLI,Sequencer: Sequencer init
CLI->>Sequencer: TemoaSequencer(config=config, mode_override?)
Sequencer-->>CLI: Ready
end
rect rgb(240,220,250)
Note over Sequencer: Build or Start
alt build-only
CLI->>Sequencer: build_model()
Sequencer->>Sequencer: _run_preliminary_checks()
Sequencer->>DB: Load data & construct model
Sequencer-->>CLI: TemoaModel
else full run
CLI->>Sequencer: start()
Sequencer->>Sequencer: _run_preliminary_checks()
Sequencer->>Sequencer: match temoa_mode -> _run_*()
Sequencer->>DB: Solve & persist results
Sequencer-->>CLI: None (success) / raise on error
end
end
rect rgb(220,250,240)
CLI->>User: report log path and output folder
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Pre-merge checks and finishing touches✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro 📒 Files selected for processing (2)
💤 Files with no reviewable changes (1)
🧰 Additional context used🧠 Learnings (1)📚 Learning: 2025-11-03T22:55:02.437ZApplied to files:
🧬 Code graph analysis (1)temoa/cli.py (4)
🪛 Ruff (0.14.3)temoa/cli.py25-25: (DTZ005) 30-30: Boolean-typed positional argument in function definition (FBT001) 30-30: Boolean default positional argument in function definition (FBT002) 30-30: Boolean-typed positional argument in function definition (FBT001) 30-30: Boolean default positional argument in function definition (FBT002) 79-79: Boolean-typed positional argument in function definition (FBT001) 80-80: Boolean-typed positional argument in function definition (FBT001) 101-101: Boolean-typed positional argument in function definition (FBT001) 108-108: Boolean-typed positional argument in function definition (FBT001) 162-162: Boolean default positional argument in function definition (FBT002) 165-165: Boolean default positional argument in function definition (FBT002) 210-210: Boolean default positional argument in function definition (FBT002) 214-214: Boolean default positional argument in function definition (FBT002) 220-220: Boolean default positional argument in function definition (FBT002) 265-265: Boolean-typed positional argument in function definition (FBT001) 273-273: Boolean-typed positional argument in function definition (FBT001) 🔇 Additional comments (5)
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 |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 4
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (15)
.gitignore(0 hunks)main.py(0 hunks)pyproject.toml(2 hunks)requirements-dev.txt(6 hunks)requirements.txt(5 hunks)temoa/_internal/temoa_sequencer.py(1 hunks)temoa/cli.py(1 hunks)tests/conftest.py(3 hunks)tests/test_cli.py(1 hunks)tests/test_emission_results.py(1 hunks)tests/test_main.py(0 hunks)tests/test_material_results.py(3 hunks)tests/test_model.py(1 hunks)tests/test_set_consistency.py(3 hunks)tests/test_temoa_sequencer.py(1 hunks)
💤 Files with no reviewable changes (3)
- .gitignore
- main.py
- tests/test_main.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: ParticularlyPythonicBS
Repo: TemoaProject/temoa PR: 177
File: temoa/model_checking/commodity_network.py:26-33
Timestamp: 2025-10-27T15:53:41.829Z
Learning: The Temoa project requires Python 3.12 or above as the minimum supported version, so PEP 695 `type` syntax for type aliases is appropriate and preferred over `TypeAlias`.
📚 Learning: 2025-10-27T15:53:41.829Z
Learnt from: ParticularlyPythonicBS
Repo: TemoaProject/temoa PR: 177
File: temoa/model_checking/commodity_network.py:26-33
Timestamp: 2025-10-27T15:53:41.829Z
Learning: The Temoa project requires Python 3.12 or above as the minimum supported version, so PEP 695 `type` syntax for type aliases is appropriate and preferred over `TypeAlias`.
Applied to files:
requirements-dev.txtpyproject.toml
🧬 Code graph analysis (8)
tests/conftest.py (3)
definitions.py (1)
set_OUTPUT_PATH(20-22)temoa/_internal/temoa_sequencer.py (1)
TemoaSequencer(42-237)temoa/core/config.py (2)
TemoaConfig(32-261)build_config(156-172)
tests/test_material_results.py (2)
temoa/core/config.py (2)
TemoaConfig(32-261)build_config(156-172)temoa/_internal/temoa_sequencer.py (1)
TemoaSequencer(42-237)
temoa/cli.py (4)
definitions.py (1)
set_OUTPUT_PATH(20-22)temoa/_internal/temoa_sequencer.py (3)
TemoaSequencer(42-237)build_model(80-109)start(111-162)temoa/core/config.py (2)
TemoaConfig(32-261)build_config(156-172)temoa/core/modes.py (1)
TemoaMode(34-44)
temoa/_internal/temoa_sequencer.py (4)
temoa/core/config.py (1)
TemoaConfig(32-261)temoa/core/modes.py (1)
TemoaMode(34-44)temoa/_internal/run_actions.py (6)
check_python_version(58-68)check_database_version(71-126)build_instance(129-195)solve_instance(212-335)check_solve_status(338-350)handle_results(353-402)temoa/data_io/hybrid_loader.py (2)
HybridLoader(65-904)load_data_portal(122-137)
tests/test_model.py (2)
temoa/core/config.py (2)
TemoaConfig(32-261)build_config(156-172)temoa/_internal/temoa_sequencer.py (2)
TemoaSequencer(42-237)build_model(80-109)
tests/test_emission_results.py (3)
temoa/_internal/temoa_sequencer.py (2)
TemoaSequencer(42-237)start(111-162)temoa/core/config.py (2)
TemoaConfig(32-261)build_config(156-172)tests/test_material_results.py (1)
solved_connection(15-40)
tests/test_set_consistency.py (2)
temoa/core/config.py (2)
TemoaConfig(32-261)build_config(156-172)temoa/_internal/temoa_sequencer.py (2)
TemoaSequencer(42-237)build_model(80-109)
tests/test_temoa_sequencer.py (2)
temoa/_internal/temoa_sequencer.py (3)
TemoaSequencer(42-237)start(111-162)build_model(80-109)temoa/core/config.py (2)
TemoaConfig(32-261)build_config(156-172)
🪛 Ruff (0.14.3)
tests/test_material_results.py
64-64: Possible SQL injection vector through string-based query construction
(S608)
temoa/cli.py
24-24: datetime.datetime.now() called without a tz argument
(DTZ005)
29-29: Boolean-typed positional argument in function definition
(FBT001)
29-29: Boolean default positional argument in function definition
(FBT002)
29-29: Boolean-typed positional argument in function definition
(FBT001)
29-29: Boolean default positional argument in function definition
(FBT002)
72-72: Logging statement uses f-string
(G004)
78-78: Boolean-typed positional argument in function definition
(FBT001)
79-79: Boolean-typed positional argument in function definition
(FBT001)
100-100: Boolean-typed positional argument in function definition
(FBT001)
107-107: Boolean-typed positional argument in function definition
(FBT001)
148-148: Boolean default positional argument in function definition
(FBT002)
151-151: Boolean default positional argument in function definition
(FBT002)
196-196: Boolean default positional argument in function definition
(FBT002)
200-200: Boolean default positional argument in function definition
(FBT002)
206-206: Boolean default positional argument in function definition
(FBT002)
251-251: Boolean-typed positional argument in function definition
(FBT001)
259-259: Boolean-typed positional argument in function definition
(FBT001)
268-268: Unnecessary pass statement
Remove unnecessary pass
(PIE790)
temoa/_internal/temoa_sequencer.py
63-63: Logging statement uses f-string
(G004)
78-78: Avoid specifying long messages outside the exception class
(TRY003)
101-101: Avoid specifying long messages outside the exception class
(TRY003)
119-119: Logging statement uses f-string
(G004)
126-128: Avoid specifying long messages outside the exception class
(TRY003)
142-144: Avoid specifying long messages outside the exception class
(TRY003)
205-209: Avoid specifying long messages outside the exception class
(TRY003)
215-217: Avoid specifying long messages outside the exception class
(TRY003)
tests/test_cli.py
116-116: Assertion should be broken down into multiple parts
Break down assertion into multiple parts
(PT018)
tests/test_emission_results.py
25-25: Logging statement uses f-string
(G004)
🔇 Additional comments (4)
tests/conftest.py (1)
79-88: Fixture aligns with config-centric API.By building the config via
TemoaConfig.build_configand seedingOUTPUT_PATHwithtmp_path, the fixture now exercises the same surface as the refactored CLI while keeping test artifacts isolated per invocation.tests/test_temoa_sequencer.py (1)
32-48: Good coverage across modes.Parameterizing
start()with explicitmode_overridevalues ensures the new contract is exercised for every supported path, and asserting theNonereturn keeps the signal crisp.tests/test_model.py (1)
23-30: Correct use of build_model for BUILD_ONLY.Switching the serialization test to call
build_model()validates the dedicated BUILD_ONLY surface and keeps the regression protection aligned with the sequencer refactor.tests/test_set_consistency.py (1)
36-42: Set consistency test now mirrors public API.Constructing the config with
build_config()and invokingbuild_model()keeps this regression harness in lockstep with the sequencer redesign while maintaining the same coverage.
There was a problem hiding this comment.
Actionable comments posted: 4
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
temoa/cli.py(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-03T22:55:02.437Z
Learnt from: ParticularlyPythonicBS
Repo: TemoaProject/temoa PR: 186
File: temoa/_internal/temoa_sequencer.py:60-63
Timestamp: 2025-11-03T22:55:02.437Z
Learning: In `TemoaSequencer.__init__`, when a `mode_override` is provided, it must be written back to `self.config.scenario_mode` (not just stored in `self.temoa_mode`) because `HybridLoader.create_data_dict()` has strict consistency checks that validate `config.scenario_mode` matches the presence/absence of `myopic_index`. Without this synchronization, MYOPIC mode would fail with a RuntimeError when the loader is instantiated.
Applied to files:
temoa/cli.py
🧬 Code graph analysis (1)
temoa/cli.py (4)
definitions.py (1)
set_OUTPUT_PATH(20-22)temoa/_internal/temoa_sequencer.py (3)
TemoaSequencer(42-250)build_model(80-122)start(124-175)temoa/core/config.py (2)
TemoaConfig(32-261)build_config(156-172)temoa/core/modes.py (1)
TemoaMode(34-44)
🪛 Ruff (0.14.3)
temoa/cli.py
25-25: datetime.datetime.now() called without a tz argument
(DTZ005)
30-30: Boolean-typed positional argument in function definition
(FBT001)
30-30: Boolean default positional argument in function definition
(FBT002)
30-30: Boolean-typed positional argument in function definition
(FBT001)
30-30: Boolean default positional argument in function definition
(FBT002)
73-73: Logging statement uses f-string
(G004)
79-79: Boolean-typed positional argument in function definition
(FBT001)
80-80: Boolean-typed positional argument in function definition
(FBT001)
101-101: Boolean-typed positional argument in function definition
(FBT001)
108-108: Boolean-typed positional argument in function definition
(FBT001)
119-119: String contains ambiguous – (EN DASH). Did you mean - (HYPHEN-MINUS)?
(RUF001)
162-162: Boolean default positional argument in function definition
(FBT002)
165-165: Boolean default positional argument in function definition
(FBT002)
210-210: Boolean default positional argument in function definition
(FBT002)
214-214: Boolean default positional argument in function definition
(FBT002)
220-220: Boolean default positional argument in function definition
(FBT002)
265-265: Boolean-typed positional argument in function definition
(FBT001)
273-273: Boolean-typed positional argument in function definition
(FBT001)
282-282: Unnecessary pass statement
Remove unnecessary pass
(PIE790)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: setup and test (3.12)
🔇 Additional comments (3)
temoa/cli.py (3)
76-95: Well-structured sequencer setup with proper dependency ordering.The function correctly sequences the initialization steps (output path → logging → global state → config → sequencer) and properly delegates mode synchronization to the
TemoaSequencerconstructor.
145-190: Validate command correctly enforces build-only validation.The validate command appropriately forces
BUILD_ONLYmode viamode_override, ensuring validation always builds without solving regardless of the config file's scenario_mode. The separation between sequencer silent mode (always True) and user output silent mode (configurable) is well-designed.
193-257: Run command correctly respects BUILD_ONLY from both CLI and config.Line 239's condition
build_only or ts.temoa_mode is TemoaMode.BUILD_ONLYproperly addresses the past review concern by checking both the CLI flag and the config file's scenario_mode. This ensures that:
- Configs with
scenario_mode = BUILD_ONLYwork without requiring--build-only- The
--build-onlyflag overrides other modes when specified- Users can validate via
temoa run config.tomlwhen config specifies BUILD_ONLYThe error handling gracefully manages user aborts (typer.Abort) and runtime errors, providing clean CLI behavior.
| def _create_output_folder() -> Path: | ||
| """Create a default time-stamped folder for outputs.""" | ||
| output_path = Path('output_files', datetime.now().strftime('%Y-%m-%d_%H%M%S')) | ||
| output_path.mkdir(parents=True, exist_ok=True) | ||
| return output_path |
There was a problem hiding this comment.
Specify timezone for timestamp consistency.
The timestamp generated by datetime.now() is timezone-naive, which can cause inconsistencies in distributed environments or when comparing logs across different systems.
Apply this diff to use UTC timezone:
-def _create_output_folder() -> Path:
- """Create a default time-stamped folder for outputs."""
- output_path = Path('output_files', datetime.now().strftime('%Y-%m-%d_%H%M%S'))
+def _create_output_folder() -> Path:
+ """Create a default time-stamped folder for outputs."""
+ output_path = Path('output_files', datetime.now(datetime.UTC).strftime('%Y-%m-%d_%H%M%S'))Note: datetime.UTC requires Python 3.11+. For earlier versions, use datetime.now(timezone.utc) with from datetime import timezone.
Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Ruff (0.14.3)
25-25: datetime.datetime.now() called without a tz argument
(DTZ005)
🤖 Prompt for AI Agents
In temoа/cli.py around lines 23 to 27, the code uses datetime.now() which is
timezone-naive; replace it with a UTC-aware timestamp by importing timezone and
using datetime.now(timezone.utc) (or datetime.now(datetime.UTC) on Python 3.11+)
when building the folder name, keep the existing strftime format, and add the
necessary import (from datetime import datetime, timezone) at the top of the
file so the output folder name is generated with a consistent UTC timestamp.
Transforms temoa from script based tool to command line application.
Replaces main.py to a CLI powered by Typer and Rich and refactors temoa sequencer to behave more like a library component.
Description of Changes
Replaced main.py with temoa/cli.py: The legacy argparse-based script has been removed. All CLI logic is now self-contained within the temoa package.
Configured Installable Entry Point: The pyproject.toml file now includes a [project.scripts] entry, which makes the temoa command available in the user's path after a pip install.
Created User-Friendly Commands:
temoa run: Mirrors the functionality of the old main.py, accepting a config file and various options (--output, --silent, --debug, etc.).
temoa validate: A new, dedicated command that allows users to quickly check if a configuration file and its associated database are valid by building the model without solving it. This is a significant usability improvement.
Improved User Interaction: Non-interactive runs are now properly supported via --silent flags, which suppress status messages and INFO-level logs on the console, making the tool scriptable and suitable for CI/CD.
The TemoaSequencer was refactored to decouple it from the application's user interface and control flow. It now behaves as a proper, reusable library component.
Removed Program-Terminating Calls: All sys.exit() calls have been replaced with specific exceptions (e.g., RuntimeError, ValueError). The sequencer no longer terminates the application; it reports errors to the caller.
Removed User Interaction: All print() and input() statements have been removed. The sequencer is now non-interactive and communicates solely via logging and exceptions.
Improved API and Dependency Injection:
The init method now accepts a pre-constructed TemoaConfig object instead of a file path. This decouples the sequencer from the configuration loading process and makes it significantly easier to test.
The BUILD_ONLY mode has been moved to a dedicated public method, build_model(), clarifying the API contract of the start() method.
Bug Fixes & Robustness:
The architectural changes required a complete update of the test suite to ensure correctness and validate the new functionality.
New CLI Tests (tests/test_cli.py): The obsolete test_main.py has been replaced with a new suite of tests using typer.testing.CliRunner. These tests validate:
Updated Unit & Integration Tests: All test files that previously instantiated TemoaSequencer (including conftest.py, test_emission_results.py, test_model.py, etc.) have been updated to use the new API (TemoaSequencer(config=...)).
Removed Global State Dependency: The refactoring exposed a hidden dependency on a global OUTPUT_PATH variable. The CLI and test fixtures now explicitly set this value, making the control flow clear and removing side effects.
Summary by CodeRabbit
New Features
Refactor
Tests
Chores