Skip to content

Commit

Permalink
Merge pull request #388 from Backblaze/cut
Browse files Browse the repository at this point in the history
Add support for custom upload timestamp
  • Loading branch information
ppolewicz committed Apr 12, 2023
2 parents 56c82aa + d073dbe commit afa22c3
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 3 deletions.
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
* Add support for custom upload timestamp

## [1.20.0] - 2023-03-23

### Added
Expand Down
47 changes: 46 additions & 1 deletion b2sdk/bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,10 +494,14 @@ def upload_bytes(
file_retention: Optional[FileRetentionSetting] = None,
legal_hold: Optional[LegalHold] = None,
large_file_sha1: Optional[Sha1HexDigest] = None,
custom_upload_timestamp: Optional[int] = None,
):
"""
Upload bytes in memory to a B2 file.
.. note:
``custom_upload_timestamp`` is disabled by default - please talk to customer support to enable it on your account (if you really need it)
:param bytes data_bytes: a byte array to upload
:param str file_name: a file name to upload bytes to
:param str,None content_type: the MIME type, or ``None`` to accept the default based on file extension of the B2 file name
Expand All @@ -507,7 +511,8 @@ def upload_bytes(
:param b2sdk.v2.FileRetentionSetting file_retention: file retention setting
:param bool legal_hold: legal hold setting
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
:rtype: generator[b2sdk.v2.FileVersion]
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
:rtype: b2sdk.v2.FileVersion
"""
upload_source = UploadSourceBytes(data_bytes)
return self.upload(
Expand All @@ -520,6 +525,7 @@ def upload_bytes(
file_retention=file_retention,
legal_hold=legal_hold,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
)

def upload_local_file(
Expand All @@ -535,10 +541,14 @@ def upload_local_file(
file_retention: Optional[FileRetentionSetting] = None,
legal_hold: Optional[LegalHold] = None,
upload_mode: UploadMode = UploadMode.FULL,
custom_upload_timestamp: Optional[int] = None,
):
"""
Upload a file on local disk to a B2 file.
.. note:
``custom_upload_timestamp`` is disabled by default - please talk to customer support to enable it on your account (if you really need it)
.. seealso::
:ref:`Synchronizer <sync>`, a *high-performance* utility that synchronizes a local folder with a :term:`bucket`.
Expand All @@ -554,6 +564,7 @@ def upload_local_file(
:param b2sdk.v2.FileRetentionSetting file_retention: file retention setting
:param bool legal_hold: legal hold setting
:param b2sdk.v2.UploadMode upload_mode: desired upload mode
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
:rtype: b2sdk.v2.FileVersion
"""
upload_source = UploadSourceLocalFile(local_path=local_file, content_sha1=sha1_sum)
Expand Down Expand Up @@ -584,6 +595,7 @@ def upload_local_file(
file_retention=file_retention,
legal_hold=legal_hold,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
)

def upload_unbound_stream(
Expand All @@ -604,6 +616,7 @@ def upload_unbound_stream(
buffer_size: Optional[int] = None,
read_size: int = 8192,
unused_buffer_timeout_seconds: float = 3600.0,
custom_upload_timestamp: Optional[int] = None,
):
"""
Upload an unbound file-like read-only object to a B2 file.
Expand Down Expand Up @@ -644,6 +657,9 @@ def upload_unbound_stream(
In rare cases, namely when the whole buffer was sent, but there was an error during sending of last bytes
and a retry was issued, another buffer (above the aforementioned limit) will be allocated.
.. note:
``custom_upload_timestamp`` is disabled by default - please talk to customer support to enable it on your account (if you really need it)
:param read_only_object: any object containing a ``read`` method accepting size of the read
:param file_name: a file name of the new B2 file
:param content_type: the MIME type, or ``None`` to accept the default based on file extension of the B2 file name
Expand All @@ -663,6 +679,7 @@ def upload_unbound_stream(
it will be determined automatically as "recommended upload size".
:param read_size: size of a single read operation performed on the ``read_only_object``
:param unused_buffer_timeout_seconds: amount of time that a buffer can be idle before returning error
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
:rtype: b2sdk.v2.FileVersion
"""
if buffers_count <= 1:
Expand Down Expand Up @@ -701,6 +718,7 @@ def upload_unbound_stream(
# is always downloading data from the stream while others are being uploaded.
max_queue_size=buffers_count - 1,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
)

def upload(
Expand All @@ -715,6 +733,7 @@ def upload(
file_retention: Optional[FileRetentionSetting] = None,
legal_hold: Optional[LegalHold] = None,
large_file_sha1: Optional[Sha1HexDigest] = None,
custom_upload_timestamp: Optional[int] = None,
):
"""
Upload a file to B2, retrying as needed.
Expand All @@ -727,6 +746,9 @@ def upload(
must be possible to call it more than once in case the upload
is retried.
.. note:
``custom_upload_timestamp`` is disabled by default - please talk to customer support to enable it on your account (if you really need it)
:param b2sdk.v2.AbstractUploadSource upload_source: an object that opens the source of the upload
:param str file_name: the file name of the new B2 file
:param str,None content_type: the MIME type, or ``None`` to accept the default based on file extension of the B2 file name
Expand All @@ -737,6 +759,7 @@ def upload(
:param b2sdk.v2.FileRetentionSetting file_retention: file retention setting
:param bool legal_hold: legal hold setting
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
:rtype: b2sdk.v2.FileVersion
"""
return self.create_file(
Expand All @@ -751,6 +774,7 @@ def upload(
file_retention=file_retention,
legal_hold=legal_hold,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
)

def create_file(
Expand All @@ -768,13 +792,17 @@ def create_file(
min_part_size=None,
max_part_size=None,
large_file_sha1=None,
custom_upload_timestamp: Optional[int] = None,
):
"""
Creates a new file in this bucket using an iterable (list, tuple etc) of remote or local sources.
Source ranges can overlap and remote sources will be prioritized over local sources (when possible).
For more information and usage examples please see :ref:`Advanced usage patterns <AdvancedUsagePatterns>`.
.. note:
``custom_upload_timestamp`` is disabled by default - please talk to customer support to enable it on your account (if you really need it)
:param list[b2sdk.v2.WriteIntent] write_intents: list of write intents (remote or local sources)
:param str file_name: file name of the new file
:param str,None content_type: content_type for the new file, if ``None`` content_type would be
Expand All @@ -795,6 +823,7 @@ def create_file(
:param int min_part_size: lower limit of part size for the transfer planner, in bytes
:param int max_part_size: upper limit of part size for the transfer planner, in bytes
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
"""
return self._create_file(
self.api.services.emerger.emerge,
Expand All @@ -811,6 +840,7 @@ def create_file(
min_part_size=min_part_size,
max_part_size=max_part_size,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
)

def create_file_stream(
Expand All @@ -828,13 +858,17 @@ def create_file_stream(
min_part_size=None,
max_part_size=None,
large_file_sha1=None,
custom_upload_timestamp: Optional[int] = None,
):
"""
Creates a new file in this bucket using a stream of multiple remote or local sources.
Source ranges can overlap and remote sources will be prioritized over local sources (when possible).
For more information and usage examples please see :ref:`Advanced usage patterns <AdvancedUsagePatterns>`.
.. note:
``custom_upload_timestamp`` is disabled by default - please talk to customer support to enable it on your account (if you really need it)
:param iterator[b2sdk.v2.WriteIntent] write_intents_iterator: iterator of write intents which
are sorted ascending by ``destination_offset``
:param str file_name: file name of the new file
Expand All @@ -857,6 +891,7 @@ def create_file_stream(
:param int min_part_size: lower limit of part size for the transfer planner, in bytes
:param int max_part_size: upper limit of part size for the transfer planner, in bytes
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
"""
return self._create_file(
self.api.services.emerger.emerge_stream,
Expand All @@ -873,6 +908,7 @@ def create_file_stream(
min_part_size=min_part_size,
max_part_size=max_part_size,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
)

def _create_file(
Expand Down Expand Up @@ -929,10 +965,14 @@ def concatenate(
min_part_size=None,
max_part_size=None,
large_file_sha1=None,
custom_upload_timestamp: Optional[int] = None,
):
"""
Creates a new file in this bucket by concatenating multiple remote or local sources.
.. note:
``custom_upload_timestamp`` is disabled by default - please talk to customer support to enable it on your account (if you really need it)
:param list[b2sdk.v2.OutboundTransferSource] outbound_sources: list of outbound sources (remote or local)
:param str file_name: file name of the new file
:param str,None content_type: content_type for the new file, if ``None`` content_type would be
Expand All @@ -953,6 +993,7 @@ def concatenate(
:param int min_part_size: lower limit of part size for the transfer planner, in bytes
:param int max_part_size: upper limit of part size for the transfer planner, in bytes
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
"""
return self.create_file(
list(WriteIntent.wrap_sources_iterator(outbound_sources)),
Expand All @@ -968,6 +1009,7 @@ def concatenate(
min_part_size=min_part_size,
max_part_size=max_part_size,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
)

def concatenate_stream(
Expand All @@ -983,6 +1025,7 @@ def concatenate_stream(
file_retention: Optional[FileRetentionSetting] = None,
legal_hold: Optional[LegalHold] = None,
large_file_sha1: Optional[Sha1HexDigest] = None,
custom_upload_timestamp: Optional[int] = None,
):
"""
Creates a new file in this bucket by concatenating stream of multiple remote or local sources.
Expand All @@ -1006,6 +1049,7 @@ def concatenate_stream(
:param b2sdk.v2.FileRetentionSetting file_retention: file retention setting
:param bool legal_hold: legal hold setting
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
"""
return self.create_file_stream(
WriteIntent.wrap_sources_iterator(outbound_sources_iterator),
Expand All @@ -1019,6 +1063,7 @@ def concatenate_stream(
file_retention=file_retention,
legal_hold=legal_hold,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
)

def get_download_url(self, filename):
Expand Down
11 changes: 11 additions & 0 deletions b2sdk/raw_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ def get_upload_file_headers(
server_side_encryption: Optional[EncryptionSetting],
file_retention: Optional[FileRetentionSetting],
legal_hold: Optional[LegalHold],
custom_upload_timestamp: Optional[int] = None,
) -> dict:
headers = {
'Authorization': upload_auth_token,
Expand All @@ -338,6 +339,9 @@ def get_upload_file_headers(
if file_retention is not None:
file_retention.add_to_to_upload_headers(headers)

if custom_upload_timestamp is not None:
headers['X-Bz-Custom-Upload-Timestamp'] = str(custom_upload_timestamp)

return headers

@abstractmethod
Expand All @@ -354,6 +358,7 @@ def upload_file(
server_side_encryption: Optional[EncryptionSetting] = None,
file_retention: Optional[FileRetentionSetting] = None,
legal_hold: Optional[LegalHold] = None,
custom_upload_timestamp: Optional[int] = None,
):
pass

Expand Down Expand Up @@ -702,6 +707,7 @@ def start_large_file(
server_side_encryption: Optional[EncryptionSetting] = None,
file_retention: Optional[FileRetentionSetting] = None,
legal_hold: Optional[LegalHold] = None,
custom_upload_timestamp: Optional[int] = None,
):
kwargs = {}
if server_side_encryption is not None:
Expand All @@ -716,6 +722,9 @@ def start_large_file(
if file_retention is not None:
kwargs['fileRetention'] = file_retention.serialize_to_json_for_request()

if custom_upload_timestamp is not None:
kwargs['custom_upload_timestamp'] = custom_upload_timestamp

return self._post_json(
api_url,
'b2_start_large_file',
Expand Down Expand Up @@ -881,6 +890,7 @@ def upload_file(
server_side_encryption: Optional[EncryptionSetting] = None,
file_retention: Optional[FileRetentionSetting] = None,
legal_hold: Optional[LegalHold] = None,
custom_upload_timestamp: Optional[int] = None,
):
"""
Upload one, small file to b2.
Expand All @@ -907,6 +917,7 @@ def upload_file(
server_side_encryption=server_side_encryption,
file_retention=file_retention,
legal_hold=legal_hold,
custom_upload_timestamp=custom_upload_timestamp,
)
return self.b2_http.post_content_return_json(upload_url, headers, data_stream)

Expand Down
Loading

0 comments on commit afa22c3

Please sign in to comment.