Skip to content

Commit

Permalink
Merge pull request #383 from reef-technologies/cache-control
Browse files Browse the repository at this point in the history
Support for cache control headers while uploading
  • Loading branch information
ppolewicz authored Apr 20, 2023
2 parents f7668d6 + e5d680e commit df4a1db
Show file tree
Hide file tree
Showing 19 changed files with 206 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
* Add support for custom upload timestamp
* Add support for cache control header while uploading

### Infrastructure
* Remove dependency from `arrow`
Expand Down
28 changes: 28 additions & 0 deletions b2sdk/bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ def upload_bytes(
legal_hold: Optional[LegalHold] = None,
large_file_sha1: Optional[Sha1HexDigest] = None,
custom_upload_timestamp: Optional[int] = None,
cache_control: Optional[str] = None,
):
"""
Upload bytes in memory to a B2 file.
Expand All @@ -512,6 +513,7 @@ def upload_bytes(
: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
: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'.
:rtype: b2sdk.v2.FileVersion
"""
upload_source = UploadSourceBytes(data_bytes)
Expand All @@ -526,6 +528,7 @@ def upload_bytes(
legal_hold=legal_hold,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
cache_control=cache_control,
)

def upload_local_file(
Expand All @@ -542,6 +545,7 @@ def upload_local_file(
legal_hold: Optional[LegalHold] = None,
upload_mode: UploadMode = UploadMode.FULL,
custom_upload_timestamp: Optional[int] = None,
cache_control: Optional[str] = None,
):
"""
Upload a file on local disk to a B2 file.
Expand All @@ -565,6 +569,7 @@ def upload_local_file(
: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
: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'.
:rtype: b2sdk.v2.FileVersion
"""
upload_source = UploadSourceLocalFile(local_path=local_file, content_sha1=sha1_sum)
Expand Down Expand Up @@ -596,6 +601,7 @@ def upload_local_file(
legal_hold=legal_hold,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
cache_control=cache_control,
)

def upload_unbound_stream(
Expand All @@ -617,6 +623,7 @@ def upload_unbound_stream(
read_size: int = 8192,
unused_buffer_timeout_seconds: float = 3600.0,
custom_upload_timestamp: Optional[int] = None,
cache_control: Optional[str] = None,
):
"""
Upload an unbound file-like read-only object to a B2 file.
Expand Down Expand Up @@ -680,6 +687,7 @@ def upload_unbound_stream(
: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
: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'.
:rtype: b2sdk.v2.FileVersion
"""
if buffers_count <= 1:
Expand Down Expand Up @@ -719,6 +727,7 @@ def upload_unbound_stream(
max_queue_size=buffers_count - 1,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
cache_control=cache_control,
)

def upload(
Expand All @@ -734,6 +743,7 @@ def upload(
legal_hold: Optional[LegalHold] = None,
large_file_sha1: Optional[Sha1HexDigest] = None,
custom_upload_timestamp: Optional[int] = None,
cache_control: Optional[str] = None,
):
"""
Upload a file to B2, retrying as needed.
Expand All @@ -760,6 +770,7 @@ def upload(
: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
: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'.
:rtype: b2sdk.v2.FileVersion
"""
return self.create_file(
Expand All @@ -775,6 +786,7 @@ def upload(
legal_hold=legal_hold,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
cache_control=cache_control,
)

def create_file(
Expand All @@ -793,6 +805,7 @@ def create_file(
max_part_size=None,
large_file_sha1=None,
custom_upload_timestamp: Optional[int] = None,
cache_control: Optional[str] = None,
):
"""
Creates a new file in this bucket using an iterable (list, tuple etc) of remote or local sources.
Expand Down Expand Up @@ -824,6 +837,7 @@ def create_file(
: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
: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'.
"""
return self._create_file(
self.api.services.emerger.emerge,
Expand All @@ -841,6 +855,7 @@ def create_file(
max_part_size=max_part_size,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
cache_control=cache_control,
)

def create_file_stream(
Expand All @@ -859,6 +874,7 @@ def create_file_stream(
max_part_size=None,
large_file_sha1=None,
custom_upload_timestamp: Optional[int] = None,
cache_control: Optional[str] = None,
):
"""
Creates a new file in this bucket using a stream of multiple remote or local sources.
Expand Down Expand Up @@ -892,6 +908,7 @@ def create_file_stream(
: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
: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'.
"""
return self._create_file(
self.api.services.emerger.emerge_stream,
Expand All @@ -909,6 +926,7 @@ def create_file_stream(
max_part_size=max_part_size,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
cache_control=cache_control,
)

def _create_file(
Expand Down Expand Up @@ -966,6 +984,7 @@ def concatenate(
max_part_size=None,
large_file_sha1=None,
custom_upload_timestamp: Optional[int] = None,
cache_control: Optional[str] = None,
):
"""
Creates a new file in this bucket by concatenating multiple remote or local sources.
Expand Down Expand Up @@ -994,6 +1013,7 @@ def concatenate(
: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
: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'.
"""
return self.create_file(
list(WriteIntent.wrap_sources_iterator(outbound_sources)),
Expand All @@ -1010,6 +1030,7 @@ def concatenate(
max_part_size=max_part_size,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
cache_control=cache_control,
)

def concatenate_stream(
Expand All @@ -1026,6 +1047,7 @@ def concatenate_stream(
legal_hold: Optional[LegalHold] = None,
large_file_sha1: Optional[Sha1HexDigest] = None,
custom_upload_timestamp: Optional[int] = None,
cache_control: Optional[str] = None,
):
"""
Creates a new file in this bucket by concatenating stream of multiple remote or local sources.
Expand All @@ -1050,6 +1072,7 @@ def concatenate_stream(
: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
: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'.
"""
return self.create_file_stream(
WriteIntent.wrap_sources_iterator(outbound_sources_iterator),
Expand All @@ -1064,6 +1087,7 @@ def concatenate_stream(
legal_hold=legal_hold,
large_file_sha1=large_file_sha1,
custom_upload_timestamp=custom_upload_timestamp,
cache_control=cache_control,
)

def get_download_url(self, filename):
Expand Down Expand Up @@ -1104,6 +1128,7 @@ def copy(
source_content_type: Optional[str] = None,
file_retention: Optional[FileRetentionSetting] = None,
legal_hold: Optional[LegalHold] = None,
cache_control: Optional[str] = None,
min_part_size=None,
max_part_size=None,
):
Expand Down Expand Up @@ -1131,6 +1156,7 @@ def copy(
:param str,None source_content_type: source file's content type, useful when copying files with SSE-C
:param b2sdk.v2.FileRetentionSetting file_retention: file retention setting for the new file.
:param bool legal_hold: legal hold 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'.
: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
"""
Expand Down Expand Up @@ -1159,6 +1185,7 @@ def copy(
source_encryption=source_encryption,
file_retention=file_retention,
legal_hold=legal_hold,
cache_control=cache_control,
).result()
except CopySourceTooBig as e:
copy_source.length = e.size
Expand All @@ -1175,6 +1202,7 @@ def copy(
encryption=destination_encryption,
file_retention=file_retention,
legal_hold=legal_hold,
cache_control=cache_control,
min_part_size=min_part_size,
max_part_size=max_part_size,
)
Expand Down
20 changes: 18 additions & 2 deletions b2sdk/file_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class BaseFileVersion:
'file_retention',
'mod_time_millis',
'replication_status',
'cache_control',
]
_TYPE_MATCHER = re.compile('[a-z0-9]+_[a-z0-9]+_f([0-9]).*')
_FILE_TYPE = {
Expand All @@ -71,6 +72,7 @@ def __init__(
file_retention: FileRetentionSetting = NO_RETENTION_FILE_SETTING,
legal_hold: LegalHold = LegalHold.UNSET,
replication_status: Optional[ReplicationStatus] = None,
cache_control: Optional[str] = None,
):
self.api = api
self.id_ = id_
Expand All @@ -84,6 +86,7 @@ def __init__(
self.file_retention = file_retention
self.legal_hold = legal_hold
self.replication_status = replication_status
self.cache_control = cache_control

if SRC_LAST_MODIFIED_MILLIS in self.file_info:
self.mod_time_millis = int(self.file_info[SRC_LAST_MODIFIED_MILLIS])
Expand Down Expand Up @@ -124,6 +127,7 @@ def _get_args_for_clone(self):
'file_retention': self.file_retention,
'legal_hold': self.legal_hold,
'replication_status': self.replication_status,
'cache_control': self.cache_control,
} # yapf: disable

def as_dict(self):
Expand All @@ -135,6 +139,7 @@ def as_dict(self):
'serverSideEncryption': self.server_side_encryption.as_dict(),
'legalHold': self.legal_hold.value,
'fileRetention': self.file_retention.as_dict(),
'cacheControl': self.cache_control,
}

if self.size is not None:
Expand Down Expand Up @@ -251,6 +256,7 @@ def __init__(
file_retention: FileRetentionSetting = NO_RETENTION_FILE_SETTING,
legal_hold: LegalHold = LegalHold.UNSET,
replication_status: Optional[ReplicationStatus] = None,
cache_control: Optional[str] = None,
):
self.account_id = account_id
self.bucket_id = bucket_id
Expand All @@ -270,6 +276,7 @@ def __init__(
file_retention=file_retention,
legal_hold=legal_hold,
replication_status=replication_status,
cache_control=cache_control,
)

def _get_args_for_clone(self):
Expand Down Expand Up @@ -342,6 +349,7 @@ def _get_upload_headers(self) -> bytes:
server_side_encryption=sse,
file_retention=self.file_retention,
legal_hold=self.legal_hold,
cache_control=self.cache_control,
)

headers_str = ''.join(
Expand Down Expand Up @@ -418,6 +426,7 @@ def __init__(
file_retention=file_retention,
legal_hold=legal_hold,
replication_status=replication_status,
cache_control=cache_control,
)

def _get_args_for_clone(self):
Expand Down Expand Up @@ -501,10 +510,10 @@ def from_api_response(self, file_version_dict, force_action=None):
file_retention = FileRetentionSetting.from_file_version_dict(file_version_dict)

legal_hold = LegalHold.from_file_version_dict(file_version_dict)

replication_status_value = file_version_dict.get('replicationStatus')
replication_status = replication_status_value and ReplicationStatus[
replication_status_value.upper()]
cache_control = file_version_dict.get('cacheControl')

return self.FILE_VERSION_CLASS(
self.api,
Expand All @@ -523,6 +532,7 @@ def from_api_response(self, file_version_dict, force_action=None):
file_retention,
legal_hold,
replication_status,
cache_control,
)


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

def from_response_headers(self, headers):
file_info = self.file_info_from_headers(headers)

if 'Content-Range' in headers:
range_, size = self.range_and_size_from_header(headers['Content-Range'])
content_length = int(headers['Content-Length'])
else:
size = content_length = int(headers['Content-Length'])
range_ = Range(0, max(size - 1, 0))

if 'Cache-Control' in headers:
cache_control = b2_url_decode(headers['Cache-Control'])
else:
cache_control = None

return DownloadVersion(
api=self.api,
id_=headers['x-bz-file-id'],
Expand All @@ -576,7 +592,7 @@ def from_response_headers(self, headers):
content_length=content_length,
content_language=headers.get('Content-Language'),
expires=headers.get('Expires'),
cache_control=headers.get('Cache-Control'),
cache_control=cache_control,
content_encoding=headers.get('Content-Encoding'),
file_retention=FileRetentionSetting.from_response_headers(headers),
legal_hold=LegalHold.from_response_headers(headers),
Expand Down
5 changes: 4 additions & 1 deletion b2sdk/large_file/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def start_large_file(
encryption: Optional[EncryptionSetting] = None,
file_retention: Optional[FileRetentionSetting] = None,
legal_hold: Optional[LegalHold] = None,
cache_control: Optional[str] = None,
):
"""
Start a large file transfer.
Expand All @@ -95,8 +96,9 @@ def start_large_file(
: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.v2.EncryptionSetting encryption: encryption settings (``None`` if unknown)
:param b2sdk.v2.LegalHold legal_hold: legal hold setting
:param b2sdk.v2.FileRetentionSetting file_retention: file retention setting
:param b2sdk.v2.LegalHold legal_hold: legal hold setting
: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'.
"""
return UnfinishedLargeFile(
self.services.session.start_large_file(
Expand All @@ -107,6 +109,7 @@ def start_large_file(
server_side_encryption=encryption,
file_retention=file_retention,
legal_hold=legal_hold,
cache_control=cache_control,
)
)

Expand Down
1 change: 1 addition & 0 deletions b2sdk/large_file/unfinished_large_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __init__(self, file_dict):
self.encryption = EncryptionSettingFactory.from_file_version_dict(file_dict)
self.file_retention = FileRetentionSetting.from_file_version_dict(file_dict)
self.legal_hold = LegalHold.from_file_version_dict(file_dict)
self.cache_control = file_dict.get('cacheControl')

def __repr__(self):
return '<%s %s %s>' % (self.__class__.__name__, self.bucket_id, self.file_name)
Expand Down
Loading

0 comments on commit df4a1db

Please sign in to comment.