Example #1
0
class Setting:
    """
    console input
    hosts TestCases lists, gateToDatabase
    operation, operation_type
    """
    __selectedTypeIdList = list()  # for tree structure (types, cases) - to know selected type when cases are selected

    def __init__(self, type_list, case_list, configuration_list, operation_type,
                 operation, test_mode, db_inst):
    
        self.__item_constituents = ItemConstituents(type_list, case_list, configuration_list)
        self.__operation_type = operation_type  # building or testing
        self.__operation = operation
        self.__test_mode = test_mode  # 0: with shell ,
        # 1: control via browser (case has stage in database), 2: with CI
        self.__db_inst = db_inst
        self.__gateToDatabase = None
        
    def __del__(self):
        pass

    def print_selected_variables(self):
        """
        print variables which have already been selected, e.g. to print preselected values
        meaning they have not value None or [None] (for type, configuration) or [[None]] (for case)
        the variables are: type, case, configuration, operation_type, operation
        (member variables of class Subject can be printed via separate Subject member function)
        :return:
        """
        if self.__item_constituents.type_list[0]:
            for i in range(0, len(self.__item_constituents.type_list)):
                message(mode='INFO', text='Set type {}'.format(self.__item_constituents.type_list[i]))
        if self.__item_constituents.case_list[0][0]:
            for i in range(0, len(self.__item_constituents.case_list)):
                for j in range(0, len(self.__item_constituents.case_list[i])):
                    message(mode='INFO', text='Set case {}'.format(self.__item_constituents.case_list[i][j]))
        if self.__item_constituents.configuration_list[0]:
            for i in range(0, len(self.__item_constituents.configuration_list)):
                message(mode='INFO', text='Set configuration {}'.format(self.__item_constituents.configuration_list[i]))

        if self.__operation_type:
            message(mode='INFO', text='Set operation type {}'.format(self.__operation_type))
        if self.__operation:
            message(mode='INFO', text='Set operation {}'.format(self.__operation))

    def connect_to_db(self):
        message(mode='INFO', text='Connect {} to database {} at {}'.format(
            self.__db_inst.user, self.__db_inst.schema, self.__db_inst.host))

        self.__gateToDatabase = GateToDatabase(self.__db_inst)

    def disconnect_from_db(self):
        # message(mode='INFO', text='Disconnecting')
        del self.__gateToDatabase

    @property
    def item_constituents(self):
        return self.__item_constituents

    @property
    def operation_type(self):
        return self.__operation_type

    @property
    def operation(self):
        return self.__operation

    @operation.setter
    def operation(self, value):
        self.__operation = value

    @property
    def test_mode(self):
        return self.__test_mode

    def query_flow_process_name_list(self, case_name):
        """
        :param case_name: (string)
        :return: (string list)
        """
        name_list = list()

        numerics_id_list = self.__gateToDatabase.query_ids_from_column_entries(
            'numerics', 'case_id', self.__gateToDatabase.query_id_for_name('cases', case_name))

        flow_process_id_list = list()

        for numerics_id in numerics_id_list:
            flow_process_id = self.__gateToDatabase.query_column_entry(
                'numerics', numerics_id, 'flow_process_id')
            if not is_in_list(flow_process_id, flow_process_id_list):
                flow_process_id_list.append(flow_process_id)
                name_list.append(self.__gateToDatabase.query_name_for_id('flow_processes', flow_process_id))
        return name_list

    def query_element_type_name_list(self, case_name, flow_process_name_list):
        """

        :param case_name:
        :param flow_process_name_list:
        :return:
        """
        name_nested_list = [[]]
        name_nested_list.clear()

        numerics_id_list = self.__gateToDatabase.query_ids_from_column_entries(
            'numerics', 'case_id', self.__gateToDatabase.query_id_for_name('cases', case_name))

        for flow_process_name in flow_process_name_list:
            name_inner_list = list()
            for numerics_id in numerics_id_list:
                running_flow_process_name = self.__gateToDatabase.query_name_for_id(
                    'flow_processes', self.__gateToDatabase.query_column_entry(
                        'numerics', numerics_id, 'flow_process_id'))
                if flow_process_name == running_flow_process_name:
                    flow_process_id = self.__gateToDatabase.query_column_entry(
                        'numerics', numerics_id, 'element_type_id')
                    name_inner_list.append(self.__gateToDatabase.query_name_for_id('element_types', flow_process_id))
            name_nested_list.append(name_inner_list)

        return name_nested_list

    def query_location(self, computer_name):
        """
        query location (local or remote) from computer table for given computer name
            by first querying computer id
        :param computer_name: (string)
        :return: (string) location
        """
        return self.__gateToDatabase.query_column_entry(
            'computer', self.__gateToDatabase.query_id_for_name('computer', computer_name), 'location')

    def query_operating_system(self, computer_name):
        """
        query an operating system from computer table for a given conputer name
            by first querying computer_id
        :param computer_name:
        :return:
        """
        return self.__gateToDatabase.query_column_entry(
            'computer', self.__gateToDatabase.query_id_for_name('computer', computer_name), 'operating_system')
 
    def query_directory_root(self, computer_name, user_name):
        """
        query a root directory from paths table for a given computer and user name
            by querying first computer and user id
        :param computer_name: (string)
        :param user_name: (string)
        :return: (string) root directory
        """
        return self.__gateToDatabase.query_directory_root(
            self.__gateToDatabase.query_id_for_name('computer', computer_name),
            self.__gateToDatabase.query_id_for_name('users', user_name))

    def query_hostname(self, computer_name):
        """
        query a hostname from computer table for a given computer name
            by querying first computer id from computer name
        :param computer_name: (string)
        :return: (string) hostname
        """
        return self.__gateToDatabase.query_column_entry(
            'computer', self.__gateToDatabase.query_id_for_name('computer', computer_name), 'hostname')
    
    def query_username(self, superuser_name, computer_name):
        """
        query a username for a given superuser and computer_name
            by first querying user id from superuser table
        :param superuser_name: (string)
        :param computer_name: (string)
        :return: (string ) username
        """
        return self.__gateToDatabase.query_name_for_id(
            'users', self.__gateToDatabase.query_userid(superuser_name, computer_name))
                                                      
    def query_column_entry_for_name(self, table, name, column_name):
        """
        query a column entry from table for a name
            by first querying case id
        :param table: (string)
        :param name: (string)
        :param column_name: (string)
        :return: (string) column entry
        """
        return self.__gateToDatabase.query_column_entry(table, self.__gateToDatabase.query_id_for_name(
            table, name), column_name)

    def query_process_numerics_data(self, numerics_type, process, item_case, item_configuration='no_configuration'):
        """
        query entry for solver_dir, preconditioner_dir, or theta_dir from cases table for a process and configuration
        :param numerics_type: (string )[solver, preconditioner, theta]
        :param process: (string) [flow, mass, heat]
        :param item_case: (string)
        :param item_configuration: (string) 'no_configuration' for numerics_type theta
        :return:
        """
        if item_configuration == 'no_configuration':
            column_name_in_cases_table = '{}_{}'.format(numerics_type, process)
        else:
            column_name_in_cases_table = '{}_{}_{}'.format(numerics_type, process, item_configuration)

        column_entry = self.__gateToDatabase.query_column_entry(
            'cases', self.__gateToDatabase.query_id_for_name('cases', item_case), column_name_in_cases_table)

        if numerics_type == 'solver' or numerics_type == 'preconditioner':
            return self.convert_numerics_entry_in_cases_table_to_specification(
                item_configuration, column_entry, numerics_type)
        else:  # theta
            return column_entry

    def select_operation_type(self):
        """
        prepare dictionary [building, simulating, plotting] for self.select_from_options() and call it
        :return: ((one-character) tring) selected operation type
        """
        if not self.__operation_type:
            operation_type_dict = OrderedDict([('b', '(b)uilding'), ('s', '(s)imulating'), ('p', '(p)lotting')])
            self.__operation_type = select_from_options(operation_type_dict, 'Select operation type')
        return self.__operation_type

    def reselect(self, subject):
        """
        prepare dictionary [operation type, computer, ...] for self.select_from_options() and call it
        call self.neutralize_previous_selections() to set previous settings to None
        this condition (value is None) lets user reselect values in functions select_*
        :param subject: (class Subject) stores variables computer, user, code, branch
        :return:
        """
        option_dict = OrderedDict([('p', 'o(p)eration type'), ('c', '(c)omputer'), ('o', 'c(o)de'),
                                   ('b', '(b)ranch'), ('n', 'co(n)figuration')])
        if self.__operation_type == 's' or self.__operation_type == 'p':
            option_dict_amendments = {'e': '(e)xample', 't': '(t)ype', 'a': 'c(a)se'}
            option_dict.update(option_dict_amendments)
        option_selected = select_from_options(option_dict, 'Select')
        self.neutralize_previous_selections(subject, option_selected)

    def neutralize_previous_selections(self, subject, option_selected):
        """
        1.  set previously selected entities in subject, self.item,
            or self.__operation type to None (case_list to [' '])
        2.  sets always operation None such that it must reselected, too
        used by self.reselect(), which provides the selected option

        :param subject: (Subject)
        :param option_selected: (one-char string)
        :return:
        """
        if option_selected == 'c':  # computer
            subject.computer = None
            subject.user = None  # user must be reselected, too
            self.__item_constituents.configuration_list = [None]  # also configurations since they depend on computer
        if option_selected == 'o':  # code
            subject.code = None
            subject.branch = None  # branch must be reselected, too
        if option_selected == 'b':  # branch
            subject.branch = None
        if option_selected == 't' or option_selected == 'e':  # type | example
            self.__item_constituents.type_list = [None]
            self.__item_constituents.case_list = [[None]]  # case must be reselected, too
            self.__selectedTypeIdList.clear()
        if option_selected == 'a' or option_selected == 'e':  # case | example
            self.__item_constituents.case_list = [[None]]
        if option_selected == 'n' or option_selected == 'e':  # configuration | example
            self.__item_constituents.configuration_list = [None]
        if option_selected == 'p':  # operation type
            self.__operation_type = None

        self.__operation = 'reselect'  # operation is checked for this value 'reselect'
        #                                after all selections are made in next loop
        #                                this way, the intended reselection is before the next process selection

    def get_name_list(self, table, computer=None):
        """
        1. get table data from data base (an item consists of the data base entries id and name)
        2. use the ids to let the user select an id (or ids for tables type, case, configurations)
        3. derive name(s) from id(s)
        :param table: (string) name of table in SQL schema
        :param computer: (string) name of computer
        :return:  names (list of strings, nested list if table = 'cases')
                  for a table obtained from database and user selection
                  contains one element except if table is type, case, configuration, where name_list can contain
                  more elements
        """
        lists_inst = ListsCollection()

        self.get_table_data_from_database(table, computer, lists_inst)
        if len(lists_inst.name) > 0:  # for table cases if in table types all or range  was selected
            return lists_inst.name  # know already names, so return them directly

        selected_id = self.select_id(table, lists_inst)
        name_list = self.id2name(table, selected_id, lists_inst)

        del lists_inst
        return name_list

    def get_table_data_from_database(self, table, computer, lists_inst):
        """
        fill lists_inst.id_name_pair with data from database
        each entry is dictionary of form {'id': ..., 'name': ... }
        if table is cases and more than one type selected, fill lists_inst.name directly
        since select_id for case not required
        class member to have access to __selectedTypeIdList
        :param table: (string)
        :param computer: (string) used since nao all configurations available on all computer
        :param lists_inst: (class listsCollection)
        :return:
        """
        if table == 'cases':  # has sublist for each type (nested list)
            if len(self.__selectedTypeIdList) > 1:  # more than one type was selected
                for typeId in self.__selectedTypeIdList:
                    lists_inst.id_name_pair = self.__gateToDatabase.query_id_name_pair_list(
                        'cases', 'a', selected_type_id=typeId)
                    for row in lists_inst.id_name_pair:
                        lists_inst.nameSub.append(str(row['name']))
                    lists_inst.name.append(deepcopy(lists_inst.nameSub))
                    # append to name to return this directly (without select_id)
                    lists_inst.nameSub.clear()
            else:  # cases for specific type
                lists_inst.id_name_pair = self.__gateToDatabase.query_id_name_pair_list(
                    'cases', 'a', selected_type_id=self.__selectedTypeIdList[0])
        elif table == 'configurations':  # since it depends on computer
            lists_inst.id_name_pair = self.__gateToDatabase.query_id_name_pair_list(
                'configurations', 'a', self.__gateToDatabase.query_id_for_name('computer', computer))
        else:  # computer, user, ... (anything but cases, configurations - see list in environment constructor)
            lists_inst.id_name_pair = self.__gateToDatabase.query_id_name_pair_list(table, 'a')
       
    def select_id(self, table, lists_inst):
        """
        print options on console
        fill lists_inst.nameSub with names from id-name-pairs in lists_inst
        recalls itself if not proper user input
        Requirements:
            id_name_pairs in lists_inst must be set
        :param table:
        :param lists_inst:
        :return:
        """
        id_name = dict() # use dict to print ids sorted in table where id is selected
        for row in lists_inst.id_name_pair:
            lists_inst.nameSub.append(deepcopy(str(row['name'])))
            lists_inst.id.append(deepcopy(str(row['id'])))
            id_name.update({deepcopy(str(row['id'])): deepcopy(str(row['name']))})

        id_name_sorted = OrderedDict(sorted(id_name.items(), key=lambda t: int(t[0])))
        if self.__test_mode == '0':
            print('\nSelect from {}:\n'.format(table))
            for key, value in id_name_sorted.items():
                print('    {} {}'.format(key, value))
            print('   a all')
            if table == 'types':  # range only for types supported
                print('   r range')
            # select
            #
            selected_id = input('\n   by entering value: ')
        else:
            # TESTLEVEL: preselect anything but examples (types, cases, configurations)
            # and specify state (for jsp) or level (for CI) in database
            # all examples are selected here and level is checked later on
            selected_id = 'a'

        if table == 'types':
            self.set_type_ids(selected_id, lists_inst)
        print('\n-----------------------------------------------------------------')

        if not string_represents_non_negative_number_or_potentially_valid_character(selected_id):
            return self.select_id(table, lists_inst)
        return selected_id

    def set_type_ids(self, selected_id, lists_inst):
        """
        set self.__selectedTypeIdList
        :param selected_id: (string)
        :param lists_inst: (string list)
        :return:
        """
        if selected_id == 'a':  # selected all
            for i in range(0, len(lists_inst.id)):
                self.__selectedTypeIdList.append(deepcopy(lists_inst.id[i]))
        elif selected_id == 'r':  # selected range - so get the lower and upper range limits now
            lower_range = input('\n       From: ')
            upper_range = input('\n         To: ')
            for i in range(int(lower_range), int(upper_range) + 1):
                self.__selectedTypeIdList.append(deepcopy(lists_inst.id[i]))
        else:
            self.__selectedTypeIdList.append(deepcopy(selected_id))

    def id2name(self, table, id_selected, lists_inst):
        """
        Convert id to name (list) and return this as a nested list [['...']]
        class member to have access to __selectedTypeIdList
        :param table:
        :param id_selected:
        :param lists_inst:
        :return:
        """
        if id_selected == 'a':
            if table == 'cases':
                lists_inst.name.append(deepcopy(lists_inst.nameSub))
                return lists_inst.name
            else:
                return lists_inst.nameSub
        elif id_selected == 'r':
            if table == 'types':
                for id_running in self.__selectedTypeIdList:
                    lists_inst.name.append(deepcopy(lists_inst.nameSub[get_list_id(id_running, lists_inst.id)]))
                return lists_inst.name
            else:
                message(mode='ERROR', text='Range supported only for types')
                return None
        else:  # single item
            lists_inst.name.append(deepcopy(lists_inst.nameSub[get_list_id(id_selected, lists_inst.id)]))
            if table == 'cases':
                name_list2 = list()
                name_list2.append(deepcopy(lists_inst.name))
                return name_list2
            else:
                return lists_inst.name
    
    def select_constituent_of_items_to_test(self, level, computer_of_subject=None):
        """
        calls member function selectNames to get item constituents
        if they are not previously selected (i.e. in environment constructor or anywhere in last operations)
        :param level:
        :param computer_of_subject:
        :return: list (string) for one item  constituent (meaning: types, cases, configuration)
              nested list if table = 'cases'
        """
        if level == 'types':
            if self.__item_constituents.type_list == [None]:
                # len(self.__item_constituents.type_list) == 0 or not self.__item_constituents.type_list[0]:
                return self.get_name_list(table='types')
            else:
                return self.__item_constituents.type_list
        elif level == 'cases':
            if self.__item_constituents.case_list == [[None]]:
                # len(self.__item_constituents.case_list) == 0 or self.__item_constituents.case_list[0] == [None]:
                return self.get_name_list(table='cases')
            else:
                return self.__item_constituents.case_list
        elif level == 'configurations':
            if self.__item_constituents.configuration_list == [None]:
                # len(self.__item_constituents.configuration_list) == 0 \
                #    or not self.__item_constituents.configuration_list[0]:
                return self.get_name_list(table='configurations', computer=computer_of_subject)
            else:
                return self.__item_constituents.configuration_list
        else:
            message(mode='ERROR', not_supported='level ' + level)

    def convert_numerics_entry_in_cases_table_to_specification(self, item_configuration, entry, numerics_type):
        """
        :param item_configuration:
        :param entry:
        :param numerics_type: 'solver' or 'preconditioner'
        :return:
        """
        try:
            if entry != 'null':
                solver_table_name = self.__gateToDatabase.query_column_entry(
                    'configurations', self.__gateToDatabase.query_id_for_name('configurations', item_configuration),
                    numerics_type + '_table_name')
                return self.__gateToDatabase.query_column_entry(solver_table_name, entry, 'specification')
            else:
                return '-1'
        except Exception as err:
            message(mode='ERROR', text="{}".format(err))

    def select_items_to_test(self, operation_type, operation, computer):
        """
        generate examples Lists to loop over in Environment run
        :param operation_type:
        :param operation:
        :param computer:
        :return:
        """
        if operation_type == 'b':  # building (only configuration)
            self.__item_constituents.type_list = [None]
            self.__item_constituents.case_list = [[None]]
            self.__item_constituents.configuration_list = self.select_constituent_of_items_to_test(
                level='configurations', computer_of_subject=computer)
        elif operation_type == 's':  # simulating (all item constituents (type, case, configuration))
            self.__item_constituents.type_list = self.select_constituent_of_items_to_test(level='types')
            self.__item_constituents.case_list = self.select_constituent_of_items_to_test(level='cases')
            self.__item_constituents.configuration_list = self.select_constituent_of_items_to_test(
                level='configurations', computer_of_subject=computer)
        elif operation_type == 'p':  # plotting
            self.__item_constituents.type_list = self.select_constituent_of_items_to_test(level='types')
            if operation == 'j' or operation == 'w':  # generate or wait for JPG (only type)
                self.__item_constituents.case_list = [[None]]
                self.__item_constituents.configuration_list = [None]
            else:  # all item constituents  (type, case, configuration)
                self.__item_constituents.case_list = self.select_constituent_of_items_to_test(level='cases')
                self.__item_constituents.configuration_list = self.select_constituent_of_items_to_test(
                    level='configurations', computer_of_subject=computer)
        else:
            message(mode='ERROR', not_supported='operation_type {}'.format(operation_type))

    def set_processing_data(self, sim_data, item_type, item_configuration):
        """
        get processing (parallelization) data from database and store in sim_data
        :param sim_data: (class SimulationData)
        :param item_type: (string)
        :param item_configuration: (string)
        :return:
        """
        processing = Processing()

        processing.number_cpus = self.__gateToDatabase.query_column_entry(
            'types', self.__gateToDatabase.query_id_for_name('types', item_type), 'number_cpus')
        processing.mode = self.__gateToDatabase.query_column_entry(
            'configurations', self.__gateToDatabase.query_id_for_name(
                'configurations', item_configuration), 'processing')

        sim_data.processing = processing

    def set_numerics_directories(self, sim_data, item_case, item_configuration,
                                 flow_process_name, element_type_name, process):
        sim_data.solver_dir[process] = self.convert_numerics_entry_in_cases_table_to_specification(
            item_configuration, self.__gateToDatabase.query_column_entry(
                'numerics', self.__gateToDatabase.query_id_from_numerics_table(
                    item_case, flow_process_name, element_type_name),
                'solver_' + process + '_' + item_configuration.lower()), 'solver')
        sim_data.preconditioner_dir[process] = self.convert_numerics_entry_in_cases_table_to_specification(
            item_configuration, self.__gateToDatabase.query_column_entry(
                'numerics', self.__gateToDatabase.query_id_from_numerics_table(
                    item_case, flow_process_name, element_type_name),
                'preconditioner_' + process + '_' + item_configuration.lower()), 'preconditioner')
        sim_data.theta_dir[process] = self.__gateToDatabase.query_column_entry(
            'numerics', self.__gateToDatabase.query_id_from_numerics_table(
                item_case, flow_process_name, element_type_name),
            'theta_' + process)

    def set_numerics_data(self, sim_data, item_case, item_configuration, flow_process_name, element_type_name):
        """
        get mumerics and parallelization data from database and store in sim_data
        data are used later on to write *.num *.pbs files
        :param sim_data: (class SimulationData)
        :param item_case: (string)
        :param item_configuration: (string)
        :param flow_process_name: (string)
        :param element_type_name: (string)
        :return:
        """
        # numerics global
        case_id = self.__gateToDatabase.query_id_for_name('cases', item_case)
        sim_data.numerics_global.processes.flow = flow_process_name
        sim_data.numerics_global.processes.mass_flag = str2bool(self.__gateToDatabase.query_column_entry(
            'cases', case_id, 'mass_flag'))
        sim_data.numerics_global.processes.heat_flag = str2bool(self.__gateToDatabase.query_column_entry(
            'cases', case_id, 'heat_flag'))
        sim_data.numerics_global.processes.deformation_flag = str2bool(self.__gateToDatabase.query_column_entry(
            'cases', case_id, 'deformation_flag'))
        sim_data.numerics_global.processes.fluid_momentum_flag = str2bool(self.__gateToDatabase.query_column_entry(
            'cases', case_id, 'fluid_momentum_flag'))
        sim_data.numerics_global.processes.overland_flag = str2bool(self.__gateToDatabase.query_column_entry(
            'cases', case_id, 'overland_flag'))

        sim_data.numerics_global.coupled_flag = str2bool(self.__gateToDatabase.query_column_entry(
            'cases', case_id, 'coupled_flag'))
        sim_data.numerics_global.lumping_flag = str2bool(self.__gateToDatabase.query_column_entry(
            'numerics', self.__gateToDatabase.query_id_from_numerics_table(
                item_case, flow_process_name, element_type_name),
            'flow_lumping_flag'))
        sim_data.numerics_global.non_linear_flag = str2bool(self.__gateToDatabase.query_column_entry(
            'flow_processes', self.__gateToDatabase.query_id_for_name(
                'flow_processes', flow_process_name), 'nonlinear_flag'))
        # solver, preconditioner, theta
        self.set_numerics_directories(sim_data, item_case, item_configuration,
                                      flow_process_name, element_type_name, 'flow')
        if sim_data.numerics_global.processes.mass_flag:
            self.set_numerics_directories(sim_data, item_case, item_configuration,
                                          flow_process_name, element_type_name, 'mass')
        if sim_data.numerics_global.processes.heat_flag:
            self.set_numerics_directories(sim_data, item_case, item_configuration,
                                          flow_process_name, element_type_name, 'heat')
        if sim_data.numerics_global.processes.deformation_flag:
            self.set_numerics_directories(sim_data, item_case, item_configuration,
                                          flow_process_name, element_type_name, 'deformation')
        if sim_data.numerics_global.processes.fluid_momentum_flag:
            self.set_numerics_directories(sim_data, item_case, item_configuration,
                                          flow_process_name, element_type_name, 'fluid_momentum')
        if sim_data.numerics_global.processes.overland_flag:
            self.set_numerics_directories(sim_data, item_case, item_configuration,
                                          flow_process_name, element_type_name, 'overland_flow')