From ebd2f720fe18e47a9d825f6707900ba515fb1553 Mon Sep 17 00:00:00 2001 From: timeforplanb123 Date: Sun, 17 Oct 2021 21:05:23 +0300 Subject: [PATCH 1/5] Add new count parameter: add new count parameter. It's number of sorted results. It can be useful for a large number of identical results. It's acceptable to use numbers with minus sign(-5 as example), then results will be from the end of results list. --- .../plugins/functions/print_result.py | 57 ++++++++++++++++--- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/nornir_utils/plugins/functions/print_result.py b/nornir_utils/plugins/functions/print_result.py index df64f10..d794fb9 100644 --- a/nornir_utils/plugins/functions/print_result.py +++ b/nornir_utils/plugins/functions/print_result.py @@ -1,7 +1,8 @@ import logging import pprint import threading -from typing import List, cast +from itertools import islice +from typing import List, cast, Optional from collections import OrderedDict import json @@ -21,7 +22,9 @@ def print_title(title: str) -> None: Helper function to print a title. """ msg = "**** {} ".format(title) - print("{}{}{}{}".format(Style.BRIGHT, Fore.GREEN, msg, "*" * (80 - len(msg)))) + print("{}{}{}{}".format( + Style.BRIGHT, Fore.GREEN, msg, "*" * (80 - len(msg))) + ) def _get_color(result: Result, failed: bool) -> str: @@ -47,7 +50,9 @@ def _print_individual_result( color = _get_color(result, failed) subtitle = ( - "" if result.changed is None else " ** changed : {} ".format(result.changed) + "" if result.changed is None else " ** changed : {} ".format( + result.changed + ) ) level_name = logging.getLevelName(result.severity_level) symbol = "v" if task_group else "-" @@ -82,6 +87,7 @@ def _print_result( failed: bool = False, severity_level: int = logging.INFO, print_host: bool = False, + count: Optional[int] = None, ) -> None: attrs = attrs or ["diff", "result", "stdout"] if isinstance(attrs, str): @@ -89,8 +95,22 @@ def _print_result( if isinstance(result, AggregatedResult): msg = result.name - print("{}{}{}{}".format(Style.BRIGHT, Fore.CYAN, msg, "*" * (80 - len(msg)))) - for host, host_data in sorted(result.items()): + msg = "{}{}{}{}".format( + Style.BRIGHT, Fore.CYAN, msg, "*" * (80 - len(msg)) + ) + result = dict(sorted(result.items())) + if count != 0: + print(msg) + if isinstance(count, int): + length = len(result) + if count >= 0: + _ = [0, length and count] + elif (length + count) < 0: + _ = [0, length] + else: + _ = [length + count, length] + result = dict(islice(result.items(), *_)) + for host, host_data in result.items(): title = ( "" if host_data.changed is None @@ -98,7 +118,9 @@ def _print_result( ) msg = "* {}{}".format(host, title) print( - "{}{}{}{}".format(Style.BRIGHT, Fore.BLUE, msg, "*" * (80 - len(msg))) + "{}{}{}{}".format( + Style.BRIGHT, Fore.BLUE, msg, "*" * (80 - len(msg)) + ) ) _print_result(host_data, attrs, failed, severity_level) elif isinstance(result, MultiResult): @@ -114,7 +136,9 @@ def _print_result( _print_result(r, attrs, failed, severity_level) color = _get_color(result[0], failed) msg = "^^^^ END {} ".format(result[0].name) - print("{}{}{}{}".format(Style.BRIGHT, color, msg, "^" * (80 - len(msg)))) + print("{}{}{}{}".format( + Style.BRIGHT, color, msg, "^" * (80 - len(msg))) + ) elif isinstance(result, Result): _print_individual_result( result, attrs, failed, severity_level, print_host=print_host @@ -126,18 +150,35 @@ def print_result( vars: List[str] = None, failed: bool = False, severity_level: int = logging.INFO, + print_host: bool = True, + count: Optional[int] = None, ) -> None: """ Prints an object of type `nornir.core.task.Result` Arguments: + result: from a previous task + vars: Which attributes you want to print + failed: if ``True`` assume the task failed + severity_level: Print only errors with this severity level or higher + + count: Number of sorted results. It's acceptable + to use numbers with minus sign(-5 as example), + then results will be from the end of results list """ LOCK.acquire() try: - _print_result(result, vars, failed, severity_level, print_host=True) + _print_result( + result, + vars, + failed=failed, + severity_level=severity_level, + print_host=print_host, + count=count, + ) finally: LOCK.release() From 6f7851cada60d5f6a1416f23563320dd724d9728 Mon Sep 17 00:00:00 2001 From: timeforplanb123 Date: Sun, 17 Oct 2021 21:13:37 +0300 Subject: [PATCH 2/5] Add new print_stat function: print_stat prints statistic for Result object --- README.rst | 1 + nornir_utils/plugins/functions/__init__.py | 13 +- nornir_utils/plugins/functions/print_stat.py | 126 +++++++++++++++++++ 3 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 nornir_utils/plugins/functions/print_stat.py diff --git a/README.rst b/README.rst index f5096ce..76bcb69 100644 --- a/README.rst +++ b/README.rst @@ -31,6 +31,7 @@ _________ * **print_result** - Formats nicely and prints results on stdout. * **print_title** - Formats nicely a title and prints it on stdout. +* **print_stat** - Prints result statistic on stdout. Processors __________ diff --git a/nornir_utils/plugins/functions/__init__.py b/nornir_utils/plugins/functions/__init__.py index a2acbf4..4b7d7a9 100644 --- a/nornir_utils/plugins/functions/__init__.py +++ b/nornir_utils/plugins/functions/__init__.py @@ -1,3 +1,12 @@ -from .print_result import print_result, print_title +from .print_result import ( + print_result, + print_title, +) -__all__ = ("print_result", "print_title") +from .print_stat import print_stat + +__all__ = ( + "print_result", + "print_title", + "print_stat", +) diff --git a/nornir_utils/plugins/functions/print_stat.py b/nornir_utils/plugins/functions/print_stat.py new file mode 100644 index 0000000..2b4d77e --- /dev/null +++ b/nornir_utils/plugins/functions/print_stat.py @@ -0,0 +1,126 @@ +import threading +from itertools import islice +from typing import Optional, Tuple +from colorama import Fore, Style, init +from nornir_utils.plugins.functions.print_result import _get_color +from nornir.core.task import AggregatedResult, MultiResult, Result + + +LOCK = threading.Lock() + +init(autoreset=True, strip=False) + + +def _print_individual_stat( + result: Result, + res_sum: int = 0, + ch_sum: int = 0, + f_sum: int = 0, +) -> Tuple[int, int, int]: + + f, ch = (result.failed, result.changed) + + res_sum += 1 + ch_sum += int(ch) + f_sum += int(f) + + color = _get_color(result, f) + msg = "{:<35} ok={:<15} changed={:<15} failed={:<15}".format( + result.name, not f, ch, f + ) + print(" {}{}{}".format(Style.BRIGHT, color, msg)) + return res_sum, ch_sum, f_sum + + +def _print_stat( + result: Result, + count: Optional[int] = None, + res_sum: int = 0, + ch_sum: int = 0, + f_sum: int = 0, +) -> Tuple[int, int, int]: + + if isinstance(result, AggregatedResult): + msg = result.name + msg = "{}{}{}{}".format(Style.BRIGHT, Fore.CYAN, msg, "*" * ( + 80 - len(msg)) + ) + + result = dict(sorted(result.items())) + + if count != 0: + print(msg) + if isinstance(count, int): + length = len(result) + if count >= 0: + _ = [0, length and count] + elif (length + count) < 0: + _ = [0, length] + else: + _ = [length + count, length] + result = dict(islice(result.items(), *_)) + + for host, host_data in result.items(): + msg_host = "* {} ".format(host) + print( + "{}{}{}{}".format(Style.BRIGHT, Fore.BLUE, msg_host, "*" * ( + 80 - len(msg_host)) + ) + ) + res_sum, ch_sum, f_sum = _print_stat( + host_data, + count, + res_sum, + ch_sum, + f_sum + ) + elif isinstance(result, MultiResult): + for res in result: + res_sum, ch_sum, f_sum = _print_stat( + res, + count, + res_sum, + ch_sum, + f_sum + ) + elif isinstance(result, Result): + res_sum, ch_sum, f_sum = _print_individual_stat( + result, + res_sum, + ch_sum, + f_sum + ) + + return res_sum, ch_sum, f_sum + + +def print_stat(result: Result, count: Optional[int] = None) -> None: + """ + Prints statistic for `nornir.core.task.Result` object + + Arguments: + + result: from a previous task + + count: Number of sorted results. It's acceptable + to use numbers with minus sign(-5 as example), + then results will be from the end of results list + """ + LOCK.acquire() + try: + res_sum, ch_sum, f_sum = _print_stat( + result, + count=count, + res_sum=0, + ch_sum=0, + f_sum=0, + ) + print() + for state, summary, color in zip( + ("OK", "CHANGED", "FAILED"), + (res_sum - f_sum, ch_sum, f_sum), + (Fore.GREEN, Fore.YELLOW, Fore.RED), + ): + print("{}{}{:<8}: {}".format(Style.BRIGHT, color, state, summary)) + finally: + LOCK.release() From 0bb264ca5574d03622813e3984d3d538f4b4685f Mon Sep 17 00:00:00 2001 From: timeforplanb123 Date: Wed, 20 Oct 2021 13:19:01 +0300 Subject: [PATCH 3/5] Change print_result.py add new count parameter to print the number of results. Useful for a lot of identical results --- .../plugins/functions/print_result.py | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/nornir_utils/plugins/functions/print_result.py b/nornir_utils/plugins/functions/print_result.py index d794fb9..dba43d6 100644 --- a/nornir_utils/plugins/functions/print_result.py +++ b/nornir_utils/plugins/functions/print_result.py @@ -2,7 +2,7 @@ import pprint import threading from itertools import islice -from typing import List, cast, Optional +from typing import List, cast, Optional, Dict, Any from collections import OrderedDict import json @@ -17,13 +17,36 @@ init(autoreset=True, strip=False) +def _slice_result( + result: AggregatedResult, + count: Optional[int] = None, +): + msg = result.name + msg = "{}{}{}{}".format( + Style.BRIGHT, Fore.CYAN, msg, "*" * (80 - len(msg)) + ) + results: Dict[Any, Any] = dict(sorted(result.items())) + if count != 0: + print(msg) + if isinstance(count, int): + length = len(results) + if count >= 0: + _ = [0, length and count] + elif (length + count) < 0: + _ = [0, length] + else: + _ = [length + count, length] + results = dict(islice(results.items(), *_)) + return results + + def print_title(title: str) -> None: """ Helper function to print a title. """ msg = "**** {} ".format(title) - print("{}{}{}{}".format( - Style.BRIGHT, Fore.GREEN, msg, "*" * (80 - len(msg))) + print( + "{}{}{}{}".format(Style.BRIGHT, Fore.GREEN, msg, "*" * (80 - len(msg))) ) @@ -50,9 +73,9 @@ def _print_individual_result( color = _get_color(result, failed) subtitle = ( - "" if result.changed is None else " ** changed : {} ".format( - result.changed - ) + "" + if result.changed is None + else " ** changed : {} ".format(result.changed) ) level_name = logging.getLevelName(result.severity_level) symbol = "v" if task_group else "-" @@ -94,22 +117,7 @@ def _print_result( attrs = [attrs] if isinstance(result, AggregatedResult): - msg = result.name - msg = "{}{}{}{}".format( - Style.BRIGHT, Fore.CYAN, msg, "*" * (80 - len(msg)) - ) - result = dict(sorted(result.items())) - if count != 0: - print(msg) - if isinstance(count, int): - length = len(result) - if count >= 0: - _ = [0, length and count] - elif (length + count) < 0: - _ = [0, length] - else: - _ = [length + count, length] - result = dict(islice(result.items(), *_)) + result = _slice_result(result, count) for host, host_data in result.items(): title = ( "" @@ -136,8 +144,8 @@ def _print_result( _print_result(r, attrs, failed, severity_level) color = _get_color(result[0], failed) msg = "^^^^ END {} ".format(result[0].name) - print("{}{}{}{}".format( - Style.BRIGHT, color, msg, "^" * (80 - len(msg))) + print( + "{}{}{}{}".format(Style.BRIGHT, color, msg, "^" * (80 - len(msg))) ) elif isinstance(result, Result): _print_individual_result( @@ -150,22 +158,16 @@ def print_result( vars: List[str] = None, failed: bool = False, severity_level: int = logging.INFO, - print_host: bool = True, count: Optional[int] = None, ) -> None: """ Prints an object of type `nornir.core.task.Result` Arguments: - result: from a previous task - vars: Which attributes you want to print - failed: if ``True`` assume the task failed - severity_level: Print only errors with this severity level or higher - count: Number of sorted results. It's acceptable to use numbers with minus sign(-5 as example), then results will be from the end of results list @@ -177,7 +179,7 @@ def print_result( vars, failed=failed, severity_level=severity_level, - print_host=print_host, + print_host=True, count=count, ) finally: From bbc9651932169e4b082a29b66c6ce11aaecf868c Mon Sep 17 00:00:00 2001 From: timeforplanb123 Date: Wed, 20 Oct 2021 13:59:42 +0300 Subject: [PATCH 4/5] Change print_result add new count parameter to print the number of results. Useful for a lot of identical results --- nornir_utils/plugins/functions/print_result.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nornir_utils/plugins/functions/print_result.py b/nornir_utils/plugins/functions/print_result.py index dba43d6..4bb2b37 100644 --- a/nornir_utils/plugins/functions/print_result.py +++ b/nornir_utils/plugins/functions/print_result.py @@ -21,13 +21,7 @@ def _slice_result( result: AggregatedResult, count: Optional[int] = None, ): - msg = result.name - msg = "{}{}{}{}".format( - Style.BRIGHT, Fore.CYAN, msg, "*" * (80 - len(msg)) - ) results: Dict[Any, Any] = dict(sorted(result.items())) - if count != 0: - print(msg) if isinstance(count, int): length = len(results) if count >= 0: @@ -117,6 +111,12 @@ def _print_result( attrs = [attrs] if isinstance(result, AggregatedResult): + msg = result.name + msg = "{}{}{}{}".format( + Style.BRIGHT, Fore.CYAN, msg, "*" * (80 - len(msg)) + ) + if count != 0: + print(msg) result = _slice_result(result, count) for host, host_data in result.items(): title = ( From 13b7cc5888e585d5d9d7a197e1648a58347f9d1a Mon Sep 17 00:00:00 2001 From: timeforplanb123 Date: Wed, 20 Oct 2021 14:09:47 +0300 Subject: [PATCH 5/5] Add print_stat function print_stat prints statistic for Result object --- nornir_utils/plugins/functions/print_stat.py | 39 ++++++-------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/nornir_utils/plugins/functions/print_stat.py b/nornir_utils/plugins/functions/print_stat.py index 8b4e70e..cc51826 100644 --- a/nornir_utils/plugins/functions/print_stat.py +++ b/nornir_utils/plugins/functions/print_stat.py @@ -1,7 +1,9 @@ import threading -from itertools import islice -from typing import Optional, Tuple, Dict, Any -from nornir_utils.plugins.functions.print_result import _get_color +from typing import Optional, Tuple +from nornir_utils.plugins.functions.print_result import ( + _get_color, + _slice_result, +) from colorama import Fore, Style, init @@ -14,29 +16,6 @@ init(autoreset=True, strip=False) -def _slice_result( - result: AggregatedResult, - count: Optional[int] = None, -): - msg = result.name - msg = "{}{}{}{}".format( - Style.BRIGHT, Fore.CYAN, msg, "*" * (80 - len(msg)) - ) - results: Dict[Any, Any] = dict(sorted(result.items())) - if count != 0: - print(msg) - if isinstance(count, int): - length = len(results) - if count >= 0: - _ = [0, length and count] - elif (length + count) < 0: - _ = [0, length] - else: - _ = [length + count, length] - results = dict(islice(results.items(), *_)) - return results - - def _print_individual_stat( result: Result, res_sum: int = 0, @@ -67,6 +46,12 @@ def _print_stat( ) -> Tuple[int, int, int]: if isinstance(result, AggregatedResult): + msg = result.name + msg = "{}{}{}{}".format( + Style.BRIGHT, Fore.CYAN, msg, "*" * (80 - len(msg)) + ) + if count != 0: + print(msg) result = _slice_result(result, count) for host, host_data in result.items(): msg_host = "* {} ".format(host) @@ -99,9 +84,7 @@ def print_stat(result: Result, count: Optional[int] = None) -> None: Prints statistic for `nornir.core.task.Result` object Arguments: - result: from a previous task - count: Number of sorted results. It's acceptable to use numbers with minus sign(-5 as example), then results will be from the end of results list