diff --git a/CHANGELOG.md b/CHANGELOG.md index 68aaa8a60..2ad19925a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,8 @@ If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOp ### ✨ Added +- Added proper deprecation tests + ### 💥 Breaking Changes ### ♻️ Changed @@ -67,6 +69,8 @@ If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOp ### 🐛 Fixed +- Fixed Deprecation warnings to specify the version of removal. + ### 🔒 Security ### 📦 Dependencies diff --git a/flixopt/calculation.py b/flixopt/calculation.py index ee6742c22..640d4c181 100644 --- a/flixopt/calculation.py +++ b/flixopt/calculation.py @@ -25,7 +25,7 @@ from . import io as fx_io from .aggregation import Aggregation, AggregationModel, AggregationParameters from .components import Storage -from .config import CONFIG +from .config import CONFIG, DEPRECATION_REMOVAL_VERSION from .core import DataConverter, TimeSeriesData, drop_constant_arrays from .features import InvestmentModel from .flow_system import FlowSystem @@ -79,7 +79,7 @@ def __init__( warnings.warn( "The 'active_timesteps' parameter is deprecated and will be removed in a future version. " 'Use flow_system.sel(time=timesteps) or flow_system.isel(time=indices) before passing ' - 'the FlowSystem to the Calculation instead.', + f'the FlowSystem to the Calculation instead. Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -162,7 +162,8 @@ def summary(self): @property def active_timesteps(self) -> pd.DatetimeIndex: warnings.warn( - 'active_timesteps is deprecated. Use flow_system.sel(time=...) or flow_system.isel(time=...) instead.', + f'active_timesteps is deprecated. Use flow_system.sel(time=...) or flow_system.isel(time=...) instead. ' + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) diff --git a/flixopt/effects.py b/flixopt/effects.py index 43afcd0cf..b4c9921dc 100644 --- a/flixopt/effects.py +++ b/flixopt/effects.py @@ -16,6 +16,7 @@ import numpy as np import xarray as xr +from .config import DEPRECATION_REMOVAL_VERSION from .core import PlausibilityError from .features import ShareAllocationModel from .structure import Element, ElementContainer, ElementModel, FlowSystemModel, Submodel, register_class_for_io @@ -252,7 +253,8 @@ def __init__( def minimum_operation(self): """DEPRECATED: Use 'minimum_temporal' property instead.""" warnings.warn( - "Property 'minimum_operation' is deprecated. Use 'minimum_temporal' instead.", + f"Property 'minimum_operation' is deprecated. Use 'minimum_temporal' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -262,7 +264,8 @@ def minimum_operation(self): def minimum_operation(self, value): """DEPRECATED: Use 'minimum_temporal' property instead.""" warnings.warn( - "Property 'minimum_operation' is deprecated. Use 'minimum_temporal' instead.", + f"Property 'minimum_operation' is deprecated. Use 'minimum_temporal' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -272,7 +275,8 @@ def minimum_operation(self, value): def maximum_operation(self): """DEPRECATED: Use 'maximum_temporal' property instead.""" warnings.warn( - "Property 'maximum_operation' is deprecated. Use 'maximum_temporal' instead.", + f"Property 'maximum_operation' is deprecated. Use 'maximum_temporal' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -282,7 +286,8 @@ def maximum_operation(self): def maximum_operation(self, value): """DEPRECATED: Use 'maximum_temporal' property instead.""" warnings.warn( - "Property 'maximum_operation' is deprecated. Use 'maximum_temporal' instead.", + f"Property 'maximum_operation' is deprecated. Use 'maximum_temporal' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -292,7 +297,8 @@ def maximum_operation(self, value): def minimum_invest(self): """DEPRECATED: Use 'minimum_periodic' property instead.""" warnings.warn( - "Property 'minimum_invest' is deprecated. Use 'minimum_periodic' instead.", + f"Property 'minimum_invest' is deprecated. Use 'minimum_periodic' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -302,7 +308,8 @@ def minimum_invest(self): def minimum_invest(self, value): """DEPRECATED: Use 'minimum_periodic' property instead.""" warnings.warn( - "Property 'minimum_invest' is deprecated. Use 'minimum_periodic' instead.", + f"Property 'minimum_invest' is deprecated. Use 'minimum_periodic' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -312,7 +319,8 @@ def minimum_invest(self, value): def maximum_invest(self): """DEPRECATED: Use 'maximum_periodic' property instead.""" warnings.warn( - "Property 'maximum_invest' is deprecated. Use 'maximum_periodic' instead.", + f"Property 'maximum_invest' is deprecated. Use 'maximum_periodic' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -322,7 +330,8 @@ def maximum_invest(self): def maximum_invest(self, value): """DEPRECATED: Use 'maximum_periodic' property instead.""" warnings.warn( - "Property 'maximum_invest' is deprecated. Use 'maximum_periodic' instead.", + f"Property 'maximum_invest' is deprecated. Use 'maximum_periodic' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -332,7 +341,8 @@ def maximum_invest(self, value): def minimum_operation_per_hour(self): """DEPRECATED: Use 'minimum_per_hour' property instead.""" warnings.warn( - "Property 'minimum_operation_per_hour' is deprecated. Use 'minimum_per_hour' instead.", + f"Property 'minimum_operation_per_hour' is deprecated. Use 'minimum_per_hour' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -342,7 +352,8 @@ def minimum_operation_per_hour(self): def minimum_operation_per_hour(self, value): """DEPRECATED: Use 'minimum_per_hour' property instead.""" warnings.warn( - "Property 'minimum_operation_per_hour' is deprecated. Use 'minimum_per_hour' instead.", + f"Property 'minimum_operation_per_hour' is deprecated. Use 'minimum_per_hour' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -352,7 +363,8 @@ def minimum_operation_per_hour(self, value): def maximum_operation_per_hour(self): """DEPRECATED: Use 'maximum_per_hour' property instead.""" warnings.warn( - "Property 'maximum_operation_per_hour' is deprecated. Use 'maximum_per_hour' instead.", + f"Property 'maximum_operation_per_hour' is deprecated. Use 'maximum_per_hour' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -362,7 +374,8 @@ def maximum_operation_per_hour(self): def maximum_operation_per_hour(self, value): """DEPRECATED: Use 'maximum_per_hour' property instead.""" warnings.warn( - "Property 'maximum_operation_per_hour' is deprecated. Use 'maximum_per_hour' instead.", + f"Property 'maximum_operation_per_hour' is deprecated. Use 'maximum_per_hour' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -372,7 +385,8 @@ def maximum_operation_per_hour(self, value): def minimum_total_per_period(self): """DEPRECATED: Use 'minimum_total' property instead.""" warnings.warn( - "Property 'minimum_total_per_period' is deprecated. Use 'minimum_total' instead.", + f"Property 'minimum_total_per_period' is deprecated. Use 'minimum_total' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -382,7 +396,8 @@ def minimum_total_per_period(self): def minimum_total_per_period(self, value): """DEPRECATED: Use 'minimum_total' property instead.""" warnings.warn( - "Property 'minimum_total_per_period' is deprecated. Use 'minimum_total' instead.", + f"Property 'minimum_total_per_period' is deprecated. Use 'minimum_total' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -392,7 +407,8 @@ def minimum_total_per_period(self, value): def maximum_total_per_period(self): """DEPRECATED: Use 'maximum_total' property instead.""" warnings.warn( - "Property 'maximum_total_per_period' is deprecated. Use 'maximum_total' instead.", + f"Property 'maximum_total_per_period' is deprecated. Use 'maximum_total' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -402,7 +418,8 @@ def maximum_total_per_period(self): def maximum_total_per_period(self, value): """DEPRECATED: Use 'maximum_total' property instead.""" warnings.warn( - "Property 'maximum_total_per_period' is deprecated. Use 'maximum_total' instead.", + f"Property 'maximum_total_per_period' is deprecated. Use 'maximum_total' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -617,7 +634,8 @@ def get_effect_label(eff: Effect | str) -> str: if isinstance(eff, Effect): warnings.warn( f'The use of effect objects when specifying EffectValues is deprecated. ' - f'Use the label of the effect instead. Used effect: {eff.label_full}', + f'Use the label of the effect instead. Used effect: {eff.label_full}. ' + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', UserWarning, stacklevel=2, ) diff --git a/flixopt/elements.py b/flixopt/elements.py index 611b0bd9f..debd15965 100644 --- a/flixopt/elements.py +++ b/flixopt/elements.py @@ -516,7 +516,8 @@ def __init__( self.bus = bus.label_full warnings.warn( f'Bus {bus.label} is passed as a Bus object to {self.label}. This is deprecated and will be removed ' - f'in the future. Add the Bus to the FlowSystem instead and pass its label to the Flow.', + f'in the future. Add the Bus to the FlowSystem instead and pass its label to the Flow. ' + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', UserWarning, stacklevel=1, ) diff --git a/flixopt/flow_system.py b/flixopt/flow_system.py index 63bb7b16d..8fc2b76be 100644 --- a/flixopt/flow_system.py +++ b/flixopt/flow_system.py @@ -15,7 +15,7 @@ import xarray as xr from . import io as fx_io -from .config import CONFIG +from .config import CONFIG, DEPRECATION_REMOVAL_VERSION from .core import ( ConversionError, DataConverter, @@ -995,7 +995,8 @@ def _connect_network(self): warnings.warn( f'The Bus {flow._bus_object.label_full} was added to the FlowSystem from {flow.label_full}.' f'This is deprecated and will be removed in the future. ' - f'Please pass the Bus.label to the Flow and the Bus to the FlowSystem instead.', + f'Please pass the Bus.label to the Flow and the Bus to the FlowSystem instead. ' + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=1, ) @@ -1109,7 +1110,7 @@ def all_elements(self) -> dict[str, Element]: "The 'all_elements' property is deprecated. Use dict-like interface instead: " "flow_system['element'], 'element' in flow_system, flow_system.keys(), " 'flow_system.values(), or flow_system.items(). ' - 'This property will be removed in v4.0.0.', + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -1165,7 +1166,8 @@ def scenario_weights(self, value: Numeric_S | None) -> None: @property def weights(self) -> Numeric_S | None: warnings.warn( - 'FlowSystem.weights is deprecated. Use FlowSystem.scenario_weights instead.', + f'FlowSystem.weights is deprecated. Use FlowSystem.scenario_weights instead. ' + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -1180,7 +1182,8 @@ def weights(self, value: Numeric_S) -> None: value: Scenario weights to set """ warnings.warn( - 'Setting FlowSystem.weights is deprecated. Set FlowSystem.scenario_weights instead.', + f'Setting FlowSystem.weights is deprecated. Set FlowSystem.scenario_weights instead. ' + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) diff --git a/flixopt/results.py b/flixopt/results.py index ccc36952f..d2479093d 100644 --- a/flixopt/results.py +++ b/flixopt/results.py @@ -15,7 +15,7 @@ from . import io as fx_io from . import plotting from .color_processing import process_colors -from .config import CONFIG +from .config import CONFIG, DEPRECATION_REMOVAL_VERSION from .flow_system import FlowSystem from .structure import CompositeContainerMixin, ElementContainer, ResultsContainer @@ -229,7 +229,8 @@ def __init__( flow_system_data = kwargs.pop('flow_system') warnings.warn( "The 'flow_system' parameter is deprecated. Use 'flow_system_data' instead. " - "Access is now via '.flow_system_data', while '.flow_system' returns the restored FlowSystem.", + "Access is now via '.flow_system_data', while '.flow_system' returns the restored FlowSystem. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -1319,10 +1320,9 @@ def plot_node_balance( "Cannot use both deprecated parameter 'indexer' and new parameter 'select'. Use only 'select'." ) - import warnings - warnings.warn( - "The 'indexer' parameter is deprecated and will be removed in a future version. Use 'select' instead.", + f"The 'indexer' parameter is deprecated and will be removed in a future version. Use 'select' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -1454,10 +1454,9 @@ def plot_node_balance_pie( "Cannot use both deprecated parameter 'indexer' and new parameter 'select'. Use only 'select'." ) - import warnings - warnings.warn( - "The 'indexer' parameter is deprecated and will be removed in a future version. Use 'select' instead.", + f"The 'indexer' parameter is deprecated and will be removed in a future version. Use 'select' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -1594,10 +1593,9 @@ def node_balance( "Cannot use both deprecated parameter 'indexer' and new parameter 'select'. Use only 'select'." ) - import warnings - warnings.warn( - "The 'indexer' parameter is deprecated and will be removed in a future version. Use 'select' instead.", + f"The 'indexer' parameter is deprecated and will be removed in a future version. Use 'select' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -1741,10 +1739,9 @@ def plot_charge_state( "Cannot use both deprecated parameter 'indexer' and new parameter 'select'. Use only 'select'." ) - import warnings - warnings.warn( - "The 'indexer' parameter is deprecated and will be removed in a future version. Use 'select' instead.", + f"The 'indexer' parameter is deprecated and will be removed in a future version. Use 'select' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -2258,11 +2255,10 @@ def plot_heatmap( "and new parameter 'reshape_time'. Use only 'reshape_time'." ) - import warnings - warnings.warn( "The 'heatmap_timeframes' and 'heatmap_timesteps_per_frame' parameters are deprecated. " - "Use 'reshape_time=(timeframes, timesteps_per_frame)' instead.", + f"Use 'reshape_time=(timeframes, timesteps_per_frame)' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -2277,10 +2273,9 @@ def plot_heatmap( "Cannot use both deprecated parameter 'color_map' and new parameter 'colors'. Use only 'colors'." ) - import warnings - warnings.warn( - "The 'color_map' parameter is deprecated. Use 'colors' instead.", + f"The 'color_map' parameter is deprecated. Use 'colors' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -2405,11 +2400,10 @@ def plot_heatmap( "and new parameter 'reshape_time'. Use only 'reshape_time'." ) - import warnings - warnings.warn( "The 'heatmap_timeframes' and 'heatmap_timesteps_per_frame' parameters are deprecated. " - "Use 'reshape_time=(timeframes, timesteps_per_frame)' instead.", + "Use 'reshape_time=(timeframes, timesteps_per_frame)' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -2424,10 +2418,9 @@ def plot_heatmap( "Cannot use both deprecated parameter 'color_map' and new parameter 'colors'. Use only 'colors'." ) - import warnings - warnings.warn( - "The 'color_map' parameter is deprecated. Use 'colors' instead.", + f"The 'color_map' parameter is deprecated. Use 'colors' instead." + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) @@ -2441,10 +2434,9 @@ def plot_heatmap( "Cannot use both deprecated parameter 'indexer' and new parameter 'select'. Use only 'select'." ) - import warnings - warnings.warn( - "The 'indexer' parameter is deprecated. Use 'select' instead.", + f"The 'indexer' parameter is deprecated. Use 'select' instead. " + f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}.', DeprecationWarning, stacklevel=2, ) diff --git a/test_deprecations.py b/tests/test_deprecations.py similarity index 50% rename from test_deprecations.py rename to tests/test_deprecations.py index d530841b2..be758666f 100644 --- a/test_deprecations.py +++ b/tests/test_deprecations.py @@ -2,11 +2,15 @@ import warnings +import numpy as np +import pandas as pd import pytest +import xarray as xr import flixopt as fx -from flixopt.config import DEPRECATION_REMOVAL_VERSION +from flixopt.config import DEPRECATION_REMOVAL_VERSION, change_logging_level from flixopt.linear_converters import CHP, Boiler, HeatPump, HeatPumpWithSource, Power2Heat +from flixopt.results import plot_heatmap # === Parameter deprecations (via _handle_deprecated_kwarg) === @@ -206,6 +210,20 @@ cop=3.0, ), ), + # TimeSeriesData parameters + ("TimeSeriesData 'agg_group'", lambda: fx.TimeSeriesData([1, 2, 3], agg_group=1)), + ("TimeSeriesData 'agg_weight'", lambda: fx.TimeSeriesData([1, 2, 3], agg_weight=2.5)), + # Storage parameter + ( + "Storage 'initial_charge_state=lastValueOfSim'", + lambda: fx.Storage( + 'stor1', + charging=fx.Flow('charge', 'bus', 10), + discharging=fx.Flow('discharge', 'bus', 10), + capacity_in_flow_hours=10, + initial_charge_state='lastValueOfSim', + ), + ), ], ids=lambda x: x if isinstance(x, str) else '', ) @@ -263,6 +281,15 @@ def deprecated_instances(): thermal_flow=fx.Flow('h_hps', 'bus', 30), cop=3.0, ), + 'source': fx.Source('source_prop', outputs=[fx.Flow('out', 'bus', 10)]), + 'sink': fx.Sink('sink_prop', inputs=[fx.Flow('in', 'bus', 10)]), + 'storage': fx.Storage( + 'storage_prop', + charging=fx.Flow('charge', 'bus', 10), + discharging=fx.Flow('discharge', 'bus', 10), + capacity_in_flow_hours=10, + ), + 'effect': fx.Effect('effect_prop', unit='€', description='test'), } @@ -304,6 +331,13 @@ def deprecated_instances(): ('HeatPumpWithSource.P_el', lambda objs: objs['hps'].P_el), ('HeatPumpWithSource.Q_ab', lambda objs: objs['hps'].Q_ab), ('HeatPumpWithSource.Q_th', lambda objs: objs['hps'].Q_th), + # Source properties + ('Source.source', lambda objs: objs['source'].source), + # Sink properties + ('Sink.sink', lambda objs: objs['sink'].sink), + # Effect property getters + ('Effect.minimum_total_per_period (getter)', lambda objs: objs['effect'].minimum_total_per_period), + ('Effect.maximum_total_per_period (getter)', lambda objs: objs['effect'].maximum_total_per_period), ], ids=lambda x: x if isinstance(x, str) else '', ) @@ -316,3 +350,255 @@ def test_property_deprecations(name, accessor, deprecated_instances): assert f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}' in str(w[0].message), ( f'Missing removal version in {name}' ) + + +# === Property setter deprecations === +@pytest.mark.parametrize( + 'name,setter', + [ + # InvestParameters setter + ('InvestParameters.optional (setter)', lambda: setattr(fx.InvestParameters(minimum_size=10), 'optional', True)), + # OnOffParameters setters + ( + 'OnOffParameters.on_hours_total_min (setter)', + lambda: setattr(fx.OnOffParameters(), 'on_hours_total_min', 10), + ), + ( + 'OnOffParameters.on_hours_total_max (setter)', + lambda: setattr(fx.OnOffParameters(), 'on_hours_total_max', 20), + ), + ( + 'OnOffParameters.switch_on_total_max (setter)', + lambda: setattr(fx.OnOffParameters(), 'switch_on_total_max', 5), + ), + # Flow setters + ('Flow.flow_hours_total_min (setter)', lambda: setattr(fx.Flow('f', 'bus', 10), 'flow_hours_total_min', 5)), + ('Flow.flow_hours_total_max (setter)', lambda: setattr(fx.Flow('f', 'bus', 10), 'flow_hours_total_max', 20)), + # Effect setters + ('Effect.minimum_operation (setter)', lambda: setattr(fx.Effect('e', '€', 'test'), 'minimum_operation', 100)), + ('Effect.maximum_operation (setter)', lambda: setattr(fx.Effect('e', '€', 'test'), 'maximum_operation', 200)), + ('Effect.minimum_invest (setter)', lambda: setattr(fx.Effect('e', '€', 'test'), 'minimum_invest', 50)), + ('Effect.maximum_invest (setter)', lambda: setattr(fx.Effect('e', '€', 'test'), 'maximum_invest', 150)), + ( + 'Effect.minimum_operation_per_hour (setter)', + lambda: setattr(fx.Effect('e', '€', 'test'), 'minimum_operation_per_hour', 10), + ), + ( + 'Effect.maximum_operation_per_hour (setter)', + lambda: setattr(fx.Effect('e', '€', 'test'), 'maximum_operation_per_hour', 30), + ), + ( + 'Effect.minimum_total_per_period (setter)', + lambda: setattr(fx.Effect('e', '€', 'test'), 'minimum_total_per_period', 100), + ), + ( + 'Effect.maximum_total_per_period (setter)', + lambda: setattr(fx.Effect('e', '€', 'test'), 'maximum_total_per_period', 200), + ), + ], + ids=lambda x: x if isinstance(x, str) else '', +) +def test_property_setter_deprecations(name, setter): + """Test all property setter deprecations include removal version message.""" + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', DeprecationWarning) + setter() + assert len(w) > 0, f'No warning raised for {name}' + assert f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}' in str(w[0].message), ( + f'Missing removal version in {name}' + ) + + +# === FlowSystem-specific deprecations === +def test_flowsystem_all_elements_property(): + """Test FlowSystem.all_elements property deprecation.""" + fs = fx.FlowSystem(timesteps=pd.date_range('2020-01-01', periods=10, freq='h')) + bus = fx.Bus('bus') + fs.add_elements(bus, fx.Source('s1', outputs=[fx.Flow('out', 'bus', 10)])) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', DeprecationWarning) + _ = fs.all_elements + assert len(w) > 0, 'No warning raised for FlowSystem.all_elements' + assert f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}' in str(w[0].message) + + +def test_flowsystem_weights_getter(): + """Test FlowSystem.weights getter deprecation.""" + fs = fx.FlowSystem( + timesteps=pd.date_range('2020-01-01', periods=10, freq='h'), scenarios=pd.Index(['A', 'B'], name='scenario') + ) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', DeprecationWarning) + _ = fs.weights + assert len(w) > 0, 'No warning raised for FlowSystem.weights getter' + assert f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}' in str(w[0].message) + + +def test_flowsystem_weights_setter(): + """Test FlowSystem.weights setter deprecation.""" + fs = fx.FlowSystem( + timesteps=pd.date_range('2020-01-01', periods=10, freq='h'), + scenarios=pd.Index(['A', 'B', 'C'], name='scenario'), + ) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', DeprecationWarning) + fs.weights = np.array([1, 2, 3]) + assert len(w) > 0, 'No warning raised for FlowSystem.weights setter' + assert f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}' in str(w[0].message) + + +# === Calculation deprecations === +def test_calculation_active_timesteps_parameter(): + """Test Calculation active_timesteps parameter deprecation.""" + fs = fx.FlowSystem(timesteps=pd.date_range('2020-01-01', periods=10, freq='h')) + bus = fx.Bus('bus') + fs.add_elements(bus, fx.Source('s1', outputs=[fx.Flow('out', 'bus', 10)])) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', DeprecationWarning) + _ = fx.calculation.Calculation('test', fs, active_timesteps=pd.date_range('2020-01-01', periods=5, freq='h')) + assert len(w) > 0, 'No warning raised for Calculation active_timesteps parameter' + assert f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}' in str(w[0].message) + + +def test_calculation_active_timesteps_property(): + """Test Calculation.active_timesteps property deprecation.""" + fs = fx.FlowSystem(timesteps=pd.date_range('2020-01-01', periods=10, freq='h')) + bus = fx.Bus('bus') + fs.add_elements(bus, fx.Source('s1', outputs=[fx.Flow('out', 'bus', 10)])) + calc = fx.calculation.Calculation('test', fs) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', DeprecationWarning) + _ = calc.active_timesteps + assert len(w) > 0, 'No warning raised for Calculation.active_timesteps property' + assert f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}' in str(w[0].message) + + +# === Config function deprecations === +def test_change_logging_level_function(): + """Test change_logging_level() function deprecation.""" + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', DeprecationWarning) + change_logging_level('INFO') + assert len(w) > 0, 'No warning raised for change_logging_level()' + assert f'will be removed in version {DEPRECATION_REMOVAL_VERSION}' in str(w[0].message) + + +# === Results-related deprecations === +@pytest.fixture +def simple_results(): + """Create a simple calculation results object for testing.""" + # Create a minimal flow system + fs = fx.FlowSystem(timesteps=pd.date_range('2020-01-01', periods=5, freq='h')) + bus1 = fx.Bus('bus1') + source = fx.Source('source1', outputs=[fx.Flow('out', 'bus1', size=10, effects_per_flow_hour=1)]) + sink = fx.Sink('sink1', inputs=[fx.Flow('in', 'bus1', size=10)]) + fs.add_elements( + bus1, + fx.Effect('costs', '€', 'Costs', is_standard=True, is_objective=True), + source, + sink, + ) + + # Create and solve calculation + calc = fx.FullCalculation('test', fs) + calc.do_modeling() + solver = fx.solvers.HighsSolver(mip_gap=0.01, time_limit_seconds=30) + calc.solve(solver) + + return calc.results + + +def test_results_flow_system_parameter(simple_results): + """Test CalculationResults flow_system parameter deprecation.""" + # Get the flow_system_data from existing results + fs_data = simple_results.flow_system_data + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', DeprecationWarning) + # Create new results with deprecated parameter + from flixopt.results import CalculationResults + + _ = CalculationResults( + solution=simple_results.solution, + flow_system_data=None, # Will be overridden by deprecated parameter + flow_system=fs_data, # deprecated parameter + name=simple_results.name, + summary=simple_results.summary, + folder=None, + ) + assert len(w) > 0, 'No warning raised for flow_system parameter' + assert f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}' in str(w[0].message) + + +def test_results_plot_node_balance_indexer(simple_results): + """Test ComponentResults.plot_node_balance indexer parameter deprecation.""" + # Get actual time values from the results + time_coords = simple_results.solution.coords['time'] + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', DeprecationWarning) + simple_results['source1'].plot_node_balance( + indexer={'time': slice(time_coords[0].values, time_coords[2].values)}, show=False, save=False + ) + assert len(w) > 0, 'No warning raised for plot_node_balance indexer parameter' + assert f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}' in str(w[0].message) + + +def test_plot_heatmap_function_heatmap_params(): + """Test plot_heatmap function heatmap_timeframes/heatmap_timesteps_per_frame parameters.""" + # Create simple test data - 7 days * 24 hours = 168 hours + data = xr.DataArray( + np.random.rand(168), + coords={'time': pd.date_range('2020-01-01', periods=168, freq='h')}, + dims=['time'], + ) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', DeprecationWarning) + plot_heatmap( + data, + name='test', + heatmap_timeframes='D', # Days + heatmap_timesteps_per_frame='h', # Hours + show=False, + save=False, + ) + assert len(w) > 0, 'No warning raised for heatmap_timeframes/heatmap_timesteps_per_frame' + assert f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}' in str(w[0].message) + + +def test_plot_heatmap_function_color_map(): + """Test plot_heatmap function color_map parameter.""" + data = xr.DataArray( + np.random.rand(24), + coords={'time': pd.date_range('2020-01-01', periods=24, freq='h')}, + dims=['time'], + ) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', DeprecationWarning) + plot_heatmap(data, name='test', color_map='viridis', show=False, save=False) + assert len(w) > 0, 'No warning raised for color_map parameter' + assert f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}' in str(w[0].message) + + +def test_plot_heatmap_function_indexer(): + """Test plot_heatmap function indexer parameter.""" + time_index = pd.date_range('2020-01-01', periods=24, freq='h') + data = xr.DataArray( + np.random.rand(24), + coords={'time': time_index}, + dims=['time'], + ) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', DeprecationWarning) + # Use actual datetime values for slicing + plot_heatmap(data, name='test', indexer={'time': slice(time_index[0], time_index[10])}, show=False, save=False) + assert len(w) > 0, 'No warning raised for indexer parameter' + assert f'Will be removed in v{DEPRECATION_REMOVAL_VERSION}' in str(w[0].message)