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

Prepare new minor release: File unhide + TruncateOutput fix #1038

Merged
merged 17 commits into from
Jul 30, 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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ env:

jobs:
lint:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -72,6 +73,7 @@ jobs:
if: ${{ env.B2_TEST_APPLICATION_KEY != '' && env.B2_TEST_APPLICATION_KEY_ID != '' }} # TODO: skip this whole job instead
run: nox -vs cleanup_buckets
test:
timeout-minutes: 90
needs: cleanup_buckets
env:
B2_TEST_APPLICATION_KEY: ${{ secrets.B2_TEST_APPLICATION_KEY }}
Expand Down Expand Up @@ -131,6 +133,7 @@ jobs:
if: ${{ env.B2_TEST_APPLICATION_KEY != '' && env.B2_TEST_APPLICATION_KEY_ID != '' && contains(fromJSON('["3.7", "pypy3.10", "3.12"]'), matrix.python-version) }}
run: nox -vs integration -p ${{ matrix.python-version }} -- -m "require_secrets" --cleanup
test-docker:
timeout-minutes: 90
needs: cleanup_buckets
env:
B2_TEST_APPLICATION_KEY: ${{ secrets.B2_TEST_APPLICATION_KEY }}
Expand Down Expand Up @@ -163,6 +166,7 @@ jobs:
if: ${{ env.B2_TEST_APPLICATION_KEY != '' && env.B2_TEST_APPLICATION_KEY_ID != '' }}
run: nox -vs docker_test -- backblazeit/b2:test
test-linux-bundle:
timeout-minutes: 90
needs: cleanup_buckets
env:
B2_TEST_APPLICATION_KEY: ${{ secrets.B2_TEST_APPLICATION_KEY }}
Expand Down Expand Up @@ -201,6 +205,7 @@ jobs:
if-no-files-found: warn
retention-days: 7
test-windows-bundle:
timeout-minutes: 90
needs: cleanup_buckets
env:
B2_TEST_APPLICATION_KEY: ${{ secrets.B2_TEST_APPLICATION_KEY }}
Expand Down Expand Up @@ -240,6 +245,7 @@ jobs:
if-no-files-found: warn
retention-days: 7
doc:
timeout-minutes: 30
needs: build
runs-on: ubuntu-latest
steps:
Expand Down
24 changes: 24 additions & 0 deletions b2/_internal/_cli/b2args.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ 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)


B2ID_URI_ARG_TYPE = wrap_with_argument_type_error(b2id_uri)
B2_BUCKET_URI_ARG_TYPE = wrap_with_argument_type_error(b2_bucket_uri)
B2ID_OR_B2_URI_ARG_TYPE = wrap_with_argument_type_error(parse_b2_uri)
Expand All @@ -86,6 +94,9 @@ def parse_bucket_name(value: str, allow_all_buckets: bool = False) -> 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 @@ -194,5 +205,18 @@ def add_b2id_or_file_like_b2_uri_argument(parser: argparse.ArgumentParser, name=
).completer = b2uri_file_completer


def add_b2id_or_file_like_b2_uri_or_bucket_name_argument(
parser: argparse.ArgumentParser, name="B2_URI"
):
"""
Add a B2 URI pointing to a file as an argument to the parser.
"""
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


def get_keyid_and_key_from_env_vars() -> Tuple[Optional[str], Optional[str]]:
return environ.get(B2_APPLICATION_KEY_ID_ENV_VAR), environ.get(B2_APPLICATION_KEY_ENV_VAR)
69 changes: 67 additions & 2 deletions b2/_internal/console_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
add_b2id_or_b2_bucket_uri_argument,
add_b2id_or_b2_uri_argument,
add_b2id_or_file_like_b2_uri_argument,
add_b2id_or_file_like_b2_uri_or_bucket_name_argument,
add_b2id_uri_argument,
add_bucket_name_argument,
get_keyid_and_key_from_env_vars,
Expand Down Expand Up @@ -712,6 +713,21 @@ def get_b2_uri_from_arg(self, args: argparse.Namespace) -> B2URIBase:
return args.B2_URI


class B2URIFileOrBucketNameFileNameArgMixin:
@classmethod
def _setup_parser(cls, parser):
add_b2id_or_file_like_b2_uri_or_bucket_name_argument(parser)
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):
return args.B2_URI

bucket_name = args.B2_URI
return bucket_name


class B2URIFileIDArgMixin:
@classmethod
def _setup_parser(cls, parser):
Expand Down Expand Up @@ -2161,6 +2177,22 @@ class FileHideBase(Command):
- **writeFiles**
"""

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)
self._print_json(file_info)
return 0


class HideFileBase(Command):
@classmethod
def _setup_parser(cls, parser):
add_bucket_name_argument(parser)
Expand All @@ -2174,6 +2206,33 @@ def _run(self, args):
return 0


class FileUnhideBase(Command):
"""
Delete the "hide marker" for a given file.

Requires capability:

- **listFiles**
- **deleteFiles**

and optionally:

- **bypassGovernance**
"""

@classmethod
def _setup_parser(cls, parser):
add_normalized_argument(parser, '--bypass-governance', action='store_true', default=False)
super()._setup_parser(parser)

def _run(self, args):
b2_uri = self.get_b2_uri_from_arg(args)
bucket = self.api.get_bucket_by_name(b2_uri.bucket_name)
file_id_and_name = bucket.unhide_file(b2_uri.path, args.bypass_governance)
self._print_json(file_id_and_name)
return 0


class BucketListBase(Command):
"""
List all of the buckets in the current account.
Expand Down Expand Up @@ -5073,11 +5132,17 @@ class FileCopyById(FileCopyByIdBase):


@File.subcommands_registry.register
class FileHide(FileHideBase):
class FileHide(B2URIFileOrBucketNameFileNameArgMixin, FileHideBase):
__doc__ = FileHideBase.__doc__
COMMAND_NAME = 'hide'


@File.subcommands_registry.register
class FileUnhide(B2URIFileArgMixin, FileUnhideBase):
__doc__ = FileUnhideBase.__doc__
COMMAND_NAME = 'unhide'


@File.subcommands_registry.register
class FileUpdate(FileUpdateBase):
__doc__ = FileUpdateBase.__doc__
Expand Down Expand Up @@ -5145,7 +5210,7 @@ class CopyFileById(CmdReplacedByMixin, FileCopyByIdBase):
replaced_by_cmd = (File, FileCopyById)


class HideFile(CmdReplacedByMixin, FileHideBase):
class HideFile(CmdReplacedByMixin, HideFileBase):
__doc__ = FileHideBase.__doc__
replaced_by_cmd = (File, FileHide)

Expand Down
1 change: 1 addition & 0 deletions changelog.d/+b2_file_hide.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support new `b2_uri` and deprecated `bucket_name file_name` arguments in `b2 file hide`.
1 change: 1 addition & 0 deletions changelog.d/+b2_file_unhide.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `b2 file unhide` command.
1 change: 1 addition & 0 deletions changelog.d/554.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update `b2sdk` to 2.5.0, to fix `TruncatedOutput` download errors when network is congested (e.g., due use of high downloader thread count).
10 changes: 5 additions & 5 deletions pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ classifiers = [
dependencies = [
"argcomplete>=2,<4",
"arrow>=1.0.2,<2.0.0",
"b2sdk>=2.4.1,<3",
"b2sdk>=2.5.0,<3",
"docutils>=0.18.1",
"idna~=3.4; platform_system == 'Java'",
"importlib-metadata>=3.3; python_version < '3.8'",
Expand Down
14 changes: 14 additions & 0 deletions test/integration/test_b2_command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,20 @@ def test_basic(b2_tool, bucket_name, sample_file, tmp_path, b2_uri_args, apiver_
)
should_equal(['a', 'b/1', 'b/2', 'd'], [f['fileName'] for f in list_of_files])

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

list_of_files = b2_tool.should_succeed_json(
['ls', '--json', '--recursive', *b2_uri_args(bucket_name)]
)
should_equal(['a', 'b/1', 'b/2', 'c', 'd'], [f['fileName'] for f in list_of_files])

b2_tool.should_succeed(['file', 'hide', bucket_name, 'c'])

list_of_files = b2_tool.should_succeed_json(
['ls', '--json', '--recursive', *b2_uri_args(bucket_name)]
)
should_equal(['a', 'b/1', 'b/2', 'd'], [f['fileName'] for f in list_of_files])

list_of_files = b2_tool.should_succeed_json(
['ls', '--json', '--recursive', '--versions', *b2_uri_args(bucket_name)]
)
Expand Down
4 changes: 2 additions & 2 deletions test/unit/_cli/test_autocomplete_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def test_complete_with_escaped_control_characters(
store=autocomplete_cache.HomeCachePickleStore(tmp_path),
)

with autocomplete_runner(f'b2 file hide {bucket} '):
with autocomplete_runner(f'b2 hide-file {bucket} '):
exit, argcomplete_output = argcomplete_result()
assert exit == 0
assert escaped_cc_file_name in argcomplete_output
Expand All @@ -200,7 +200,7 @@ def test_complete_with_file_suggestions(
tracker=autocomplete_cache.VersionTracker(),
store=autocomplete_cache.HomeCachePickleStore(tmp_path),
)
with autocomplete_runner(f'b2 file hide {bucket} '):
with autocomplete_runner(f'b2 hide-file {bucket} '):
exit, argcomplete_output = argcomplete_result()
assert exit == 0
assert file_name in argcomplete_output
Expand Down
Loading