Skip to content

Commit afa22c3

Browse files
authored
Merge pull request #388 from Backblaze/cut
Add support for custom upload timestamp
2 parents 56c82aa + d073dbe commit afa22c3

File tree

10 files changed

+167
-3
lines changed

10 files changed

+167
-3
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
### Added
10+
* Add support for custom upload timestamp
11+
912
## [1.20.0] - 2023-03-23
1013

1114
### Added

b2sdk/bucket.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,10 +494,14 @@ def upload_bytes(
494494
file_retention: Optional[FileRetentionSetting] = None,
495495
legal_hold: Optional[LegalHold] = None,
496496
large_file_sha1: Optional[Sha1HexDigest] = None,
497+
custom_upload_timestamp: Optional[int] = None,
497498
):
498499
"""
499500
Upload bytes in memory to a B2 file.
500501
502+
.. note:
503+
``custom_upload_timestamp`` is disabled by default - please talk to customer support to enable it on your account (if you really need it)
504+
501505
:param bytes data_bytes: a byte array to upload
502506
:param str file_name: a file name to upload bytes to
503507
:param str,None content_type: the MIME type, or ``None`` to accept the default based on file extension of the B2 file name
@@ -507,7 +511,8 @@ def upload_bytes(
507511
:param b2sdk.v2.FileRetentionSetting file_retention: file retention setting
508512
:param bool legal_hold: legal hold setting
509513
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
510-
:rtype: generator[b2sdk.v2.FileVersion]
514+
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
515+
:rtype: b2sdk.v2.FileVersion
511516
"""
512517
upload_source = UploadSourceBytes(data_bytes)
513518
return self.upload(
@@ -520,6 +525,7 @@ def upload_bytes(
520525
file_retention=file_retention,
521526
legal_hold=legal_hold,
522527
large_file_sha1=large_file_sha1,
528+
custom_upload_timestamp=custom_upload_timestamp,
523529
)
524530

525531
def upload_local_file(
@@ -535,10 +541,14 @@ def upload_local_file(
535541
file_retention: Optional[FileRetentionSetting] = None,
536542
legal_hold: Optional[LegalHold] = None,
537543
upload_mode: UploadMode = UploadMode.FULL,
544+
custom_upload_timestamp: Optional[int] = None,
538545
):
539546
"""
540547
Upload a file on local disk to a B2 file.
541548
549+
.. note:
550+
``custom_upload_timestamp`` is disabled by default - please talk to customer support to enable it on your account (if you really need it)
551+
542552
.. seealso::
543553
544554
:ref:`Synchronizer <sync>`, a *high-performance* utility that synchronizes a local folder with a :term:`bucket`.
@@ -554,6 +564,7 @@ def upload_local_file(
554564
:param b2sdk.v2.FileRetentionSetting file_retention: file retention setting
555565
:param bool legal_hold: legal hold setting
556566
:param b2sdk.v2.UploadMode upload_mode: desired upload mode
567+
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
557568
:rtype: b2sdk.v2.FileVersion
558569
"""
559570
upload_source = UploadSourceLocalFile(local_path=local_file, content_sha1=sha1_sum)
@@ -584,6 +595,7 @@ def upload_local_file(
584595
file_retention=file_retention,
585596
legal_hold=legal_hold,
586597
large_file_sha1=large_file_sha1,
598+
custom_upload_timestamp=custom_upload_timestamp,
587599
)
588600

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

706724
def upload(
@@ -715,6 +733,7 @@ def upload(
715733
file_retention: Optional[FileRetentionSetting] = None,
716734
legal_hold: Optional[LegalHold] = None,
717735
large_file_sha1: Optional[Sha1HexDigest] = None,
736+
custom_upload_timestamp: Optional[int] = None,
718737
):
719738
"""
720739
Upload a file to B2, retrying as needed.
@@ -727,6 +746,9 @@ def upload(
727746
must be possible to call it more than once in case the upload
728747
is retried.
729748
749+
.. note:
750+
``custom_upload_timestamp`` is disabled by default - please talk to customer support to enable it on your account (if you really need it)
751+
730752
:param b2sdk.v2.AbstractUploadSource upload_source: an object that opens the source of the upload
731753
:param str file_name: the file name of the new B2 file
732754
:param str,None content_type: the MIME type, or ``None`` to accept the default based on file extension of the B2 file name
@@ -737,6 +759,7 @@ def upload(
737759
:param b2sdk.v2.FileRetentionSetting file_retention: file retention setting
738760
:param bool legal_hold: legal hold setting
739761
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
762+
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
740763
:rtype: b2sdk.v2.FileVersion
741764
"""
742765
return self.create_file(
@@ -751,6 +774,7 @@ def upload(
751774
file_retention=file_retention,
752775
legal_hold=legal_hold,
753776
large_file_sha1=large_file_sha1,
777+
custom_upload_timestamp=custom_upload_timestamp,
754778
)
755779

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

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

878914
def _create_file(
@@ -929,10 +965,14 @@ def concatenate(
929965
min_part_size=None,
930966
max_part_size=None,
931967
large_file_sha1=None,
968+
custom_upload_timestamp: Optional[int] = None,
932969
):
933970
"""
934971
Creates a new file in this bucket by concatenating multiple remote or local sources.
935972
973+
.. note:
974+
``custom_upload_timestamp`` is disabled by default - please talk to customer support to enable it on your account (if you really need it)
975+
936976
:param list[b2sdk.v2.OutboundTransferSource] outbound_sources: list of outbound sources (remote or local)
937977
:param str file_name: file name of the new file
938978
:param str,None content_type: content_type for the new file, if ``None`` content_type would be
@@ -953,6 +993,7 @@ def concatenate(
953993
:param int min_part_size: lower limit of part size for the transfer planner, in bytes
954994
:param int max_part_size: upper limit of part size for the transfer planner, in bytes
955995
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
996+
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
956997
"""
957998
return self.create_file(
958999
list(WriteIntent.wrap_sources_iterator(outbound_sources)),
@@ -968,6 +1009,7 @@ def concatenate(
9681009
min_part_size=min_part_size,
9691010
max_part_size=max_part_size,
9701011
large_file_sha1=large_file_sha1,
1012+
custom_upload_timestamp=custom_upload_timestamp,
9711013
)
9721014

9731015
def concatenate_stream(
@@ -983,6 +1025,7 @@ def concatenate_stream(
9831025
file_retention: Optional[FileRetentionSetting] = None,
9841026
legal_hold: Optional[LegalHold] = None,
9851027
large_file_sha1: Optional[Sha1HexDigest] = None,
1028+
custom_upload_timestamp: Optional[int] = None,
9861029
):
9871030
"""
9881031
Creates a new file in this bucket by concatenating stream of multiple remote or local sources.
@@ -1006,6 +1049,7 @@ def concatenate_stream(
10061049
:param b2sdk.v2.FileRetentionSetting file_retention: file retention setting
10071050
:param bool legal_hold: legal hold setting
10081051
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
1052+
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
10091053
"""
10101054
return self.create_file_stream(
10111055
WriteIntent.wrap_sources_iterator(outbound_sources_iterator),
@@ -1019,6 +1063,7 @@ def concatenate_stream(
10191063
file_retention=file_retention,
10201064
legal_hold=legal_hold,
10211065
large_file_sha1=large_file_sha1,
1066+
custom_upload_timestamp=custom_upload_timestamp,
10221067
)
10231068

10241069
def get_download_url(self, filename):

b2sdk/raw_api.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ def get_upload_file_headers(
316316
server_side_encryption: Optional[EncryptionSetting],
317317
file_retention: Optional[FileRetentionSetting],
318318
legal_hold: Optional[LegalHold],
319+
custom_upload_timestamp: Optional[int] = None,
319320
) -> dict:
320321
headers = {
321322
'Authorization': upload_auth_token,
@@ -338,6 +339,9 @@ def get_upload_file_headers(
338339
if file_retention is not None:
339340
file_retention.add_to_to_upload_headers(headers)
340341

342+
if custom_upload_timestamp is not None:
343+
headers['X-Bz-Custom-Upload-Timestamp'] = str(custom_upload_timestamp)
344+
341345
return headers
342346

343347
@abstractmethod
@@ -354,6 +358,7 @@ def upload_file(
354358
server_side_encryption: Optional[EncryptionSetting] = None,
355359
file_retention: Optional[FileRetentionSetting] = None,
356360
legal_hold: Optional[LegalHold] = None,
361+
custom_upload_timestamp: Optional[int] = None,
357362
):
358363
pass
359364

@@ -702,6 +707,7 @@ def start_large_file(
702707
server_side_encryption: Optional[EncryptionSetting] = None,
703708
file_retention: Optional[FileRetentionSetting] = None,
704709
legal_hold: Optional[LegalHold] = None,
710+
custom_upload_timestamp: Optional[int] = None,
705711
):
706712
kwargs = {}
707713
if server_side_encryption is not None:
@@ -716,6 +722,9 @@ def start_large_file(
716722
if file_retention is not None:
717723
kwargs['fileRetention'] = file_retention.serialize_to_json_for_request()
718724

725+
if custom_upload_timestamp is not None:
726+
kwargs['custom_upload_timestamp'] = custom_upload_timestamp
727+
719728
return self._post_json(
720729
api_url,
721730
'b2_start_large_file',
@@ -881,6 +890,7 @@ def upload_file(
881890
server_side_encryption: Optional[EncryptionSetting] = None,
882891
file_retention: Optional[FileRetentionSetting] = None,
883892
legal_hold: Optional[LegalHold] = None,
893+
custom_upload_timestamp: Optional[int] = None,
884894
):
885895
"""
886896
Upload one, small file to b2.
@@ -907,6 +917,7 @@ def upload_file(
907917
server_side_encryption=server_side_encryption,
908918
file_retention=file_retention,
909919
legal_hold=legal_hold,
920+
custom_upload_timestamp=custom_upload_timestamp,
910921
)
911922
return self.b2_http.post_content_return_json(upload_url, headers, data_stream)
912923

0 commit comments

Comments
 (0)