Module presalytics.lib.plugins.base
Expand source code Browse git
import abc
import typing
import logging
import collections
import lxml
import lxml.etree
import sys
import six
import lxml
import presalytics
import presalytics.lib.exceptions
import presalytics.lib.registry
logger = logging.getLogger('presalytics.lib.plugins.base')
class PluginBase(abc.ABC):
"""
A plugin converts a dictionary of configuration values into an html script.
Typically plugins are are used as reusable mapping classes to add script or link tags to
to a rendered html body.
Attributes
----------
__plugin_kind__ : str
The __plugin_kind__ is a static string that instructs classes
the render story outlines (e.g., presalytics.story.revealer.Revealer) where
to where render the plugin (i.e., at the bottom of the html body for scripts).
__plugin_name__ : str
The __plugin_name__ is a static string that uniquely identifies this plugin to classes
the render story outlines (e.g., `presalytics.story.revealer.Revealer`).
__dependencies__ : list of dict
A list of plugs that should be rendered above this plugin in an html document. This ensures
the needed javascript or css is loaded prior user's plugin runs.
For example, if a user creates plugin requires d3.js to function, dependencies should include
the following configuration:
__dependencies__ = [
{
'kind': 'script',
'name': 'external_scripts',
'config': {
'approved_scripts_key': 'd3'
}
}
]
This ensures that the `presalytics.lib.plugins.external.ApprovedExternalScripts` loads during the
rendering pushes a `<script>` tag into the render html that tells the browser to download d3.js from
a CDN.
"""
__plugin_kind__: str
__plugin_name__: str
__dependencies__: typing.List[typing.Dict]
__dependencies__ = []
config: typing.Dict
def __init__(self, **kwargs):
self.validate_metadata()
def validate_metadata(self):
if self.__plugin_kind__ == "" or self.__plugin_name__ == "":
message = "Plugin class {0} has not defined either __plugin_kind__ or __plugin_name__ metadata".format(self.__class__.__name__)
raise presalytics.lib.exceptions.ValidationError(message)
@abc.abstractmethod
def get_tag(self, config: typing.Dict[str, typing.Any], **kwargs) -> str:
"""
Generic method to be implemented by all inheriting classes. Allow plugin
to be render without knowledge of the underlying plugin kind.
Parameters:
----------
config: Dict
A set configuration values for the plugin. Required keys should be
specified by the docstring of the inheriting classes.
Can be implemented via a mypy_extensions.TypedDict object if warranted.
The presalytics.story.outline.Plugin's config element should be passed
as this value to render the script.
Returns:
----------
A string carrying a html script that can be embedded in a html document
"""
raise NotImplementedError
class ScriptPlugin(PluginBase):
"""
A script plugin incorporates whitelisted or local `<script>` tags into a
rendered story
"""
__plugin_kind__ = 'script'
def get_tag(self, config, **kwargs):
return self.to_script(config, **kwargs)
@abc.abstractmethod
def to_script(self, config: typing.Dict[str, typing.Any], **kwargs) -> str:
"""
The to_script method maps a dictionary to an html script tag.
Parameters:
----------
config: Dict
A set configuration values for the plugin. Required keys should be
specified by the docstring of the inheriting classes.
Can be implemented via a `mypy_extensions.TypedDict` object if warranted.
The `presalytics.story.outline.Plugin`'s config element should be passed
as this value to render the script.
Returns:
----------
A string carrying a html `<script>` fragments that can be embedded in a html document
"""
raise NotImplementedError
class StylePlugin(PluginBase):
"""
A style plugin to incorporates `<link>` and `<style>` tags into a rendered story
"""
__plugin_kind__ = 'style'
def get_tag(self, config, **kwargs):
return self.to_style(config, **kwargs)
@abc.abstractmethod
def to_style(self, config: typing.Dict[str, typing.Any], **kwargs) -> str:
"""
The to_style method maps a dictionary to an html style tag.
Parameters:
----------
config: Dict
A set configuration values for the plugin. Required keys should be
specified by the docstring of the inheriting classes.
Can be implemented via a `mypy_extensions.TypedDict` object if warranted.
The `presalytics.story.outline.Plugin`'s config element should be passed
as this value to render the script.
Returns:
----------
A string carrying a `<link>` or `<style>` tags that can be embedded in a html document
"""
raise NotImplementedError
class PluginRegistry(presalytics.lib.registry.RegistryBase):
"""
The Plugin Registry class
"""
def __init__(self, **kwargs):
super(PluginRegistry, self).__init__(**kwargs)
def get_name(self, klass):
return getattr(klass, "__plugin_name__", None)
def get_type(self, klass):
return getattr(klass, "__plugin_kind__", None)
class Graph():
"""
Utility for identifying circular dependencies in plugins
Adapted from https://www.geeksforgeeks.org/detect-cycle-in-a-graph/
"""
def __init__(self, vertices):
self.graph = collections.defaultdict(list)
self.V = vertices
def addEdge(self, u, v):
self.graph[u].append(v)
def isCyclicUtil(self, v, visited, recStack):
# Mark current node as visited and
# adds to recursion stack
visited[v] = True
recStack[v] = True
# Recur for all neighbours
# if any neighbour is visited and in
# recStack then graph is cyclic
for neighbour in self.graph[v]:
if not visited[neighbour]:
if self.isCyclicUtil(neighbour, visited, recStack):
return True
elif recStack[neighbour] is True:
return True
# The node needs to be poped from
# recursion stack before function ends
recStack[v] = False
return False
# Returns true if graph is cyclic else false
def isCyclic(self):
visited = [False] * self.V
recStack = [False] * self.V
for node in range(self.V):
if not visited[node]:
if self.isCyclicUtil(node, visited, recStack):
return True
return False
class PluginManager(object):
"""
Manager class for sorting, validating and rendering plugins
"""
dependency_map: typing.Dict[str, typing.Dict[str, typing.Any]]
dependency_order: typing.List[str]
def __init__(self, plugins: typing.List[typing.Dict], ignore_errors=True):
self.ignore_errors = ignore_errors
self.dependency_map = {}
self.dependency_order = []
self.load_dependencies(plugins)
self.dependency_graph = Graph(len(self.dependency_map))
self.load_graph()
self.check_for_cyclic_dependencies()
self.sort_plugins()
def sort_plugins(self):
resort = False
for key in self.dependency_order:
current_index = self.dependency_order.index(key)
lowest_dep = None
deps = self.dependency_map[key]["dependencies"]
for dep in deps:
for dep_key, dep_map in self.dependency_map.items():
if dep == dep_map["plugin"]:
dep_index = self.dependency_order.index(dep_key)
if lowest_dep:
if dep_index < lowest_dep:
lowest_dep = dep_index
else:
lowest_dep = dep_index
break
if lowest_dep:
if lowest_dep > current_index:
self.dependency_order.insert(lowest_dep + 1, self.dependency_order.pop(current_index))
resort = True
break
if resort:
self.sort_plugins()
def add_plugin_to_dep_map(self, plugin):
current_plugins = [x["plugin"] for x in self.dependency_map.values()]
if plugin not in current_plugins:
lookup_key = plugin["kind"] + "." + plugin["name"]
plugin_class = presalytics.PLUGINS.get(lookup_key)
if plugin_class is None:
message = "Required plugin {0} not found.".format(lookup_key)
if self.ignore_errors:
logger.error(message)
else:
raise presalytics.lib.exceptions.ValidationError(message)
else:
index = len(self.dependency_map.keys())
dict_key = str(index) + "." + lookup_key
self.dependency_order.append(dict_key)
entry = {
dict_key: {
"plugin": plugin,
"class": plugin_class,
"dependencies": plugin_class.__dependencies__
}
}
self.dependency_map.update(entry)
if len(plugin_class.__dependencies__) > 0:
self.load_dependencies(plugin_class.__dependencies__)
def load_dependencies(self, plugins):
for plugin in plugins:
self.add_plugin_to_dep_map(plugin)
def load_graph(self):
for key, val in self.dependency_map.items():
for dep in val["dependencies"]:
key_node = list(self.dependency_map.keys()).index(key)
plugin_list = [x["plugin"] for x in self.dependency_map.values()]
dep_node = plugin_list.index(dep)
self.dependency_graph.addEdge(key_node, dep_node)
def check_for_cyclic_dependencies(self) -> None:
if self.dependency_graph.isCyclic():
message = "Loaded plugins container circular dependencies. Plugins may not load in correct order"
if self.ignore_errors:
logger.error(message)
else:
raise presalytics.lib.exceptions.ValidationError(message)
def get_styles(self) -> typing.List[str]:
return self.render_plugins('style')
def get_scripts(self) -> typing.List[str]:
return self.render_plugins('script')
def render_plugins(self, plugin_kind: str) -> typing.List[str]:
rendered_list: typing.List[str]
rendered_list = []
for key in self.dependency_order:
dep_map = self.dependency_map[key]
if dep_map["plugin"]["kind"] == plugin_kind:
plugin_config = dep_map["plugin"]
plugin_class = dep_map["class"]
try:
plugin_instance = plugin_class()
tag = plugin_instance.get_tag(config=plugin_config["config"])
except Exception as ex:
logger.exception(ex)
t, v, tb = sys.exc_info()
if not presalytics.CONFIG.get("DEBUG", False):
div = presalytics.lib.exceptions.RenderExceptionHandler(ex, "plugin", traceback=tb).render_exception()
template = lxml.html.Element('template')
template.extend(list(lxml.html.fragment_fromstring(div)))
tag = lxml.html.tostring(template).decode('utf-8')
else:
six.reraise(t, v, tb)
rendered_list.append(tag)
return rendered_list
@staticmethod
def get_plugins_from_nested_dict(source_dict: typing.Dict, plugin_list: typing.List[typing.Dict] = None) -> typing.List[typing.Dict]:
if not plugin_list:
plugin_list = []
for key, val in source_dict.items():
if key == "plugins":
if isinstance(val, list):
for list_item in val:
if isinstance(list_item, dict):
if "config" in list_item and "name" in list_item and "kind" in list_item:
plugin_list.append(list_item)
continue
if isinstance(val, dict):
plugin_list.extend(PluginManager.get_plugins_from_nested_dict(val, plugin_list))
if isinstance(val, list):
for list_item in val:
if isinstance(list_item, dict):
plugin_list.extend(PluginManager.get_plugins_from_nested_dict(list_item, plugin_list))
return plugin_list
Classes
class PluginBase (**kwargs)
-
A plugin converts a dictionary of configuration values into an html script. Typically plugins are are used as reusable mapping classes to add script or link tags to to a rendered html body.
Attributes
__plugin_kind__
:str
- The plugin_kind is a static string that instructs classes the render story outlines (e.g., presalytics.story.revealer.Revealer) where to where render the plugin (i.e., at the bottom of the html body for scripts).
__plugin_name__
:str
- The plugin_name is a static string that uniquely identifies this plugin to classes
the render story outlines (e.g.,
Revealer
). __dependencies__
:list
ofdict
-
A list of plugs that should be rendered above this plugin in an html document. This ensures the needed javascript or css is loaded prior user's plugin runs.
For example, if a user creates plugin requires d3.js to function, dependencies should include the following configuration:
__dependencies__ = [ { 'kind': 'script', 'name': 'external_scripts', 'config': { 'approved_scripts_key': 'd3' } } ]
This ensures that the
ApprovedExternalScripts
loads during the rendering pushes a<script>
tag into the render html that tells the browser to download d3.js from a CDN.
Expand source code Browse git
class PluginBase(abc.ABC): """ A plugin converts a dictionary of configuration values into an html script. Typically plugins are are used as reusable mapping classes to add script or link tags to to a rendered html body. Attributes ---------- __plugin_kind__ : str The __plugin_kind__ is a static string that instructs classes the render story outlines (e.g., presalytics.story.revealer.Revealer) where to where render the plugin (i.e., at the bottom of the html body for scripts). __plugin_name__ : str The __plugin_name__ is a static string that uniquely identifies this plugin to classes the render story outlines (e.g., `presalytics.story.revealer.Revealer`). __dependencies__ : list of dict A list of plugs that should be rendered above this plugin in an html document. This ensures the needed javascript or css is loaded prior user's plugin runs. For example, if a user creates plugin requires d3.js to function, dependencies should include the following configuration: __dependencies__ = [ { 'kind': 'script', 'name': 'external_scripts', 'config': { 'approved_scripts_key': 'd3' } } ] This ensures that the `presalytics.lib.plugins.external.ApprovedExternalScripts` loads during the rendering pushes a `<script>` tag into the render html that tells the browser to download d3.js from a CDN. """ __plugin_kind__: str __plugin_name__: str __dependencies__: typing.List[typing.Dict] __dependencies__ = [] config: typing.Dict def __init__(self, **kwargs): self.validate_metadata() def validate_metadata(self): if self.__plugin_kind__ == "" or self.__plugin_name__ == "": message = "Plugin class {0} has not defined either __plugin_kind__ or __plugin_name__ metadata".format(self.__class__.__name__) raise presalytics.lib.exceptions.ValidationError(message) @abc.abstractmethod def get_tag(self, config: typing.Dict[str, typing.Any], **kwargs) -> str: """ Generic method to be implemented by all inheriting classes. Allow plugin to be render without knowledge of the underlying plugin kind. Parameters: ---------- config: Dict A set configuration values for the plugin. Required keys should be specified by the docstring of the inheriting classes. Can be implemented via a mypy_extensions.TypedDict object if warranted. The presalytics.story.outline.Plugin's config element should be passed as this value to render the script. Returns: ---------- A string carrying a html script that can be embedded in a html document """ raise NotImplementedError
Ancestors
- abc.ABC
Subclasses
Methods
def validate_metadata(self)
-
Expand source code Browse git
def validate_metadata(self): if self.__plugin_kind__ == "" or self.__plugin_name__ == "": message = "Plugin class {0} has not defined either __plugin_kind__ or __plugin_name__ metadata".format(self.__class__.__name__) raise presalytics.lib.exceptions.ValidationError(message)
def get_tag(self, config, **kwargs)
-
Generic method to be implemented by all inheriting classes. Allow plugin to be render without knowledge of the underlying plugin kind.
Parameters:
config: Dict A set configuration values for the plugin. Required keys should be specified by the docstring of the inheriting classes. Can be implemented via a mypy_extensions.TypedDict object if warranted. The presalytics.story.outline.Plugin's config element should be passed as this value to render the script.
Returns:
A string carrying a html script that can be embedded in a html document
Expand source code Browse git
@abc.abstractmethod def get_tag(self, config: typing.Dict[str, typing.Any], **kwargs) -> str: """ Generic method to be implemented by all inheriting classes. Allow plugin to be render without knowledge of the underlying plugin kind. Parameters: ---------- config: Dict A set configuration values for the plugin. Required keys should be specified by the docstring of the inheriting classes. Can be implemented via a mypy_extensions.TypedDict object if warranted. The presalytics.story.outline.Plugin's config element should be passed as this value to render the script. Returns: ---------- A string carrying a html script that can be embedded in a html document """ raise NotImplementedError
class ScriptPlugin (**kwargs)
-
A script plugin incorporates whitelisted or local
<script>
tags into a rendered storyExpand source code Browse git
class ScriptPlugin(PluginBase): """ A script plugin incorporates whitelisted or local `<script>` tags into a rendered story """ __plugin_kind__ = 'script' def get_tag(self, config, **kwargs): return self.to_script(config, **kwargs) @abc.abstractmethod def to_script(self, config: typing.Dict[str, typing.Any], **kwargs) -> str: """ The to_script method maps a dictionary to an html script tag. Parameters: ---------- config: Dict A set configuration values for the plugin. Required keys should be specified by the docstring of the inheriting classes. Can be implemented via a `mypy_extensions.TypedDict` object if warranted. The `presalytics.story.outline.Plugin`'s config element should be passed as this value to render the script. Returns: ---------- A string carrying a html `<script>` fragments that can be embedded in a html document """ raise NotImplementedError
Ancestors
- PluginBase
- abc.ABC
Subclasses
Methods
def to_script(self, config, **kwargs)
-
The to_script method maps a dictionary to an html script tag.
Parameters:
config: Dict A set configuration values for the plugin. Required keys should be specified by the docstring of the inheriting classes. Can be implemented via a
mypy_extensions.TypedDict
object if warranted. ThePlugin
's config element should be passed as this value to render the script.Returns:
A string carrying a html
<script>
fragments that can be embedded in a html documentExpand source code Browse git
@abc.abstractmethod def to_script(self, config: typing.Dict[str, typing.Any], **kwargs) -> str: """ The to_script method maps a dictionary to an html script tag. Parameters: ---------- config: Dict A set configuration values for the plugin. Required keys should be specified by the docstring of the inheriting classes. Can be implemented via a `mypy_extensions.TypedDict` object if warranted. The `presalytics.story.outline.Plugin`'s config element should be passed as this value to render the script. Returns: ---------- A string carrying a html `<script>` fragments that can be embedded in a html document """ raise NotImplementedError
Inherited members
class StylePlugin (**kwargs)
-
A style plugin to incorporates
<link>
and<style>
tags into a rendered storyExpand source code Browse git
class StylePlugin(PluginBase): """ A style plugin to incorporates `<link>` and `<style>` tags into a rendered story """ __plugin_kind__ = 'style' def get_tag(self, config, **kwargs): return self.to_style(config, **kwargs) @abc.abstractmethod def to_style(self, config: typing.Dict[str, typing.Any], **kwargs) -> str: """ The to_style method maps a dictionary to an html style tag. Parameters: ---------- config: Dict A set configuration values for the plugin. Required keys should be specified by the docstring of the inheriting classes. Can be implemented via a `mypy_extensions.TypedDict` object if warranted. The `presalytics.story.outline.Plugin`'s config element should be passed as this value to render the script. Returns: ---------- A string carrying a `<link>` or `<style>` tags that can be embedded in a html document """ raise NotImplementedError
Ancestors
- PluginBase
- abc.ABC
Subclasses
Methods
def to_style(self, config, **kwargs)
-
The to_style method maps a dictionary to an html style tag.
Parameters:
config: Dict A set configuration values for the plugin. Required keys should be specified by the docstring of the inheriting classes. Can be implemented via a
mypy_extensions.TypedDict
object if warranted. ThePlugin
's config element should be passed as this value to render the script.Returns:
A string carrying a
<link>
or<style>
tags that can be embedded in a html documentExpand source code Browse git
@abc.abstractmethod def to_style(self, config: typing.Dict[str, typing.Any], **kwargs) -> str: """ The to_style method maps a dictionary to an html style tag. Parameters: ---------- config: Dict A set configuration values for the plugin. Required keys should be specified by the docstring of the inheriting classes. Can be implemented via a `mypy_extensions.TypedDict` object if warranted. The `presalytics.story.outline.Plugin`'s config element should be passed as this value to render the script. Returns: ---------- A string carrying a `<link>` or `<style>` tags that can be embedded in a html document """ raise NotImplementedError
Inherited members
class PluginRegistry (**kwargs)
-
The Plugin Registry class
Expand source code Browse git
class PluginRegistry(presalytics.lib.registry.RegistryBase): """ The Plugin Registry class """ def __init__(self, **kwargs): super(PluginRegistry, self).__init__(**kwargs) def get_name(self, klass): return getattr(klass, "__plugin_name__", None) def get_type(self, klass): return getattr(klass, "__plugin_kind__", None)
Ancestors
- RegistryBase
- abc.ABC
Methods
def get_name(self, klass)
-
Expand source code Browse git
def get_name(self, klass): return getattr(klass, "__plugin_name__", None)
def get_type(self, klass)
-
Expand source code Browse git
def get_type(self, klass): return getattr(klass, "__plugin_kind__", None)
Inherited members
class Graph (vertices)
-
Utility for identifying circular dependencies in plugins
Adapted from https://www.geeksforgeeks.org/detect-cycle-in-a-graph/
Expand source code Browse git
class Graph(): """ Utility for identifying circular dependencies in plugins Adapted from https://www.geeksforgeeks.org/detect-cycle-in-a-graph/ """ def __init__(self, vertices): self.graph = collections.defaultdict(list) self.V = vertices def addEdge(self, u, v): self.graph[u].append(v) def isCyclicUtil(self, v, visited, recStack): # Mark current node as visited and # adds to recursion stack visited[v] = True recStack[v] = True # Recur for all neighbours # if any neighbour is visited and in # recStack then graph is cyclic for neighbour in self.graph[v]: if not visited[neighbour]: if self.isCyclicUtil(neighbour, visited, recStack): return True elif recStack[neighbour] is True: return True # The node needs to be poped from # recursion stack before function ends recStack[v] = False return False # Returns true if graph is cyclic else false def isCyclic(self): visited = [False] * self.V recStack = [False] * self.V for node in range(self.V): if not visited[node]: if self.isCyclicUtil(node, visited, recStack): return True return False
Methods
def addEdge(self, u, v)
-
Expand source code Browse git
def addEdge(self, u, v): self.graph[u].append(v)
def isCyclicUtil(self, v, visited, recStack)
-
Expand source code Browse git
def isCyclicUtil(self, v, visited, recStack): # Mark current node as visited and # adds to recursion stack visited[v] = True recStack[v] = True # Recur for all neighbours # if any neighbour is visited and in # recStack then graph is cyclic for neighbour in self.graph[v]: if not visited[neighbour]: if self.isCyclicUtil(neighbour, visited, recStack): return True elif recStack[neighbour] is True: return True # The node needs to be poped from # recursion stack before function ends recStack[v] = False return False
def isCyclic(self)
-
Expand source code Browse git
def isCyclic(self): visited = [False] * self.V recStack = [False] * self.V for node in range(self.V): if not visited[node]: if self.isCyclicUtil(node, visited, recStack): return True return False
class PluginManager (plugins, ignore_errors=True)
-
Manager class for sorting, validating and rendering plugins
Expand source code Browse git
class PluginManager(object): """ Manager class for sorting, validating and rendering plugins """ dependency_map: typing.Dict[str, typing.Dict[str, typing.Any]] dependency_order: typing.List[str] def __init__(self, plugins: typing.List[typing.Dict], ignore_errors=True): self.ignore_errors = ignore_errors self.dependency_map = {} self.dependency_order = [] self.load_dependencies(plugins) self.dependency_graph = Graph(len(self.dependency_map)) self.load_graph() self.check_for_cyclic_dependencies() self.sort_plugins() def sort_plugins(self): resort = False for key in self.dependency_order: current_index = self.dependency_order.index(key) lowest_dep = None deps = self.dependency_map[key]["dependencies"] for dep in deps: for dep_key, dep_map in self.dependency_map.items(): if dep == dep_map["plugin"]: dep_index = self.dependency_order.index(dep_key) if lowest_dep: if dep_index < lowest_dep: lowest_dep = dep_index else: lowest_dep = dep_index break if lowest_dep: if lowest_dep > current_index: self.dependency_order.insert(lowest_dep + 1, self.dependency_order.pop(current_index)) resort = True break if resort: self.sort_plugins() def add_plugin_to_dep_map(self, plugin): current_plugins = [x["plugin"] for x in self.dependency_map.values()] if plugin not in current_plugins: lookup_key = plugin["kind"] + "." + plugin["name"] plugin_class = presalytics.PLUGINS.get(lookup_key) if plugin_class is None: message = "Required plugin {0} not found.".format(lookup_key) if self.ignore_errors: logger.error(message) else: raise presalytics.lib.exceptions.ValidationError(message) else: index = len(self.dependency_map.keys()) dict_key = str(index) + "." + lookup_key self.dependency_order.append(dict_key) entry = { dict_key: { "plugin": plugin, "class": plugin_class, "dependencies": plugin_class.__dependencies__ } } self.dependency_map.update(entry) if len(plugin_class.__dependencies__) > 0: self.load_dependencies(plugin_class.__dependencies__) def load_dependencies(self, plugins): for plugin in plugins: self.add_plugin_to_dep_map(plugin) def load_graph(self): for key, val in self.dependency_map.items(): for dep in val["dependencies"]: key_node = list(self.dependency_map.keys()).index(key) plugin_list = [x["plugin"] for x in self.dependency_map.values()] dep_node = plugin_list.index(dep) self.dependency_graph.addEdge(key_node, dep_node) def check_for_cyclic_dependencies(self) -> None: if self.dependency_graph.isCyclic(): message = "Loaded plugins container circular dependencies. Plugins may not load in correct order" if self.ignore_errors: logger.error(message) else: raise presalytics.lib.exceptions.ValidationError(message) def get_styles(self) -> typing.List[str]: return self.render_plugins('style') def get_scripts(self) -> typing.List[str]: return self.render_plugins('script') def render_plugins(self, plugin_kind: str) -> typing.List[str]: rendered_list: typing.List[str] rendered_list = [] for key in self.dependency_order: dep_map = self.dependency_map[key] if dep_map["plugin"]["kind"] == plugin_kind: plugin_config = dep_map["plugin"] plugin_class = dep_map["class"] try: plugin_instance = plugin_class() tag = plugin_instance.get_tag(config=plugin_config["config"]) except Exception as ex: logger.exception(ex) t, v, tb = sys.exc_info() if not presalytics.CONFIG.get("DEBUG", False): div = presalytics.lib.exceptions.RenderExceptionHandler(ex, "plugin", traceback=tb).render_exception() template = lxml.html.Element('template') template.extend(list(lxml.html.fragment_fromstring(div))) tag = lxml.html.tostring(template).decode('utf-8') else: six.reraise(t, v, tb) rendered_list.append(tag) return rendered_list @staticmethod def get_plugins_from_nested_dict(source_dict: typing.Dict, plugin_list: typing.List[typing.Dict] = None) -> typing.List[typing.Dict]: if not plugin_list: plugin_list = [] for key, val in source_dict.items(): if key == "plugins": if isinstance(val, list): for list_item in val: if isinstance(list_item, dict): if "config" in list_item and "name" in list_item and "kind" in list_item: plugin_list.append(list_item) continue if isinstance(val, dict): plugin_list.extend(PluginManager.get_plugins_from_nested_dict(val, plugin_list)) if isinstance(val, list): for list_item in val: if isinstance(list_item, dict): plugin_list.extend(PluginManager.get_plugins_from_nested_dict(list_item, plugin_list)) return plugin_list
Static methods
def get_plugins_from_nested_dict(source_dict, plugin_list=None)
-
Expand source code Browse git
@staticmethod def get_plugins_from_nested_dict(source_dict: typing.Dict, plugin_list: typing.List[typing.Dict] = None) -> typing.List[typing.Dict]: if not plugin_list: plugin_list = [] for key, val in source_dict.items(): if key == "plugins": if isinstance(val, list): for list_item in val: if isinstance(list_item, dict): if "config" in list_item and "name" in list_item and "kind" in list_item: plugin_list.append(list_item) continue if isinstance(val, dict): plugin_list.extend(PluginManager.get_plugins_from_nested_dict(val, plugin_list)) if isinstance(val, list): for list_item in val: if isinstance(list_item, dict): plugin_list.extend(PluginManager.get_plugins_from_nested_dict(list_item, plugin_list)) return plugin_list
Methods
def sort_plugins(self)
-
Expand source code Browse git
def sort_plugins(self): resort = False for key in self.dependency_order: current_index = self.dependency_order.index(key) lowest_dep = None deps = self.dependency_map[key]["dependencies"] for dep in deps: for dep_key, dep_map in self.dependency_map.items(): if dep == dep_map["plugin"]: dep_index = self.dependency_order.index(dep_key) if lowest_dep: if dep_index < lowest_dep: lowest_dep = dep_index else: lowest_dep = dep_index break if lowest_dep: if lowest_dep > current_index: self.dependency_order.insert(lowest_dep + 1, self.dependency_order.pop(current_index)) resort = True break if resort: self.sort_plugins()
def add_plugin_to_dep_map(self, plugin)
-
Expand source code Browse git
def add_plugin_to_dep_map(self, plugin): current_plugins = [x["plugin"] for x in self.dependency_map.values()] if plugin not in current_plugins: lookup_key = plugin["kind"] + "." + plugin["name"] plugin_class = presalytics.PLUGINS.get(lookup_key) if plugin_class is None: message = "Required plugin {0} not found.".format(lookup_key) if self.ignore_errors: logger.error(message) else: raise presalytics.lib.exceptions.ValidationError(message) else: index = len(self.dependency_map.keys()) dict_key = str(index) + "." + lookup_key self.dependency_order.append(dict_key) entry = { dict_key: { "plugin": plugin, "class": plugin_class, "dependencies": plugin_class.__dependencies__ } } self.dependency_map.update(entry) if len(plugin_class.__dependencies__) > 0: self.load_dependencies(plugin_class.__dependencies__)
def load_dependencies(self, plugins)
-
Expand source code Browse git
def load_dependencies(self, plugins): for plugin in plugins: self.add_plugin_to_dep_map(plugin)
def load_graph(self)
-
Expand source code Browse git
def load_graph(self): for key, val in self.dependency_map.items(): for dep in val["dependencies"]: key_node = list(self.dependency_map.keys()).index(key) plugin_list = [x["plugin"] for x in self.dependency_map.values()] dep_node = plugin_list.index(dep) self.dependency_graph.addEdge(key_node, dep_node)
def check_for_cyclic_dependencies(self)
-
Expand source code Browse git
def check_for_cyclic_dependencies(self) -> None: if self.dependency_graph.isCyclic(): message = "Loaded plugins container circular dependencies. Plugins may not load in correct order" if self.ignore_errors: logger.error(message) else: raise presalytics.lib.exceptions.ValidationError(message)
def get_styles(self)
-
Expand source code Browse git
def get_styles(self) -> typing.List[str]: return self.render_plugins('style')
def get_scripts(self)
-
Expand source code Browse git
def get_scripts(self) -> typing.List[str]: return self.render_plugins('script')
def render_plugins(self, plugin_kind)
-
Expand source code Browse git
def render_plugins(self, plugin_kind: str) -> typing.List[str]: rendered_list: typing.List[str] rendered_list = [] for key in self.dependency_order: dep_map = self.dependency_map[key] if dep_map["plugin"]["kind"] == plugin_kind: plugin_config = dep_map["plugin"] plugin_class = dep_map["class"] try: plugin_instance = plugin_class() tag = plugin_instance.get_tag(config=plugin_config["config"]) except Exception as ex: logger.exception(ex) t, v, tb = sys.exc_info() if not presalytics.CONFIG.get("DEBUG", False): div = presalytics.lib.exceptions.RenderExceptionHandler(ex, "plugin", traceback=tb).render_exception() template = lxml.html.Element('template') template.extend(list(lxml.html.fragment_fromstring(div))) tag = lxml.html.tostring(template).decode('utf-8') else: six.reraise(t, v, tb) rendered_list.append(tag) return rendered_list