Source code for luci.finders

# -*- coding: utf-8 -*-

import abc
import logging
import os
import re

import six

from frutils import dict_merge
from frutils.defaults import KEY_LUCI_NAME
from .defaults import *
from .exceptions import NoSuchDictletException

log = logging.getLogger("lucify")

DICTLET_CACHE = {}


[docs]def create_dictlet_finder(dictlet_finder_name, dictlet_finder_config=None): """Create a dictlet reader with the provided config. Args: dictlet_finder_name (str): the registered name (in setup.py entry point) dictlet_finder_config (dict): optional configuration for the finder object Returns: DictletFinder: the finder object """ from .plugins import load_dictlet_finder_extension if dictlet_finder_config is None: dictlet_finder_config = {} dictlet_finder = load_dictlet_finder_extension( dictlet_finder_name, init_params=dictlet_finder_config ) return dictlet_finder
[docs]def check_dictlet_content(file_path, regex): """Checks whether a dictlet file matches the specified regular expression. Args: file_path (str): the path to the dictlet regex (str, _sre.SRE_Pattern): the regular expression Returns: bool: whether there was a match or not """ if isinstance(regex, six.string_types): regex = re.compile(regex) try: with open(file_path) as f: line = f.readline() while line != "": match = regex.search(line) if match: return True # Read next line line = f.readline() except (Exception) as e: log.debug( "Could not check file '{}' for dictlet properties: {}".format(file_path, e) ) return False return False
[docs]@six.add_metaclass(abc.ABCMeta) class DictletFinder(object): """Abstract base class for an object that can find and load dictlets. The task of a DictletFinder is to find all available dictlets for the current environment. A sub-class of this should implement the :meth:`find_available_dictlets` and/or, if applicable, overwrite the :meth:`get_dictlet_details` methods. If it's not possible to get a list of all available dictlets, it's advisable to return an empty dict for the :meth:`find_available_dictlets` method and implement :meth:`get_dictlet_details`. """ def __init__(self): self.available_dictlets = None
[docs] def init_dictlet_cache(self, name=None): """Fills the cache, either with all dictlets, or only the one whose name was specified. Args: name (str): if specified, only the dictlet with that name is loaded, otherwise all available Returns: dict: a dictionary with either one (requested) dictlet and it's details, or all of them """ if name is None: if self.available_dictlets is None: self.available_dictlets = {} dictlet_names = self.get_all_dictlet_names() for name in dictlet_names: self.available_dictlets[name] = self.get_dictlet(name) return self.available_dictlets else: if ( self.available_dictlets is None or name not in self.available_dictlets.keys() ): details = self.get_dictlet(name) if not details: return {} else: details = self.available_dictlets[name] return {name: details}
[docs] @abc.abstractmethod def get_dictlet(self, name): """Loads the dictlet with the specified name/path. Args: name (str): the name (or path) of the dictlet Return: dict: details for this dictlet, None if not found """ pass
[docs] @abc.abstractmethod def get_all_dictlet_names(self): """Returns a list of all available dictlet names. Returns: list: all dictlet names """ pass
[docs] def get_all_dictlets(self): """Returns all available dictlets. Returns: dict: a dictionary with the dictlet name as key, and it's url as value """ return self.init_dictlet_cache()
[docs] def get_dictlet_details(self, name): """Returns the details of the dictlet with the specified name. The returned details should contain at least a 'type' key, indicating the type of the dictlet (e.g. 'file', 'class'). Which and whether other keys are necessary depends on that type. Args: name: the name or url of a dictlet Returns: dict: details about this dictlet """ cache = self.init_dictlet_cache(name=name) result = cache.get(name, None) if not result: raise NoSuchDictletException(name) return result
[docs]class LucifierFinder(DictletFinder): """Finds all lucifiers. This lucifier is mainly used to create a command-line interface using the :class:`luci.cli_lucifer.CliLucifier` class. """ def __init__(self): super(LucifierFinder, self).__init__() # otherwise we have a module load loop from .plugins import get_plugin_names, extension_details self.plugin_names_lookup = get_plugin_names self.plugin_details_lookup = extension_details
[docs] def get_all_dictlet_names(self): return self.plugin_names_lookup()
[docs] def get_dictlet(self, name): """Loads (stevedore-registered) available lucifier class in this Python environment. Returns: dict: lucifier details """ result = self.plugin_details_lookup.get(name, None) return result
[docs]class PathDictletFinder(DictletFinder): """Can find dictlets using a list of paths. Args: paths (list): a list of paths where to look for dictlets max_folders (int): how many child-folder levels to look for dictlets, this is restricted by default because for performance reasons """ CONTENT_MARKER_REGEX = re.compile( "{}|{}".format(DICTLET_READER_MARKER_STRING, KEY_LUCI_NAME), re.IGNORECASE ) def __init__(self, paths=None, max_folders=PATH_FINDER_MAX_FOLDER_DEFAULT): super(PathDictletFinder, self).__init__() if paths is None: paths = [os.getcwd()] self.paths = paths self.max_level = max_folders self.dictlet_cache = {}
[docs] def find_available_dictlets_path( self, base_path, relative_path="", current_result=None, current_level=0 ): """Checks a path for valid dictlet files. Args: base_bath (str): the base path (corresponding to one of the object initial paths) relative_path (str): relative path (from the base_path) to the folder/file in question current_result (dict): used for recursive call of this method current_level (int): used for recursive call of this method Return: dict: a list of available dictlets (so far) """ if current_result is None: current_result = {} if current_level >= self.max_level: return current_result if relative_path.startswith(".") and current_level != 0: return current_result path = os.path.join(base_path, relative_path) for f in os.listdir(path): file_path = os.path.join(path, f) if os.path.isdir(file_path): new_relative_path = os.path.join(relative_path, f) current_result = self.find_available_dictlets_path( base_path, new_relative_path, current_result, current_level + 1 ) elif os.path.isfile(file_path) and check_dictlet_content( file_path, PathDictletFinder.CONTENT_MARKER_REGEX ): rel_path = os.path.join(relative_path, f) current_result[rel_path] = { "path": os.path.join(base_path, rel_path), "type": "file", } return current_result
[docs] def find_available_dictlets(self, paths=None): """Finds all available dictlets under the specified paths. Args: paths (list): the paths to look in. If not specified, the objects default paths will be used Return: dict: all dictlets and their details """ result = {} if paths is None: paths = self.paths if isinstance(paths, six.string_types): paths = [paths] for path in paths: path_dictlets = None if path not in self.dictlet_cache.keys(): self.dictlet_cache["path"] = {} real_path = os.path.realpath(path) if os.path.isdir(real_path): path_dictlets = self.find_available_dictlets_path(path) dict_merge( self.dictlet_cache["path"], path_dictlets, copy_dct=False ) elif os.path.isfile(real_path): path_dictlets = {path: {"path": path, "type": "file"}} log.debug("Found dictlets in path '{}': {}".format(path, path_dictlets)) else: path_dictlets = self.dictlet_cache[path] if path_dictlets: dict_merge(result, path_dictlets, copy_dct=False) return result
[docs] def get_all_dictlet_names(self): return self.find_available_dictlets().keys()
[docs] def get_dictlet(self, name): result = self.find_available_dictlets(name).get(name, None) return result
[docs]class FolderFinder(DictletFinder): """Simple finder for folders.""" def __init__(self, **kwargs): super(FolderFinder, self).__init__(**kwargs)
[docs] def get_all_dictlet_names(self): return []
[docs] def get_dictlet(self, name): abs_path = os.path.realpath(name) if os.path.isdir(abs_path): return {"path": abs_path, "type": "folder"} return None
[docs]class FolderOrFileFinder(DictletFinder): """Simple finder for folders.""" def __init__(self, **kwargs): super(FolderOrFileFinder, self).__init__(**kwargs)
[docs] def get_all_dictlet_names(self): return []
[docs] def get_dictlet(self, name): abs_path = os.path.realpath(name) if os.path.isdir(abs_path): return {"path": abs_path, "type": "folder"} elif os.path.isfile(abs_path): return {"path": abs_path, "type": "file"} return None