flextool 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flextool/__init__.py +41 -0
- flextool/_mem_sampler.py +193 -0
- flextool/_resources.py +43 -0
- flextool/calibrate/__init__.py +51 -0
- flextool/calibrate/__main__.py +11 -0
- flextool/calibrate/_cli.py +316 -0
- flextool/calibrate/_db_alt.py +166 -0
- flextool/calibrate/_final_outputs.py +110 -0
- flextool/calibrate/_guard.py +151 -0
- flextool/calibrate/_loop.py +558 -0
- flextool/calibrate/_readers.py +223 -0
- flextool/calibrate/_report.py +263 -0
- flextool/calibrate/_sizing.py +699 -0
- flextool/calibrate/_solve.py +134 -0
- flextool/calibrate/_solve_status.py +495 -0
- flextool/cli/__init__.py +9 -0
- flextool/cli/_console.py +51 -0
- flextool/cli/_timing.py +147 -0
- flextool/cli/cmd_execute_flextool_workflow.py +187 -0
- flextool/cli/cmd_export_to_tabular.py +56 -0
- flextool/cli/cmd_import_sensitivities.py +75 -0
- flextool/cli/cmd_migrate_database.py +13 -0
- flextool/cli/cmd_open_results_db.py +269 -0
- flextool/cli/cmd_read_matpower.py +66 -0
- flextool/cli/cmd_read_old_flextool.py +63 -0
- flextool/cli/cmd_read_self_describing_tabular_input.py +50 -0
- flextool/cli/cmd_read_tabular_input.py +81 -0
- flextool/cli/cmd_run_flextool.py +1095 -0
- flextool/cli/cmd_scenario_results.py +284 -0
- flextool/cli/cmd_solve_mps.py +169 -0
- flextool/cli/cmd_update_flextool.py +17 -0
- flextool/cli/cmd_write_outputs.py +125 -0
- flextool/common_utils/__init__.py +1 -0
- flextool/common_utils/plot_mem_shape.py +77 -0
- flextool/common_utils/precision.py +451 -0
- flextool/decomposition/__init__.py +0 -0
- flextool/decomposition/region_decomposition.py +128 -0
- flextool/decomposition/region_filter.py +1261 -0
- flextool/engine_polars/__init__.py +110 -0
- flextool/engine_polars/_axis_enums.py +742 -0
- flextool/engine_polars/_benders.py +3462 -0
- flextool/engine_polars/_block_layout.py +1479 -0
- flextool/engine_polars/_blocks.py +1515 -0
- flextool/engine_polars/_commodity_ladder.py +660 -0
- flextool/engine_polars/_cumulative_invest.py +1165 -0
- flextool/engine_polars/_db_loader.py +153 -0
- flextool/engine_polars/_db_reader.py +127 -0
- flextool/engine_polars/_dc_power_flow.py +445 -0
- flextool/engine_polars/_delay.py +442 -0
- flextool/engine_polars/_derived_arithmetic.py +432 -0
- flextool/engine_polars/_derived_block.py +990 -0
- flextool/engine_polars/_derived_branch.py +769 -0
- flextool/engine_polars/_derived_existing.py +1353 -0
- flextool/engine_polars/_derived_npv.py +1297 -0
- flextool/engine_polars/_derived_params.py +9850 -0
- flextool/engine_polars/_derived_profile.py +881 -0
- flextool/engine_polars/_derived_walks.py +276 -0
- flextool/engine_polars/_determinism.py +70 -0
- flextool/engine_polars/_direct_params.py +2186 -0
- flextool/engine_polars/_dump_csvs.py +1009 -0
- flextool/engine_polars/_emit_arc_unions.py +1631 -0
- flextool/engine_polars/_emit_calc_params.py +729 -0
- flextool/engine_polars/_emit_chain_params.py +709 -0
- flextool/engine_polars/_emit_co2_accumulators.py +400 -0
- flextool/engine_polars/_emit_dispatchers.py +690 -0
- flextool/engine_polars/_emit_energy_margin.py +125 -0
- flextool/engine_polars/_emit_energy_margin_adder.py +290 -0
- flextool/engine_polars/_emit_entity_annual.py +428 -0
- flextool/engine_polars/_emit_inflow_scaling.py +1420 -0
- flextool/engine_polars/_emit_leaf_sets.py +550 -0
- flextool/engine_polars/_emit_lp_scaling.py +665 -0
- flextool/engine_polars/_emit_mid_sets.py +859 -0
- flextool/engine_polars/_emit_pdt_params.py +759 -0
- flextool/engine_polars/_emit_per_solve.py +774 -0
- flextool/engine_polars/_emit_period_calc.py +504 -0
- flextool/engine_polars/_emit_period_params.py +2398 -0
- flextool/engine_polars/_emit_provider_io.py +141 -0
- flextool/engine_polars/_emit_reserve.py +574 -0
- flextool/engine_polars/_emit_solve_time.py +311 -0
- flextool/engine_polars/_emit_solve_writers.py +1249 -0
- flextool/engine_polars/_flex_data_accumulator.py +388 -0
- flextool/engine_polars/_flex_data_provider.py +478 -0
- flextool/engine_polars/_group_slack.py +1253 -0
- flextool/engine_polars/_inmemory_reader.py +140 -0
- flextool/engine_polars/_input_source.py +336 -0
- flextool/engine_polars/_invest_seeds.py +191 -0
- flextool/engine_polars/_native_input_writer.py +100 -0
- flextool/engine_polars/_native_run_model.py +1348 -0
- flextool/engine_polars/_orchestration.py +4314 -0
- flextool/engine_polars/_output_writer.py +439 -0
- flextool/engine_polars/_param_shapes.py +1595 -0
- flextool/engine_polars/_parquet_bundle.py +723 -0
- flextool/engine_polars/_pdt_join.py +167 -0
- flextool/engine_polars/_pdt_lookup.py +547 -0
- flextool/engine_polars/_per_solve_sets.py +335 -0
- flextool/engine_polars/_projection_params.py +2056 -0
- flextool/engine_polars/_provider_keys.py +173 -0
- flextool/engine_polars/_provider_translators.py +225 -0
- flextool/engine_polars/_recursive_solve.py +703 -0
- flextool/engine_polars/_region_filter.py +2508 -0
- flextool/engine_polars/_reserve.py +649 -0
- flextool/engine_polars/_solve_acceptance.py +331 -0
- flextool/engine_polars/_solve_config.py +1001 -0
- flextool/engine_polars/_solve_context.py +885 -0
- flextool/engine_polars/_solve_handoff.py +164 -0
- flextool/engine_polars/_solve_state.py +232 -0
- flextool/engine_polars/_solver_base.py +36 -0
- flextool/engine_polars/_solver_dispatch.py +511 -0
- flextool/engine_polars/_spinedb_reader.py +1165 -0
- flextool/engine_polars/_stochastic.py +593 -0
- flextool/engine_polars/_subprocess_solve.py +1838 -0
- flextool/engine_polars/_timeline.py +1416 -0
- flextool/engine_polars/_vectorize.py +438 -0
- flextool/engine_polars/_warm.py +858 -0
- flextool/engine_polars/autoscale/__init__.py +107 -0
- flextool/engine_polars/autoscale/_config.py +218 -0
- flextool/engine_polars/autoscale/_layer2.py +1253 -0
- flextool/engine_polars/autoscale/_layer2_types.py +584 -0
- flextool/engine_polars/autoscale/_quantity_types.py +621 -0
- flextool/engine_polars/autoscale/_report.py +336 -0
- flextool/engine_polars/chain.py +259 -0
- flextool/engine_polars/input.py +6638 -0
- flextool/engine_polars/model.py +4754 -0
- flextool/env_check.py +388 -0
- flextool/export_to_tabular/__init__.py +5 -0
- flextool/export_to_tabular/db_reader.py +224 -0
- flextool/export_to_tabular/excel_writer.py +3559 -0
- flextool/export_to_tabular/export_settings.yaml +377 -0
- flextool/export_to_tabular/export_to_excel.py +227 -0
- flextool/export_to_tabular/formatting.py +543 -0
- flextool/export_to_tabular/sheet_config.py +876 -0
- flextool/gui/__init__.py +0 -0
- flextool/gui/__main__.py +118 -0
- flextool/gui/calibrate_commands.py +184 -0
- flextool/gui/calibrate_jobs.py +424 -0
- flextool/gui/check_tree.py +142 -0
- flextool/gui/cli_format.py +83 -0
- flextool/gui/config_parser.py +68 -0
- flextool/gui/data_models.py +362 -0
- flextool/gui/db_editor_integration.py +202 -0
- flextool/gui/db_version_check.py +269 -0
- flextool/gui/dialogs/__init__.py +0 -0
- flextool/gui/dialogs/add_dialog.py +1098 -0
- flextool/gui/dialogs/calibrate_dialog.py +1259 -0
- flextool/gui/dialogs/file_picker.py +473 -0
- flextool/gui/dialogs/group_picker.py +299 -0
- flextool/gui/dialogs/migration_consent_dialog.py +106 -0
- flextool/gui/dialogs/migration_progress_dialog.py +237 -0
- flextool/gui/dialogs/plot_dialog.py +459 -0
- flextool/gui/dialogs/plot_settings_picker.py +2184 -0
- flextool/gui/dialogs/project_dialog.py +426 -0
- flextool/gui/dialogs/update_dialog.py +212 -0
- flextool/gui/downsampling.py +88 -0
- flextool/gui/error_handling.py +50 -0
- flextool/gui/execution_manager.py +1715 -0
- flextool/gui/execution_window.py +1377 -0
- flextool/gui/hover_tooltip.py +111 -0
- flextool/gui/input_sources.py +730 -0
- flextool/gui/main_window.py +6181 -0
- flextool/gui/network_graph.py +215 -0
- flextool/gui/output_actions.py +393 -0
- flextool/gui/output_log_window.py +159 -0
- flextool/gui/platform_utils.py +421 -0
- flextool/gui/plot_cache.py +88 -0
- flextool/gui/plot_canvas.py +543 -0
- flextool/gui/plot_config_reader.py +272 -0
- flextool/gui/project_utils.py +100 -0
- flextool/gui/result_viewer.py +4394 -0
- flextool/gui/scenario_key.py +162 -0
- flextool/gui/scenario_lists.py +516 -0
- flextool/gui/settings_io.py +360 -0
- flextool/gui/solve_reader.py +103 -0
- flextool/gui/tree_reorder.py +88 -0
- flextool/gui/ui_metrics.py +420 -0
- flextool/input_derivation/__init__.py +281 -0
- flextool/input_derivation/_commodity_ladder.py +375 -0
- flextool/input_derivation/_commodity_ladder_sets.py +70 -0
- flextool/input_derivation/_dc_power_flow.py +377 -0
- flextool/input_derivation/_method_constants.py +77 -0
- flextool/input_derivation/_process_method.py +258 -0
- flextool/input_derivation/_specs.py +1026 -0
- flextool/input_derivation/_validators.py +321 -0
- flextool/lean_parquet.py +159 -0
- flextool/model_builder/__init__.py +5 -0
- flextool/model_builder/build_model.py +589 -0
- flextool/model_builder/encoding.py +67 -0
- flextool/model_builder/names.py +34 -0
- flextool/model_builder/profiles.py +129 -0
- flextool/plot_outputs/__init__.py +14 -0
- flextool/plot_outputs/axis_helpers.py +355 -0
- flextool/plot_outputs/color_template.py +888 -0
- flextool/plot_outputs/config.py +171 -0
- flextool/plot_outputs/format_helpers.py +345 -0
- flextool/plot_outputs/legend_helpers.py +143 -0
- flextool/plot_outputs/orchestrator.py +1141 -0
- flextool/plot_outputs/perf.py +37 -0
- flextool/plot_outputs/plan.py +1787 -0
- flextool/plot_outputs/plot_bars.py +1510 -0
- flextool/plot_outputs/plot_bars_detail.py +753 -0
- flextool/plot_outputs/plot_lines.py +951 -0
- flextool/plot_outputs/shared_manifest.py +564 -0
- flextool/plot_outputs/subplot_helpers.py +137 -0
- flextool/process_inputs/__init__.py +188 -0
- flextool/process_inputs/import_old_excel_input.json +4159 -0
- flextool/process_inputs/read_matpower.py +451 -0
- flextool/process_inputs/read_old_flextool.py +1288 -0
- flextool/process_inputs/read_self_describing_excel.py +1423 -0
- flextool/process_inputs/read_tabular_with_specification.py +1114 -0
- flextool/process_inputs/write_old_flextool_to_db.py +3077 -0
- flextool/process_inputs/write_self_describing_to_db.py +977 -0
- flextool/process_inputs/write_to_input_db.py +269 -0
- flextool/process_outputs/__init__.py +7 -0
- flextool/process_outputs/_annualize.py +55 -0
- flextool/process_outputs/_inmemory_helpers.py +292 -0
- flextool/process_outputs/_output_meta.py +672 -0
- flextool/process_outputs/calc_capacity_flows.py +107 -0
- flextool/process_outputs/calc_connections.py +136 -0
- flextool/process_outputs/calc_costs.py +260 -0
- flextool/process_outputs/calc_group_flows.py +192 -0
- flextool/process_outputs/calc_slacks.py +103 -0
- flextool/process_outputs/calc_storage_vre.py +160 -0
- flextool/process_outputs/drop_levels.py +208 -0
- flextool/process_outputs/handoff_writers.py +1315 -0
- flextool/process_outputs/out_ancillary.py +544 -0
- flextool/process_outputs/out_capacity.py +179 -0
- flextool/process_outputs/out_costs.py +334 -0
- flextool/process_outputs/out_flowgroup.py +189 -0
- flextool/process_outputs/out_flows.py +301 -0
- flextool/process_outputs/out_group.py +475 -0
- flextool/process_outputs/out_node.py +190 -0
- flextool/process_outputs/persist_realized_slice.py +601 -0
- flextool/process_outputs/process_results.py +24 -0
- flextool/process_outputs/read_highs_solution.py +2256 -0
- flextool/process_outputs/read_parameters.py +1799 -0
- flextool/process_outputs/read_sets.py +1095 -0
- flextool/process_outputs/read_variables.py +553 -0
- flextool/process_outputs/solve_order.py +81 -0
- flextool/process_outputs/spinedb_replay.py +412 -0
- flextool/process_outputs/union_realized_slice.py +224 -0
- flextool/process_outputs/write_outputs.py +1286 -0
- flextool/process_outputs/write_spinedb.py +1267 -0
- flextool/representative_periods/__init__.py +5 -0
- flextool/representative_periods/clustering.py +165 -0
- flextool/representative_periods/force_include.py +563 -0
- flextool/representative_periods/netload.py +365 -0
- flextool/representative_periods/netload_inputs.py +345 -0
- flextool/representative_periods/netload_iterate.py +722 -0
- flextool/representative_periods/preprocess.py +948 -0
- flextool/representative_periods/scenario_stack.py +195 -0
- flextool/representative_periods/weights.py +124 -0
- flextool/scenario_comparison/__init__.py +13 -0
- flextool/scenario_comparison/config_builder.py +158 -0
- flextool/scenario_comparison/constants.py +20 -0
- flextool/scenario_comparison/data_models.py +222 -0
- flextool/scenario_comparison/db_reader.py +399 -0
- flextool/scenario_comparison/dispatch_data.py +1002 -0
- flextool/scenario_comparison/dispatch_mappings.py +205 -0
- flextool/scenario_comparison/dispatch_plots.py +691 -0
- flextool/scenario_comparison/input_entity_colors.py +319 -0
- flextool/scenario_comparison/orchestrator.py +453 -0
- flextool/scenario_comparison/plan_union.py +244 -0
- flextool/scenario_comparison/plot_settings_seed.py +205 -0
- flextool/schemas/AXIS_CONTRACT.md +71 -0
- flextool/schemas/canonical_databases/howto_aggregate_output.json +6225 -0
- flextool/schemas/canonical_databases/howto_connections.json +5606 -0
- flextool/schemas/canonical_databases/howto_demand.json +5518 -0
- flextool/schemas/canonical_databases/howto_hydro_reservoir.json +6239 -0
- flextool/schemas/canonical_databases/howto_hydro_reservoir_with_pump.json +5933 -0
- flextool/schemas/canonical_databases/howto_non_sync_and_curtailment.json +5794 -0
- flextool/schemas/canonical_databases/howto_ramp_and_start_up.json +5707 -0
- flextool/schemas/canonical_databases/howto_stochastics.json +6032 -0
- flextool/schemas/canonical_databases/templates_examples.json +13532 -0
- flextool/schemas/canonical_databases/templates_time_settings_only.json +5340 -0
- flextool/schemas/comparison_settings_template.json +197 -0
- flextool/schemas/default_plot_settings.yaml +260 -0
- flextool/schemas/default_plots.yaml +2293 -0
- flextool/schemas/flextool_axis_contract.json +303 -0
- flextool/schemas/flextool_axis_contract.schema.json +247 -0
- flextool/schemas/old_flextool_import_template.json +4443 -0
- flextool/schemas/output_info_template.json +48 -0
- flextool/schemas/output_settings_template.json +256 -0
- flextool/schemas/pre_v26/flextool_template_constant_default.json +2105 -0
- flextool/schemas/pre_v26/flextool_template_default_optional_output.json +2152 -0
- flextool/schemas/pre_v26/flextool_template_default_value.json +2094 -0
- flextool/schemas/pre_v26/flextool_template_drop_down.json +2080 -0
- flextool/schemas/pre_v26/flextool_template_lifetime_method.json +1990 -0
- flextool/schemas/pre_v26/flextool_template_optional_outputs.json +2094 -0
- flextool/schemas/pre_v26/flextool_template_output_node_flows.json +2105 -0
- flextool/schemas/pre_v26/flextool_template_results_master.json +493 -0
- flextool/schemas/pre_v26/flextool_template_rolling_start_remove.json +2087 -0
- flextool/schemas/pre_v26/flextool_template_rolling_window.json +2059 -0
- flextool/schemas/pre_v26/flextool_template_storage_binding_defaults.json +46 -0
- flextool/schemas/pre_v26/flextool_template_v2.json +1990 -0
- flextool/schemas/pre_v26/flextool_template_v25.json +3864 -0
- flextool/schemas/spinedb_results_schema.json +581 -0
- flextool/schemas/spinedb_schema.json +4636 -0
- flextool/solver_config/copt.opt.template +18 -0
- flextool/solver_config/cplex.opt.template +25 -0
- flextool/solver_config/gurobi.opt.template +18 -0
- flextool/solver_config/highs.opt.template +18 -0
- flextool/solver_config/xpress.opt.template +26 -0
- flextool/spinedb_backend/__init__.py +26 -0
- flextool/spinedb_backend/_axis_enums.py +1119 -0
- flextool/spinedb_backend/_backend.py +1139 -0
- flextool/update_flextool/__init__.py +12 -0
- flextool/update_flextool/canonical_databases.py +251 -0
- flextool/update_flextool/db_migration.py +7108 -0
- flextool/update_flextool/ensure_settings_db.py +138 -0
- flextool/update_flextool/export_database.py +103 -0
- flextool/update_flextool/extend_tests_fixture.py +772 -0
- flextool/update_flextool/generate_canonical.py +274 -0
- flextool/update_flextool/initialize_database.py +42 -0
- flextool/update_flextool/install_info.py +225 -0
- flextool/update_flextool/self_update.py +464 -0
- flextool/update_flextool/sync_master_json_template.py +125 -0
- flextool/update_flextool/test_fixtures.py +187 -0
- flextool-4.0.0.dist-info/METADATA +217 -0
- flextool-4.0.0.dist-info/RECORD +322 -0
- flextool-4.0.0.dist-info/WHEEL +5 -0
- flextool-4.0.0.dist-info/entry_points.txt +17 -0
- flextool-4.0.0.dist-info/licenses/LICENSE.txt +19 -0
- flextool-4.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Update and migration: GitHub update, database schema versioning."""
|
|
2
|
+
|
|
3
|
+
#: The current FlexTool database schema version.
|
|
4
|
+
#: This is the single source of truth — all other modules import from here.
|
|
5
|
+
#: Defined before submodule imports to avoid circular-import issues.
|
|
6
|
+
FLEXTOOL_DB_VERSION: int = 69
|
|
7
|
+
|
|
8
|
+
from flextool.update_flextool.self_update import update_flextool, ensure_runtime_files # noqa: E402 # FLEXTOOL_DB_VERSION must precede submodule imports to break circular dep
|
|
9
|
+
from flextool.update_flextool.db_migration import migrate_database # noqa: E402 # see FLEXTOOL_DB_VERSION ordering note above
|
|
10
|
+
from flextool.update_flextool.initialize_database import initialize_database # noqa: E402 # see FLEXTOOL_DB_VERSION ordering note above
|
|
11
|
+
|
|
12
|
+
__all__ = ['update_flextool', 'ensure_runtime_files', 'migrate_database', 'initialize_database', 'FLEXTOOL_DB_VERSION']
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Canonical FlexTool databases stored as JSON in the package.
|
|
2
|
+
|
|
3
|
+
Each entry in :data:`CANONICAL_DATABASES` pairs a JSON resource — the
|
|
4
|
+
source of truth, shipped inside ``flextool/schemas/canonical_databases/``
|
|
5
|
+
in the wheel — with the on-disk ``.sqlite`` path it materialises into
|
|
6
|
+
relative to the user's current working directory.
|
|
7
|
+
|
|
8
|
+
The user-facing workflow is one of:
|
|
9
|
+
|
|
10
|
+
``python -m flextool.update_flextool.canonical_databases materialize``
|
|
11
|
+
Recreate any missing ``.sqlite`` files from the bundled JSON sources.
|
|
12
|
+
Idempotent for files that already exist. Called automatically by
|
|
13
|
+
:func:`flextool.update_flextool.update_flextool` and by
|
|
14
|
+
``tests/conftest.py`` so users and CI always have ``.sqlite`` files
|
|
15
|
+
to point Spine Toolbox at.
|
|
16
|
+
|
|
17
|
+
``python -m flextool.update_flextool.canonical_databases migrate-all``
|
|
18
|
+
Round-trip every canonical JSON through
|
|
19
|
+
``initialize_database -> migrate_database -> export_database``.
|
|
20
|
+
Run from a source checkout (editable install) whenever
|
|
21
|
+
``FLEXTOOL_DB_VERSION`` is bumped; commit the resulting JSON diffs
|
|
22
|
+
alongside the migration step. See ``CONTRIBUTING.md``.
|
|
23
|
+
|
|
24
|
+
``python -m flextool.update_flextool.canonical_databases verify``
|
|
25
|
+
Check that every committed canonical JSON is byte-stable through
|
|
26
|
+
the current schema. Intended for CI: exits non-zero with a per-file
|
|
27
|
+
diff summary when any committed JSON has drifted from what the
|
|
28
|
+
current migration chain would produce.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import argparse
|
|
34
|
+
import json
|
|
35
|
+
import os
|
|
36
|
+
import tempfile
|
|
37
|
+
from dataclasses import dataclass
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
|
|
40
|
+
from flextool._resources import package_data_path
|
|
41
|
+
from flextool.update_flextool.db_migration import migrate_database
|
|
42
|
+
from flextool.update_flextool.export_database import export_database
|
|
43
|
+
from flextool.update_flextool.initialize_database import initialize_database
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ``json_name`` is resolved against the bundled
|
|
47
|
+
# ``flextool/schemas/canonical_databases/`` directory via
|
|
48
|
+
# ``importlib.resources``. ``sqlite_path`` is interpreted relative to
|
|
49
|
+
# the user's current working directory — Spine Toolbox project files and
|
|
50
|
+
# scenario references still see the same layout they always have.
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class CanonicalDatabase:
|
|
53
|
+
json_name: str
|
|
54
|
+
sqlite_path: str
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
CANONICAL_DATABASES: tuple[CanonicalDatabase, ...] = (
|
|
58
|
+
CanonicalDatabase("templates_examples.json", "templates/examples.sqlite"),
|
|
59
|
+
CanonicalDatabase("templates_time_settings_only.json", "templates/time_settings_only.sqlite"),
|
|
60
|
+
CanonicalDatabase("howto_aggregate_output.json", "how to example databases/aggregate_output.sqlite"),
|
|
61
|
+
CanonicalDatabase("howto_connections.json", "how to example databases/connections.sqlite"),
|
|
62
|
+
CanonicalDatabase("howto_demand.json", "how to example databases/Demand.sqlite"),
|
|
63
|
+
CanonicalDatabase("howto_hydro_reservoir.json", "how to example databases/hydro_reservoir.sqlite"),
|
|
64
|
+
CanonicalDatabase("howto_hydro_reservoir_with_pump.json", "how to example databases/hydro_reservoir_with_pump.sqlite"),
|
|
65
|
+
CanonicalDatabase("howto_non_sync_and_curtailment.json", "how to example databases/non_sync_and_curtailment.sqlite"),
|
|
66
|
+
CanonicalDatabase("howto_ramp_and_start_up.json", "how to example databases/ramp_and_start_up.sqlite"),
|
|
67
|
+
CanonicalDatabase("howto_stochastics.json", "how to example databases/stochastics.sqlite"),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _json_source(db: CanonicalDatabase) -> Path:
|
|
72
|
+
return package_data_path(f"schemas/canonical_databases/{db.json_name}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def materialize(overwrite: bool = False) -> None:
|
|
76
|
+
"""Create every canonical SQLite from its JSON source.
|
|
77
|
+
|
|
78
|
+
With ``overwrite=False`` (the default), existing ``.sqlite`` files
|
|
79
|
+
in the working tree are left untouched — preserves user edits.
|
|
80
|
+
Pass ``overwrite=True`` to force a refresh (used by the migrate-all
|
|
81
|
+
command and by callers that need a guaranteed-current SQLite).
|
|
82
|
+
|
|
83
|
+
Destination paths are interpreted relative to the current working
|
|
84
|
+
directory; the canonical JSON sources are read from the installed
|
|
85
|
+
``flextool`` package via :mod:`importlib.resources`.
|
|
86
|
+
"""
|
|
87
|
+
for db in CANONICAL_DATABASES:
|
|
88
|
+
json_abs = _json_source(db)
|
|
89
|
+
sqlite_abs = Path.cwd() / db.sqlite_path
|
|
90
|
+
if not json_abs.is_file():
|
|
91
|
+
print(f"!! canonical JSON missing in package: {db.json_name}")
|
|
92
|
+
continue
|
|
93
|
+
if sqlite_abs.exists():
|
|
94
|
+
if not overwrite:
|
|
95
|
+
continue
|
|
96
|
+
sqlite_abs.unlink()
|
|
97
|
+
sqlite_abs.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
initialize_database(str(json_abs), str(sqlite_abs))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def migrate_all() -> None:
|
|
102
|
+
"""Round-trip every canonical JSON through the current schema.
|
|
103
|
+
|
|
104
|
+
For each entry:
|
|
105
|
+
|
|
106
|
+
1. Materialise the bundled JSON into a throwaway ``.sqlite``.
|
|
107
|
+
2. Run :func:`migrate_database` against it — walks
|
|
108
|
+
``model.version`` up to :data:`FLEXTOOL_DB_VERSION`.
|
|
109
|
+
3. Re-export the migrated ``.sqlite`` back over the bundled JSON
|
|
110
|
+
file in place.
|
|
111
|
+
|
|
112
|
+
Requires an editable install: the JSON files are written back to the
|
|
113
|
+
on-disk package source, which only exists for ``pip install -e .``
|
|
114
|
+
checkouts. Wheel installs (``site-packages/``) are read-only and
|
|
115
|
+
will fail here — that's intentional; only the maintainer running
|
|
116
|
+
a schema bump should be regenerating canonical JSONs.
|
|
117
|
+
"""
|
|
118
|
+
for db in CANONICAL_DATABASES:
|
|
119
|
+
json_abs = _json_source(db)
|
|
120
|
+
if not json_abs.is_file():
|
|
121
|
+
print(f"!! canonical JSON missing in package: {db.json_name}")
|
|
122
|
+
continue
|
|
123
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
124
|
+
staging = os.path.join(tmp, "staging.sqlite")
|
|
125
|
+
initialize_database(str(json_abs), staging)
|
|
126
|
+
migrate_database(staging)
|
|
127
|
+
export_database(staging, str(json_abs))
|
|
128
|
+
print(f"migrated canonical JSON: {db.json_name}")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _summarise_diff(committed: Path, regenerated: Path) -> str:
|
|
132
|
+
"""Return a short human-readable summary of which top-level sections
|
|
133
|
+
differ between two canonical JSONs.
|
|
134
|
+
|
|
135
|
+
Used by ``verify`` to surface drift without flooding the CI log.
|
|
136
|
+
Mirrors :func:`flextool.update_flextool.test_fixtures._summarise_diff`.
|
|
137
|
+
"""
|
|
138
|
+
with open(committed) as f:
|
|
139
|
+
a = json.load(f)
|
|
140
|
+
with open(regenerated) as f:
|
|
141
|
+
b = json.load(f)
|
|
142
|
+
keys = sorted(set(a) | set(b))
|
|
143
|
+
lines = []
|
|
144
|
+
for key in keys:
|
|
145
|
+
av = a.get(key)
|
|
146
|
+
bv = b.get(key)
|
|
147
|
+
if av == bv:
|
|
148
|
+
continue
|
|
149
|
+
la = len(av) if isinstance(av, list) else "?"
|
|
150
|
+
lb = len(bv) if isinstance(bv, list) else "?"
|
|
151
|
+
lines.append(f" {key}: committed={la} regenerated={lb}")
|
|
152
|
+
return "\n".join(lines) if lines else " (no top-level section differs — check value-level)"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def verify_all() -> int:
|
|
156
|
+
"""Verify that every committed canonical JSON matches its migrated form.
|
|
157
|
+
|
|
158
|
+
Round-trips each ``CANONICAL_DATABASES`` entry through
|
|
159
|
+
``initialize_database -> migrate_database -> export_database`` into a
|
|
160
|
+
temp file and compares to the committed source. Returns 0 when every
|
|
161
|
+
file is byte-stable, or a positive count of mismatched files
|
|
162
|
+
otherwise. Intended for CI use via
|
|
163
|
+
``python -m flextool.update_flextool.canonical_databases verify``.
|
|
164
|
+
|
|
165
|
+
Mirrors :func:`flextool.update_flextool.test_fixtures.verify_all`.
|
|
166
|
+
"""
|
|
167
|
+
mismatches = 0
|
|
168
|
+
for db in CANONICAL_DATABASES:
|
|
169
|
+
json_abs = _json_source(db)
|
|
170
|
+
if not json_abs.is_file():
|
|
171
|
+
print(f"!! canonical JSON missing in package: {db.json_name}")
|
|
172
|
+
mismatches += 1
|
|
173
|
+
continue
|
|
174
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
175
|
+
staging = os.path.join(tmp, "staging.sqlite")
|
|
176
|
+
regenerated = Path(tmp) / db.json_name
|
|
177
|
+
initialize_database(str(json_abs), staging)
|
|
178
|
+
migrate_database(staging)
|
|
179
|
+
export_database(staging, str(regenerated))
|
|
180
|
+
with open(json_abs) as f:
|
|
181
|
+
committed_text = f.read()
|
|
182
|
+
with open(regenerated) as f:
|
|
183
|
+
regenerated_text = f.read()
|
|
184
|
+
if committed_text == regenerated_text:
|
|
185
|
+
print(f"ok: {db.json_name}")
|
|
186
|
+
else:
|
|
187
|
+
mismatches += 1
|
|
188
|
+
print(f"DRIFT: {db.json_name}")
|
|
189
|
+
print(_summarise_diff(json_abs, regenerated))
|
|
190
|
+
if mismatches:
|
|
191
|
+
print(
|
|
192
|
+
f"\n{mismatches} canonical JSON(s) out of sync with current schema. "
|
|
193
|
+
"Run `python -m flextool.update_flextool.canonical_databases migrate-all`."
|
|
194
|
+
)
|
|
195
|
+
return mismatches
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _seed_from_cwd_sqlites() -> None:
|
|
199
|
+
"""One-time: populate JSONs from the SQLites currently in the tree.
|
|
200
|
+
|
|
201
|
+
Reads each ``CanonicalDatabase.sqlite_path`` (CWD-relative) and
|
|
202
|
+
exports it back over the package-bundled JSON source. Only useful
|
|
203
|
+
in a source checkout where the JSON files live on disk and can be
|
|
204
|
+
written.
|
|
205
|
+
"""
|
|
206
|
+
for db in CANONICAL_DATABASES:
|
|
207
|
+
sqlite_abs = Path.cwd() / db.sqlite_path
|
|
208
|
+
json_abs = _json_source(db)
|
|
209
|
+
if not sqlite_abs.exists():
|
|
210
|
+
print(f"!! cannot seed {db.json_name}: source SQLite missing at {db.sqlite_path}")
|
|
211
|
+
continue
|
|
212
|
+
json_abs.parent.mkdir(parents=True, exist_ok=True)
|
|
213
|
+
export_database(str(sqlite_abs), str(json_abs))
|
|
214
|
+
print(f"seeded {db.json_name} from {db.sqlite_path}")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def main(argv: list[str] | None = None) -> int:
|
|
218
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
219
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
220
|
+
sub.add_parser("materialize", help="Create missing .sqlite files from JSON")
|
|
221
|
+
sub.add_parser(
|
|
222
|
+
"materialize-force", help="Recreate every canonical .sqlite from JSON"
|
|
223
|
+
)
|
|
224
|
+
sub.add_parser(
|
|
225
|
+
"migrate-all", help="Round-trip every JSON through the current schema"
|
|
226
|
+
)
|
|
227
|
+
sub.add_parser(
|
|
228
|
+
"verify",
|
|
229
|
+
help="Check every canonical JSON is byte-stable through the current schema",
|
|
230
|
+
)
|
|
231
|
+
sub.add_parser(
|
|
232
|
+
"seed",
|
|
233
|
+
help="One-time: export every CWD .sqlite to its canonical JSON",
|
|
234
|
+
)
|
|
235
|
+
args = parser.parse_args(argv)
|
|
236
|
+
|
|
237
|
+
if args.cmd == "materialize":
|
|
238
|
+
materialize(overwrite=False)
|
|
239
|
+
elif args.cmd == "materialize-force":
|
|
240
|
+
materialize(overwrite=True)
|
|
241
|
+
elif args.cmd == "migrate-all":
|
|
242
|
+
migrate_all()
|
|
243
|
+
elif args.cmd == "verify":
|
|
244
|
+
return verify_all()
|
|
245
|
+
elif args.cmd == "seed":
|
|
246
|
+
_seed_from_cwd_sqlites()
|
|
247
|
+
return 0
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
if __name__ == "__main__":
|
|
251
|
+
raise SystemExit(main())
|