Skip to content

Commit

Permalink
feat: add ability to use sqlite3 account info
Browse files Browse the repository at this point in the history
This commit is added due to the exposure of
Backblaze/b2-sdk-python#175
which is an issue in the thread-safety of
InMemoryAccountInfo
  • Loading branch information
ehossack committed Jan 6, 2021
1 parent a9128e8 commit 6deb13a
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 17 deletions.
28 changes: 15 additions & 13 deletions django_backblaze_b2/options.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Dict, Optional, Union
from typing import Any, Dict, Optional, Union, cast

try:
from typing import TypedDict
Expand Down Expand Up @@ -26,18 +26,20 @@ class BackblazeB2StorageOptions(TypedDict):
nonExistentBucketDetails: Optional[Dict[str, Union[str, Dict[str, Any]]]]
defaultFileInfo: Dict[str, Any]
specificBucketNames: ProxiedBucketNames
sqliteDatabase: str # default unset


def getDefaultB2StorageOptions() -> BackblazeB2StorageOptions:
return {
"realm": "production",
"application_key_id": "---",
"application_key": "---",
"bucket": "django",
"authorizeOnInit": True,
"validateOnInit": True,
"allowFileOverwrites": False,
"nonExistentBucketDetails": None,
"defaultFileInfo": {},
"specificBucketNames": {"public": None, "loggedIn": None, "staff": None},
}
return cast(
BackblazeB2StorageOptions,
{
"realm": "production",
"bucket": "django",
"authorizeOnInit": True,
"validateOnInit": True,
"allowFileOverwrites": False,
"nonExistentBucketDetails": None,
"defaultFileInfo": {},
"specificBucketNames": {"public": None, "loggedIn": None, "staff": None},
},
)
13 changes: 10 additions & 3 deletions django_backblaze_b2/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from logging import getLogger
from typing import IO, Any, Dict, List, Optional, Tuple

from b2sdk.v1 import B2Api, Bucket, InMemoryAccountInfo
from b2sdk.v1 import B2Api, Bucket, InMemoryAccountInfo, SqliteAccountInfo
from b2sdk.v1.exception import FileNotPresent, NonExistentBucket
from django.core.exceptions import ImproperlyConfigured
from django.core.files.base import File
Expand Down Expand Up @@ -30,6 +30,9 @@ def __init__(self, **kwargs):
)
self._allowFileOverwrites = opts["allowFileOverwrites"]

if opts.get("sqliteDatabase"):
self._sqliteDbPath = opts["sqliteDatabase"]

logger.info(f"{self.__class__.__name__} instantiated to use bucket {self._bucketName}")
if opts["authorizeOnInit"]:
logger.debug(f"{self.__class__.__name__} authorizing")
Expand All @@ -47,7 +50,7 @@ def _getDjangoSettingsOptions(self, kwargOpts: Dict) -> BackblazeB2StorageOption
raise ImproperlyConfigured("add BACKBLAZE_CONFIG dict to django settings")
if "application_key_id" not in settings.BACKBLAZE_CONFIG or "application_key" not in settings.BACKBLAZE_CONFIG:
raise ImproperlyConfigured(
"At minimium BACKBLAZE_CONFIG must contain auth 'application_key' and 'application_key_id'"
"At minimum BACKBLAZE_CONFIG must contain auth 'application_key' and 'application_key_id'"
f"\nfound: {settings.BACKBLAZE_CONFIG}"
)
opts = getDefaultB2StorageOptions()
Expand All @@ -57,7 +60,11 @@ def _getDjangoSettingsOptions(self, kwargOpts: Dict) -> BackblazeB2StorageOption
@property
def b2Api(self) -> B2Api:
if not hasattr(self, "_b2Api"):
self._accountInfo = InMemoryAccountInfo()
self._accountInfo = (
SqliteAccountInfo(file_name=self._sqliteDbPath)
if hasattr(self, "_sqliteDbPath")
else InMemoryAccountInfo()
)
self._b2Api = B2Api(self._accountInfo)
self._b2Api.authorize_account(**self._authInfo)
return self._b2Api
Expand Down
16 changes: 15 additions & 1 deletion tests/test_b2_storage_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from unittest import mock

import pytest
from b2sdk.account_info.exception import CorruptAccountInfo
from b2sdk.exception import FileNotPresent
from b2sdk.file_version import FileVersionInfoFactory
from b2sdk.v1 import B2Api, Bucket
Expand All @@ -25,7 +26,7 @@ def test_requiresConfigurationForAuth(settings):
with pytest.raises(ImproperlyConfigured) as error:
BackblazeB2Storage()

assert ("At minimium BACKBLAZE_CONFIG must contain auth 'application_key' and 'application_key_id'") in str(
assert ("At minimum BACKBLAZE_CONFIG must contain auth 'application_key' and 'application_key_id'") in str(
error
)

Expand Down Expand Up @@ -177,5 +178,18 @@ def test_existsFileDoesNotExist(settings):
assert mockedBucket.get_file_info_by_name.call_count == 1


def test_canUseSqliteAccountInfo(settings, tmpdir):
tempFile = tmpdir.mkdir("sub").join("database.sqlite3")
tempFile.write("some-invalid-context")
with mock.patch.object(
settings, "BACKBLAZE_CONFIG", _settingsDict({"sqliteDatabase": str(tempFile)})
), mock.patch.object(B2Api, "authorize_account"), mock.patch.object(B2Api, "get_bucket_by_name"):

with pytest.raises(CorruptAccountInfo) as error:
BackblazeB2Storage(opts={})

assert str(tempFile) in str(error.value)


def _settingsDict(config: Dict[str, Any]) -> Dict[str, Any]:
return {"application_key_id": "---", "application_key": "---", **config}

0 comments on commit 6deb13a

Please sign in to comment.