Skip to content

Commit df4a1db

Browse files
authored
Merge pull request #383 from reef-technologies/cache-control
Support for cache control headers while uploading
2 parents f7668d6 + e5d680e commit df4a1db

File tree

19 files changed

+206
-12
lines changed

19 files changed

+206
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010

1111
### Added
1212
* Add support for custom upload timestamp
13+
* Add support for cache control header while uploading
1314

1415
### Infrastructure
1516
* Remove dependency from `arrow`

b2sdk/bucket.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ def upload_bytes(
495495
legal_hold: Optional[LegalHold] = None,
496496
large_file_sha1: Optional[Sha1HexDigest] = None,
497497
custom_upload_timestamp: Optional[int] = None,
498+
cache_control: Optional[str] = None,
498499
):
499500
"""
500501
Upload bytes in memory to a B2 file.
@@ -512,6 +513,7 @@ def upload_bytes(
512513
:param bool legal_hold: legal hold setting
513514
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
514515
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
516+
: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'.
515517
:rtype: b2sdk.v2.FileVersion
516518
"""
517519
upload_source = UploadSourceBytes(data_bytes)
@@ -526,6 +528,7 @@ def upload_bytes(
526528
legal_hold=legal_hold,
527529
large_file_sha1=large_file_sha1,
528530
custom_upload_timestamp=custom_upload_timestamp,
531+
cache_control=cache_control,
529532
)
530533

531534
def upload_local_file(
@@ -542,6 +545,7 @@ def upload_local_file(
542545
legal_hold: Optional[LegalHold] = None,
543546
upload_mode: UploadMode = UploadMode.FULL,
544547
custom_upload_timestamp: Optional[int] = None,
548+
cache_control: Optional[str] = None,
545549
):
546550
"""
547551
Upload a file on local disk to a B2 file.
@@ -565,6 +569,7 @@ def upload_local_file(
565569
:param bool legal_hold: legal hold setting
566570
:param b2sdk.v2.UploadMode upload_mode: desired upload mode
567571
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
572+
: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'.
568573
:rtype: b2sdk.v2.FileVersion
569574
"""
570575
upload_source = UploadSourceLocalFile(local_path=local_file, content_sha1=sha1_sum)
@@ -596,6 +601,7 @@ def upload_local_file(
596601
legal_hold=legal_hold,
597602
large_file_sha1=large_file_sha1,
598603
custom_upload_timestamp=custom_upload_timestamp,
604+
cache_control=cache_control,
599605
)
600606

601607
def upload_unbound_stream(
@@ -617,6 +623,7 @@ def upload_unbound_stream(
617623
read_size: int = 8192,
618624
unused_buffer_timeout_seconds: float = 3600.0,
619625
custom_upload_timestamp: Optional[int] = None,
626+
cache_control: Optional[str] = None,
620627
):
621628
"""
622629
Upload an unbound file-like read-only object to a B2 file.
@@ -680,6 +687,7 @@ def upload_unbound_stream(
680687
:param read_size: size of a single read operation performed on the ``read_only_object``
681688
:param unused_buffer_timeout_seconds: amount of time that a buffer can be idle before returning error
682689
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
690+
: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'.
683691
:rtype: b2sdk.v2.FileVersion
684692
"""
685693
if buffers_count <= 1:
@@ -719,6 +727,7 @@ def upload_unbound_stream(
719727
max_queue_size=buffers_count - 1,
720728
large_file_sha1=large_file_sha1,
721729
custom_upload_timestamp=custom_upload_timestamp,
730+
cache_control=cache_control,
722731
)
723732

724733
def upload(
@@ -734,6 +743,7 @@ def upload(
734743
legal_hold: Optional[LegalHold] = None,
735744
large_file_sha1: Optional[Sha1HexDigest] = None,
736745
custom_upload_timestamp: Optional[int] = None,
746+
cache_control: Optional[str] = None,
737747
):
738748
"""
739749
Upload a file to B2, retrying as needed.
@@ -760,6 +770,7 @@ def upload(
760770
:param bool legal_hold: legal hold setting
761771
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
762772
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
773+
: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'.
763774
:rtype: b2sdk.v2.FileVersion
764775
"""
765776
return self.create_file(
@@ -775,6 +786,7 @@ def upload(
775786
legal_hold=legal_hold,
776787
large_file_sha1=large_file_sha1,
777788
custom_upload_timestamp=custom_upload_timestamp,
789+
cache_control=cache_control,
778790
)
779791

780792
def create_file(
@@ -793,6 +805,7 @@ def create_file(
793805
max_part_size=None,
794806
large_file_sha1=None,
795807
custom_upload_timestamp: Optional[int] = None,
808+
cache_control: Optional[str] = None,
796809
):
797810
"""
798811
Creates a new file in this bucket using an iterable (list, tuple etc) of remote or local sources.
@@ -824,6 +837,7 @@ def create_file(
824837
:param int max_part_size: upper limit of part size for the transfer planner, in bytes
825838
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
826839
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
840+
: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'.
827841
"""
828842
return self._create_file(
829843
self.api.services.emerger.emerge,
@@ -841,6 +855,7 @@ def create_file(
841855
max_part_size=max_part_size,
842856
large_file_sha1=large_file_sha1,
843857
custom_upload_timestamp=custom_upload_timestamp,
858+
cache_control=cache_control,
844859
)
845860

846861
def create_file_stream(
@@ -859,6 +874,7 @@ def create_file_stream(
859874
max_part_size=None,
860875
large_file_sha1=None,
861876
custom_upload_timestamp: Optional[int] = None,
877+
cache_control: Optional[str] = None,
862878
):
863879
"""
864880
Creates a new file in this bucket using a stream of multiple remote or local sources.
@@ -892,6 +908,7 @@ def create_file_stream(
892908
:param int max_part_size: upper limit of part size for the transfer planner, in bytes
893909
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
894910
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
911+
: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'.
895912
"""
896913
return self._create_file(
897914
self.api.services.emerger.emerge_stream,
@@ -909,6 +926,7 @@ def create_file_stream(
909926
max_part_size=max_part_size,
910927
large_file_sha1=large_file_sha1,
911928
custom_upload_timestamp=custom_upload_timestamp,
929+
cache_control=cache_control,
912930
)
913931

914932
def _create_file(
@@ -966,6 +984,7 @@ def concatenate(
966984
max_part_size=None,
967985
large_file_sha1=None,
968986
custom_upload_timestamp: Optional[int] = None,
987+
cache_control: Optional[str] = None,
969988
):
970989
"""
971990
Creates a new file in this bucket by concatenating multiple remote or local sources.
@@ -994,6 +1013,7 @@ def concatenate(
9941013
:param int max_part_size: upper limit of part size for the transfer planner, in bytes
9951014
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
9961015
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
1016+
: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'.
9971017
"""
9981018
return self.create_file(
9991019
list(WriteIntent.wrap_sources_iterator(outbound_sources)),
@@ -1010,6 +1030,7 @@ def concatenate(
10101030
max_part_size=max_part_size,
10111031
large_file_sha1=large_file_sha1,
10121032
custom_upload_timestamp=custom_upload_timestamp,
1033+
cache_control=cache_control,
10131034
)
10141035

10151036
def concatenate_stream(
@@ -1026,6 +1047,7 @@ def concatenate_stream(
10261047
legal_hold: Optional[LegalHold] = None,
10271048
large_file_sha1: Optional[Sha1HexDigest] = None,
10281049
custom_upload_timestamp: Optional[int] = None,
1050+
cache_control: Optional[str] = None,
10291051
):
10301052
"""
10311053
Creates a new file in this bucket by concatenating stream of multiple remote or local sources.
@@ -1050,6 +1072,7 @@ def concatenate_stream(
10501072
:param bool legal_hold: legal hold setting
10511073
:param Sha1HexDigest,None large_file_sha1: SHA-1 hash of the result file or ``None`` if unknown
10521074
:param int,None custom_upload_timestamp: override object creation date, expressed as a number of milliseconds since epoch
1075+
: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'.
10531076
"""
10541077
return self.create_file_stream(
10551078
WriteIntent.wrap_sources_iterator(outbound_sources_iterator),
@@ -1064,6 +1087,7 @@ def concatenate_stream(
10641087
legal_hold=legal_hold,
10651088
large_file_sha1=large_file_sha1,
10661089
custom_upload_timestamp=custom_upload_timestamp,
1090+
cache_control=cache_control,
10671091
)
10681092

10691093
def get_download_url(self, filename):
@@ -1104,6 +1128,7 @@ def copy(
11041128
source_content_type: Optional[str] = None,
11051129
file_retention: Optional[FileRetentionSetting] = None,
11061130
legal_hold: Optional[LegalHold] = None,
1131+
cache_control: Optional[str] = None,
11071132
min_part_size=None,
11081133
max_part_size=None,
11091134
):
@@ -1131,6 +1156,7 @@ def copy(
11311156
:param str,None source_content_type: source file's content type, useful when copying files with SSE-C
11321157
:param b2sdk.v2.FileRetentionSetting file_retention: file retention setting for the new file.
11331158
:param bool legal_hold: legal hold setting for the new file.
1159+
: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'.
11341160
:param int min_part_size: lower limit of part size for the transfer planner, in bytes
11351161
:param int max_part_size: upper limit of part size for the transfer planner, in bytes
11361162
"""
@@ -1159,6 +1185,7 @@ def copy(
11591185
source_encryption=source_encryption,
11601186
file_retention=file_retention,
11611187
legal_hold=legal_hold,
1188+
cache_control=cache_control,
11621189
).result()
11631190
except CopySourceTooBig as e:
11641191
copy_source.length = e.size
@@ -1175,6 +1202,7 @@ def copy(
11751202
encryption=destination_encryption,
11761203
file_retention=file_retention,
11771204
legal_hold=legal_hold,
1205+
cache_control=cache_control,
11781206
min_part_size=min_part_size,
11791207
max_part_size=max_part_size,
11801208
)

b2sdk/file_version.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class BaseFileVersion:
4848
'file_retention',
4949
'mod_time_millis',
5050
'replication_status',
51+
'cache_control',
5152
]
5253
_TYPE_MATCHER = re.compile('[a-z0-9]+_[a-z0-9]+_f([0-9]).*')
5354
_FILE_TYPE = {
@@ -71,6 +72,7 @@ def __init__(
7172
file_retention: FileRetentionSetting = NO_RETENTION_FILE_SETTING,
7273
legal_hold: LegalHold = LegalHold.UNSET,
7374
replication_status: Optional[ReplicationStatus] = None,
75+
cache_control: Optional[str] = None,
7476
):
7577
self.api = api
7678
self.id_ = id_
@@ -84,6 +86,7 @@ def __init__(
8486
self.file_retention = file_retention
8587
self.legal_hold = legal_hold
8688
self.replication_status = replication_status
89+
self.cache_control = cache_control
8790

8891
if SRC_LAST_MODIFIED_MILLIS in self.file_info:
8992
self.mod_time_millis = int(self.file_info[SRC_LAST_MODIFIED_MILLIS])
@@ -124,6 +127,7 @@ def _get_args_for_clone(self):
124127
'file_retention': self.file_retention,
125128
'legal_hold': self.legal_hold,
126129
'replication_status': self.replication_status,
130+
'cache_control': self.cache_control,
127131
} # yapf: disable
128132

129133
def as_dict(self):
@@ -135,6 +139,7 @@ def as_dict(self):
135139
'serverSideEncryption': self.server_side_encryption.as_dict(),
136140
'legalHold': self.legal_hold.value,
137141
'fileRetention': self.file_retention.as_dict(),
142+
'cacheControl': self.cache_control,
138143
}
139144

140145
if self.size is not None:
@@ -251,6 +256,7 @@ def __init__(
251256
file_retention: FileRetentionSetting = NO_RETENTION_FILE_SETTING,
252257
legal_hold: LegalHold = LegalHold.UNSET,
253258
replication_status: Optional[ReplicationStatus] = None,
259+
cache_control: Optional[str] = None,
254260
):
255261
self.account_id = account_id
256262
self.bucket_id = bucket_id
@@ -270,6 +276,7 @@ def __init__(
270276
file_retention=file_retention,
271277
legal_hold=legal_hold,
272278
replication_status=replication_status,
279+
cache_control=cache_control,
273280
)
274281

275282
def _get_args_for_clone(self):
@@ -342,6 +349,7 @@ def _get_upload_headers(self) -> bytes:
342349
server_side_encryption=sse,
343350
file_retention=self.file_retention,
344351
legal_hold=self.legal_hold,
352+
cache_control=self.cache_control,
345353
)
346354

347355
headers_str = ''.join(
@@ -418,6 +426,7 @@ def __init__(
418426
file_retention=file_retention,
419427
legal_hold=legal_hold,
420428
replication_status=replication_status,
429+
cache_control=cache_control,
421430
)
422431

423432
def _get_args_for_clone(self):
@@ -501,10 +510,10 @@ def from_api_response(self, file_version_dict, force_action=None):
501510
file_retention = FileRetentionSetting.from_file_version_dict(file_version_dict)
502511

503512
legal_hold = LegalHold.from_file_version_dict(file_version_dict)
504-
505513
replication_status_value = file_version_dict.get('replicationStatus')
506514
replication_status = replication_status_value and ReplicationStatus[
507515
replication_status_value.upper()]
516+
cache_control = file_version_dict.get('cacheControl')
508517

509518
return self.FILE_VERSION_CLASS(
510519
self.api,
@@ -523,6 +532,7 @@ def from_api_response(self, file_version_dict, force_action=None):
523532
file_retention,
524533
legal_hold,
525534
replication_status,
535+
cache_control,
526536
)
527537

528538

@@ -554,13 +564,19 @@ def file_info_from_headers(cls, headers: dict) -> dict:
554564

555565
def from_response_headers(self, headers):
556566
file_info = self.file_info_from_headers(headers)
567+
557568
if 'Content-Range' in headers:
558569
range_, size = self.range_and_size_from_header(headers['Content-Range'])
559570
content_length = int(headers['Content-Length'])
560571
else:
561572
size = content_length = int(headers['Content-Length'])
562573
range_ = Range(0, max(size - 1, 0))
563574

575+
if 'Cache-Control' in headers:
576+
cache_control = b2_url_decode(headers['Cache-Control'])
577+
else:
578+
cache_control = None
579+
564580
return DownloadVersion(
565581
api=self.api,
566582
id_=headers['x-bz-file-id'],
@@ -576,7 +592,7 @@ def from_response_headers(self, headers):
576592
content_length=content_length,
577593
content_language=headers.get('Content-Language'),
578594
expires=headers.get('Expires'),
579-
cache_control=headers.get('Cache-Control'),
595+
cache_control=cache_control,
580596
content_encoding=headers.get('Content-Encoding'),
581597
file_retention=FileRetentionSetting.from_response_headers(headers),
582598
legal_hold=LegalHold.from_response_headers(headers),

b2sdk/large_file/services.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def start_large_file(
8787
encryption: Optional[EncryptionSetting] = None,
8888
file_retention: Optional[FileRetentionSetting] = None,
8989
legal_hold: Optional[LegalHold] = None,
90+
cache_control: Optional[str] = None,
9091
):
9192
"""
9293
Start a large file transfer.
@@ -95,8 +96,9 @@ def start_large_file(
9596
:param str,None content_type: the MIME type, or ``None`` to accept the default based on file extension of the B2 file name
9697
:param dict,None file_info: a file info to store with the file or ``None`` to not store anything
9798
:param b2sdk.v2.EncryptionSetting encryption: encryption settings (``None`` if unknown)
98-
:param b2sdk.v2.LegalHold legal_hold: legal hold setting
9999
:param b2sdk.v2.FileRetentionSetting file_retention: file retention setting
100+
:param b2sdk.v2.LegalHold legal_hold: legal hold setting
101+
: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'.
100102
"""
101103
return UnfinishedLargeFile(
102104
self.services.session.start_large_file(
@@ -107,6 +109,7 @@ def start_large_file(
107109
server_side_encryption=encryption,
108110
file_retention=file_retention,
109111
legal_hold=legal_hold,
112+
cache_control=cache_control,
110113
)
111114
)
112115

b2sdk/large_file/unfinished_large_file.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def __init__(self, file_dict):
3737
self.encryption = EncryptionSettingFactory.from_file_version_dict(file_dict)
3838
self.file_retention = FileRetentionSetting.from_file_version_dict(file_dict)
3939
self.legal_hold = LegalHold.from_file_version_dict(file_dict)
40+
self.cache_control = file_dict.get('cacheControl')
4041

4142
def __repr__(self):
4243
return '<%s %s %s>' % (self.__class__.__name__, self.bucket_id, self.file_name)

0 commit comments

Comments
 (0)