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

Fix hide file b2://bucket/filename #1048

Merged
merged 2 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 38 additions & 19 deletions b2/_internal/_cli/b2args.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,20 @@ def b2id_or_b2_bucket_uri(value: str) -> Union[B2URI, B2FileIdURI]:
return b2_uri


def b2id_or_file_like_b2_uri(value: str) -> B2URIBase:
def b2id_or_file_like_b2_uri(value: str, *, by_id: Optional[bool] = None) -> B2URIBase:
b2_uri = parse_b2_uri(value)
if isinstance(b2_uri, B2URI):
if b2_uri.is_dir():
raise ValueError(
f"B2 URI pointing to a file-like object is required, but {value} was provided"
)
return b2_uri
elif isinstance(b2_uri, B2FileIdURI):
if by_id is False:
raise ValueError(
f"B2 URI pointing to file-like object by name is required (e.g. b2://bucketName/fileName),"
f" but {value} was provided"
)

return b2_uri

Expand All @@ -78,12 +84,17 @@ def parse_bucket_name(value: str, allow_all_buckets: bool = False) -> str:
return str(value)


def b2id_or_file_like_b2_uri_or_bucket_name(value: str) -> Union[B2URIBase, str]:
try:
bucket_name = parse_bucket_name(value)
return bucket_name
except ValueError:
return b2id_or_file_like_b2_uri(value)
def b2id_or_file_like_b2_uri_or_bucket_name(value: str, *,
by_id: Optional[bool] = None) -> Union[B2URIBase, str]:
if "://" not in value:
return value
else:
b2_uri = b2id_or_file_like_b2_uri(value, by_id=by_id)
if isinstance(b2_uri, B2FileIdURI) and by_id is False:
raise ValueError(
"This command doesn't support file id as an argument, use b2://bucketName/fileName instead"
)
return b2_uri


B2ID_URI_ARG_TYPE = wrap_with_argument_type_error(b2id_uri)
Expand All @@ -94,9 +105,6 @@ def b2id_or_file_like_b2_uri_or_bucket_name(value: str) -> Union[B2URIBase, str]
functools.partial(parse_b2_uri, allow_all_buckets=True)
)
B2ID_OR_FILE_LIKE_B2_URI_ARG_TYPE = wrap_with_argument_type_error(b2id_or_file_like_b2_uri)
B2ID_OR_FILE_LIKE_B2_URI_OR_BUCKET_NAME_ARG_TYPE = wrap_with_argument_type_error(
b2id_or_file_like_b2_uri_or_bucket_name
)


def add_bucket_name_argument(
Expand Down Expand Up @@ -187,35 +195,46 @@ def add_b2id_or_b2_uri_argument(


def add_b2id_or_b2_bucket_uri_argument(parser: argparse.ArgumentParser, name="B2_URI"):
parser.add_argument(
arg = parser.add_argument(
name,
type=B2ID_OR_B2_BUCKET_URI_ARG_TYPE,
help="B2 URI pointing to a bucket, or a file id. e.g. b2://yourBucket, or b2id://fileId",
).completer = b2uri_file_completer
)
arg.completer = b2uri_file_completer
return arg


def add_b2id_or_file_like_b2_uri_argument(parser: argparse.ArgumentParser, name="B2_URI"):
"""
Add a B2 URI pointing to a file as an argument to the parser.
"""
parser.add_argument(
arg = parser.add_argument(
name,
type=B2ID_OR_FILE_LIKE_B2_URI_ARG_TYPE,
help="B2 URI pointing to a file, e.g. b2://yourBucket/file.txt or b2id://fileId",
).completer = b2uri_file_completer
)
arg.completer = b2uri_file_completer
return arg


def add_b2id_or_file_like_b2_uri_or_bucket_name_argument(
parser: argparse.ArgumentParser, name="B2_URI"
parser: argparse.ArgumentParser, name="B2_URI", by_id: Optional[bool] = None
):
"""
Add a B2 URI pointing to a file as an argument to the parser.
"""
parser.add_argument(
help_ = "B2 URI pointing to a file, e.g. b2://yourBucket/file.txt"
if by_id is not False:
help_ += " or b2id://fileId"
arg = parser.add_argument(
name,
type=B2ID_OR_FILE_LIKE_B2_URI_OR_BUCKET_NAME_ARG_TYPE,
help="B2 URI pointing to a file, e.g. b2://yourBucket/file.txt or b2id://fileId",
).completer = b2uri_file_completer
type=wrap_with_argument_type_error(
functools.partial(b2id_or_file_like_b2_uri_or_bucket_name, by_id=by_id)
),
help=help_,
)
arg.completer = b2uri_file_completer
return arg


def get_keyid_and_key_from_env_vars() -> Tuple[Optional[str], Optional[str]]:
Expand Down
9 changes: 8 additions & 1 deletion b2/_internal/arg_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@

from rst2ansi import rst2ansi

try:
getencoding = locale.getencoding
except AttributeError: # Python <=3.10

def getencoding():
return locale.getdefaultlocale()[1]


class B2RawTextHelpFormatter(argparse.RawTextHelpFormatter):
"""
Expand Down Expand Up @@ -142,7 +149,7 @@ def error(self, message):

@classmethod
def _get_encoding(cls):
_, locale_encoding = locale.getdefaultlocale()
locale_encoding = getencoding()

# Check if the stdout is properly set
if sys.stdout.encoding is not None:
Expand Down
39 changes: 25 additions & 14 deletions b2/_internal/console_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,18 +714,34 @@ def get_b2_uri_from_arg(self, args: argparse.Namespace) -> B2URIBase:


class B2URIFileOrBucketNameFileNameArgMixin:
SUPPORTS_B2_ID: bool = True

@classmethod
def _setup_parser(cls, parser):
add_b2id_or_file_like_b2_uri_or_bucket_name_argument(parser)
cls._b2_uri_arg = add_b2id_or_file_like_b2_uri_or_bucket_name_argument(
parser, by_id=cls.SUPPORTS_B2_ID
)
parser.add_argument('fileName', nargs='?', help=argparse.SUPPRESS)
super()._setup_parser(parser)

def get_b2_uri_from_arg(self, args: argparse.Namespace) -> B2URIBase | str:
if isinstance(args.B2_URI, B2URI):
def get_b2_uri_from_arg(self, args: argparse.Namespace) -> B2URIBase:
if isinstance(args.B2_URI, B2URIBase):
if args.fileName:
raise argparse.ArgumentError(
self._b2_uri_arg,
"Both B2 URI and file name were provided, but only one is expected"
)
return args.B2_URI

bucket_name = args.B2_URI
return bucket_name
if not args.fileName:
raise argparse.ArgumentError(
self._b2_uri_arg,
f"Incorrect B2 URI was provided, expected `b2://bucketName/fileName`, but got {args.B2_URI!r}"
)
self._print_stderr(
'WARNING: "bucketName fileName" arguments syntax is deprecated, use "b2://bucketName/fileName" instead'
)
return B2URI(args.B2_URI, args.fileName)


class B2URIFileIDArgMixin:
Expand Down Expand Up @@ -2179,15 +2195,9 @@ class FileHideBase(Command):

def _run(self, args):
b2_uri = self.get_b2_uri_from_arg(args)
if isinstance(b2_uri, B2URI):
bucket_name = b2_uri.bucket_name
file_name = b2_uri.path
else:
bucket_name = b2_uri
file_name = args.fileName

bucket = self.api.get_bucket_by_name(bucket_name)
file_info = bucket.hide_file(file_name)
bucket = self.api.get_bucket_by_name(b2_uri.bucket_name)
file_info = bucket.hide_file(b2_uri.path)
self._print_json(file_info)
return 0

Expand Down Expand Up @@ -5086,7 +5096,7 @@ class File(Command):
{NAME} file cat b2://yourBucket/file.txt
{NAME} file copy-by-id sourceFileId yourBucket file.txt
{NAME} file download b2://yourBucket/file.txt localFile.txt
{NAME} file hide yourBucket file.txt
{NAME} file hide b2://yourBucket/file.txt
{NAME} file info b2://yourBucket/file.txt
{NAME} file update --legal-hold off b2://yourBucket/file.txt
{NAME} file upload yourBucket localFile.txt file.txt
Expand Down Expand Up @@ -5135,6 +5145,7 @@ class FileCopyById(FileCopyByIdBase):
class FileHide(B2URIFileOrBucketNameFileNameArgMixin, FileHideBase):
__doc__ = FileHideBase.__doc__
COMMAND_NAME = 'hide'
SUPPORTS_B2_ID = False


@File.subcommands_registry.register
Expand Down
1 change: 1 addition & 0 deletions changelog.d/+fix_hide_file.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix `b2 file hide b2://bucket/file` handling and test coverage.
1 change: 1 addition & 0 deletions changelog.d/+getdefaultlocale.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix `getdefaultlocale` deprecation warning on Python 3.11+.
4 changes: 2 additions & 2 deletions test/integration/test_b2_command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ def test_basic(b2_tool, persistent_bucket, sample_file, tmp_path, b2_uri_args, a
['file', 'download', '--quiet', f'b2://{bucket_name}/{subfolder}b/1', tmp_path / 'a']
)

b2_tool.should_succeed(['file', 'hide', bucket_name, f'{subfolder}c'])
b2_tool.should_succeed(['file', 'hide', f'b2://{bucket_name}/{subfolder}c'])

list_of_files = b2_tool.should_succeed_json(
['ls', '--json', '--recursive', *b2_uri_args(bucket_name, f'{subfolder}')]
Expand Down Expand Up @@ -396,7 +396,7 @@ def test_basic(b2_tool, persistent_bucket, sample_file, tmp_path, b2_uri_args, a
], [f['fileName'] for f in list_of_files]
)

b2_tool.should_succeed(['file', 'hide', bucket_name, f'{subfolder}c'])
b2_tool.should_succeed(['file', 'hide', f'b2://{bucket_name}/{subfolder}c'])

list_of_files = b2_tool.should_succeed_json(
['ls', '--json', '--recursive', *b2_uri_args(bucket_name, f'{subfolder}')]
Expand Down
44 changes: 44 additions & 0 deletions test/unit/console_tool/test_file_hide.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
######################################################################
#
# File: test/unit/console_tool/test_file_hide.py
#
# Copyright 2024 Backblaze Inc. All Rights Reserved.
#
# License https://www.backblaze.com/using_b2_code.html
#
######################################################################
from __future__ import annotations

import pytest


@pytest.mark.apiver(to_ver=3)
def test_legacy_hide_file(b2_cli, api_bucket, uploaded_file):
b2_cli.run(
["hide-file", uploaded_file["bucket"], uploaded_file["fileName"]],
expected_stderr='WARNING: `hide-file` command is deprecated. Use `file hide` instead.\n'
)
assert not list(api_bucket.ls())


@pytest.mark.apiver(to_ver=4)
def test_file_hide__by_bucket_and_file_name(b2_cli, api_bucket, uploaded_file):
b2_cli.run(
["file", "hide", uploaded_file["bucket"], uploaded_file["fileName"]],
expected_stderr=(
'WARNING: "bucketName fileName" arguments syntax is deprecated, use "b2://bucketName/fileName" instead\n'
),
)
assert not list(api_bucket.ls())


@pytest.mark.apiver
def test_file_hide__by_b2_uri(b2_cli, api_bucket, uploaded_file):
b2_cli.run(["file", "hide", f"b2://{uploaded_file['bucket']}/{uploaded_file['fileName']}"])
assert not list(api_bucket.ls())


@pytest.mark.apiver
def test_file_hide__cannot_hide_by_b2id(b2_cli, api_bucket, uploaded_file):
b2_cli.run(["file", "hide", f"b2id://{uploaded_file['fileId']}"], expected_status=2)
assert list(api_bucket.ls())
24 changes: 11 additions & 13 deletions test/unit/test_console_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -1018,7 +1018,7 @@ def test_rm_fileid_v4(self):
)

# Hide file
self._run_command(['file', 'hide', 'my-bucket', 'file1.txt'],)
self._run_command(['file', 'hide', 'b2://my-bucket/file1.txt'],)

# Delete one file version
self._run_command(['rm', 'b2id://9998'])
Expand Down Expand Up @@ -1087,7 +1087,7 @@ def test_hide_file_legacy_syntax(self):
}

self._run_command(
['file', 'hide', 'my-bucket', 'file1.txt'],
['file', 'hide', 'b2://my-bucket/file1.txt'],
expected_json_in_stdout=expected_json,
)

Expand Down Expand Up @@ -1178,7 +1178,7 @@ def test_files(self):
}

self._run_command(
['file', 'hide', 'my-bucket', 'file1.txt'],
['file', 'hide', 'b2://my-bucket/file1.txt'],
expected_json_in_stdout=expected_json,
)

Expand Down Expand Up @@ -1386,7 +1386,7 @@ def test_files_encrypted(self):
}

self._run_command(
['file', 'hide', 'my-bucket', 'file1.txt'],
['file', 'hide', 'b2://my-bucket/file1.txt'],
expected_json_in_stdout=expected_json,
)

Expand Down Expand Up @@ -2144,9 +2144,9 @@ def test_get_bucket_with_hidden(self):
stdout, stderr = self._get_stdouterr()
console_tool = self.console_tool_class(stdout, stderr)
console_tool.run_command(['b2', 'file', 'hide', 'b2://my-bucket/hidden1'])
console_tool.run_command(['b2', 'file', 'hide', 'my-bucket', 'hidden2'])
console_tool.run_command(['b2', 'file', 'hide', 'my-bucket', 'hidden3'])
console_tool.run_command(['b2', 'hide-file', 'my-bucket', 'hidden4'])
console_tool.run_command(['b2', 'file', 'hide', 'b2://my-bucket/hidden2'])
console_tool.run_command(['b2', 'file', 'hide', 'b2://my-bucket/hidden3'])
console_tool.run_command(['b2', 'file', 'hide', 'b2://my-bucket/hidden4'])

# unhide one file
console_tool.run_command(['b2', 'file', 'unhide', 'b2://my-bucket/hidden2'])
Expand Down Expand Up @@ -2207,13 +2207,11 @@ def test_get_bucket_complex(self):
# something has failed if the output of 'bucket get' does not match the canon.
stdout, stderr = self._get_stdouterr()
console_tool = self.console_tool_class(stdout, stderr)
console_tool.run_command(['b2', 'file', 'hide', 'b2://my-bucket/1/hidden1'])
console_tool.run_command(['b2', 'file', 'hide', 'my-bucket', '1/hidden1'])
for _ in range(2):
console_tool.run_command(['b2', 'file', 'hide', 'b2://my-bucket/hidden1'])
console_tool.run_command(['b2', 'file', 'hide', 'b2://my-bucket/1/hidden2'])
console_tool.run_command(['b2', 'file', 'hide', 'my-bucket', '1/2/hidden3'])
console_tool.run_command(['b2', 'file', 'hide', 'my-bucket', '1/2/hidden3'])
console_tool.run_command(['b2', 'file', 'hide', 'my-bucket', '1/2/hidden3'])
console_tool.run_command(['b2', 'file', 'hide', 'my-bucket', '1/2/hidden3'])
for _ in range(4):
console_tool.run_command(['b2', 'file', 'hide', 'b2://my-bucket/1/2/hidden3'])

# Unhide a file
console_tool.run_command(['b2', 'file', 'unhide', 'b2://my-bucket/1/hidden2'])
Expand Down
Loading