From 04719685e44b2b99c4539c34f2c4247bad18aeb9 Mon Sep 17 00:00:00 2001 From: Jiri Suchan Date: Wed, 22 Nov 2023 17:56:15 +0700 Subject: [PATCH] add backward-compatible lookup for pre-existing config file --- b2sdk/account_info/sqlite_account_info.py | 38 +++++++++++++------ changelog.d/b2cli958.changed.md | 2 +- test/unit/account_info/test_account_info.py | 26 ++++++++++--- .../account_info/test_sqlite_account_info.py | 18 +++++++-- 4 files changed, 63 insertions(+), 21 deletions(-) diff --git a/b2sdk/account_info/sqlite_account_info.py b/b2sdk/account_info/sqlite_account_info.py index b996d2773..d997a94bb 100644 --- a/b2sdk/account_info/sqlite_account_info.py +++ b/b2sdk/account_info/sqlite_account_info.py @@ -54,15 +54,16 @@ def __init__(self, file_name=None, last_upgrade_to_run=None, profile: str | None If ``profile`` arg is provided: - * ``${XDG_CONFIG_HOME_ENV_VAR}/b2/db-.sqlite`` on XDG-compatible OSes (Linux) + * ``{B2_ACCOUNT_INFO_PROFILE_FILE}`` if it already exists + * ``${XDG_CONFIG_HOME_ENV_VAR}/b2/db-.sqlite`` on XDG-compatible OSes (Linux, BSD) * ``{B2_ACCOUNT_INFO_PROFILE_FILE}`` Otherwise: * ``file_name``, if truthy * ``{B2_ACCOUNT_INFO_ENV_VAR}`` env var's value, if set - * ``{B2_ACCOUNT_INFO_DEFAULT_FILE}``, if it exists - * ``${XDG_CONFIG_HOME_ENV_VAR}/b2/account_info`` on XDG-compatible OSes (Linux) + * ``{B2_ACCOUNT_INFO_DEFAULT_FILE}``, if it already exists + * ``${XDG_CONFIG_HOME_ENV_VAR}/b2/account_info`` on XDG-compatible OSes (Linux, BSD) * ``{B2_ACCOUNT_INFO_DEFAULT_FILE}``, as default If the directory ``${XDG_CONFIG_HOME_ENV_VAR}/b2`` does not exist (and is needed), it is created. @@ -91,12 +92,26 @@ def __init__(self, file_name=None, last_upgrade_to_run=None, profile: str | None ) ) + @classmethod + def get_xdg_config_path(cls, home_dir: str = '~') -> str | None: + """ + Return XDG config path if the OS is XDG-compatible (Linux, BSD), None otherwise. + + If $XDG_CONFIG_HOME is empty but the OS is XDG compliant, fallback to ~/.config as expected by XDG standard. + """ + xdg_config_home = os.getenv(XDG_CONFIG_HOME_ENV_VAR) + if xdg_config_home or sys.platform.startswith(('linux', 'freebsd', 'openbsd', 'netbsd')): + config_path = xdg_config_home or os.path.join(home_dir, '.config') + return os.path.expanduser(config_path) + return None + @classmethod def _get_user_account_info_path(cls, file_name: str | None = None, profile: str | None = None): if profile and not B2_ACCOUNT_INFO_PROFILE_NAME_REGEXP.match(profile): raise ValueError(f'Invalid profile name: {profile}') - xdg_compatible_os = sys.platform in ('linux',) or 'bsd' in sys.platform + profile_file = B2_ACCOUNT_INFO_PROFILE_FILE.format(profile=profile) if profile else None + xdg_config_path = cls.get_xdg_config_path() if file_name: if profile: @@ -109,16 +124,17 @@ def _get_user_account_info_path(cls, file_name: str | None = None, profile: str format(B2_ACCOUNT_INFO_ENV_VAR) ) user_account_info_path = os.environ[B2_ACCOUNT_INFO_ENV_VAR] - elif os.path.exists(os.path.expanduser(B2_ACCOUNT_INFO_DEFAULT_FILE)) and not profile: + elif not profile and os.path.exists(os.path.expanduser(B2_ACCOUNT_INFO_DEFAULT_FILE)): user_account_info_path = B2_ACCOUNT_INFO_DEFAULT_FILE - elif xdg_compatible_os: - # if following env. variable not present, fallback to ~/.config as expected by XDG standard - config_home = os.getenv(XDG_CONFIG_HOME_ENV_VAR) or os.path.expanduser('~/.config') + elif profile and os.path.exists(profile_file): + user_account_info_path = profile_file + elif xdg_config_path: + os.makedirs(os.path.join(xdg_config_path, 'b2'), mode=0o755, exist_ok=True) + file_name = f'db-{profile}.sqlite' if profile else 'account_info' - user_account_info_path = os.path.join(config_home, 'b2', file_name) - os.makedirs(os.path.join(config_home, 'b2'), mode=0o755, exist_ok=True) + user_account_info_path = os.path.join(xdg_config_path, 'b2', file_name) elif profile: - user_account_info_path = B2_ACCOUNT_INFO_PROFILE_FILE.format(profile=profile) + user_account_info_path = profile_file else: user_account_info_path = B2_ACCOUNT_INFO_DEFAULT_FILE diff --git a/changelog.d/b2cli958.changed.md b/changelog.d/b2cli958.changed.md index 62de63eaf..804d8a35a 100644 --- a/changelog.d/b2cli958.changed.md +++ b/changelog.d/b2cli958.changed.md @@ -1 +1 @@ -Breaking change: on XDG compatible OSes, the config file lookup path has now changed to `$XDG_CONFIG_HOME` (with fallback to `~/.config/` in absence of given env. variable). +On XDG compatible OSes (Linux, BSD), the profile file is now created in `$XDG_CONFIG_HOME` (with a fallback to `~/.config/` in absence of given env. variable). diff --git a/test/unit/account_info/test_account_info.py b/test/unit/account_info/test_account_info.py index e67893344..ae05c34ef 100644 --- a/test/unit/account_info/test_account_info.py +++ b/test/unit/account_info/test_account_info.py @@ -415,29 +415,43 @@ def _make_sqlite_account_info(self, env=None, last_upgrade_to_run=None): ) def test_uses_default(self): + xdg_config_path = SqliteAccountInfo.get_xdg_config_path(self.test_home) + account_info = self._make_sqlite_account_info( env={ - 'HOME': self.test_home, - 'USERPROFILE': self.test_home, + 'HOME': + self.test_home, + 'USERPROFILE': + self.test_home, + XDG_CONFIG_HOME_ENV_VAR: + bool(xdg_config_path) and os.path.join(self.test_home, '.config'), } ) - actual_path = os.path.abspath(account_info.filename) - assert os.path.join(self.test_home, '.b2_account_info') == actual_path + + if xdg_config_path: + expected_path = os.path.join(xdg_config_path, 'b2', 'account_info') + else: + expected_path = os.path.join(self.test_home, '.b2_account_info') + assert expected_path == os.path.abspath(account_info.filename) def test_uses_xdg_config_home(self, apiver): + is_xdg_os = bool(SqliteAccountInfo.get_xdg_config_path()) with WindowsSafeTempDir() as d: account_info = self._make_sqlite_account_info( env={ 'HOME': self.test_home, 'USERPROFILE': self.test_home, - XDG_CONFIG_HOME_ENV_VAR: d, + # pass the env. variable on XDG-like OS only + XDG_CONFIG_HOME_ENV_VAR: is_xdg_os and d, } ) if apiver in ['v0', 'v1']: expected_path = os.path.abspath(os.path.join(self.test_home, '.b2_account_info')) - else: + elif is_xdg_os: assert os.path.exists(os.path.join(d, 'b2')) expected_path = os.path.abspath(os.path.join(d, 'b2', 'account_info')) + else: + expected_path = os.path.abspath(os.path.join(self.test_home, '.b2_account_info')) actual_path = os.path.abspath(account_info.filename) assert expected_path == actual_path diff --git a/test/unit/account_info/test_sqlite_account_info.py b/test/unit/account_info/test_sqlite_account_info.py index 75f7b9ca1..c87f636c0 100644 --- a/test/unit/account_info/test_sqlite_account_info.py +++ b/test/unit/account_info/test_sqlite_account_info.py @@ -95,9 +95,15 @@ def test_profile_and_xdg_config_env_var(self, monkeypatch): os.path.join('~', 'custom', 'b2', 'db-secondary.sqlite') ) - def test_profile(self): + def test_profile(self, monkeypatch): + xdg_config_path = SqliteAccountInfo.get_xdg_config_path() + if xdg_config_path: + expected_path = (xdg_config_path, 'b2', 'db-foo.sqlite') + else: + expected_path = ('~', '.b2db-foo.sqlite') + account_info_path = SqliteAccountInfo._get_user_account_info_path(profile='foo') - assert account_info_path == os.path.expanduser(os.path.join('~', '.b2db-foo.sqlite')) + assert account_info_path == os.path.expanduser(os.path.join(*expected_path)) def test_file_name(self): account_info_path = SqliteAccountInfo._get_user_account_info_path( @@ -129,5 +135,11 @@ def test_xdg_config_env_var(self, monkeypatch): ) def test_default_file(self): + xdg_config_path = SqliteAccountInfo.get_xdg_config_path() + if xdg_config_path: + expected_path = os.path.join(xdg_config_path, 'b2', 'account_info') + else: + expected_path = B2_ACCOUNT_INFO_DEFAULT_FILE + account_info_path = SqliteAccountInfo._get_user_account_info_path() - assert account_info_path == os.path.expanduser(B2_ACCOUNT_INFO_DEFAULT_FILE) + assert account_info_path == os.path.expanduser(expected_path)