diff --git a/CHANGELOG.md b/CHANGELOG.md index ac6bb0c..233d4e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.5] - 2026-05-07 + +### Fixed + +- `skypro simulate` now skips constructing a SQLAlchemy engine for the + flows database when all imbalance data sources are `csvTimeseries`. + Previously the engine was created eagerly and unconditionally, so a + placeholder or unparseable `env_config["flows"]["dbUrl"]` raised + before any simulation logic ran — blocking standalone CSV-only + configuration bundles. When at least one source is `flowsMarketData` + the engine is still constructed, with a clearer error if `dbUrl` is + missing. + ## [2.0.0] - Unreleased ### Breaking Changes diff --git a/pyproject.toml b/pyproject.toml index a421671..a6143a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "skypro" -version = "2.0.4" +version = "2.0.5" description = "Skyprospector by Cepro" authors = ["damonrand "] license = "MIT" diff --git a/src/skypro/commands/simulator/main.py b/src/skypro/commands/simulator/main.py index 23b91c1..bd66734 100644 --- a/src/skypro/commands/simulator/main.py +++ b/src/skypro/commands/simulator/main.py @@ -510,6 +510,33 @@ def _get_rates_from_config( and a dataframe containing live and final imbalance data. """ + # Build the flows DB engine only if at least one imbalance source is + # `flowsMarketData`. CSV-only configs (`csvTimeseries` for all four + # sources) don't need the engine — `get_timeseries` accepts + # `db_engine=None` and only forwards it down the flowsMarketData + # branch. Mirrors the rates-DB gating pattern used below for + # `rates_config.{live,final}.rates_db`. + imbalance_sources = [ + rates_config.final.imbalance_data_source.price, + rates_config.final.imbalance_data_source.volume, + rates_config.live.imbalance_data_source.price, + rates_config.live.imbalance_data_source.volume, + ] + needs_flows_db = any( + s.flows_market_data_source is not None for s in imbalance_sources + ) + if needs_flows_db: + try: + flows_db_url = env_config["flows"]["dbUrl"] + except KeyError: + raise SystemExit( + "simulate.yaml references a flowsMarketData imbalance source, but " + "env_config['flows']['dbUrl'] is not set in the environment file." + ) + db_engine = sqlalchemy.create_engine(flows_db_url) + else: + db_engine = None + def read_imbalance_data(source: TimeseriesDataSource, context: str): """ Convenience function for reading imbalance data @@ -519,7 +546,7 @@ def read_imbalance_data(source: TimeseriesDataSource, context: str): start=time_index[0], end=time_index[-1], file_path_resolver_func=file_path_resolver_func, - db_engine=sqlalchemy.create_engine(env_config["flows"]["dbUrl"]), + db_engine=db_engine, context=context ) for notice in notices: diff --git a/src/tests/integration/fixtures/env_no_flows_db.json b/src/tests/integration/fixtures/env_no_flows_db.json new file mode 100644 index 0000000..a195ce4 --- /dev/null +++ b/src/tests/integration/fixtures/env_no_flows_db.json @@ -0,0 +1,15 @@ +{ + "_doc": "Variant of env.json with an unparseable flows.dbUrl. Used to verify that skypro simulate does NOT eagerly construct a SQLAlchemy engine for the flows DB when all imbalance sources are csvTimeseries (the case for standalone CSV-only handoff bundles). See test_integration_simulator.py::test_integration_csv_only_no_flows_db.", + "vars": { + }, + "flows": { + "dbUrl": "REPLACE_OR_REMOVE_NOT_USED_BY_SIMULATE" + }, + "flux": { + "dbUrl": "postgresql://username:password@someserver.com:5687/database", + "schema": "flows" + }, + "rates": { + "dbUrl": "postgresql://username:password@someserver.com:5687/database" + } +} diff --git a/src/tests/integration/test_integration_simulator.py b/src/tests/integration/test_integration_simulator.py index f74b06e..befddcc 100644 --- a/src/tests/integration/test_integration_simulator.py +++ b/src/tests/integration/test_integration_simulator.py @@ -131,3 +131,49 @@ class SubTest: 0, msg=f"Summary value out of tolerance:\n{error.transpose()}" ) + + def test_integration_csv_only_no_flows_db(self): + """ + Regression test for the eager-flows-DB bug fixed in v2.0.5. + + The integration fixture has only csvTimeseries imbalance sources + and file-based rates — no flowsMarketData, no rates_db. This + means env_config["flows"]["dbUrl"] is never queried at runtime. + Before v2.0.5, the simulator constructed a SQLAlchemy engine for + the flows DB unconditionally, so an unparseable placeholder URL + in flows.dbUrl would crash the run before any simulation logic + executed — blocking standalone CSV-only handoff bundles. + + env_no_flows_db.json sets flows.dbUrl to the literal placeholder + string "REPLACE_OR_REMOVE_NOT_USED_BY_SIMULATE" — SQLAlchemy + can't parse it as a URL. With the v2.0.5 fix, the simulator + skips engine construction when no source is flowsMarketData and + the run completes successfully. + """ + print( + "\n\n\n\nSTARTING SIMULATION INTEGRATION TEST 'csv_only_no_flows_db' " + "- - - - - - - - - - - - - - - - - - - - -" + ) + + res = subprocess.run([ + 'python3', + './src/skypro/main.py', + 'simulate', + '--env', + './src/tests/integration/fixtures/env_no_flows_db.json', + '-y', + '--config', + './src/tests/integration/fixtures/simulation/config.yaml', + '--sim', + 'integrationTestPriceCurve', + ]) + + self.assertEqual( + res.returncode, + 0, + msg=( + "Simulator exited non-zero with an unparseable flows.dbUrl " + "in the env file, despite all imbalance sources being " + "csvTimeseries. Pre-v2.0.5 regression." + ), + )