class helper: @staticmethod def list_of_dict_to_dataset(dict_data,dict_column): #dict_data and dict_column are lists of dictionaries import pprint #get keys in the first dictionary in the list #This is used to ensure only real fields are used in the column if len(dict_data) != 0 and len(dict_column) != 0: if isinstance(dict_data[0], dict) and isinstance(dict_column[0], dict): pass else: raise TypeError('Dictionary','The items in lists are not Dictionaries.') else: raise TypeError('List','List provided is empty') test = dict_data[0] pprint.pprint(test) d_keys = dict_data[0].keys() #create the column headers from provided dict_column #but only if the column name is used as a dict in the data. column_header = [] pprint.pprint(dict_column) for row in dict_column: if row['field'] in d_keys: column_header.append(row['field']) pprint.pprint(column_header) # create rows used as final list of lists for conversion to dataset rows = [] for di in dict_data: #create individual row row = [] for column in column_header: if di.has_key(column): row.append(di[column]) else: row.append('') #append each row to rows so all data is captured in a list of lists. rows.append(row) #screate dataset from header and rows of data dataset = system.dataset.toDataSet(column_header,rows) return({'basic_dataset':dataset}) @staticmethod def sanitize_tree(element): """ Arguments: element: array/list or dict to be sanitized (remove Java "object wrappers") Returns: element: sanitized input array/list/dict Usage: from helper.helper import sanitize_tree sanitizedTableData = sanitize_tree(myTable.props.data) ## This function loops recursively over a component property and converts all ## siblings and children to base elements """ if hasattr(element, '__iter__'): if hasattr(element, 'keys'): return dict((helper.string_decode(k), helper.sanitize_tree(helper.string_decode(element[k]))) for k in element.keys()) else: return list(helper.sanitize_tree(helper.string_decode(x)) for x in element) return element @staticmethod def dataset_to_dict(dataset): #provide a basicdataset as input #verify basic dataset was passed in string_type = str(type(dataset)) if string_type != "": raise TypeError('Dataset','expected BaiscDataset type received %s'%(string_type)) else: raw_dataset = system.dataset.toPyDataSet(dataset) #convert from PyDataSet to list of lists and header names. headers = [((str(col).replace(' ','')).replace('_','')) for col in raw_dataset.getColumnNames()] raw_dataset = [[col for col in row] for row in raw_dataset] #create needed empty lists to put dictionaries in col_data = [] data_data = [] #create column names for column field of a table for col in headers: col_data.append({'field':col, 'visible': True, 'editable':False, 'render':'auto', 'justify':'auto', 'align':'center', 'resizable':True, 'sortable':True, 'sort':'none', 'boolean':'checkbox', 'number':'value', 'numberFormat': '0,0.##', 'dateFormat':'MM/DD/YYYY' }) #Create Data dictionaries used for data field of a table data_counter = 0 for row in raw_dataset: data = {} for col_num in range(len(row)): data[headers[col_num]] = row[col_num] data_data.append(data) data_counter = data_counter+1 #pass out list of dictionary, list of dictionary, string return({'data':data_data,'column':col_data}) @staticmethod def xyChartTransform(ds=None, columns=None): ## This function is for use with the XY chart in Perspective ## This component requires an array of dictionaries for the chart data structure, rather than an Ignition dataset ## Each dict item should have the format of: {'Column_X': valueX, 'Column_Y': valueY} ## ds = Ignition dataset, such as that returned by a SQL query binding ## columns = list of column names to be included in the array of dicts returned by function. ## NOTE: the first column name listed will be the X axis ## If no column list passed in, the function will grab all names from the dataset ## NOTE: This function will return every column/value in the dataset as a dict key rows = [] if ds is None: return rows ## return empty array if no dataset passed in try: data = system.dataset.toPyDataSet(ds) ## convert to python dataset if columns is None: columns = system.dataset.getColumnHeaders(ds) ## extract column names from dataset for row in data: ## Loop over python dataset d = {} ## initialize empty dictionary for each row for c in columns: ## loop over column list d[c] = row[c] ## add key for each column as key and assign value rows.append(d) ## append new row return rows except: return rows @staticmethod def string_decode(v): """ Arguments: v: value to be decoded (ignore if not unicode string) Returns: v: decoded value Usage: from helper.helper import string_decode my_unicode_string = (u'hello world') decoded_string = string_decode(my_unicode_string) ## This function is a helper for the sanitize_tree function, normally """ # vscode pylint v2.7.* will complain about this but it works, also works in Ignition v.8.1.* if isinstance(v, unicode): # replace any nasty em- or en-dashes with a more sane '-' short dash # this should avoid most of the nested try:except: blocks below if u'\u2013' in v: v = v.replace(u'\u2013', '-') try: v = str(v) except: import traceback from loggerConfig import getLogger logger = getLogger('sanitize_tree_string_decode') logger.warn(traceback.format_exc()) try: v = v.encode('utf-8') v = str(v) except: logger.warn(traceback.format_exc()) try: v.encode('ascii', 'ignore') except: logger.warn(traceback.format_exc()) try: v = repr(v) except: logger.warn(traceback.format_exc()) return v return v @staticmethod def centerJustifyTableColumns(columns=[]): """ ## This method takes a table column config object (array of dicts) and returns it with all columns and their headers set to center justified Arguments: columns: [array of dict] table column configuration Returns: columns: [array of dict] table column config, with all columns/headers center justified Usage: from helper.helper import centerJustifyTableColumns myTable.props.columns = centerJustifyTableColumns(myTable.props.columns) """ try: for column in columns: try: column['justify'] = 'center' column['header']['justify'] = 'center' except: continue except: pass return columns @staticmethod def get_dropdown_options_from_dataset(ds=None, value_column=None, label_column=None): """ This method takes an ignition dataset object, and a name or index for "value" and "label" columns and returns an array of dict objects for each row to use in the binding of perspective dropdown "options" prop. Arguments: ds: Ignition dataset object, ie from named SQL query value_column: [string or integer] if string, the name of the column to represent the value when option is selected. if integer, the column index for the value label_column: [string or integer] if string, the name of the column to represent the label (display) for option selection. if integer, the column index for the label Returns: [{ value: value to be assigned to dropdown "value" property when options selection label: value to be displayed in the dropdown for each option }] Usage: # in a script transform on the dropdown.props.options binding from helper.helper import get_dropdown_options_from_dataset options = get_dropdown_options_from_dataset( ds=value, # from property/query binding above this transform value_column='my_value_column_name', label_column='my_label_column_name ) return options """ # Check required arguments for null values if ds is None: msg = 'No dataset passed in' return {'error': msg} if value_column is None: msg = 'No value_column name or index passed in' return {'error': msg} if label_column is None: msg = 'No label_column name or index passed in' return {'error': msg} # convert the ignition dataset to python dataset to iterate over try: data = system.dataset.toPyDataSet(ds) except: import traceback msg = 'Error converting dataset to python data: %s' % traceback.format_exc() return {'error': msg} # grab the column headers from input dataset headers = system.dataset.getColumnHeaders(ds) # verify both the value and label columns are in the list of column headers, if passed in as strings # if passed in as integer indexes, make sure they are valid if isinstance(value_column, str): if value_column not in headers: msg = 'value_column (%s) not in dataset column headers!' % value_column return {'error': msg} elif isinstance(value_column, int): if value_column not in range(len(headers)): msg = 'value_column index (%d) not valid!' % value_column return {'error': msg} else: # if not string or integer, invalid type msg = 'invalid type for value_column (%s). Must be integer or string' % type(value_column) return {'error': msg} if isinstance(label_column, str): if label_column not in headers: msg = 'label_column (%s) not in dataset column headers!' % label_column return {'error': msg} elif isinstance(label_column, int): if label_column not in range(len(headers)): msg = 'label_column index (%d) not valid!' % label_column return {'error': msg} else: # if not string or integer, invalid type msg = 'invalid type for label_column (%s). Must be integer or string' % type(label_column) return {'error': msg} # if passed all verification checks, build array of objects representing label/value pairs for each dataset row options = [{'value': row[value_column], 'label': row[label_column]} for row in data] return options @staticmethod def keys_exists(element, *keys): ''' Check if *keys (nested) exists in `element` (dict). ''' if not isinstance(element, dict): raise AttributeError('keys_exists() expects dict as first argument.') if len(keys) == 0: raise AttributeError('keys_exists() expects at least two arguments, one given.') _element = element for key in keys: try: _element = _element[key] except KeyError: return False return True