Skip to content

Commit

Permalink
Merge pull request Backblaze#1048 from reef-technologies/fix_hide_file
Browse files Browse the repository at this point in the history
Fix hide file b2://bucket/filename
  • Loading branch information
mjurbanski-reef authored Oct 10, 2024
2 parents 0c04c66 + ebbd84f commit 6c1f81e
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 49 deletions.
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

0 comments on commit 6c1f81e

Please sign in to comment.