From 3c4501ce5c47931d35f15854c476b05daa0c2e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Luk=C5=A1i=C4=8D?= <31988337+zigaLuksic@users.noreply.github.com> Date: Fri, 3 Nov 2023 07:49:38 +0100 Subject: [PATCH 1/3] Make sh-config CLI use environmental variable (#487) * Make sh-config CLI use environmental variable * add tests * make save and load use env profile --- sentinelhub/commands.py | 6 +++--- sentinelhub/config.py | 13 +++++++++---- tests/test_config.py | 8 ++++++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/sentinelhub/commands.py b/sentinelhub/commands.py index 519ba5db..89e6e2ad 100644 --- a/sentinelhub/commands.py +++ b/sentinelhub/commands.py @@ -9,7 +9,7 @@ import click -from .config import DEFAULT_PROFILE, SHConfig +from .config import SHConfig from .download import DownloadClient, DownloadRequest FC = TypeVar("FC", bound=Callable[..., Any]) @@ -40,9 +40,9 @@ def _config_options(func: FC) -> FC: @click.command() @click.option("--show", is_flag=True, default=False, help="Show current configuration") -@click.option("--profile", default=DEFAULT_PROFILE, help="Selects profile to show/configure.") +@click.option("--profile", default=None, help="Selects profile to show/configure.") @_config_options -def config(show: bool, profile: str, **params: Any) -> None: +def config(show: bool, profile: str | None, **params: Any) -> None: """Inspect and configure parameters in your local sentinelhub configuration file \b diff --git a/sentinelhub/config.py b/sentinelhub/config.py index a40e4df0..026af574 100644 --- a/sentinelhub/config.py +++ b/sentinelhub/config.py @@ -114,8 +114,7 @@ def __init__(self, profile: str | None = None, *, use_defaults: bool = False, ** :param use_defaults: Does not load the configuration file, returns config object with defaults only. :param kwargs: Any fields of `SHConfig` to be updated. Overrides settings from `config.toml` and environment. """ - if profile is None: - profile = os.environ.get(SH_PROFILE_ENV_VAR, default=DEFAULT_PROFILE) + profile = self._get_profile(profile) if not use_defaults: env_kwargs = { @@ -141,12 +140,17 @@ def __repr__(self) -> str: content = ",\n ".join(f"{key}={value!r}" for key, value in config_dict.items()) return f"{self.__class__.__name__}(\n {content},\n)" + @staticmethod + def _get_profile(profile: str | None) -> str: + return profile if profile is not None else os.environ.get(SH_PROFILE_ENV_VAR, default=DEFAULT_PROFILE) + @classmethod - def load(cls, profile: str = DEFAULT_PROFILE) -> SHConfig: + def load(cls, profile: str | None = None) -> SHConfig: """Loads configuration parameters from the config file at `SHConfig.get_config_location()`. :param profile: Which profile to load from the configuration file. """ + profile = cls._get_profile(profile) filename = cls.get_config_location() if not os.path.exists(filename): cls(use_defaults=True).save(profile) # store default configuration to standard location @@ -159,11 +163,12 @@ def load(cls, profile: str = DEFAULT_PROFILE) -> SHConfig: return cls(use_defaults=True, **configurations_dict[profile]) - def save(self, profile: str = DEFAULT_PROFILE) -> None: + def save(self, profile: str | None = None) -> None: """Saves configuration parameters to the config file at `SHConfig.get_config_location()`. :param profile: Under which profile to save the configuration. """ + profile = self._get_profile(profile) file_path = Path(self.get_config_location()) file_path.parent.mkdir(parents=True, exist_ok=True) diff --git a/tests/test_config.py b/tests/test_config.py index fe4b7d34..669f4d37 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -147,8 +147,16 @@ def test_profiles_from_env(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv(SH_PROFILE_ENV_VAR, "beekeeper") assert SHConfig().instance_id == "bee", "Environment profile is not used." + assert SHConfig.load().instance_id == "bee", "The load method does not respect the environment profile." assert SHConfig(profile=DEFAULT_PROFILE).instance_id == "", "Explicit profile overrides environment." + config = SHConfig() + config.instance_id = "many bee" + config.save() + + assert SHConfig(profile="beekeeper").instance_id == "many bee", "Save method does not respect the env profile." + assert SHConfig(profile=DEFAULT_PROFILE).instance_id == "", "Saving with env profile changed default profile." + def test_loading_unknown_profile_fails() -> None: with pytest.raises(KeyError): From b8c96e959229a16887a333afb0dbde7c59f4c21f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Luk=C5=A1i=C4=8D?= <31988337+zigaLuksic@users.noreply.github.com> Date: Fri, 3 Nov 2023 08:31:03 +0100 Subject: [PATCH 2/3] config no longer falsely loads profiles if file doesn't exist (#486) --- sentinelhub/config.py | 4 ++-- tests/test_config.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/sentinelhub/config.py b/sentinelhub/config.py index 026af574..8b31f3ea 100644 --- a/sentinelhub/config.py +++ b/sentinelhub/config.py @@ -153,13 +153,13 @@ def load(cls, profile: str | None = None) -> SHConfig: profile = cls._get_profile(profile) filename = cls.get_config_location() if not os.path.exists(filename): - cls(use_defaults=True).save(profile) # store default configuration to standard location + cls(use_defaults=True).save() # store default configuration to standard location with open(filename, "rb") as cfg_file: configurations_dict = tomli.load(cfg_file) if profile not in configurations_dict: - raise KeyError(f"Profile {profile} not found in configuration file.") + raise KeyError(f"Profile `{profile}` not found in configuration file.") return cls(use_defaults=True, **configurations_dict[profile]) diff --git a/tests/test_config.py b/tests/test_config.py index 669f4d37..2318dea9 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -134,6 +134,20 @@ def test_profiles() -> None: assert SHConfig(profile="beep").sh_client_id == "bap" +@pytest.mark.dependency(depends=["test_user_config_is_masked"]) +@pytest.mark.usefixtures("_restore_config_file") +def test_initialize_nondefault_profile() -> None: + """Since there is no config, loading a non-default profile should fail.""" + config = SHConfig() + os.remove(config.get_config_location()) + + SHConfig() # works for default + + os.remove(config.get_config_location()) + with pytest.raises(KeyError): + SHConfig("mr_president") + + @pytest.mark.dependency(depends=["test_user_config_is_masked"]) @pytest.mark.usefixtures("_restore_config_file") def test_profiles_from_env(monkeypatch: pytest.MonkeyPatch) -> None: From 7fbdcd7ee65e90075bd0d01a108dd92c84e81a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20Luk=C5=A1i=C4=8D?= <31988337+zigaLuksic@users.noreply.github.com> Date: Fri, 3 Nov 2023 09:38:12 +0100 Subject: [PATCH 3/3] Prepare for release (#488) * update precommit * update version * update changelog --- .pre-commit-config.yaml | 2 +- CHANGELOG.MD | 8 ++++++++ sentinelhub/_version.py | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 567b1655..515843b2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: language_version: python3 - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: "v0.1.1" + rev: "v0.1.3" hooks: - id: ruff diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 1da35a8c..a9c8d18d 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,3 +1,10 @@ +## [Version 3.9.3] - 2023-11-03 + +- `SHConfig` now correctly initializes a default profile in the file even if the first initialization call is done with a custom profile. +- Save and load methods of `SHConfig` adjusted to work with the environmental variable `SH_PROFILE` +- CLI command `sentinelhub.config --show` works with the environmental variable `SH_PROFILE` + + ## [Version 3.9.2] - 2023-10-24 - Adjusted how user credentials are passed to the OAuth service. @@ -5,6 +12,7 @@ - Batch statistical API now supports IAM role style credentials. - Various minor improvements + ## [Version 3.9.1] - 2023-05-04 - The parameter `sh_auth_base_url` has been replaced with `sh_token_url` to allow authentication on endpoints with suffixes other than `oauth/token`. For the new parameter the address must be provided in full, e.g. `https://services.sentinel-hub.com/oauth/token` instead of `https://services.sentinel-hub.com`. The change only affects users who manually adjusted this field. diff --git a/sentinelhub/_version.py b/sentinelhub/_version.py index 1967ddbf..ef2d670b 100644 --- a/sentinelhub/_version.py +++ b/sentinelhub/_version.py @@ -1,3 +1,3 @@ """Version of the sentinelhub package.""" -__version__ = "3.9.2" +__version__ = "3.9.3"