def test_settings_env_variables_do_not_write_to_disk(tmp_path, monkeypatch): # create a settings file with light theme data = "appearance:\n theme: light" fake_path = tmp_path / 'fake_path.yml' fake_path.write_text(data) # make sure they wrote correctly disk_settings = fake_path.read_text() assert 'theme: light' in disk_settings # make sure they load correctly assert NapariSettings(fake_path).appearance.theme == "light" # now load settings again with an Env-var override monkeypatch.setenv('NAPARI_APPEARANCE_THEME', 'dark') settings = NapariSettings(fake_path) # make sure the override worked, and save again assert settings.appearance.theme == 'dark' # data from the config file is still "known" assert settings._config_file_settings['appearance']['theme'] == 'light' # but we know what came from env vars as well: assert settings.env_settings()['appearance']['theme'] == 'dark' # when we save it shouldn't use environment variables and it shouldn't # have overriden our non-default value of `theme: light` settings.save() disk_settings = fake_path.read_text() assert 'theme: light' in disk_settings # and it's back if we reread without the env var override monkeypatch.delenv('NAPARI_APPEARANCE_THEME') assert NapariSettings(fake_path).appearance.theme == "light"
def test_no_save_path(): """trying to save without a config path is an error""" s = NapariSettings(config_path=None) assert s.config_path is None with pytest.raises(ValueError): # the original `save()` method is patched in conftest.fresh_settings # so we "unmock" it here to assert the failure NapariSettings.__original_save__(s) # type: ignore
def test_settings_env_variables(monkeypatch): assert NapariSettings(None).appearance.theme == 'dark' # NOTE: this was previously tested as NAPARI_THEME monkeypatch.setenv('NAPARI_APPEARANCE_THEME', 'light') assert NapariSettings(None).appearance.theme == 'light' # can also use json assert NapariSettings(None).application.first_time is True # NOTE: this was previously tested as NAPARI_THEME monkeypatch.setenv('NAPARI_APPLICATION', '{"first_time": "false"}') assert NapariSettings(None).application.first_time is False
def test_settings_load_invalid_type(tmp_path, caplog): # The invalid data will be replaced by the default value data = "appearance:\n theme: 1" fake_path = tmp_path / 'fake_path.yml' fake_path.write_text(data) assert NapariSettings(fake_path).application.save_window_geometry is True assert 'Validation errors in config file' in str(caplog.records[0])
def test_settings_load_strict(tmp_path, monkeypatch): # use Config.strict_config_check to enforce good config files monkeypatch.setattr(NapariSettings.__config__, 'strict_config_check', True) data = "appearance:\n theme: 1" fake_path = tmp_path / 'fake_path.yml' fake_path.write_text(data) with pytest.raises(pydantic.ValidationError): NapariSettings(fake_path)
def test_settings_to_dict_no_env(monkeypatch): """Test that exclude_env works to exclude variables coming from the env.""" s = NapariSettings(None, appearance={'theme': 'light'}) assert s.dict()['appearance']['theme'] == 'light' assert s.dict(exclude_env=True)['appearance']['theme'] == 'light' monkeypatch.setenv("NAPARI_APPEARANCE_THEME", 'light') s = NapariSettings(None) assert s.dict()['appearance']['theme'] == 'light' assert 'theme' not in s.dict(exclude_env=True).get('appearance', {})
def test_settings_load_invalid_key(tmp_path, monkeypatch): # The invalid key will be removed fake_path = tmp_path / 'fake_path.yml' data = """ application: non_existing_key: [1, 2] first_time: false """ fake_path.write_text(data) monkeypatch.setattr(os, 'environ', {}) s = NapariSettings(fake_path) assert getattr(s, "non_existing_key", None) is None s.save() text = fake_path.read_text() # removed bad key assert safe_load(text) == {'application': {'first_time': False}}
def test_settings_load_invalid_section(tmp_path): # The invalid section will be removed from the file data = "non_existing_section:\n foo: bar" fake_path = tmp_path / 'fake_path.yml' fake_path.write_text(data) settings = NapariSettings(fake_path) assert getattr(settings, "non_existing_section", None) is None
def test_settings_only_saves_non_default_values(monkeypatch, tmp_path): from yaml import safe_load # prevent error during NAPARI_ASYNC tests monkeypatch.setattr(os, 'environ', {}) # manually get all default data and write to yaml file all_data = NapariSettings(None).yaml() fake_path = tmp_path / 'fake_path.yml' assert 'appearance' in all_data assert 'application' in all_data fake_path.write_text(all_data) # load that yaml file and resave NapariSettings(fake_path).save() # make sure that it's now just an empty dict assert not safe_load(fake_path.read_text())
def test_migration_saves(_test_migrator): @_test_migrator('0.1.0', '0.2.0') def _(model: NapariSettings): ... with patch.object(NapariSettings, 'save') as mock: mock.assert_not_called() settings = NapariSettings(config_path='junk', schema_version='0.1.0') assert settings.schema_version == '0.2.0' mock.assert_called()
def test_settings_only_saves_non_default_values(monkeypatch, tmp_path): from yaml import safe_load # prevent error during NAPARI_ASYNC tests monkeypatch.setattr(os, 'environ', {}) # manually get all default data and write to yaml file all_data = NapariSettings(None).yaml() fake_path = tmp_path / 'fake_path.yml' assert 'appearance' in all_data assert 'application' in all_data fake_path.write_text(all_data) # load that yaml file and resave NapariSettings(fake_path).save() # make sure that the only value is now the schema version assert safe_load(fake_path.read_text()) == { 'schema_version': CURRENT_SCHEMA_VERSION }
def test_migration_works(_test_migrator): # test that a basic migrator works to change the version # and mutate the model @_test_migrator('0.1.0', '0.2.0') def _(model: NapariSettings): model.appearance.theme = 'light' settings = NapariSettings(schema_version='0.1.0') assert settings.schema_version == '0.2.0' assert settings.appearance.theme == 'light'
def test_040_to_050_migration(): # Prior to 0.5.0 existing preferences may have reader extensions # preferences saved without a leading *. # fnmatch would fail on these so we coerce them to include a * # e.g. '.csv' becomes '*.csv' settings = NapariSettings( schema_version='0.4.0', plugins={'extension2reader': { '.tif': 'napari' }}, ) assert '.tif' not in settings.plugins.extension2reader assert '*.tif' in settings.plugins.extension2reader
def test_failed_migration_leaves_version(_test_migrator): # if an error occurs IN the migrator, the version should stay # where it was before the migration, and any changes reverted. @_test_migrator('0.1.0', '0.2.0') def _(model: NapariSettings): model.appearance.theme = 'light' assert model.appearance.theme == 'light' raise ValueError('broken migration') with pytest.warns(UserWarning) as e: settings = NapariSettings(schema_version='0.1.0') assert settings.schema_version == '0.1.0' # test migration was atomic, and reverted the theme change assert settings.appearance.theme == 'dark' # test that the user was warned assert 'Failed to migrate settings from v0.1.0 to v0.2.0' in str(e[0])
def test_030_to_040_migration(): # Prior to v0.4.0, npe2 plugins were automatically "disabled" # 0.3.0 -> 0.4.0 should remove any installed npe2 plugins from the # set of disabled plugins (see migrator for details) try: d = distribution('napari-svg') assert 'napari.manifest' in {ep.group for ep in d.entry_points} except Exception: pytest.fail('napari-svg not present as an npe2 plugin. ' 'This test needs updating') settings = NapariSettings( schema_version='0.3.0', plugins={'disabled_plugins': {'napari-svg', 'napari'}}, ) assert 'napari-svg' not in settings.plugins.disabled_plugins assert 'napari' not in settings.plugins.disabled_plugins
def test_settings_env_variables(monkeypatch): assert NapariSettings(None).appearance.theme == 'dark' # NOTE: this was previously tested as NAPARI_THEME monkeypatch.setenv('NAPARI_APPEARANCE_THEME', 'light') assert NapariSettings(None).appearance.theme == 'light' # can also use json assert NapariSettings(None).application.first_time is True # NOTE: this was previously tested as NAPARI_THEME monkeypatch.setenv('NAPARI_APPLICATION', '{"first_time": "false"}') assert NapariSettings(None).application.first_time is False # can also use json in nested vars assert NapariSettings(None).plugins.extension2reader == {} monkeypatch.setenv('NAPARI_PLUGINS_EXTENSION2READER', '{"*.zarr": "hi"}') assert NapariSettings(None).plugins.extension2reader == {"*.zarr": "hi"}
def _mock_save(self, path=None, **dict_kwargs): if not (path or self.config_path): return NapariSettings.__original_save__(self, path, **dict_kwargs)
def test_settings_env_variables_fails(monkeypatch): monkeypatch.setenv('NAPARI_APPEARANCE_THEME', 'FOOBAR') with pytest.raises(pydantic.ValidationError): NapariSettings()
def test_settings_load_invalid_content(tmp_path): # This is invalid content fake_path = tmp_path / 'fake_path.yml' fake_path.write_text(":") NapariSettings(fake_path)
def test_settings_loads(tmp_path): data = "appearance:\n theme: light" fake_path = tmp_path / 'fake_path.yml' fake_path.write_text(data) assert NapariSettings(fake_path).appearance.theme == "light"
def test_full_serialize(test_settings: NapariSettings, tmp_path, ext): """Make sure that every object in the settings is serializeable. Should work with both json and yaml. """ test_settings.save(tmp_path / f't.{ext}', exclude_defaults=False)
def test_no_migrations_available(_test_migrator): # no migrators exist... nothing should happen settings = NapariSettings(schema_version='0.1.0') assert settings.schema_version == '0.1.0'
def test_first_time(): """This test just confirms that we don't load an existing file (locally)""" assert NapariSettings().application.first_time is True