forked from Azure/azure-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Progress Reporting for Long Running Operations + Custom Commands (Azu…
…re#3130) * minor changes * progress classes and added reportign * repackage * check for value before getting it in parsed args * undoing check * minor * mvc for progress outline * shell additions * repackaging + shell implementation start * remove vscode * heart beat * progress bar * minor changes * CLI spinning wheel * upload download storage * humanfriendly in dependencies * clean up controller * minor changes * split deterministic and nondeterministic * separate the reporter * design simplification * minor * errors * minor * clear changing * update readme * Revert "clear changing" This reverts commit 2532835. * Revert "update readme" This reverts commit 55c6ad9. * minor * mute stderr * varying number of parameters capability * hooking up shell with progress * hooking up progress bar * message in determiniate progress * add testing * cleaning code + writing tests * tests * removed files * shell + other modifications * minor changes * history * pylint + formatting * flake8 * flake8 * pylint * cleaning + comments * tests * reviews * kwargs cleaning * clean * flake8 * tests + cleaning formatting * travis failing * removed namespace in nspkg * shell correction * cleaning + formatting * char hack * redir stderr * tests + patch * tests * flake8 * test * tests * tests * tests * tests * tests * tests * tests * tests * tests * tests * tests * commenting out test * removed unnecessary subclass * mock up the progress for tests * pylint * typo * some style + renaming * keyword * kwargs * calling function * calling function * adding tests back * changed outstream * comment out * flake8 * stringio * trying fdopen * reviewed changes * change to not close * delete file * tests * remove completion tests * try fdopen * change which tests dont run * remove from longrunning * cleaning * does this work? * patch storage * write * write * tests * tests * formatting * mock outstream * undo * making tests pass * remodel to decouple the view * reorg * patch tests * style * remocked * pylint * reformat * test * typo * outstream for indetstout * flake8 * vcr * mock view * pylint * minor * remock * remock * reorg * test * typos
- Loading branch information
1 parent
097fb91
commit 743b4fb
Showing
18 changed files
with
529 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
# -------------------------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for license information. | ||
# -------------------------------------------------------------------------------------------- | ||
from __future__ import division | ||
import sys | ||
|
||
import humanfriendly | ||
|
||
BAR_LEN = 70 | ||
|
||
|
||
class ProgressViewBase(object): | ||
""" a view base for progress reporting """ | ||
def __init__(self, out): | ||
self.out = out | ||
|
||
def write(self, args): | ||
""" writes the progress """ | ||
raise NotImplementedError | ||
|
||
def flush(self): | ||
""" flushes the message out the pipeline""" | ||
self.out.flush() | ||
|
||
|
||
class ProgressReporter(object): | ||
""" generic progress reporter """ | ||
def __init__(self, message='', value=None, total_value=None): | ||
self.message = message | ||
self.value = value | ||
self.total_val = total_value | ||
self.closed = False | ||
|
||
def add(self, **kwargs): | ||
""" | ||
adds a progress report | ||
:param kwargs: dictionary containing 'message', 'total_val', 'value' | ||
""" | ||
message = kwargs.get('message', self.message) | ||
total_val = kwargs.get('total_val', self.total_val) | ||
value = kwargs.get('value', self.value) | ||
if value and total_val: | ||
assert value >= 0 and value <= total_val and total_val >= 0 | ||
self.closed = value == total_val | ||
self.total_val = total_val | ||
self.value = value | ||
self.message = message | ||
|
||
def report(self): | ||
""" report the progress """ | ||
percent = self.value / self.total_val if self.value is not None and self.total_val else None | ||
return {'message': self.message, 'percent': percent} | ||
|
||
|
||
class ProgressHook(object): | ||
""" sends the progress to the view """ | ||
def __init__(self): | ||
self.reporter = ProgressReporter() | ||
self.active_progress = None | ||
|
||
def init_progress(self, progress_view): | ||
""" activate a view """ | ||
self.active_progress = progress_view | ||
|
||
def add(self, **kwargs): | ||
""" adds a progress report """ | ||
self.reporter.add(**kwargs) | ||
self.update() | ||
|
||
def update(self): | ||
""" updates the view with the progress """ | ||
self.active_progress.write(self.reporter.report()) | ||
self.active_progress.flush() | ||
|
||
def stop(self): | ||
""" if there is an abupt stop before ending """ | ||
self.add(message='Interrupted') | ||
|
||
def begin(self, **kwargs): | ||
""" start reporting progress """ | ||
kwargs['message'] = kwargs.get('message', 'Starting') | ||
self.add(**kwargs) | ||
|
||
def end(self, **kwargs): | ||
""" ending reporting of progress """ | ||
kwargs['message'] = kwargs.get('message', 'Finished') | ||
self.add(**kwargs) | ||
|
||
|
||
class IndeterminateStandardOut(ProgressViewBase): | ||
""" custom output for progress reporting """ | ||
def __init__(self, out=None): | ||
super(IndeterminateStandardOut, self).__init__( | ||
out if out else sys.stderr) | ||
self.spinner = humanfriendly.Spinner(label='In Progress', stream=self.out) | ||
self.spinner.hide_cursor = False | ||
|
||
def write(self, args): | ||
""" | ||
writes the progress | ||
:param args: dictionary containing key 'message' | ||
""" | ||
msg = args.get('message', 'In Progress') | ||
self.spinner.step(label=msg) | ||
|
||
|
||
def _format_value(msg, percent): | ||
bar_len = BAR_LEN - len(msg) - 1 | ||
completed = int(bar_len * percent) | ||
|
||
message = '\r{}['.format(msg) | ||
message += ('#' * completed).ljust(bar_len) | ||
message += '] {:.4%}'.format(percent) | ||
return message | ||
|
||
|
||
class DeterminateStandardOut(ProgressViewBase): | ||
""" custom output for progress reporting """ | ||
def __init__(self, out=None): | ||
super(DeterminateStandardOut, self).__init__(out if out else sys.stderr) | ||
|
||
def write(self, args): | ||
""" | ||
writes the progress | ||
:param args: args is a dictionary containing 'percent', 'message' | ||
""" | ||
percent = args.get('percent', 0) | ||
message = args.get('message', '') | ||
|
||
if percent: | ||
percent = percent | ||
progress = _format_value(message, percent) | ||
self.out.write(progress) | ||
|
||
|
||
def get_progress_view(determinant=False, outstream=sys.stderr): | ||
""" gets your view """ | ||
if determinant: | ||
return DeterminateStandardOut(out=outstream) | ||
else: | ||
return IndeterminateStandardOut(out=outstream) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# -------------------------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for license information. | ||
# -------------------------------------------------------------------------------------------- | ||
import unittest | ||
|
||
import azure.cli.core.commands.progress as progress | ||
|
||
|
||
class MockOutstream(progress.ProgressViewBase): | ||
""" mock outstream for testing """ | ||
def __init__(self): | ||
self.string = '' | ||
|
||
def write(self, message): | ||
self.string = message | ||
|
||
def flush(self): | ||
pass | ||
|
||
|
||
class TestProgress(unittest.TestCase): # pylint: disable=too-many-public-methods | ||
""" test the progress reporting """ | ||
|
||
def test_progress_indicator_det_model(self): | ||
""" test the progress reporter """ | ||
reporter = progress.ProgressReporter() | ||
args = reporter.report() | ||
self.assertEqual(args['message'], '') | ||
self.assertEqual(args['percent'], None) | ||
|
||
reporter.add(message='Progress', total_val=10, value=0) | ||
self.assertEqual(reporter.message, 'Progress') | ||
self.assertEqual(reporter.value, 0) | ||
self.assertEqual(reporter.total_val, 10) | ||
args = reporter.report() | ||
self.assertEqual(args['message'], 'Progress') | ||
self.assertEqual(args['percent'], 0) | ||
|
||
with self.assertRaises(AssertionError): | ||
reporter.add(message='In words', total_val=-1, value=10) | ||
with self.assertRaises(AssertionError): | ||
reporter.add(message='In words', total_val=1, value=-10) | ||
with self.assertRaises(AssertionError): | ||
reporter.add(message='In words', total_val=30, value=12340) | ||
|
||
reporter = progress.ProgressReporter() | ||
message = reporter.report() | ||
self.assertEqual(message['message'], '') | ||
|
||
reporter.add(message='Progress') | ||
self.assertEqual(reporter.message, 'Progress') | ||
|
||
message = reporter.report() | ||
self.assertEqual(message['message'], 'Progress') | ||
|
||
def test_progress_indicator_indet_stdview(self): | ||
""" tests the indeterminate progress standardout view """ | ||
outstream = MockOutstream() | ||
view = progress.IndeterminateStandardOut(out=outstream) | ||
before = view.spinner.total | ||
self.assertEqual(view.spinner.label, 'In Progress') | ||
view.write({}) | ||
after = view.spinner.total | ||
self.assertTrue(after >= before) | ||
view.write({'message': 'TESTING'}) | ||
|
||
def test_progress_indicator_det_stdview(self): | ||
""" test the determinate progress standardout view """ | ||
outstream = MockOutstream() | ||
view = progress.DeterminateStandardOut(out=outstream) | ||
view.write({'message': 'hihi', 'percent': .5}) | ||
# 95 length, 48 complete, 4 dec percent | ||
bar_str = ('#' * int(.5 * 65)).ljust(65) | ||
self.assertEqual(outstream.string, '\rhihi[{}] {:.4%}'.format(bar_str, .5)) | ||
|
||
view.write({'message': '', 'percent': .9}) | ||
# 99 length, 90 complete, 4 dec percent | ||
bar_str = ('#' * int(.9 * 69)).ljust(69) | ||
self.assertEqual(outstream.string, '\r[{}] {:.4%}'.format(bar_str, .9)) | ||
|
||
def test_progress_indicator_controller(self): | ||
""" tests the controller for progress reporting """ | ||
controller = progress.ProgressHook() | ||
view = MockOutstream() | ||
|
||
controller.init_progress(view) | ||
self.assertTrue(view == controller.active_progress) | ||
|
||
controller.begin() | ||
|
||
self.assertEqual(controller.active_progress.string['message'], 'Starting') | ||
|
||
controller.end() | ||
self.assertEqual(controller.active_progress.string['message'], 'Finished') | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.