From c96ba205071d4c6bbf0fd72c96a41f6bb4ea12ca Mon Sep 17 00:00:00 2001 From: indianwhocodes Date: Mon, 27 Nov 2023 18:35:41 -0800 Subject: [PATCH] Support part-num in python swiftClient Change-Id: Ib3b076581eb0440da1071ea2cb53a90315f55775 --- .gitignore | 1 + swiftclient/command_helpers.py | 8 ++++++++ swiftclient/service.py | 8 ++++++++ swiftclient/shell.py | 24 +++++++++++++++++++++--- test/unit/test_shell.py | 24 ++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index af50ddda..82451cad 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ coverage.xml doc/build doc/source/api/ .idea +test/ diff --git a/swiftclient/command_helpers.py b/swiftclient/command_helpers.py index f37040f8..36f00da4 100644 --- a/swiftclient/command_helpers.py +++ b/swiftclient/command_helpers.py @@ -146,6 +146,14 @@ def stat_object(conn, options, container, obj): query_string = None if options.get('version_id') is not None: query_string = 'version-id=%s' % options['version_id'] + if options.get('part_number') is not None: + query_string = 'part-number=%s' % options['part_number'] + elif (options.get('version_id') is not None and options.get('part_number') + is not None): + query_string = 'version-id=%s&part-number=%s' % ( + options['version_id'], options['part_number'] + ) + headers = conn.head_object(container, obj, headers=req_headers, query_string=query_string) items = [] diff --git a/swiftclient/service.py b/swiftclient/service.py index 3d1b4764..c169eebe 100644 --- a/swiftclient/service.py +++ b/swiftclient/service.py @@ -1219,6 +1219,14 @@ class SwiftService: if options.get('version_id') is not None: get_args['query_string'] = ( 'version-id=%s' % options['version_id']) + if options.get('part_number') is not None: + get_args['query_string'] = ( + 'part-number=%s' % options['part_number']) + elif options.get('version_id') is not None and \ + options.get('part_number') is not None: + get_args['query_string'] = ( + 'version-id=%s&part-number=%s' % ( + options['version_id'], options['part_number'])) if options['skip_identical']: # Assume the file is a large object; if we're wrong, the query # string is ignored and the If-None-Match header will trigger diff --git a/swiftclient/shell.py b/swiftclient/shell.py index c5fb2190..dd3741d0 100755 --- a/swiftclient/shell.py +++ b/swiftclient/shell.py @@ -236,6 +236,7 @@ st_download_options = '''[--all] [--marker ] [--prefix ] [--container-threads ] [--no-download] [--skip-identical] [--remove-prefix] [--version-id ] + [--part-number ] [--header ] [--no-shuffle] [ [] [...]] ''' @@ -282,6 +283,8 @@ Optional arguments: sides. --version-id Download specific version of a versioned object. + --part-number + Download a specific part of a static large object. --ignore-checksum Turn off checksum validation for downloads. --no-shuffle By default, when downloading a complete account or container, download order is randomised in order to @@ -346,6 +349,9 @@ def st_download(parser, args, output_manager, return_parser=False): parser.add_argument( '--version-id', action='store', default=None, help='Download a specific version of a versioned object') + parser.add_argument( + '--part-number', action='store', default=None, + help='Download a specific version of a versioned object') parser.add_argument( '--ignore-checksum', action='store_false', dest='checksum', default=True, help='Turn off checksum validation for downloads.') @@ -389,6 +395,9 @@ def st_download(parser, args, output_manager, return_parser=False): if options['version_id'] and len(args) < 2: exit('--version-id option only allowed for object downloads') + if options['part_number'] and len(args) < 2: + exit('--part-number option only allowed for object downloads') + if options['object_threads'] <= 0: output_manager.error( 'ERROR: option --object-threads should be a positive integer.\n\n' @@ -675,6 +684,7 @@ def st_list(parser, args, output_manager, return_parser=False): st_stat_options = '''[--lh] [--header ] [--version-id ] + [--part-number ] [ []] ''' @@ -690,6 +700,8 @@ Optional arguments: ls -lh. --version-id Report stat of specific version of a versioned object. + --part-number + Report a specific part of a static large object. -H, --header Adds a custom request header to use for stat. '''.strip('\n') @@ -702,6 +714,9 @@ def st_stat(parser, args, output_manager, return_parser=False): parser.add_argument( '--version-id', action='store', default=None, help='Report stat of a specific version of a versioned object') + parser.add_argument( + '--part-number', action='store', default=None, + help='Report a specific part of a static large object') parser.add_argument( '-H', '--header', action='append', dest='header', default=[], @@ -715,6 +730,8 @@ def st_stat(parser, args, output_manager, return_parser=False): args = args[1:] if options['version_id'] and len(args) < 2: exit('--version-id option only allowed for object stats') + if options['part_number'] and len(args) < 2: + exit('--part-number option only allowed for object stats') with SwiftService(options=options) as swift: try: @@ -754,7 +771,7 @@ def st_stat(parser, args, output_manager, return_parser=False): items, headers, output_manager ) else: - raise(stat_result["error"]) + raise (stat_result["error"]) else: output_manager.error( 'Usage: %s stat %s\n%s', BASENAME, @@ -864,7 +881,7 @@ def st_post(parser, args, output_manager, return_parser=False): else: result = swift.post(container=container) if not result["success"]: - raise(result["error"]) + raise (result["error"]) except SwiftError as e: output_manager.error(e.value) @@ -1516,7 +1533,8 @@ def st_tempurl(parser, args, thread_manager, return_parser=False): thread_manager.print_msg(url) -st_bash_completion_help = '''Retrieve command specific flags used by bash_completion. +st_bash_completion_help = '''Retrieve command specific flags used by + bash_completion. Optional positional arguments: Swift client command to filter the flags by. diff --git a/test/unit/test_shell.py b/test/unit/test_shell.py index 1bd26fff..f8877ac5 100644 --- a/test/unit/test_shell.py +++ b/test/unit/test_shell.py @@ -259,6 +259,30 @@ class TestShell(unittest.TestCase): query_string='version-id=1')], connection.return_value.head_object.mock_calls) + @mock.patch('swiftclient.service.Connection') + def test_stat_part_number(self, connection): + argv = ["", "stat", "--part-number", "3"] + with self.assertRaises(SystemExit) as caught: + swiftclient.shell.main(argv) + self.assertEqual(str(caught.exception), + "--part-number option only allowed for " + "object stats") + + argv = ["", "stat", "--part-number", "8", "container"] + with self.assertRaises(SystemExit) as caught: + swiftclient.shell.main(argv) + self.assertEqual(str(caught.exception), + "--part-number option only allowed for " + "object stats") + + argv = ["", "stat", "--part-number", "20", "container", "object"] + connection.return_value.head_object.return_value = {} + with CaptureOutput(): + swiftclient.shell.main(argv) + self.assertEqual([mock.call('container', 'object', headers={}, + query_string='part-number=20')], + connection.return_value.head_object.mock_calls) + @mock.patch('swiftclient.service.Connection') def test_stat_object(self, connection): return_headers = {