Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Header args #221

Merged
merged 12 commits into from
Nov 23, 2023
135 changes: 68 additions & 67 deletions b2/console_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from concurrent.futures import Executor, Future, ThreadPoolExecutor
from contextlib import suppress
from enum import Enum
from typing import Any, BinaryIO, Dict, List, Optional, Tuple
from typing import Any, BinaryIO, Callable, Dict, List, Optional, Tuple

import argcomplete
import b2sdk
Expand Down Expand Up @@ -393,6 +393,68 @@ def _get_file_retention_setting(cls, args):
return FileRetentionSetting(file_retention_mode, args.retainUntil)


class HeaderFlagsMixin(Described):
@classmethod
def _setup_parser(cls, parser: argparse.ArgumentParser) -> None:
parser.add_argument(
'--cache-control',
help=
"optional Cache-Control header, value based on RFC 2616 section 14.9, example: 'public, max-age=86400')"
)
parser.add_argument(
'--content-disposition',
help=
"optional Content-Disposition header, value based on RFC 2616 section 19.5.1, example: 'attachment; filename=\"fname.ext\"'"
)
parser.add_argument(
'--content-encoding',
help=
"optional Content-Encoding header, value based on RFC 2616 section 14.11, example: 'gzip'"
)
parser.add_argument(
'--content-language',
help=
"optional Content-Language header, value based on RFC 2616 section 14.12, example: 'mi, en'"
)
parser.add_argument(
'--expires',
help=
"optional Expires header, value based on RFC 2616 section 14.21, example: 'Thu, 01 Dec 2050 16:00:00 GMT'"
)
super()._setup_parser(parser)

def _file_info_with_header_args(self, args, file_info: dict[str, str] | None) -> dict[str, str] | None:
"""Construct an updated file_info dictionary.
Print a warning if any of file_info items will be overwritten by explicit header arguments.
"""
add_file_info = {}
overwritten = []
if args.cache_control is not None:
add_file_info['b2-cache-control'] = args.cache_control
if args.content_disposition is not None:
add_file_info['b2-content-disposition'] = args.content_disposition
if args.content_encoding is not None:
add_file_info['b2-content-encoding'] = args.content_encoding
if args.content_language is not None:
add_file_info['b2-content-language'] = args.content_language
if args.expires is not None:
add_file_info['b2-expires'] = args.expires

for key, value in add_file_info.items():
if file_info is not None and key in file_info and file_info[key] != value:
overwritten.append(key)

if overwritten:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is great stuff!

self._print_stderr(
'The following file info items will be overwritten by explicit arguments:\n ' +
'\n '.join(f'{key} = {add_file_info[key]}' for key in overwritten)
)

if add_file_info:
return {**(file_info or {}), **add_file_info}
return file_info


class LegalHoldMixin(Described):
"""
Setting legal holds requires the **writeFileLegalHolds** capability, and only works in bucket
Expand Down Expand Up @@ -1010,7 +1072,7 @@ def run(self, args):

@B2.register_subcommand
class CopyFileById(
DestinationSseMixin, SourceSseMixin, FileRetentionSettingMixin, LegalHoldMixin, Command
HeaderFlagsMixin, DestinationSseMixin, SourceSseMixin, FileRetentionSettingMixin, LegalHoldMixin, Command
):
"""
Copy a file version to the given bucket (server-side, **not** via download+upload).
Expand Down Expand Up @@ -1053,31 +1115,6 @@ def _setup_parser(cls, parser):
parser.add_argument('--metadataDirective', default=None, help=argparse.SUPPRESS)
parser.add_argument('--contentType')
parser.add_argument('--range', type=parse_range)
parser.add_argument(
'--cache-control',
help=
"optional Cache-Control header, value based on RFC 2616 section 14.9, example: 'public, max-age=86400')"
)
parser.add_argument(
'--content-disposition',
help=
"optional Content-Disposition header, value based on RFC 2616 section 19.5.1, example: 'attachment; filename=\"fname.ext\"'"
)
parser.add_argument(
'--content-encoding',
help=
"optional Content-Encoding header, value based on RFC 2616 section 14.11, example: 'gzip'"
)
parser.add_argument(
'--content-language',
help=
"optional Content-Language header, value based on RFC 2616 section 14.12, example: 'mi, en'"
)
parser.add_argument(
'--expires',
help=
"optional Expires header, value based on RFC 2616 section 14.21, example: 'Thu, 01 Dec 2050 16:00:00 GMT'"
)

info_group = parser.add_mutually_exclusive_group()

Expand All @@ -1097,6 +1134,7 @@ def run(self, args):
file_infos = self._parse_file_infos(args.info)
elif args.noInfo:
file_infos = {}
file_infos = self._file_info_with_header_args(args, file_infos)

if args.metadataDirective is not None:
self._print_stderr(
Expand Down Expand Up @@ -1136,11 +1174,6 @@ def run(self, args):
file_retention=file_retention,
source_file_info=source_file_info,
source_content_type=source_content_type,
cache_control=args.cache_control,
expires=args.expires,
content_disposition=args.content_disposition,
content_encoding=args.content_encoding,
content_language=args.content_language,
)
self._print_json(file_version)
return 0
Expand Down Expand Up @@ -2892,6 +2925,7 @@ def _setup_parser(cls, parser):


class UploadFileMixin(
HeaderFlagsMixin,
MinPartSizeMixin,
ThreadsMixin,
ProgressMixin,
Expand Down Expand Up @@ -2920,31 +2954,6 @@ def _setup_parser(cls, parser):
parser.add_argument(
'--sha1', help="SHA-1 of the data being uploaded for verifying file integrity"
)
parser.add_argument(
'--cache-control',
help=
"optional Cache-Control header, value based on RFC 2616 section 14.9, example: 'public, max-age=86400')"
)
parser.add_argument(
'--content-disposition',
help=
"optional Content-Disposition header, value based on RFC 2616 section 19.5.1, example: 'attachment; filename=\"fname.ext\"'"
)
parser.add_argument(
'--content-encoding',
help=
"optional Content-Encoding header, value based on RFC 2616 section 14.11, example: 'gzip'"
)
parser.add_argument(
'--content-language',
help=
"optional Content-Language header, value based on RFC 2616 section 14.12, example: 'mi, en'"
)
parser.add_argument(
'--expires',
help=
"optional Expires header, value based on RFC 2616 section 14.21, example: 'Thu, 01 Dec 2050 16:00:00 GMT'"
)
parser.add_argument(
'--info',
action='append',
Expand Down Expand Up @@ -2991,25 +3000,17 @@ def get_execute_kwargs(self, args) -> dict:
else:
file_infos[SRC_LAST_MODIFIED_MILLIS] = str(int(mtime * 1000))

file_infos = self._file_info_with_header_args(args, file_infos)

return {
"bucket":
self.api.get_bucket_by_name(args.bucketName),
"cache_control":
args.cache_control,
"content_disposition":
args.content_disposition,
"content_encoding":
args.content_encoding,
"content_language":
args.content_language,
"content_type":
args.contentType,
"custom_upload_timestamp":
args.custom_upload_timestamp,
"encryption":
self._get_destination_sse_setting(args),
"expires":
args.expires,
"file_info":
file_infos,
"file_name":
Expand Down
10 changes: 9 additions & 1 deletion test/integration/test_b2_command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -2714,7 +2714,7 @@ def assert_expected(file_info, expected=expected_file_info):
for key, val in expected.items():
assert file_info[key] == val

file_version = b2_tool.should_succeed_json(
status, stdout, stderr = b2_tool.execute(
[
'upload-file',
'--quiet',
Expand All @@ -2723,10 +2723,18 @@ def assert_expected(file_info, expected=expected_file_info):
str(sample_filepath),
'sample_file',
*args,
'--info', 'b2-content-disposition=will-be-overwritten',
]
)
assert status == 0
file_version = json.loads(stdout)
assert_expected(file_version['fileInfo'])

# Since we used both --info and --content-disposition to set b2-content-disposition,
# a warning should be emitted
assert 'will be overwritten' in stderr and 'b2-content-disposition = attachment' in stderr


copied_version = b2_tool.should_succeed_json(
[
'copy-file-by-id', '--quiet', *args, '--contentType', 'text/plain',
Expand Down
Loading