load script from script_dict and append to scripts, if additional instruments are required create them and add them to instruments

        Args:
            script_dict: dictionary of form

                script_dict = {
                name_of_script_1 :
                    {"settings" : settings_dictionary, "class" : name_of_class}
                name_of_instrument_2 :
                    {"settings" : settings_dictionary, "class" : name_of_class}
                ...
                }

            or

                script_dict = {
                name_of_script_1 : name_of_class,
                name_of_script_2 : name_of_class
                ...
                }

            where name_of_class is either a class or the name of a class

            scripts: dictionary of form

                scripts = {
                name_of_script_1 : instance_of_script_1,
                name_of_script_2 : instance_of_script_2,
                ...
                }

            instruments: dictionary of form

                instruments = {
                name_of_instrument_1 : instance_of_instrument_1,
                name_of_instrument_2 : instance_of_instrument_2,
                ...
                }
            log_function: function that takes a string

            data_path: absolute path where data is saved, in case the path in the script is definded as a relative path

            raise_errors: if True errors are raised
        Returns:
                dictionary of form
                script_dict = { name_of_script_1 : script_1_instance, name_of_script_2 : script_2_instance, ...}
                load_failed = {name_of_script_1: exception_1, name_of_script_2: exception_2, ....}
                updated_instruments = {name_of_instrument_1 : instance_of_instrument_1, ..}

def load_and_append(script_dict, scripts=None, instruments=None, log_function=None, data_path=None,
                        raise_errors=False, package='pylabcontrol', verbose=False):
        """
        load script from script_dict and append to scripts, if additional instruments are required create them and add them to instruments

        Args:
            script_dict: dictionary of form

                script_dict = {
                name_of_script_1 :
                    {"settings" : settings_dictionary, "class" : name_of_class}
                name_of_instrument_2 :
                    {"settings" : settings_dictionary, "class" : name_of_class}
                ...
                }

            or

                script_dict = {
                name_of_script_1 : name_of_class,
                name_of_script_2 : name_of_class
                ...
                }

            where name_of_class is either a class or the name of a class

            scripts: dictionary of form

                scripts = {
                name_of_script_1 : instance_of_script_1,
                name_of_script_2 : instance_of_script_2,
                ...
                }

            instruments: dictionary of form

                instruments = {
                name_of_instrument_1 : instance_of_instrument_1,
                name_of_instrument_2 : instance_of_instrument_2,
                ...
                }
            log_function: function that takes a string

            data_path: absolute path where data is saved, in case the path in the script is definded as a relative path

            raise_errors: if True errors are raised
        Returns:
                dictionary of form
                script_dict = { name_of_script_1 : script_1_instance, name_of_script_2 : script_2_instance, ...}
                load_failed = {name_of_script_1: exception_1, name_of_script_2: exception_2, ....}
                updated_instruments = {name_of_instrument_1 : instance_of_instrument_1, ..}

        """
        if scripts is None:
            scripts = {}
        if instruments is None:
            instruments = {}

        load_failed = {}
        updated_scripts = {}
        updated_scripts.update(scripts)
        updated_instruments = {}
        updated_instruments.update(instruments)

        if verbose:
            print(('script_dict', script_dict))

        def get_instruments(class_of_script, script_instruments, instruments):
            """

            creates the dictionary with the instruments needed for the script and update the instrument dictionary if new instruments are required

            Args:
                class_of_script: the class of the script
                instruments: the instruments that have been loaded already

            Returns: dictionary with the instruments that the script needs and the updated instruments dictionary

            """

            default_instruments = getattr(class_of_script, '_INSTRUMENTS')
            instrument_dict = {}
            instruments_updated = {}
            instruments_updated.update(instruments)
            # check if instruments needed by script already exist, if not create an instance
            for instrument_name, instrument_class in default_instruments.items():
                # check if instruments needed by script already exist
                instrument = [instance for name, instance in instruments_updated.items() if
                              isinstance(instance, instrument_class) and name == instrument_name]

                if len(instrument) == 0:
                    # create new instance of instrument
                    instruments_updated, __ = Instrument.load_and_append({instrument_name: instrument_class}, instruments_updated, raise_errors)

                if script_instruments is not None and instrument_name in script_instruments:
                    instrument_settings_dict = script_instruments[instrument_name]['settings']
                else:
                    instrument_settings_dict = instruments_updated[instrument_name].settings

                instrument_instance = instruments_updated[instrument_name]

                # make a deepcopy of _DEFAULT_SETTINGS to get a parameter object
                instrument_settings = deepcopy(instrument_instance._DEFAULT_SETTINGS)

                #now update parameter object with new values
                instrument_settings.update(instrument_settings_dict)

                instrument_dict.update({instrument_name: {"instance":instrument_instance, "settings":instrument_settings}})


            return instrument_dict, instruments_updated

        def get_sub_scripts(class_of_script, instruments, sub_scripts_dict, log_function = None):
            """

            creates the dictionary with the sub scripts needed by the script and updates the instrument dictionary if new instruments are required

            Args:
                class_of_script: the class of the script
                instruments: the instruments that have been loaded already
                sub_scripts_dict: settings of script in dictionary form

            Returns:dictionary with the sub scripts that the script needs

            """

            default_scripts = getattr(class_of_script, '_SCRIPTS')
            #
            # create instruments that script needs
            sub_scripts = {}
            sub_scripts, scripts_failed, instruments_updated = Script.load_and_append(default_scripts, sub_scripts,
                                                                                      instruments,
                                                                                      log_function=log_function,
                                                                                      raise_errors=raise_errors)
            try:
                if sub_scripts_dict is not None:
                    for k, v in sub_scripts_dict.items():
                        #update settings, updates instrument and settings
                        sub_scripts[k].update(v)
            except TypeError: #if actually an object, as with dynamic scripts
                pass

            if len(scripts_failed)>0:
                raise ImportError('script {:s}: failed to load subscripts'.format(class_of_script))
            return sub_scripts, instruments_updated

        for script_name, script_info in script_dict.items():

            # check if script already exists
            if script_name in list(scripts.keys()):
                print(('WARNING: script {:s} already exists. Did not load!'.format(script_name)))
                load_failed[script_name] = ValueError('script {:s} already exists. Did not load!'.format(script_name))
            else:
                module, script_class_name, script_settings, script_instruments, script_sub_scripts, script_doc, package = Script.get_script_information(script_info, package=package)
                # creates all dynamic scripts so they can be imported following the if statement
                # if script_class_name == 'ScriptIterator':
                if 'ScriptIterator' in script_class_name:
                    # creates all the dynamic classes in the script and the class of the script itself
                    # and updates the script info with these new classes
                    from pylabcontrol.core.script_iterator import ScriptIterator #CAUTION: imports ScriptIterator, which inherits from script. Local scope should avoid circular imports.

                    script_info, _ = ScriptIterator.create_dynamic_script_class(script_info)

                    # now get the info for the dynamically created class
                    module, script_class_name, script_settings, script_instruments, script_sub_scripts, script_doc, package = Script.get_script_information(script_info)
                if verbose:
                    print(('load_and_append.module', module))
                    print(('load_and_append.script_info', script_info))
                    print(('load_and_append.package', package))

                if module is None and inspect.isclass(script_info):
                    class_of_script = script_info
                else:
                    class_of_script = getattr(module, script_class_name)

                #  ========= create the instruments that are needed by the script =========
                try:
                    script_instruments, updated_instruments = get_instruments(class_of_script, script_instruments, updated_instruments)
                except Exception as err:
                    print(('loading script {:s} failed. Could not load instruments!'.format(script_name)))
                    load_failed[script_name] = err
                    if raise_errors:
                        raise err
                    continue
                #  ========= create the subscripts that are needed by the script =========
                try:
                    sub_scripts, updated_instruments = get_sub_scripts(class_of_script, updated_instruments, script_sub_scripts, log_function = log_function)
                except Exception as err:
                    print(('loading script {:s} failed. Could not load subscripts!'.format(script_name)))
                    load_failed[script_name] = err
                    if raise_errors:
                        raise err
                    continue

                #  ========= create the script if instruments and subscripts have been loaded successfully =========
                class_creation_string = ''
                if script_instruments:
                    class_creation_string += ', instruments = script_instruments'
                if sub_scripts:
                    class_creation_string += ', scripts = sub_scripts'
                if script_settings:
                    class_creation_string += ', settings = script_settings'
                if log_function:
                    class_creation_string += ', log_function = log_function'
                if data_path:
                    class_creation_string += ', data_path = data_path'
                class_creation_string = 'class_of_script(name=script_name{:s})'.format(class_creation_string)

                if verbose:
                    print(('class_creation_string', class_creation_string))
                    print(('class_of_script', class_of_script))
                    print(('scripts', sub_scripts))

                try:
                    script_instance = eval(class_creation_string)
                except Exception as err:
                    print('loading ' + script_name + ' failed:')
                    print(traceback.format_exc())
                    # print(('loading script {:s} failed. Could not create instance of script!'.format(script_name)))
                    load_failed[script_name] = err
                    if raise_errors:
                        raise err
                    continue

                if script_doc:
                    script_instance.__doc__ = script_doc

                updated_scripts.update({script_name: script_instance})


        return updated_scripts, load_failed, updated_instruments