1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
|
######################################################################
#
# File: b2sdk/v1/bucket.py
#
# Copyright 2021 Backblaze Inc. All Rights Reserved.
#
# License https://www.backblaze.com/using_b2_code.html
#
######################################################################
from __future__ import annotations
from contextlib import suppress
from typing import overload
from .download_dest import AbstractDownloadDestination
from .file_metadata import FileMetadata
from .file_version import (
FileVersionInfo,
FileVersionInfoFactory,
file_version_info_from_download_version,
)
from b2sdk import v2
from b2sdk._internal.utils import validate_b2_file_name
from b2sdk._internal.raw_api import LifecycleRule
# Overridden to retain the obsolete copy_file and start_large_file methods
# and to retain old style FILE_VERSION_FACTORY attribute
# and to retain old style download_file_by_name signature
# and to retain old style download_file_by_id signature (allowing for the new one as well)
# and to retain old style get_file_info_by_name return type
# and to to adjust to old style B2Api.get_file_info return type
# and to retain old style update return type
class Bucket(v2.Bucket):
FILE_VERSION_FACTORY = staticmethod(FileVersionInfoFactory)
def copy_file(
self,
file_id,
new_file_name,
bytes_range=None,
metadata_directive=None,
content_type=None,
file_info=None,
destination_encryption: v2.EncryptionSetting | None = None,
source_encryption: v2.EncryptionSetting | None = None,
file_retention: v2.FileRetentionSetting | None = None,
legal_hold: v2.LegalHold | None = None,
cache_control: str | None = None,
):
"""
Creates a new file in this bucket by (server-side) copying from an existing file.
:param str file_id: file ID of existing file
:param str new_file_name: file name of the new file
:param tuple[int,int],None bytes_range: start and end offsets (**inclusive!**), default is the entire file
:param b2sdk.v1.MetadataDirectiveMode,None metadata_directive: default is :py:attr:`b2sdk.v1.MetadataDirectiveMode.COPY`
:param str,None content_type: content_type for the new file if metadata_directive is set to :py:attr:`b2sdk.v1.MetadataDirectiveMode.REPLACE`, default will copy the content_type of old file
:param dict,None file_info: file_info for the new file if metadata_directive is set to :py:attr:`b2sdk.v1.MetadataDirectiveMode.REPLACE`, default will copy the file_info of old file
:param b2sdk.v1.EncryptionSetting destination_encryption: encryption settings for the destination
(``None`` if unknown)
:param b2sdk.v1.EncryptionSetting source_encryption: encryption settings for the source
(``None`` if unknown)
:param b2sdk.v1.FileRetentionSetting file_retention: retention setting for the new file
:param bool legal_hold: legalHold setting for the new file
:param str cache_control: cache control setting for the new file. Syntax based on the section 14.9 of RC 2616. Example string value: 'public, max-age=86400, s-maxage=3600, no-transform'.
"""
file_info = file_info or {}
if cache_control is not None:
file_info['b2-cache-control'] = cache_control
return self.api.session.copy_file(
file_id,
new_file_name,
bytes_range,
metadata_directive,
content_type,
file_info,
self.id_,
destination_server_side_encryption=destination_encryption,
source_server_side_encryption=source_encryption,
file_retention=file_retention,
legal_hold=legal_hold,
)
def start_large_file(
self,
file_name,
content_type=None,
file_info=None,
file_retention: v2.FileRetentionSetting | None = None,
legal_hold: v2.LegalHold | None = None,
cache_control: str | None = None,
):
"""
Start a large file transfer.
:param str file_name: a file name
:param str,None content_type: the MIME type, or ``None`` to accept the default based on file extension of the B2 file name
:param dict,None file_info: a file info to store with the file or ``None`` to not store anything
:param b2sdk.v1.FileRetentionSetting file_retention: retention setting for the new file
:param bool legal_hold: legalHold setting for the new file
:param str,None cache_control: an optional cache control setting. Syntax based on the section 14.9 of RFC 2616. Example string value: 'public, max-age=86400, s-maxage=3600, no-transform'.
"""
file_info = file_info or {}
if cache_control is not None:
file_info['b2-cache-control'] = cache_control
validate_b2_file_name(file_name)
return self.api.services.large_file.start_large_file(
self.id_,
file_name,
content_type=content_type,
file_info=file_info,
file_retention=file_retention,
legal_hold=legal_hold,
)
def download_file_by_name(
self,
file_name: str,
download_dest: AbstractDownloadDestination,
progress_listener: v2.AbstractProgressListener | None = None,
range_: tuple[int, int] | None = None,
encryption: v2.EncryptionSetting | None = None,
):
"""
Download a file by name.
.. seealso::
:ref:`Synchronizer <sync>`, a *high-performance* utility that synchronizes a local folder with a Bucket.
:param str file_name: a file name
:param download_dest: an instance of the one of the following classes: \
:class:`~b2sdk.v1.DownloadDestLocalFile`,\
:class:`~b2sdk.v1.DownloadDestBytes`,\
:class:`~b2sdk.v1.PreSeekedDownloadDest`,\
or any sub class of :class:`~b2sdk.v1.AbstractDownloadDestination`
:param progress_listener: a progress listener object to use, or ``None`` to not track progress
:param range_: two integer values, start and end offsets
:param encryption: encryption settings (``None`` if unknown)
"""
downloaded_file = super().download_file_by_name(
file_name=file_name,
progress_listener=progress_listener,
range_=range_,
encryption=encryption,
)
try:
return download_file_and_return_info_dict(downloaded_file, download_dest, range_)
except ValueError as ex:
if ex.args == ('no strategy suitable for download was found!',):
raise AssertionError('no strategy suitable for download was found!')
raise
@overload
def download_file_by_id(
self,
file_id: str,
download_dest: AbstractDownloadDestination = None,
progress_listener: v2.AbstractProgressListener | None = None,
range_: tuple[int, int] | None = None,
encryption: v2.EncryptionSetting | None = None,
) -> dict: ...
@overload
def download_file_by_id(
self,
file_id: str,
progress_listener: v2.AbstractProgressListener | None = None,
range_: tuple[int, int] | None = None,
encryption: v2.EncryptionSetting | None = None,
) -> v2.DownloadedFile: ...
def download_file_by_id(
self,
file_id: str,
download_dest: AbstractDownloadDestination | None = None,
progress_listener: v2.AbstractProgressListener | None = None,
range_: tuple[int, int] | None = None,
encryption: v2.EncryptionSetting | None = None,
):
"""
Download a file by ID.
.. note::
download_file_by_id actually belongs in :py:class:`b2sdk.v1.B2Api`, not in :py:class:`b2sdk.v1.Bucket`; we just provide a convenient redirect here
:param file_id: a file ID
:param download_dest: an instance of the one of the following classes: \
:class:`~b2sdk.v1.DownloadDestLocalFile`,\
:class:`~b2sdk.v1.DownloadDestBytes`,\
:class:`~b2sdk.v1.PreSeekedDownloadDest`,\
or any sub class of :class:`~b2sdk.v1.AbstractDownloadDestination`
:param progress_listener: a progress listener object to use, or ``None`` to not report progress
:param range_: two integer values, start and end offsets
:param encryption: encryption settings (``None`` if unknown)
"""
return self.api.download_file_by_id(
file_id,
download_dest,
progress_listener,
range_=range_,
encryption=encryption,
)
def get_file_info_by_name(self, file_name: str) -> FileVersionInfo:
return file_version_info_from_download_version(super().get_file_info_by_name(file_name))
def get_file_info_by_id(self, file_id: str) -> FileVersionInfo:
"""
Gets a file version's by ID.
:param str file_id: the id of the file.
"""
return self.api.file_version_factory.from_api_response(self.api.get_file_info(file_id))
def update(
self,
bucket_type: str | None = None,
bucket_info: dict | None = None,
cors_rules: dict | None = None,
lifecycle_rules: list[LifecycleRule] | None = None,
if_revision_is: int | None = None,
default_server_side_encryption: v2.EncryptionSetting | None = None,
default_retention: v2.BucketRetentionSetting | None = None,
is_file_lock_enabled: bool | None = None,
**kwargs,
):
"""
Update various bucket parameters.
:param bucket_type: a bucket type, e.g. ``allPrivate`` or ``allPublic``
:param bucket_info: an info to store with a bucket
:param cors_rules: CORS rules to store with a bucket
:param lifecycle_rules: lifecycle rules of the bucket
:param if_revision_is: revision number, update the info **only if** *revision* equals to *if_revision_is*
:param default_server_side_encryption: default server side encryption settings (``None`` if unknown)
:param default_retention: bucket default retention setting
:param bool is_file_lock_enabled: specifies whether bucket should get File Lock-enabled
"""
# allow common tests to execute without hitting attributeerror
with suppress(KeyError):
del kwargs['replication']
self.replication = None
assert (
not kwargs
) # after we get rid of everything we don't support in this apiver, this should be empty
account_id = self.api.account_info.get_account_id()
return self.api.session.update_bucket(
account_id,
self.id_,
bucket_type=bucket_type,
bucket_info=bucket_info,
cors_rules=cors_rules,
lifecycle_rules=lifecycle_rules,
if_revision_is=if_revision_is,
default_server_side_encryption=default_server_side_encryption,
default_retention=default_retention,
is_file_lock_enabled=is_file_lock_enabled,
)
def ls(
self,
folder_to_list: str = '',
show_versions: bool = False,
recursive: bool = False,
fetch_count: int | None = 10000,
**kwargs,
):
"""
Pretend that folders exist and yields the information about the files in a folder.
B2 has a flat namespace for the files in a bucket, but there is a convention
of using "/" as if there were folders. This method searches through the
flat namespace to find the files and "folders" that live within a given
folder.
When the `recursive` flag is set, lists all of the files in the given
folder, and all of its sub-folders.
:param folder_to_list: the name of the folder to list; must not start with "/".
Empty string means top-level folder
:param show_versions: when ``True`` returns info about all versions of a file,
when ``False``, just returns info about the most recent versions
:param recursive: if ``True``, list folders recursively
:param fetch_count: how many entries to return or ``None`` to use the default. Acceptable values: 1 - 10000
:rtype: generator[tuple[b2sdk.v1.FileVersionInfo, str]]
:returns: generator of (file_version, folder_name) tuples
.. note::
In case of `recursive=True`, folder_name is not returned.
"""
return super().ls(folder_to_list, not show_versions, recursive, fetch_count, **kwargs)
def download_file_and_return_info_dict(
downloaded_file: v2.DownloadedFile,
download_dest: AbstractDownloadDestination,
range_: tuple[int, int] | None,
):
with download_dest.make_file_context(
file_id=downloaded_file.download_version.id_,
file_name=downloaded_file.download_version.file_name,
content_length=downloaded_file.download_version.size,
content_type=downloaded_file.download_version.content_type,
content_sha1=downloaded_file.download_version.content_sha1,
file_info=downloaded_file.download_version.file_info,
mod_time_millis=downloaded_file.download_version.mod_time_millis,
range_=range_,
) as file:
downloaded_file.save(file)
return FileMetadata.from_download_version(downloaded_file.download_version).as_info_dict()
class BucketFactory(v2.BucketFactory):
BUCKET_CLASS = staticmethod(Bucket)
|