Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get_fresh_state method added to FileVersion and Bucket #272

Merged
merged 1 commit into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
* `get_fresh_state` method added to `FileVersion` and `Bucket`

### Changed
* `download_file_*` methods refactored to allow for inspecting DownloadVersion before downloading the whole file
* `B2Api.get_file_info` returns a `FileVersion` object in v2
Expand Down
2 changes: 2 additions & 0 deletions b2sdk/_v2/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from b2sdk.exception import BadFileInfo
from b2sdk.exception import BadJson
from b2sdk.exception import BadUploadUrl
from b2sdk.exception import BucketIdNotFound
from b2sdk.exception import BrokenPipe
from b2sdk.exception import BucketNotAllowed
from b2sdk.exception import CapabilityNotAllowed
Expand Down Expand Up @@ -95,6 +96,7 @@
'BadJson',
'BadUploadUrl',
'BrokenPipe',
'BucketIdNotFound',
'BucketNotAllowed',
'CapabilityNotAllowed',
'CapExceeded',
Expand Down
4 changes: 2 additions & 2 deletions b2sdk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from .cache import AbstractCache
from .bucket import Bucket, BucketFactory
from .encryption.setting import EncryptionSetting
from .exception import NonExistentBucket, RestrictedBucket
from .exception import BucketIdNotFound, NonExistentBucket, RestrictedBucket
from .file_lock import FileRetentionSetting, LegalHold
from .file_version import DownloadVersionFactory, FileIdAndName, FileVersion, FileVersionFactory
from .large_file.services import LargeFileServices
Expand Down Expand Up @@ -286,7 +286,7 @@ def get_bucket_by_id(self, bucket_id: str) -> Bucket:
return bucket

# There is no such bucket.
raise NonExistentBucket(bucket_name)
raise BucketIdNotFound(bucket_id)

def get_bucket_by_name(self, bucket_name):
"""
Expand Down
12 changes: 11 additions & 1 deletion b2sdk/bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from .encryption.setting import EncryptionSetting, EncryptionSettingFactory
from .encryption.types import EncryptionMode
from .exception import FileNotPresent, FileOrBucketNotFound, UnexpectedCloudBehaviour, UnrecognizedBucketType
from .exception import BucketIdNotFound, FileNotPresent, FileOrBucketNotFound, UnexpectedCloudBehaviour, UnrecognizedBucketType
from .file_lock import (
BucketRetentionSetting,
FileLockConfiguration,
Expand Down Expand Up @@ -88,6 +88,16 @@ def __init__(
self.default_retention = default_retention
self.is_file_lock_enabled = is_file_lock_enabled

def get_fresh_state(self) -> 'Bucket':
"""
Fetch all the information about this bucket and return a new bucket object.
This method does NOT change the object it is called on.
"""
buckets_found = self.api.list_buckets(bucket_id=self.id_)
if not buckets_found:
raise BucketIdNotFound(self.id_)
return buckets_found[0]

def get_id(self):
"""
Return bucket ID.
Expand Down
8 changes: 8 additions & 0 deletions b2sdk/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,14 @@ def __str__(self):
return 'Could not find %s within %s' % (file_str, bucket_str)


class BucketIdNotFound(ResourceNotFound):
def __init__(self, bucket_id):
self.bucket_id = bucket_id

def __str__(self):
return 'Bucket with id=%s not found' % (self.bucket_id,)


class FileAlreadyHidden(B2SimpleError):
pass

Expand Down
7 changes: 7 additions & 0 deletions b2sdk/file_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ def as_dict(self):

return result

def get_fresh_state(self) -> 'FileVersion':
"""
Fetch all the information about this file version and return a new FileVersion object.
This method does NOT change the object it is called on.
"""
return self.api.get_file_info(self.id_)


class DownloadVersion(BaseFileVersion):
"""
Expand Down
8 changes: 8 additions & 0 deletions b2sdk/v1/file_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
# Override to retain legacy class name, __init__ signature, slots
# and old formatting methods
# and to omit 'api' property when doing __eq__ and __repr__
# and to make get_fresh_state return proper objects, even though v1.B2Api.get_file_info returns dicts
class FileVersionInfo(v2.FileVersion):
__slots__ = ['_api']

Expand Down Expand Up @@ -92,6 +93,13 @@ def format_ls_entry(self):
def format_folder_ls_entry(cls, name):
return cls.LS_ENTRY_TEMPLATE % ('-', '-', '-', '-', 0, name)

def get_fresh_state(self) -> 'FileVersionInfo':
"""
Fetch all the information about this file version and return a new FileVersion object.
This method does NOT change the object it is called on.
"""
return self.api.file_version_factory.from_api_response(self.api.get_file_info(self.id_))


def file_version_info_from_new_file_version(file_version: v2.FileVersion) -> FileVersionInfo:
return FileVersionInfo(
Expand Down
23 changes: 22 additions & 1 deletion test/unit/bucket/test_bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
AlreadyFailed,
B2Error,
B2RequestTimeoutDuringUpload,
BucketIdNotFound,
InvalidAuthToken,
InvalidMetadataDirective,
InvalidRange,
Expand All @@ -39,7 +40,7 @@
from apiver_deps import FileVersion as VFileVersionInfo
from apiver_deps import B2Api
from apiver_deps import B2HttpApiConfig
from apiver_deps import Bucket
from apiver_deps import Bucket, BucketFactory
from apiver_deps import DownloadedFile
from apiver_deps import DownloadVersion
from apiver_deps import LargeFileUploadState
Expand Down Expand Up @@ -477,6 +478,26 @@ def test_delete_file_version(self):
self.assertBucketContents(expected, '', show_versions=True)


class TestGetFreshState(TestCaseWithBucket):
def test_ok(self):
same_but_different = self.api.get_bucket_by_id(self.bucket.id_)
same_but_different = same_but_different.get_fresh_state()
assert isinstance(same_but_different, Bucket)
assert id(same_but_different) != id(self.bucket)
assert same_but_different.as_dict() == self.bucket.as_dict()
same_but_different = same_but_different.update(bucket_info={'completely': 'new info'})
if apiver_deps.V <= 1:
same_but_different = BucketFactory.from_api_bucket_dict(self.api, same_but_different)
assert same_but_different.as_dict() != self.bucket.as_dict()
refreshed_bucket = self.bucket.get_fresh_state()
assert same_but_different.as_dict() == refreshed_bucket.as_dict()

def test_fail(self):
self.api.delete_bucket(self.bucket)
with pytest.raises(BucketIdNotFound):
self.bucket.get_fresh_state()


class TestListVersions(TestCaseWithBucket):
def test_single_version(self):
data = b'hello world'
Expand Down
31 changes: 31 additions & 0 deletions test/unit/file_version/test_file_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
import pytest

import apiver_deps
from apiver_deps import B2Api
from apiver_deps import B2HttpApiConfig
from apiver_deps import DummyCache
from apiver_deps import InMemoryAccountInfo
from apiver_deps import LegalHold
from apiver_deps import RawSimulator

if apiver_deps.V <= 1:
from apiver_deps import FileVersionInfo as VFileVersion
Expand All @@ -19,6 +25,17 @@


class TestFileVersion:
@pytest.fixture(autouse=True)
def setUp(self):
self.account_info = InMemoryAccountInfo()
self.cache = DummyCache()
self.api = B2Api(
self.account_info, self.cache, api_config=B2HttpApiConfig(_raw_api_class=RawSimulator)
)
self.raw_api = self.api.session.raw_api
(self.application_key_id, self.master_key) = self.raw_api.create_account()
self.api.authorize_account('production', self.application_key_id, self.master_key)

@pytest.mark.apiver(to_ver=1)
def test_format_ls_entry(self):
file_version_info = VFileVersion(
Expand All @@ -30,3 +47,17 @@ def test_format_ls_entry(self):
'00:00:02 200 inner/a.txt'
)
assert expected_entry == file_version_info.format_ls_entry()

def test_get_fresh_state(self):
self.bucket = self.api.create_bucket('testbucket', 'allPrivate', is_file_lock_enabled=True)
initial_file_version = self.bucket.upload_bytes(b'nothing', 'test_file')
self.api.update_file_legal_hold(
initial_file_version.id_, initial_file_version.file_name, LegalHold.ON
)
fetched_version = self.api.get_file_info(initial_file_version.id_)
if apiver_deps.V <= 1:
fetched_version = self.api.file_version_factory.from_api_response(fetched_version)
assert initial_file_version.as_dict() != fetched_version.as_dict()
refreshed_version = initial_file_version.get_fresh_state()
assert isinstance(refreshed_version, VFileVersion)
assert refreshed_version.as_dict() == fetched_version.as_dict()
4 changes: 2 additions & 2 deletions test/unit/v_all/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from apiver_deps import EncryptionSetting
from apiver_deps import InMemoryAccountInfo
from apiver_deps import RawSimulator
from apiver_deps_exception import NonExistentBucket
from apiver_deps_exception import BucketIdNotFound
from ..test_base import TestBase


Expand Down Expand Up @@ -56,7 +56,7 @@ def test_get_bucket_by_id_up_to_v1(self):
@pytest.mark.apiver(from_ver=2)
def test_get_bucket_by_id_v2(self):
self._authorize_account()
with pytest.raises(NonExistentBucket):
with pytest.raises(BucketIdNotFound):
self.api.get_bucket_by_id("this id doesn't even exist")
created_bucket = self.api.create_bucket('bucket1', 'allPrivate')
read_bucket = self.api.get_bucket_by_id(created_bucket.id_)
Expand Down