Exemple #1
0
class Loader(YomboLibrary, object):
    """
    Responsible for loading libraries, and then delegating loading modules to
    the modules library.

    Libraries are never reloaded, however, during a reconfiguration,
    modules are unloaded, and then reloaded after configurations are done
    being downloaded.
    """
    @property
    def operating_mode(self):
        return self._operating_mode

    @operating_mode.setter
    def operating_mode(self, val):
        if RUN_PHASE[self._run_phase] > 2:
            self.loadedLibraries['states']['loader.operating_mode'] = val
        self._operating_mode = val

    @property
    def run_phase(self):
        return (self._run_phase, RUN_PHASE[self._run_phase])

    @operating_mode.setter
    def run_phase(self, val):
        if RUN_PHASE[val] > 2:
            self.loadedLibraries['states']['loader.run_phase'] = val
        self._run_phase = val

    def __getitem__(self, component_requested):
        """
        """
        logger.debug("looking for: {component_requested}",
                     component_requested=component_requested)
        if component_requested in self.loadedComponents:
            logger.debug("found by loadedComponents! {component_requested}",
                         component_requested=component_requested)
            return self.loadedComponents[component_requested]
        elif component_requested in self.loadedLibraries:
            logger.debug("found by loadedLibraries! {component_requested}",
                         component_requested=component_requested)
            return self.loadedLibraries[component_requested]
        elif component_requested in self._moduleLibrary:
            logger.debug("found by self._moduleLibrary! {component_requested}",
                         component_requested=self._moduleLibrary)
            return self._moduleLibrary[component_requested]
        else:
            raise YomboWarning(
                "Loader could not find requested component: {%s}" %
                component_requested, '101', '__getitem__', 'loader')

    def __init__(self, testing=False, loop=None):
        self._operating_mode = "system_init"
        self._run_phase = "system_init"
        self.unittest = testing
        self._moduleLibrary = None
        YomboLibrary.__init__(self)

        self.loadedComponents = FuzzySearch({self._FullName.lower(): self},
                                            .95)
        self.loadedLibraries = FuzzySearch({self._Name.lower(): self}, .95)
        self.libraryNames = {}
        self._moduleLibrary = None
        self._invoke_list_cache = {
        }  # Store a list of hooks that exist or not. A cache.
        self._operating_mode = None  # One of: first_run, config, run
        self.sigint = False  # will be set to true if SIGINT is received
        self.hook_counts = OrderedDict(
        )  # keep track of hook names, and how many times it's called.
        reactor.addSystemEventTrigger("before", "shutdown", self.shutdown)

    def shutdown(self):
        """
        This is called if SIGINT (ctrl-c) was caught. Very useful incase it was called during startup.
        :return:
        """
        self.run_phase = "shutdown"
        self.sigint = True

    @inlineCallbacks
    def start(self):  #on startup, load libraried, then modules
        """
        This is effectively the main start function.

        This function is called when the gateway is to startup. In turn,
        this function will load all the components and modules of the gateway.
        """
        logger.info("Importing libraries, this can take a few moments.")

        # Get a reference to the asyncio event loop.
        yield yombo.utils.sleep(0.01)  # kick the asyncio event loop
        self.event_loop = asyncio.get_event_loop()

        yield self.import_libraries()  # import and init all libraries

        if self.sigint:
            return
        logger.debug("Calling load functions of libraries.")

        self.run_phase = "modules_import"
        self.operating_mode = self.operating_mode  # so we can update the State!
        yield self._moduleLibrary.import_modules()
        for name, config in HARD_LOAD.items():
            if self.sigint:
                return
            self._log_loader('debug', name, 'library', 'modules_imported',
                             'About to call _modules_imported_.')
            if self.check_operating_mode(config['operating_mode']):
                libraryName = name.lower()
                yield self.library_invoke(libraryName,
                                          "_modules_imported_",
                                          called_by=self)
                HARD_LOAD[name]['_modules_imported_'] = True
            else:
                HARD_LOAD[name]['_modules_imported_'] = False
            self._log_loader('debug', name, 'library', 'modules_imported',
                             'Finished call to _modules_imported_.')

        self.run_phase = "libraries_load"
        for name, config in HARD_LOAD.items():
            # print "sigint: %s" % self.sigint
            if self.sigint:
                return
            # self._log_loader('debug', name, 'library', 'load', 'About to call _load_.')
            if self.check_operating_mode(config['operating_mode']):
                HARD_LOAD[name]['_load_'] = 'Starting'
                libraryName = name.lower()
                yield self.library_invoke(libraryName,
                                          "_load_",
                                          called_by=self)
                HARD_LOAD[name]['_load_'] = True
            else:
                HARD_LOAD[name]['_load_'] = False
            self._log_loader('debug', name, 'library', 'load',
                             'Finished call to _load_.')

        self._moduleLibrary = self.loadedLibraries['modules']

        self.run_phase = "modules_init"
        yield self._moduleLibrary.init_modules()

        #        logger.debug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1Calling start function of libraries.")
        self.run_phase = "libraries_start"
        for name, config in HARD_LOAD.items():
            if self.sigint:
                return
            self._log_loader('debug', name, 'library', 'start',
                             'About to call _start_.')
            if self.check_operating_mode(config['operating_mode']):
                libraryName = name.lower()
                yield self.library_invoke(libraryName,
                                          "_start_",
                                          called_by=self)
                HARD_LOAD[name]['_start_'] = True
            else:
                HARD_LOAD[name]['_start_'] = False
            self._log_loader('debug', name, 'library', '_start_',
                             'Finished call to _start_.')

        self.run_phase = "modules_start"
        yield self._moduleLibrary.load_modules()  #includes load & start
        self.run_phase = "modules_started"

        self.run_phase = "libraries_started"
        for name, config in HARD_LOAD.items():
            if self.sigint:
                return
            self._log_loader('debug', name, 'library', 'started',
                             'About to call _started_.')
            if self.check_operating_mode(config['operating_mode']):
                libraryName = name.lower()
                yield self.library_invoke(libraryName,
                                          "_started_",
                                          called_by=self)
                HARD_LOAD[name]['_started_'] = True
            else:
                HARD_LOAD[name]['_started_'] = False

        for name, config in HARD_LOAD.items():
            if self.sigint:
                return
            self._log_loader('debug', name, 'library', '_modules_started_',
                             'About to call _modules_started_.')
            if self.check_operating_mode(config['operating_mode']):
                libraryName = name.lower()
                yield self.library_invoke(libraryName,
                                          "_modules_started_",
                                          called_by=self)
                HARD_LOAD[name]['_started_'] = True
            else:
                HARD_LOAD[name]['_started_'] = False

        self.loadedLibraries['notifications'].add({
            'title': 'System started',
            'message': 'System successfully started.',
            'timeout': 300,
            'source': 'Yombo Gateway System',
            'persist': False,
            'always_show': False,
        })

        logger.info("Yombo Gateway started.")

    @inlineCallbacks
    def unload(self):
        """
        Called when the gateway should stop. This will gracefully stop the gateway.

        First, unload all modules, then unload all components.
        """
        self.sigint = True  # it's 99.999% true - usually only shutdown due to this.
        if self._moduleLibrary is not None:
            yield self._moduleLibrary.unload_modules()
        yield self.unload_libraries()
        # self.loop.close()

    def Loader_i18n_atoms(self, **kwargs):
        return [
            {
                'loader.operating_mode': {
                    'en': 'One of: first_run, run, config',
                },
            },
        ]

    def check_component_status(self, name, function):
        if name in HARD_LOAD:
            if function in HARD_LOAD[name]:
                return HARD_LOAD[name][function]
        return None

    def _log_loader(self, level, label, type, method, msg=""):
        """
        A common log format for loading/unloading libraries and modules.

        :param level: Log level - debug, info, warn...
        :param label: Module label "x10", "messages"
        :param type: Type of item being loaded: library, module
        :param method: Method being called.
        :param msg: Optional message to include.
        :return:
        """
        logit = func = getattr(logger, level)
        logit("Loader: {label}({type})::{method} - {msg}",
              label=label,
              type=type,
              method=method,
              msg=msg)

    def import_libraries_failure(self, failure):
        logger.error("Got failure during import of library: {failure}",
                     failure=failure)

    @inlineCallbacks
    def import_libraries(self):
        """
        Import then "init" all libraries. Call "loadLibraries" when done.
        """
        logger.debug("Importing server libraries.")
        self._run_phase = "libraries_import"
        for name, config in HARD_LOAD.items():
            if self.sigint:
                return
            HARD_LOAD[name]['__init__'] = 'Starting'
            pathName = "yombo.lib.%s" % name
            self.import_component(pathName, name, 'library')
            HARD_LOAD[name]['__init__'] = True

        logger.debug("Calling init functions of libraries.")
        self._run_phase = "libraries_init"
        for name, config in HARD_LOAD.items():
            if self.sigint:
                return
            if self.check_operating_mode(config['operating_mode']) is False:
                HARD_LOAD[name]['_init_'] = False
                continue
            HARD_LOAD[name]['_init_'] = 'Starting'
            # self._log_loader('debug', name, 'library', 'init', 'About to call _init_.')

            component = name.lower()
            library = self.loadedLibraries[component]
            library._event_loop = self.event_loop
            library._AMQP = self.loadedLibraries['amqp']
            library._AMQPYombo = self.loadedLibraries['amqpyombo']
            library._Atoms = self.loadedLibraries['atoms']
            library._Automation = self.loadedLibraries['automation']
            library._Commands = self.loadedLibraries['commands']
            library._Configs = self.loadedLibraries['configuration']
            library._Devices = self.loadedLibraries['devices']
            library._Locations = self.loadedLibraries['locations']
            library._DeviceTypes = self.loadedLibraries['devicetypes']
            library._Gateways = self.loadedLibraries['gateways']
            library._GPG = self.loadedLibraries['gpg']
            library._InputTypes = self.loadedLibraries['inputtypes']
            library._Libraries = self.loadedLibraries
            library._Loader = self
            library._LocalDB = self.loadedLibraries['localdb']
            library._Modules = self._moduleLibrary
            library._Nodes = self.loadedLibraries['nodes']
            library._Notifications = self.loadedLibraries['notifications']
            library._Localize = self.loadedLibraries['localize']
            library._MQTT = self.loadedLibraries['mqtt']
            library._Queue = self.loadedLibraries['queue']
            library._SQLDict = self.loadedLibraries['sqldict']
            library._SSLCerts = self.loadedLibraries['sslcerts']
            library._States = self.loadedLibraries['states']
            library._Statistics = self.loadedLibraries['statistics']
            library._Tasks = self.loadedLibraries['tasks']
            library._Times = self.loadedLibraries['times']
            library._YomboAPI = self.loadedLibraries['yomboapi']
            library._Variables = self.loadedLibraries['variables']
            library._Validate = self.loadedLibraries['validate']
            if hasattr(library, '_init_') and isinstance(library._init_, Callable) \
                    and yombo.utils.get_method_definition_level(library._init_) != 'yombo.core.module.YomboModule':
                d = Deferred()
                d.addCallback(lambda ignored: self._log_loader(
                    'debug', name, 'library', 'init', 'About to call _init_.'))
                d.addCallback(lambda ignored: maybeDeferred(library._init_))
                d.addErrback(self.import_libraries_failure)
                # d.addCallback(lambda ignored: self._log_loader('debug', name, 'library', 'init', 'Done with call _init_.'))
                d.callback(1)
                yield d
                # d.addCallback(maybeDeferred, library._init_)
                # self._log_loader('debug', name, 'library', 'init', 'Finished to call _init_.')
                # try:
                #     d = yield maybeDeferred(library._init_, self)
                # except YomboCritical, e:
                #     logger.error("---==(Critical Server Error in init function for library: {name})==----", name=name)
                #     logger.error("--------------------------------------------------------")
                #     logger.error("Error message: {e}", e=e)
                #     logger.error("--------------------------------------------------------")
                #     e.exit()
                # except:
                #     logger.error("-------==(Error in init function for library: {name})==---------", name=name)
                #     logger.error("1:: {e}", e=sys.exc_info())
                #     logger.error("---------------==(Traceback)==--------------------------")
                #     logger.error("{e}", e=traceback.print_exc(file=sys.stdout))
                #     logger.error("--------------------------------------------------------")
                HARD_LOAD[name]['_init_'] = True
            else:
                logger.error(
                    "----==(Library doesn't have init function: {name})==-----",
                    name=name)

    def check_operating_mode(self, allowed):
        """
        Checks if something should be run based on the current operating_mode.
        :param config: Either string or list or posible operating_modes
        :return: True/False
        """
        operating_mode = self.operating_mode

        if operating_mode is None:
            return True

        def check_operating_mode_inside(mode, operating_mode):
            if mode == 'all':
                return True
            elif mode == operating_mode:
                return True
            return False

        if isinstance(allowed, str):  # we have a string
            return check_operating_mode_inside(allowed, operating_mode)
        else:  # we have something else
            for item in allowed:
                if check_operating_mode_inside(item, operating_mode):
                    return True

    def library_invoke_failure(self, failure, requested_library, hook_name):
        logger.error(
            "Got failure during library invoke for hook ({requested_library}::{hook_name}): {failure}",
            requested_library=requested_library,
            hook_name=hook_name,
            failure=failure)

    @inlineCallbacks
    def library_invoke(self, requested_library, hook, **kwargs):
        """
        Invokes a hook for a a given library. Passes kwargs in, returns the results to caller.
        """
        requested_library = requested_library.lower()
        if requested_library not in self.loadedLibraries:
            raise YomboWarning('Requested library is missing: %s' %
                               requested_library)

        if 'called_by' not in kwargs:
            raise YomboWarning(
                "Unable to call hook '%s:%s', missing 'called_by' named argument."
                % (requested_library, hook))
        calling_component = kwargs['called_by']

        cache_key = requested_library + hook
        if cache_key in self._invoke_list_cache:
            if self._invoke_list_cache[cache_key] is False:
                # logger.warn("Cache hook ({cache_key})...SKIPPED", cache_key=cache_key)
                returnValue(
                    None)  # skip. We already know function doesn't exist.
        library = self.loadedLibraries[requested_library]
        if requested_library == 'Loader':
            returnValue(None)
        if not (hook.startswith("_") and hook.endswith("_")):
            hook = library._Name.lower() + "_" + hook
        if hasattr(library, hook):
            method = getattr(library, hook)
            if isinstance(method, Callable):
                if library._Name not in self.hook_counts:
                    self.hook_counts[library._Name] = {}
                if hook not in self.hook_counts:
                    self.hook_counts[library._Name][hook] = {
                        'Total Count': {
                            'count': 0
                        }
                    }
                # print "hook counts: %s" % self.hook_counts
                # print "hook counts: %s" % self.hook_counts[library._Name][hook]
                if calling_component not in self.hook_counts[
                        library._Name][hook]:
                    self.hook_counts[
                        library._Name][hook][calling_component] = {
                            'count': 0
                        }
                self.hook_counts[library._Name][hook][calling_component][
                    'count'] = self.hook_counts[
                        library._Name][hook][calling_component]['count'] + 1
                self.hook_counts[library._Name][hook]['Total Count'][
                    'count'] = self.hook_counts[
                        library._Name][hook]['Total Count']['count'] + 1
                self._invoke_list_cache[cache_key] = True

                try:
                    d = Deferred()
                    d.addCallback(lambda ignored: self._log_loader(
                        'debug', library._Name, 'library', hook,
                        'About to call %s' % hook))
                    # print("calling %s:%s" % (library._Name, hook))
                    d.addCallback(
                        lambda ignored: maybeDeferred(method, **kwargs))
                    d.addErrback(self.library_invoke_failure,
                                 requested_library, hook)
                    d.callback(1)
                    results = yield d
                    return results
                except RuntimeWarning as e:
                    pass
            else:
                logger.debug(
                    "Cache library hook ({library}:{hook})...setting false",
                    library=library._FullName,
                    hook=hook)
                logger.debug(
                    "----==(Library {library} doesn't have a callable function: {function})==-----",
                    library=library._FullName,
                    function=hook)
                raise YomboWarning("Hook is not callable: %s" % hook)
        else:
            #            logger.debug("Cache hook ({library}:{hook})...setting false", library=library._FullName, hook=hook)
            self._invoke_list_cache[cache_key] = False

    @inlineCallbacks
    def library_invoke_all(self, hook, fullName=False, **kwargs):
        """
        Calls library_invoke for all loaded libraries.
        """
        results = {}
        to_process = {}
        if 'components' in kwargs:
            to_process = kwargs['components']
        else:
            for library_name, library in self.loadedLibraries.items():
                #                print "library %s" % library
                label = library._FullName.lower(
                ) if fullName else library._Name.lower()
                to_process[library_name] = label
        if 'stoponerror' in kwargs:
            stoponerror = kwargs['stoponerror']
        else:
            kwargs['stoponerror'] = True
            stoponerror = True

        for library_name, library in self.loadedLibraries.items():
            # logger.debug("invoke all:{libraryName} -> {hook}", libraryName=library_name, hook=hook )
            try:
                result = yield self.library_invoke(library_name, hook,
                                                   **kwargs)
                if result is None:
                    continue
                results[library._FullName] = result
            except YomboWarning:
                pass
            except YomboHookStopProcessing as e:
                if stoponerror is not True:
                    pass
                e.collected = results
                e.by_who = label
                raise

        returnValue(results)

    def import_component(self,
                         pathName,
                         componentName,
                         componentType,
                         componentUUID=None):
        """
        Load component of given name. Can be a core library, or a module.
        """
        pymodulename = pathName.lower()
        self._log_loader('debug', componentName, componentType, 'import',
                         'About to import.')
        try:
            pyclassname = ReSearch("(?<=\.)([^.]+)$", pathName).group(1)
        except AttributeError:
            self._log_loader('error', componentName, componentType, 'import',
                             'Not found. Path: %s' % pathName)
            logger.error("Library or Module not found: {pathName}",
                         pathName=pathName)
            raise YomboCritical("Library or Module not found: %s", pathName)
        try:
            module_root = __import__(pymodulename, globals(), locals(), [], 0)
        except ImportError as detail:
            self._log_loader('error', componentName, componentType, 'import',
                             'Not found. Path: %s' % pathName)
            logger.error(
                "--------==(Error: Library or Module not found)==--------")
            logger.error("----Name: {pathName},  Details: {detail}",
                         pathName=pathName,
                         detail=detail)
            logger.error(
                "---------------==(Traceback)==--------------------------")
            logger.error("{trace}", trace=traceback.format_exc())
            logger.error(
                "--------------------------------------------------------")
            raise ImportError("Cannot import module, not found.")
        except Exception as e:
            logger.error(
                "---------------==(Traceback)==--------------------------")
            logger.error("{trace}", trace=traceback.format_exc())
            logger.error(
                "--------------------------------------------------------")
            logger.warn(
                "An exception of type {etype} occurred in yombo.lib.nodes:import_component. Message: {msg}",
                etype=type(e),
                msg=e)
            raise ImportError(e)
        module_tail = reduce(lambda p1, p2: getattr(p1, p2), [
            module_root,
        ] + pymodulename.split('.')[1:])
        # print "module_tail: %s   pyclassname: %s" % (module_tail, pyclassname)
        klass = getattr(module_tail, pyclassname)
        # print "klass: %s  " % klass

        # Put the component into various lists for mgmt
        if not isinstance(klass, Callable):
            logger.error(
                "Unable to start class '{classname}', it's not callable.",
                classname=pyclassname)
            raise ImportError(
                "Unable to start class '%s', it's not callable." % pyclassname)

        try:
            # Instantiate the class
            # logger.debug("Instantiate class: {pyclassname}", pyclassname=pyclassname)
            moduleinst = klass(
            )  # start the class, only libraries get the loader
            if componentType == 'library':
                if componentName.lower() == 'modules':
                    self._moduleLibrary = moduleinst

                self.loadedComponents["yombo.gateway.lib." +
                                      str(componentName.lower())] = moduleinst
                self.loadedLibraries[str(componentName.lower())] = moduleinst
                # this is mostly for manhole module, but maybe useful elsewhere?
                temp = componentName.split(".")
                self.libraryNames[temp[-1]] = moduleinst
            else:
                self.loadedComponents["yombo.gateway.modules." +
                                      str(componentName.lower())] = moduleinst
                return moduleinst, componentName.lower()

        except YomboCritical as e:
            logger.debug(
                "@!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
            )
            logger.debug("{e}", e=e)
            logger.debug(
                "@!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
            )
            e.exit()
            raise

    @inlineCallbacks
    def unload_libraries(self):
        """
        Only called when server is doing shutdown. Stops controller, server control and server data..
        """
        logger.debug("Stopping libraries: {stuff}", stuff=HARD_UNLOAD)
        for name, config in HARD_UNLOAD.items():
            if self.check_operating_mode(config['operating_mode']):
                logger.debug("stopping: {name}", name=name)
                yield self.library_invoke(name, "_stop_", called_by=self)

        for name, config in HARD_UNLOAD.items():
            if self.check_operating_mode(config['operating_mode']):
                logger.debug("_unload_: {name}", name=name)
                yield self.library_invoke(name, "_unload_", called_by=self)

    def _handleError(self, err):
        #        logger.error("Error caught: %s", err.getErrorMessage())
        #        logger.error("Error type: %s  %s", err.type, err.value)
        err.raiseException()

    def get_loaded_component(self, name):
        """
        Returns loaded module object by name. Module must be loaded.
        """
        return self.loadedComponents[name.lower()]

    def get_all_loaded_components(self):
        """
        Returns loaded module object by name. Module must be loaded.
        """
        return self.loadedComponents

    def find_function(self, component_type, component_name,
                      component_function):
        """
        Finds a function within the system by namme. This is useful for when you need to
        save a pointer to a callback to sql or a dictionary, but cannot save pointers to
        a function because the system may restart. This offers another method to reach
        various functions within the system.

        :param component_type: Either 'module' or 'library'.
        :param component_name: Module or libary name.
        :param component_function: Name of the function. A string for direct access to the function or a list
            can be provided and it will search the a dictionary of items for a callback.
        :return:
        """

        if component_type == 'library':
            if component_name not in self.loadedLibraries:
                logger.info("Library not found: {loadedLibraries}",
                            loadedLibraries=loadedLibraries)
                raise YomboWarning("Cannot library name.")

            if isinstance(component_function, list):
                if hasattr(self.loadedLibraries[component_name],
                           component_function[0]):
                    remote_attribute = getattr(
                        self.loadedLibraries[component_name],
                        component_function[0])  # the dictionary
                    if component_function[1] in remote_attribute:
                        if not isinstance(
                                remote_attribute[component_function[1]],
                                Callable):  # the key should be callable.
                            logger.info(
                                "Could not find callable library function by name: '{component_type} :: {component_name} :: (list) {component_function}'",
                                component_type=component_type,
                                component_name=component_name,
                                component_function=component_function)
                            raise YomboWarning("Cannot find callable")
                        else:
                            logger.info(
                                "Look ma, I found a cool function here.")
                            return remote_attribute[component_function[1]]
            else:
                if hasattr(self.loadedLibraries[component_name],
                           component_function):
                    method = getattr(self.loadedLibraries[component_name],
                                     component_function)
                    if not isinstance(method, Callable):
                        logger.info(
                            "Could not find callable modoule function by name: '{component_type} :: {component_name} :: {component_function}'",
                            component_type=component_type,
                            component_name=component_name,
                            component_function=component_function)
                        raise YomboWarning("Cannot find callable")
                    else:
                        return method
        elif component_type == 'module':
            modules = self._moduleLibrary
            if component_name not in modules._modulesByName:
                raise YomboWarning("Cannot module name.")

            if hasattr(modules._modulesByName[component_name],
                       component_function[0]):
                remote_attribute = getattr(
                    modules._modulesByName[component_name],
                    component_function[0])
                if component_function[1] in remote_attribute:
                    if not isinstance(remote_attribute[component_function[1]],
                                      Callable):  # the key should be callable.
                        logger.info(
                            "Could not find callable module function by name: '{component_type} :: {component_name} :: (list){component_function}'",
                            component_type=component_type,
                            component_name=component_name,
                            component_function=component_function)
                        raise YomboWarning("Cannot find callable")
                    else:
                        logger.info("Look ma, I found a cool function here.")
                        return remote_attribute[component_function[1]]
            else:
                if hasattr(modules._modulesByName[component_name],
                           component_function):
                    method = getattr(modules._modulesByName[component_name],
                                     component_function)
                    if not isinstance(method, Callable):
                        logger.info(
                            "Could not find callable module function by name: '{component_type} :: {component_name} :: {component_function}'",
                            component_type=component_type,
                            component_name=component_name,
                            component_function=component_function)
                        raise YomboWarning("Cannot find callable")
                    else:
                        return method
        else:
            logger.warn("Not a valid component_type: {component_type}",
                        component_type=component_type)
            raise YomboWarning("Invalid component_type.")
Exemple #2
0
class VoiceCmds(YomboLibrary):
    """
    Store all voice commands here and perfrom searches.

    The purpose of this class is two fold: it provides a single repository for
    all possible noun/verb combinations, and provides an ability to *not* have
    to be 100% accurate when looking up noun/verb pairs.

    Convert "voice_cmd" strings into voice commands.

    Also, provides searching for voice commands.
    """
    def __contains__(self, voice_command_requested):
        """
        Checks to if a provided voice command exists.

            >>> if 'cpu.count' in self._VoiceCmds:

        :raises YomboWarning: Raised when request is malformed.
        :param voice_command_requested: The voice command key to search for.
        :type voice_command_requested: string
        :return: Returns true if exists, otherwise false.
        :rtype: bool
        """
        if voice_command_requested in self._VoiceCmds:
            return True
        else:
            return False

    def __getitem__(self, voice_command_requested):
        """
        Search for a voice command. this will return a dictionary: id, cmd object, device object.

            >>> voice_command = self._VoiceCmds['living room light on']

        :raises YomboWarning: Raised when request is malformed.
        :raises KeyError: Raised when request is not found.
        :param voice_command_requested: The voice command key to search for.
        :type voice_command_requested: string
        :return: dict containing: 'id', 'cmd', 'device'
        :rtype: dict
        """
        return self.get(voice_command_requested)

    def __setitem__(self, voice_command_requested, value):
        """
        Sets are not allowed. Raises exception.

        :raises Exception: Always raised.
        """
        raise Exception("Not allowed.")

    def __delitem__(self, voice_command_requested):
        """
        Deletes are not allowed. Raises exception.

        :raises Exception: Always raised.
        """
        raise Exception("Not allowed.")

    def __iter__(self):
        """ iter voice commands. """
        return self.__yombocommands.__iter__()

    def __len__(self):
        """
        Returns an int of the number of voice commands defined.

        :return: The number of voice commands defined.
        :rtype: int
        """
        return len(self.__yombocommands)

    def __str__(self):
        """
        Returns the name of the library.
        :return: Name of the library
        :rtype: string
        """
        return "Yombo voice commands library"

    def keys(self):
        """
        Returns the keys of the voice commands that are defined.

        :return: A list of voice commands defined. 
        :rtype: list
        """
        return list(self.__yombocommands.keys())

    def items(self):
        """
        Gets a list of tuples representing the voice commands defined.

        :return: A list of tuples.
        :rtype: list
        """
        return list(self.__yombocommands.items())

    def iteritems(self):
        return iter(self.__yombocommands.items())

    def iterkeys(self):
        return iter(self.__yombocommands.keys())

    def itervalues(self):
        return iter(self.__yombocommands.values())

    def values(self):
        return list(self.__yombocommands.values())

    def _init_(self, **kwargs):
        """
        Construct a new voice_cmds Instance

        items is an dictionary to copy items from (optional)
        limiter is the match ratio below which mathes should not be considered
        """
        self.voice_command_strings = FuzzySearch(None, .80)
        self.voice_command_data = {}

        self.commandsByVoice = self._Commands.get_commands_by_voice()

    @inlineCallbacks
    def _modules_loaded_(self, **kwargs):
        """
        Implements the _modules_loaded_ and is called after _load_ is called for all the modules.

        Expects a list of events to subscribe to.

        **Hooks called**:

        * _voicecmds_add_ : Expects a list of message subscription events to subscrib to.

        **Usage**:

        .. code-block:: python

           def ModuleName_voice_cmds_load(self, **kwargs):
               return ['status']
        """
        voicecommands_to_add = yield global_invoke_all('_voicecmds_add_',
                                                       called_by=self)
        #        logger.info("voicecommands_to_add: {voice_cmds}", voice_cmds=voicecommands_to_add)

        for componentName, voice_cmds in voicecommands_to_add.items():
            if voice_cmds is None:
                continue
            for list in voice_cmds:
                logger.debug(
                    "For module '{fullName}', adding voice_cmd: {voice_cmd}, order: {order}",
                    voice_cmd=list['voice_cmd'],
                    fullName=componentName,
                    order=list['order'])
                self.add_by_string(list['voice_cmd'], list['call_back'],
                                   list['device'], list['order'])

    def get_all(self):
        """
        Returns a list of voice commands.
        :return:
        """
        results = {}
        for voice, voice_id in self.voice_command_strings.items():
            results[voice] = self.voice_command_data[voice_id]
        return results

    def get(self, voice_command_requested, limiter_override=None):
        """
        Search for a voice command. this will return a dictionary: id, cmd object, device object.

        You can use the device and command objects as desired.

            >>> self._VoiceCmds['living room light on']  #by name

        :param voicecmd_requested: Search all available voice commands for a phrase.
        :type voicecmd_requested: string
        :return: dict containing: 'id', 'cmd', 'device'
        :rtype: dict
        """
        try:
            result = self.voice_commands.search2(
                voice_command_requested, limiter_override=limiter_override)
            self._Statistics.increment("lib.voicecmds.search.found",
                                       bucket_size=30,
                                       anon=True)
            return self.voice_command_data[result]
        except:
            self._Statistics.increment("lib.voicecmds.search.not_found",
                                       bucket_size=30,
                                       anon=True)
            raise KeyError("Searched for voice command, none found: %s" %
                           voice_command_requested)

    def add_by_string(self,
                      voice_string,
                      call_back=None,
                      device=None,
                      order='devicecmd'):
        """
        Adds a voice command by using a string like "desk lamp [on, off]". This will add the following voice commands
        based on the value of 'order'.  If both is provided, the following will be added:

        * desklamp on
        * desklamp off
        * off desklamp
        * on desklamp

        You can specify 'order' as: both, devicecmd, cmddevice. This determines what ordering the voice commands
        are added. In the above example, specifying 'devicecmd', then only the first two items are added.

        Either a callback function must be provided or a device must be provided. Otherwise, the voice command
        will not be added and a YomboWarning will be raised if either or both are defined.

        :raises YomboWarning: If voiceString or destination is invalid.
        :param voice_string: Voice command string to process: "desklamp [on, off]"
        :type voice_string: string
        :param call_back: A function to send the voice command id, device, and command objects to.
        :type call_back: pointer to function
        :param device: A device id or device label to search for a matching device.
        :type device: string
        :param order: The ordering in which to add voice command text lookup. Default: devicecmd
        :type order: string
        """
        logger.debug("Adding voice command: {voice_string}",
                     voice_string=voice_string)
        if call_back is None and device is None:
            raise YomboWarning("'call_back' and 'device' are mising.", 1000,
                               'add_by_string', 'voicecmds')

        if call_back is not None and device is not None:
            raise YomboWarning(
                "Either specifiy 'call_back' or 'device', not both.", 1001,
                'add_by_string', 'voicecmds')

        try:
            tag_re = re.compile('(%s.*?%s)' % (re.escape('['), re.escape(']')))
            string_parts = tag_re.split(voice_string)
            string_parts = [_f for _f in string_parts
                            if _f]  # remove empty bits

            device_obj = None
            if device is not None:
                if isclass(device):
                    device_obj = device
                else:
                    device_obj = self._Devices[device]

            commands = []
        except:
            raise YomboWarning("Invalid format for 'voice_string'", 1010,
                               'add_by_string', 'voicecmds')

        if len(string_parts) > 2:
            raise YomboWarning("Invalid format for 'voice_string'", 1003,
                               'add_by_string', 'voicecmds')

        for part in range(len(string_parts)):
            string_parts[part] = string_parts[part].strip()
            if string_parts[part].startswith(
                    "[") and string_parts[part].endswith("]"):
                temp_commands = string_parts[part][1:-1].split(',')
                for cmd in range(len(temp_commands)):
                    cmd_temp = temp_commands[cmd].strip()
                    if len(cmd_temp) > 0:
                        commands.append(cmd_temp)
            else:
                device_label = string_parts[part]

        if len(commands) == 0:
            raise YomboWarning("No commands found in voice_string.", 1003,
                               'add_by_string', 'voicecmds')

        # logger.debug("commands by voice: {commandsByVoice}:{verb}", commandsByVoice=self.commandsByVoice, verb=verb)
        for cmd in commands:
            if cmd not in self.commandsByVoice:
                continue
            command = self.commandsByVoice[cmd]
            vc_id = random_string(length=12)

            if order == 'both':
                self.voice_command_strings["%s %s" %
                                           (device_label, cmd)] = vc_id
                self.voice_command_strings["%s %s" %
                                           (cmd, device_label)] = vc_id

            elif order == 'devicecmd':
                self.voice_command_strings["%s %s" %
                                           (device_label, cmd)] = vc_id

            else:
                self.voice_command_strings["%s %s" %
                                           (cmd, device_label)] = vc_id

            self.voice_command_data[vc_id] = {
                'id': vc_id,
                'cmd': command,
                'device': device_obj,
                'call_back': call_back
            }