diff --git a/scopesim/commands/user_commands.py b/scopesim/commands/user_commands.py index ea32c0f4..eaba04e0 100644 --- a/scopesim/commands/user_commands.py +++ b/scopesim/commands/user_commands.py @@ -169,6 +169,7 @@ def __init__(self, *maps, **kwargs): self._kwargs = deepcopy(kwargs) self.ignore_effects = [] self.package_name = "" + self.package_status = "" self.default_yamls = [] self.modes_dict = {} @@ -201,6 +202,25 @@ def _load_yaml_dict(self, yaml_dict): if "mode_yamls" in yaml_dict: logger.debug(" found mode_yamls") self.update(mode_yamls=yaml_dict["mode_yamls"]) + + if yaml_dict.get("object", "") == "configuration": + # we're in default.yaml + self.package_status = yaml_dict.get("status", "unknown") + if self.package_status == "deprecated": + warn("The selected instrument package is deprecated!", + DeprecationWarning, stacklevel=7) + if self.package_status == "concept": + raise NotImplementedError( + "The selected instrument package is not yet supported." + ) + if self.package_status == "experimental": + # or rather warning? + logger.info( + "The selected instrument package is still in experimental " + "stage, results may not be representative of physical " + "instrument, use with care." + ) + logger.debug(" dict yaml done") def _load_yamls(self, yamls: Collection) -> None: @@ -351,8 +371,24 @@ def set_modes(self, *modes) -> None: raise ValueError(f"mode '{mode}' was not recognised") defyam["properties"]["modes"].append(mode) - if depmsg := self.modes_dict[mode].get("deprecate"): - warn(depmsg, DeprecationWarning, stacklevel=2) + if ((msg := self.modes_dict[mode].get("deprecate", "")) or + self.modes_dict[mode].get("status") == "deprecated"): + # Fallback if status: deprecated but not deprecate key + msg = msg or f"Instrument mode '{mode}' is deprecated." + warn(msg, DeprecationWarning, stacklevel=2) + + if self.modes_dict[mode].get("status") == "experimental": + # or rather warning? + logger.info( + "Mode '%s' is still in experimental stage, results " + "may not be representative of physical instrument.", + mode + ) + + if self.modes_dict[mode].get("status") == "concept": + raise NotImplementedError( + f"Instrument mode '{mode}' is not yet supported." + ) # Note: This used to completely reset the instance via the line below. # Calling init like this is bad design, so I replaced is with a @@ -371,8 +407,15 @@ def list_modes(self) -> Iterable[tuple[str, ...]]: cases, it now returns a generator of tuples of strings. """ for mode, subdict in self.modes_dict.items(): - desc = (subdict.get("description", "") + - ":DEPRECATED" * ("deprecate" in subdict)) + # TODO: maybe find a prettier way to print the status... + # TODO: maybe also print mode type (e.g. MICADO SCAO) + desc = ( + subdict.get("description", "") + + f":status={subdict.get('status')}" * ("status" in subdict) + + ":DEPRECATED" * ( + "deprecate" in subdict and "status" not in subdict + ) + ) yield mode, *(s.strip() for s in desc.split(":")) @property diff --git a/scopesim/tests/mocks/basic_instrument/default.yaml b/scopesim/tests/mocks/basic_instrument/default.yaml index fae338b5..1f270daa 100644 --- a/scopesim/tests/mocks/basic_instrument/default.yaml +++ b/scopesim/tests/mocks/basic_instrument/default.yaml @@ -1,8 +1,9 @@ # Default config file for setting up the instrument -object : observation +object: configuration alias : OBS name : test_instrument description : default parameters needed for a basic simulation +status: development packages : - basic_instrument # name of package dependencies @@ -24,6 +25,7 @@ properties : mode_yamls : - name : imaging description: Basic NIR imager + status: development alias : OBS properties : include_slit : False @@ -32,6 +34,7 @@ mode_yamls : - name : spectroscopy description: Basic three-trace long-slit spectrograph + status: development alias: OBS properties : include_slit : True @@ -42,6 +45,7 @@ mode_yamls : - name : ifu description: Basic three-trace integral-field-unit spectrograph + status: development alias: OBS properties: include_slit: False @@ -49,3 +53,20 @@ mode_yamls : filter_name: "open" yamls: - YAML_mode_ifu.yaml # mode specific effects and properties + +- name: mock_concept_mode + description: Dummy mode to test concept status. + status: concept + +- name: mock_experimental_mode + description: Dummy mode to test experimental status. + status: experimental + +- name: mock_deprecated_mode + description: Dummy mode to test deprecated status without message. + status: deprecated + +- name: mock_deprecated_mode_msg + description: Dummy mode to test deprecated status with message. + status: deprecated + deprecate: This mode is deprecated. diff --git a/scopesim/tests/test_basic_instrument/test_basic_instrument.py b/scopesim/tests/test_basic_instrument/test_basic_instrument.py index 18391bdc..4f305479 100644 --- a/scopesim/tests/test_basic_instrument/test_basic_instrument.py +++ b/scopesim/tests/test_basic_instrument/test_basic_instrument.py @@ -1,4 +1,4 @@ - +# -*- coding: utf-8 -*- import pytest import numpy as np @@ -116,6 +116,7 @@ def test_runs(self): trace_flux = det_im[:, sl].sum() # sum along a trace assert round(trace_flux / spot_flux) == n + @pytest.mark.usefixtures("protect_currsys", "patch_all_mock_paths") class TestSourceImageNotAffected: """ @@ -228,6 +229,57 @@ def test_source_keywords_in_header(self): assert hdr["ESO ATM SEEING"] == opt.cmds["!OBS.psf_fwhm"] +@pytest.mark.usefixtures("protect_currsys", "patch_all_mock_paths") +class TestModeStatus: + def test_concept_mode_init(self): + with pytest.raises(NotImplementedError): + _ = sim.UserCommands(use_instrument="basic_instrument", + set_modes=["mock_concept_mode"]) + + def test_concept_mode_change(self): + cmd = sim.UserCommands(use_instrument="basic_instrument") + with pytest.raises(NotImplementedError): + cmd.set_modes("mock_concept_mode") + + def test_experimental_mode_init(self, caplog): + _ = sim.UserCommands(use_instrument="basic_instrument", + set_modes=["mock_experimental_mode"]) + assert ("Mode 'mock_experimental_mode' is still in experimental stage" + in caplog.text) + + def test_experimental_mode_change(self, caplog): + cmd = sim.UserCommands(use_instrument="basic_instrument") + cmd.set_modes("mock_experimental_mode") + assert ("Mode 'mock_experimental_mode' is still in experimental stage" + in caplog.text) + + def test_deprecated_mode_init(self): + with pytest.raises( + DeprecationWarning, + match="Instrument mode 'mock_deprecated_mode' is deprecated."): + _ = sim.UserCommands(use_instrument="basic_instrument", + set_modes=["mock_deprecated_mode"]) + + def test_deprecated_mode_change(self): + cmd = sim.UserCommands(use_instrument="basic_instrument") + with pytest.raises( + DeprecationWarning, + match="Instrument mode 'mock_deprecated_mode' is deprecated."): + cmd.set_modes("mock_deprecated_mode") + + def test_deprecated_msg_mode_init(self): + with pytest.raises( + DeprecationWarning, match="This mode is deprecated."): + _ = sim.UserCommands(use_instrument="basic_instrument", + set_modes=["mock_deprecated_mode_msg"]) + + def test_deprecated_msg_mode_change(self, caplog): + cmd = sim.UserCommands(use_instrument="basic_instrument") + with pytest.raises( + DeprecationWarning, match="This mode is deprecated."): + cmd.set_modes("mock_deprecated_mode_msg") + + @pytest.fixture(scope="function", name="obs") def basic_opt_observed(): src = st.star(flux=15)