Skip to content

Commit 59f4f72

Browse files
authored
Add support for results that do not print newlines via new SkipFileResult (fix printing bugs when using no-overwrite flag) (#9717)
Fixed bug when using `--no-overwrite` parameter where the last character printed to CLI would be a carriage return, which would cause incompatibility with some terminal program. * Keep track of files skipped. * Update files remaining progress computation to take into account files skipped. * Add support for results that do not print newlines via SkipFileResult and updates to ResultPrinter.
1 parent ab29b24 commit 59f4f72

File tree

2 files changed

+68
-7
lines changed

2 files changed

+68
-7
lines changed

awscli/customizations/s3/results.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def _create_new_result_cls(name, extra_fields=None, base_cls=BaseResult):
5757
FailureResult = _create_new_result_cls('FailureResult', ['exception'])
5858

5959
DryRunResult = _create_new_result_cls('DryRunResult')
60+
SkipFileResult = _create_new_result_cls('SkipFileResult')
6061

6162
ErrorResult = namedtuple('ErrorResult', ['exception'])
6263

@@ -132,6 +133,13 @@ def _on_failure(self, future, e):
132133
self._src,
133134
self._dest,
134135
)
136+
self._result_queue.put(
137+
SkipFileResult(
138+
transfer_type=self._transfer_type,
139+
src=self._src,
140+
dest=self._dest,
141+
)
142+
)
135143
else:
136144
self._result_queue.put(
137145
FailureResult(
@@ -167,6 +175,7 @@ def __init__(self):
167175
self.files_transferred = 0
168176
self.files_failed = 0
169177
self.files_warned = 0
178+
self.files_skipped = 0
170179
self.errors = 0
171180
self.expected_bytes_transferred = 0
172181
self.expected_files_transferred = 0
@@ -184,6 +193,7 @@ def __init__(self):
184193
SuccessResult: self._record_success_result,
185194
FailureResult: self._record_failure_result,
186195
WarningResult: self._record_warning_result,
196+
SkipFileResult: self._record_skipped_file_result,
187197
ErrorResult: self._record_error_result,
188198
CtrlCResult: self._record_error_result,
189199
FinalTotalSubmissionsResult: self._record_final_expected_files,
@@ -299,6 +309,9 @@ def _record_failure_result(self, result, **kwargs):
299309
self.files_failed += 1
300310
self.files_transferred += 1
301311

312+
def _record_skipped_file_result(self, result, **kwargs):
313+
self.files_skipped += 1
314+
302315
def _record_warning_result(self, **kwargs):
303316
self.files_warned += 1
304317

@@ -359,6 +372,7 @@ def __init__(self, result_recorder, out_file=None, error_file=None):
359372
SuccessResult: self._print_success,
360373
FailureResult: self._print_failure,
361374
WarningResult: self._print_warning,
375+
SkipFileResult: self._print_skip,
362376
ErrorResult: self._print_error,
363377
CtrlCResult: self._print_ctrl_c,
364378
DryRunResult: self._print_dry_run,
@@ -375,6 +389,10 @@ def _print_noop(self, **kwargs):
375389
# If the result does not have a handler, then do nothing with it.
376390
pass
377391

392+
def _print_skip(self, **kwargs):
393+
# Don't reset progress length since this result printer doesn't print a newline
394+
self._redisplay_progress(reset_progress_length=False)
395+
378396
def _print_dry_run(self, result, **kwargs):
379397
statement = self.DRY_RUN_FORMAT.format(
380398
transfer_type=result.transfer_type,
@@ -427,23 +445,26 @@ def _get_transfer_location(self, result):
427445
src=result.src, dest=result.dest
428446
)
429447

430-
def _redisplay_progress(self):
448+
def _redisplay_progress(self, reset_progress_length=True):
431449
# Reset to zero because done statements are printed with new lines
432450
# meaning there are no carriage returns to take into account when
433451
# printing the next line.
434-
self._progress_length = 0
452+
if reset_progress_length:
453+
self._progress_length = 0
435454
self._add_progress_if_needed()
436455

437456
def _add_progress_if_needed(self):
438457
if self._has_remaining_progress():
439458
self._print_progress()
459+
else:
460+
self._clear_progress_if_no_more_expected_transfers(ending_char='\r')
440461

441462
def _print_progress(self, **kwargs):
442-
# Get all of the statistics in the correct form.
463+
# Get all the statistics in the correct form.
443464
remaining_files = self._get_expected_total(
444465
str(
445466
self._result_recorder.expected_files_transferred
446-
- self._result_recorder.files_transferred
467+
- (self._result_recorder.files_transferred + self._result_recorder.files_skipped)
447468
)
448469
)
449470

@@ -506,7 +527,7 @@ def _adjust_statement_padding(self, print_statement, ending_char='\n'):
506527
def _has_remaining_progress(self):
507528
if not self._result_recorder.expected_totals_are_final():
508529
return True
509-
actual = self._result_recorder.files_transferred
530+
actual = self._result_recorder.files_transferred + self._result_recorder.files_skipped
510531
expected = self._result_recorder.expected_files_transferred
511532
return actual != expected
512533

@@ -516,9 +537,9 @@ def _print_to_out_file(self, statement):
516537
def _print_to_error_file(self, statement):
517538
uni_print(statement, self._error_file)
518539

519-
def _clear_progress_if_no_more_expected_transfers(self, **kwargs):
540+
def _clear_progress_if_no_more_expected_transfers(self, ending_char='\n', **kwargs):
520541
if self._progress_length and not self._has_remaining_progress():
521-
uni_print(self._adjust_statement_padding(''), self._out_file)
542+
uni_print(self._adjust_statement_padding('', ending_char=ending_char), self._out_file)
522543

523544

524545
class NoProgressResultPrinter(ResultPrinter):

tests/unit/customizations/s3/test_results.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
13+
from botocore.exceptions import HTTPClientError
1314
from s3transfer.exceptions import CancelledError, FatalError
1415

1516
from awscli.compat import StringIO, queue
@@ -31,6 +32,7 @@
3132
ResultProcessor,
3233
ResultRecorder,
3334
ShutdownThreadRequest,
35+
SkipFileResult,
3436
SuccessResult,
3537
)
3638
from awscli.customizations.s3.utils import WarningResult
@@ -159,6 +161,27 @@ def test_on_done_failure(self):
159161
),
160162
)
161163

164+
def test_on_done_precondition_failed(self):
165+
subscriber = self.get_result_subscriber(DoneResultSubscriber)
166+
precondition_failed = HTTPClientError(
167+
request=mock.Mock(),
168+
response={
169+
'Error': {
170+
'Code': 'PreconditionFailed'
171+
}
172+
},
173+
error='PreconditionFailed',
174+
)
175+
precondition_failed_future = self.get_failed_transfer_future(precondition_failed)
176+
subscriber.on_done(precondition_failed_future)
177+
result = self.get_queued_result()
178+
self.assert_result_queue_is_empty()
179+
self.assertEqual(
180+
result,
181+
SkipFileResult(transfer_type=mock.ANY, src=mock.ANY, dest=mock.ANY)
182+
)
183+
184+
162185
def test_on_done_unexpected_cancelled(self):
163186
subscriber = self.get_result_subscriber(DoneResultSubscriber)
164187
cancelled_exception = FatalError('some error')
@@ -1493,6 +1516,23 @@ def test_print_unicode_error(self):
14931516
ref_error_statement = 'fatal error: unicode exists \u2713\n'
14941517
self.assertEqual(self.error_file.getvalue(), ref_error_statement)
14951518

1519+
def test_does_not_print_skipped_file(self):
1520+
transfer_type = 'upload'
1521+
src = 'file'
1522+
dest = 's3://mybucket/mykey'
1523+
skip_file_result = SkipFileResult(
1524+
transfer_type=transfer_type, src=src, dest=dest
1525+
)
1526+
1527+
# Pretend that this is the final result in the result queue that
1528+
# is processed.
1529+
self.result_recorder.final_expected_files_transferred = 1
1530+
self.result_recorder.expected_files_transferred = 1
1531+
self.result_recorder.files_skipped = 1
1532+
1533+
self.result_printer(skip_file_result)
1534+
self.assertEqual(self.out_file.getvalue(), '')
1535+
14961536

14971537
class TestNoProgressResultPrinter(BaseResultPrinterTest):
14981538
def setUp(self):

0 commit comments

Comments
 (0)