Skip to content

Commit 9e3daa7

Browse files
0azmpnowacki-reef
authored andcommitted
Add support for XDG_CONFIG_HOME
1 parent a8b4230 commit 9e3daa7

File tree

3 files changed

+119
-10
lines changed

3 files changed

+119
-10
lines changed

b2sdk/account_info/sqlite_account_info.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,43 @@ class SqliteAccountInfo(UrlPoolAccountInfo):
3737

3838
def __init__(self, file_name=None, last_upgrade_to_run=None):
3939
"""
40-
If ``file_name`` argument is empty or ``None``, path from ``B2_ACCOUNT_INFO`` environment variable is used. If that is not available, a default of ``~/.b2_account_info`` is used.
40+
Initialize SqliteAccountInfo.
41+
42+
The exact algorithm used to determine the location of the database file is not API in any sense.
43+
If the location of the database file is required (for cleanup, etc), do not assume a specific resolution:
44+
instead, use ``self.filename`` to get the actual resolved location.
45+
46+
SqliteAccountInfo currently checks locations in the following order:
47+
* ``file_name``, if truthy
48+
* ``B2_ACCOUNT_INFO_ENV_VAR``'s value, if set
49+
* ``~/.b2_account_info``, if it exists
50+
* ``$XDG_CONFIG_HOME/b2/account_info``, if XDG_CONFIG_HOME is set
51+
* ``~/.b2_account_info``, as default
52+
53+
If the directory ``$XDG_CONFIG_HOME/b2`` does not exist, it is created.
4154
4255
:param str file_name: The sqlite file to use; overrides the default.
4356
:param int last_upgrade_to_run: For testing only, override the auto-update on the db.
4457
"""
4558
self.thread_local = threading.local()
46-
user_account_info_path = file_name or os.environ.get(
47-
B2_ACCOUNT_INFO_ENV_VAR, B2_ACCOUNT_INFO_DEFAULT_FILE
48-
)
49-
self.filename = file_name or os.path.expanduser(user_account_info_path)
59+
60+
if file_name:
61+
user_account_info_path = file_name
62+
elif B2_ACCOUNT_INFO_ENV_VAR in os.environ:
63+
user_account_info_path = os.environ[B2_ACCOUNT_INFO_ENV_VAR]
64+
elif os.path.exists(os.path.expanduser(B2_ACCOUNT_INFO_DEFAULT_FILE)):
65+
user_account_info_path = B2_ACCOUNT_INFO_DEFAULT_FILE
66+
elif 'XDG_CONFIG_HOME' in os.environ:
67+
config_home = os.environ['XDG_CONFIG_HOME']
68+
user_account_info_path = os.path.join(config_home, 'b2', 'account_info')
69+
if not os.path.exists(os.path.join(config_home, 'b2')):
70+
os.makedirs(os.path.join(config_home, 'b2'), mode=0o755)
71+
else:
72+
user_account_info_path = B2_ACCOUNT_INFO_DEFAULT_FILE
73+
74+
self.filename = os.path.expanduser(user_account_info_path)
5075
logger.debug('%s file path to use: %s', self.__class__.__name__, self.filename)
76+
5177
self._validate_database()
5278
with self._get_connection() as conn:
5379
self._create_tables(conn, last_upgrade_to_run)

b2sdk/v1/account_info.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@
88
#
99
######################################################################
1010
import inspect
11+
import logging
12+
import threading
13+
import os
1114
from typing import Optional
1215

1316
from b2sdk import _v2 as v2
1417

18+
logger = logging.getLogger(__name__)
1519

1620
class OldAccountInfoMethods:
1721
def set_auth_data(
@@ -76,7 +80,24 @@ class UrlPoolAccountInfo(v2.UrlPoolAccountInfo, AbstractAccountInfo):
7680

7781

7882
class SqliteAccountInfo(v2.SqliteAccountInfo, AbstractAccountInfo):
79-
pass
83+
def __init__(self, file_name=None, last_upgrade_to_run=None):
84+
"""
85+
If ``file_name`` argument is empty or ``None``, path from ``B2_ACCOUNT_INFO`` environment variable is used. If that is not available, a default of ``~/.b2_account_info`` is used.
86+
87+
:param str file_name: The sqlite file to use; overrides the default.
88+
:param int last_upgrade_to_run: For testing only, override the auto-update on the db.
89+
"""
90+
# use legacy env var resolution, XDG not supported
91+
self.thread_local = threading.local()
92+
user_account_info_path = file_name or os.environ.get(
93+
v2.B2_ACCOUNT_INFO_ENV_VAR, v2.B2_ACCOUNT_INFO_DEFAULT_FILE
94+
)
95+
self.filename = file_name or os.path.expanduser(user_account_info_path)
96+
logger.debug('%s file path to use: %s', self.__class__.__name__, self.filename)
97+
self._validate_database()
98+
with self._get_connection() as conn:
99+
self._create_tables(conn, last_upgrade_to_run)
100+
super(v2.SqliteAccountInfo, self).__init__()
80101

81102

82103
class StubAccountInfo(v2.StubAccountInfo, AbstractAccountInfo):

test/unit/account_info/test_account_info.py

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
import unittest.mock as mock
1414
import os
1515
import platform
16+
import shutil
1617
import tempfile
1718

1819
import pytest
1920

20-
from apiver_deps import AbstractAccountInfo, InMemoryAccountInfo, UploadUrlPool, SqliteAccountInfo
21+
from apiver_deps import AbstractAccountInfo, InMemoryAccountInfo, UploadUrlPool, SqliteAccountInfo, TempDir, B2_ACCOUNT_INFO_ENV_VAR
2122
from apiver_deps_exception import CorruptAccountInfo, MissingAccountData
2223

2324
from .fixtures import *
@@ -288,12 +289,14 @@ def setUp(self, request):
288289
os.unlink(self.db_path)
289290
except OSError:
290291
pass
291-
print('using %s' % self.db_path)
292+
self.home = tempfile.mkdtemp()
293+
292294
yield
293295
try:
294296
os.unlink(self.db_path)
295297
except OSError:
296298
pass
299+
shutil.rmtree(self.home)
297300

298301
def test_corrupted(self):
299302
"""
@@ -331,8 +334,67 @@ def test_convert_from_json(self):
331334
def _make_info(self):
332335
return self._make_sqlite_account_info()
333336

334-
def _make_sqlite_account_info(self, last_upgrade_to_run=None):
337+
def _make_sqlite_account_info(self, env=None, last_upgrade_to_run=None):
335338
"""
336339
Returns a new SqliteAccountInfo that has just read the data from the file.
340+
341+
:param dict env: Override Environment variables.
337342
"""
338-
return SqliteAccountInfo(file_name=self.db_path, last_upgrade_to_run=None)
343+
# Override HOME to ensure hermetic tests
344+
with mock.patch('os.environ', env or {'HOME': self.home}):
345+
return SqliteAccountInfo(
346+
file_name=self.db_path if not env else None,
347+
last_upgrade_to_run=last_upgrade_to_run,
348+
)
349+
350+
def test_uses_default(self):
351+
account_info = self._make_sqlite_account_info(
352+
env={
353+
'HOME': self.home,
354+
}
355+
)
356+
actual_path = os.path.abspath(account_info.filename)
357+
assert os.path.join(self.home, '.b2_account_info') == actual_path
358+
359+
def test_uses_xdg_config_home(self, apiver):
360+
with TempDir() as d:
361+
account_info = self._make_sqlite_account_info(
362+
env={
363+
'HOME': self.home,
364+
'XDG_CONFIG_HOME': d,
365+
}
366+
)
367+
if apiver in ['v0', 'v1']:
368+
expected_path = os.path.abspath(os.path.join(self.home, '.b2_account_info'))
369+
else:
370+
assert os.path.exists(os.path.join(d, 'b2'))
371+
expected_path = os.path.abspath(os.path.join(d, 'b2', 'account_info'))
372+
actual_path = os.path.abspath(account_info.filename)
373+
assert expected_path == actual_path
374+
375+
def test_uses_existing_file_and_ignores_xdg(self):
376+
with TempDir() as d:
377+
default_db_file_location = os.path.join(self.home, '.b2_account_info')
378+
open(default_db_file_location, 'a').close()
379+
account_info = self._make_sqlite_account_info(
380+
env={
381+
'HOME': self.home,
382+
'XDG_CONFIG_HOME': d,
383+
}
384+
)
385+
assert not os.path.exists(os.path.join(d, 'b2'))
386+
actual_path = os.path.abspath(account_info.filename)
387+
assert default_db_file_location == actual_path
388+
389+
def test_account_info_env_var_overrides_xdg_config_home(self):
390+
with TempDir() as d:
391+
account_info = self._make_sqlite_account_info(
392+
env={
393+
'HOME': self.home,
394+
'XDG_CONFIG_HOME': d,
395+
B2_ACCOUNT_INFO_ENV_VAR: os.path.join(d, 'b2_account_info'),
396+
}
397+
)
398+
expected_path = os.path.abspath(os.path.join(d, 'b2_account_info'))
399+
actual_path = os.path.abspath(account_info.filename)
400+
assert expected_path == actual_path

0 commit comments

Comments
 (0)