| #!/usr/bin/env python3 |
| |
| r""" |
| Define the tally_sheet class. |
| """ |
| |
| import collections |
| import copy |
| import re |
| import sys |
| |
| try: |
| from robot.utils import DotDict |
| except ImportError: |
| pass |
| |
| import gen_print as gp |
| |
| |
| class tally_sheet: |
| r""" |
| This class is the implementation of a tally sheet. |
| The sheet can be viewed as rows and columns. Each |
| row has a unique key field. |
| |
| This class provides methods to tally the results (totals, etc.). |
| |
| Example code: |
| |
| # Create an ordered dict to represent your field names/initial values. |
| try: |
| boot_results_fields = collections.OrderedDict([('total', 0), ('pass', 0), ('fail', 0)]) |
| except AttributeError: |
| boot_results_fields = DotDict([('total', 0), ('pass', 0), ('fail', 0)]) |
| # Create the tally sheet. |
| boot_test_results = tally_sheet('boot type', boot_results_fields, 'boot_test_results') |
| # Set your sum fields (fields which are to be totalled). |
| boot_test_results.set_sum_fields(['total', 'pass', 'fail']) |
| # Set calc fields (within a row, a certain field can be derived from other fields in the row. |
| boot_test_results.set_calc_fields(['total=pass+fail']) |
| |
| # Create some records. |
| boot_test_results.add_row('BMC Power On') |
| boot_test_results.add_row('BMC Power Off') |
| |
| # Increment field values. |
| boot_test_results.inc_row_field('BMC Power On', 'pass') |
| boot_test_results.inc_row_field('BMC Power Off', 'pass') |
| boot_test_results.inc_row_field('BMC Power On', 'fail') |
| # Have the results tallied... |
| boot_test_results.calc() |
| # And printed... |
| boot_test_results.print_report() |
| |
| Example result: |
| |
| Boot Type Total Pass Fail |
| ----------------------------------- ----- ---- ---- |
| BMC Power On 2 1 1 |
| BMC Power Off 1 1 0 |
| =================================================== |
| Totals 3 2 1 |
| |
| """ |
| |
| def __init__( |
| self, |
| row_key_field_name="Description", |
| init_fields_dict=dict(), |
| obj_name="tally_sheet", |
| ): |
| r""" |
| Create a tally sheet object. |
| |
| Description of arguments: |
| row_key_field_name The name of the row key field (e.g. boot_type, team_name, etc.) |
| init_fields_dict A dictionary which contains field names/initial values. |
| obj_name The name of the tally sheet. |
| """ |
| |
| self.__obj_name = obj_name |
| # The row key field uniquely identifies the row. |
| self.__row_key_field_name = row_key_field_name |
| # Create a "table" which is an ordered dictionary. |
| # If we're running python 2.7 or later, collections has an OrderedDict |
| # we can use. Otherwise, we'll try to use the DotDict (a robot library). |
| # If neither of those are available, we fail. |
| try: |
| self.__table = collections.OrderedDict() |
| except AttributeError: |
| self.__table = DotDict() |
| # Save the initial fields dictionary. |
| self.__init_fields_dict = init_fields_dict |
| self.__totals_line = init_fields_dict |
| self.__sum_fields = [] |
| self.__calc_fields = [] |
| |
| def init( |
| self, row_key_field_name, init_fields_dict, obj_name="tally_sheet" |
| ): |
| self.__init__( |
| row_key_field_name, init_fields_dict, obj_name="tally_sheet" |
| ) |
| |
| def set_sum_fields(self, sum_fields): |
| r""" |
| Set the sum fields, i.e. create a list of field names which are to be |
| summed and included on the totals line of reports. |
| |
| Description of arguments: |
| sum_fields A list of field names. |
| """ |
| |
| self.__sum_fields = sum_fields |
| |
| def set_calc_fields(self, calc_fields): |
| r""" |
| Set the calc fields, i.e. create a list of field names within a given |
| row which are to be calculated |
| for the user. |
| |
| Description of arguments: |
| calc_fields A string expression such as 'total=pass+fail' |
| which shows which field on a given row is |
| derived from other fields in the same row. |
| """ |
| |
| self.__calc_fields = calc_fields |
| |
| def add_row(self, row_key, init_fields_dict=None): |
| r""" |
| Add a row to the tally sheet. |
| |
| Description of arguments: |
| row_key A unique key value. |
| init_fields_dict A dictionary of field names/initial values. |
| The number of fields in this dictionary must |
| be the same as what was specified when the |
| tally sheet was created. If no value is passed, |
| the value used to create the tally sheet will |
| be used. |
| """ |
| |
| if row_key in self.__table: |
| # If we allow this, the row values get re-initialized. |
| message = 'An entry for "' + row_key + '" already exists in' |
| message += " tally sheet." |
| raise ValueError(message) |
| if init_fields_dict is None: |
| init_fields_dict = self.__init_fields_dict |
| try: |
| self.__table[row_key] = collections.OrderedDict(init_fields_dict) |
| except AttributeError: |
| self.__table[row_key] = DotDict(init_fields_dict) |
| |
| def update_row_field(self, row_key, field_key, value): |
| r""" |
| Update a field in a row with the specified value. |
| |
| Description of arguments: |
| row_key A unique key value that identifies the row to |
| be updated. |
| field_key The key that identifies which field in the row |
| that is to be updated. |
| value The value to set into the specified row/field. |
| """ |
| |
| self.__table[row_key][field_key] = value |
| |
| def inc_row_field(self, row_key, field_key): |
| r""" |
| Increment the value of the specified field in the specified row. |
| The value of the field must be numeric. |
| |
| Description of arguments: |
| row_key A unique key value that identifies the row to |
| be updated. |
| field_key The key that identifies which field in the row |
| that is to be updated. |
| """ |
| |
| self.__table[row_key][field_key] += 1 |
| |
| def dec_row_field(self, row_key, field_key): |
| r""" |
| Decrement the value of the specified field in the specified row. |
| The value of the field must be |
| numeric. |
| |
| Description of arguments: |
| row_key A unique key value that identifies the row to |
| be updated. |
| field_key The key that identifies which field in the row |
| that is to be updated. |
| """ |
| |
| self.__table[row_key][field_key] -= 1 |
| |
| def calc(self): |
| r""" |
| Calculate totals and row calc fields. Also, return totals_line dictionary. |
| """ |
| |
| self.__totals_line = copy.deepcopy(self.__init_fields_dict) |
| # Walk through the rows of the table. |
| for row_key, value in self.__table.items(): |
| # Walk through the calc fields and process them. |
| for calc_field in self.__calc_fields: |
| tokens = [i for i in re.split(r"(\d+|\W+)", calc_field) if i] |
| cmd_buf = "" |
| for token in tokens: |
| if token in ("=", "+", "-", "*", "/"): |
| cmd_buf += token + " " |
| else: |
| # Note: Using "mangled" name for the sake of the exec |
| # statement (below). |
| cmd_buf += ( |
| "self._" |
| + self.__class__.__name__ |
| + "__table['" |
| + row_key |
| + "']['" |
| + token |
| + "'] " |
| ) |
| exec(cmd_buf) |
| |
| for field_key, sub_value in value.items(): |
| if field_key in self.__sum_fields: |
| self.__totals_line[field_key] += sub_value |
| |
| return self.__totals_line |
| |
| def sprint_obj(self): |
| r""" |
| sprint the fields of this object. This would normally be for debug purposes. |
| """ |
| |
| buffer = "" |
| |
| buffer += "class name: " + self.__class__.__name__ + "\n" |
| buffer += gp.sprint_var(self.__obj_name) |
| buffer += gp.sprint_var(self.__row_key_field_name) |
| buffer += gp.sprint_var(self.__table) |
| buffer += gp.sprint_var(self.__init_fields_dict) |
| buffer += gp.sprint_var(self.__sum_fields) |
| buffer += gp.sprint_var(self.__totals_line) |
| buffer += gp.sprint_var(self.__calc_fields) |
| buffer += gp.sprint_var(self.__table) |
| |
| return buffer |
| |
| def print_obj(self): |
| r""" |
| print the fields of this object to stdout. This would normally be for debug purposes. |
| """ |
| |
| sys.stdout.write(self.sprint_obj()) |
| |
| def sprint_report(self): |
| r""" |
| sprint the tally sheet in a formatted way. |
| """ |
| |
| buffer = "" |
| # Build format strings. |
| col_names = [self.__row_key_field_name.title()] |
| report_width = 40 |
| key_width = 40 |
| format_string = "{0:<" + str(key_width) + "}" |
| dash_format_string = "{0:-<" + str(key_width) + "}" |
| field_num = 0 |
| |
| try: |
| first_rec = next(iter(self.__table.items())) |
| for row_key, value in first_rec[1].items(): |
| field_num += 1 |
| if isinstance(value, int): |
| align = ":>" |
| else: |
| align = ":<" |
| format_string += ( |
| " {" + str(field_num) + align + str(len(row_key)) + "}" |
| ) |
| dash_format_string += ( |
| " {" + str(field_num) + ":->" + str(len(row_key)) + "}" |
| ) |
| report_width += 1 + len(row_key) |
| col_names.append(row_key.title()) |
| except StopIteration: |
| pass |
| num_fields = field_num + 1 |
| totals_line_fmt = "{0:=<" + str(report_width) + "}" |
| |
| buffer += format_string.format(*col_names) + "\n" |
| buffer += dash_format_string.format(*([""] * num_fields)) + "\n" |
| for row_key, value in self.__table.items(): |
| buffer += format_string.format(row_key, *value.values()) + "\n" |
| |
| buffer += totals_line_fmt.format("") + "\n" |
| buffer += ( |
| format_string.format("Totals", *self.__totals_line.values()) + "\n" |
| ) |
| |
| return buffer |
| |
| def print_report(self): |
| r""" |
| print the tally sheet in a formatted way. |
| """ |
| |
| sys.stdout.write(self.sprint_report()) |