Source code for selinon.predicate
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ######################################################################
# Copyright (C) 2016-2018 Fridolin Pokorny, fridolin.pokorny@gmail.com
# This file is part of Selinon project.
# ######################################################################
"""Predicate interface - predicate for building conditions."""
import abc
import codegen
from .errors import ConfigurationError
from .helpers import check_conf_keys
from .helpers import dict2json
[docs]class Predicate(metaclass=abc.ABCMeta):
"""An abstract predicate representation."""
@abc.abstractmethod
def __init__(self):
"""Instantiate predicate representation."""
@abc.abstractmethod
def __str__(self):
"""Create a string representation of this predicate.
:return: string representation (Python code)
:rtype: str
"""
[docs] @abc.abstractclassmethod
def create(cls, tree, nodes_from, flow, can_inspect_results):
"""Create the predicate.
:param tree: node from which should be predicate instantiated
:type tree: List
:param nodes_from: nodes which are used within edge definition
:type nodes_from: List[Nodes]
:param flow: flow to which predicate belongs to
:type flow: Flow
:param can_inspect_results: True if predicates in the condition can query task result
:type can_inspect_results: bool
:return: Predicate instance
"""
[docs] @abc.abstractmethod
def ast(self):
"""Python AST of this predicate (construct transitively for all indirect children as well).
:return: AST of describing all children predicates
"""
[docs] @abc.abstractmethod
def predicates_used(self):
"""Compute all predicates that are used (transitively) by child/children.
:return: used predicates by children
:rtype: List[Predicate]
"""
[docs] @abc.abstractmethod
def nodes_used(self):
"""Compute all nodes that are used (transitively) by child/children.
:return: list of nodes that are used
:rtype: List[Node]
"""
[docs] @staticmethod
def construct_default(flow):
"""Construct default predicate for edge.
:param flow: flow to which predicate belongs to
:type flow: Flow
:rtype: Predicate
"""
from .builtin_predicate import AlwaysTruePredicate
return AlwaysTruePredicate(flow=flow)
[docs] @staticmethod
def construct_default_dict():
"""Construct default predicate definition as it would be written in the configuration file."""
return {'name': 'alwaysTrue'}
[docs] @staticmethod
def construct(tree, nodes_from, flow, can_inspect_results=True): # pylint: disable=too-many-branches
"""Top-down creation of predicates - recursively called to construct predicates.
:param tree: a dictionary describing nodes
:type tree: dict
:param nodes_from: nodes which are used within edge
:param flow: flow to which predicate belongs to
:type flow: Flow
:param can_inspect_results: True if predicates in the condition can query task result
:type can_inspect_results: bool
:rtype: Predicate
"""
from .leaf_predicate import LeafPredicate
from .builtin_predicate import OrPredicate, AndPredicate, NotPredicate
if not tree:
raise ConfigurationError("Bad condition '%s'" % tree)
if 'name' in tree:
if 'node' in tree:
node = None
for node_from in nodes_from:
if node_from.name == tree['node']:
node = node_from
break
if node is None:
raise ConfigurationError("Node '%s' listed in predicate '%s' is not requested in 'nodes_from'"
% (tree['node'], tree['name']))
else:
if len(nodes_from) == 1:
node = nodes_from[0]
else:
# e.g. starting edge has no nodes_from
node = None
unknown_conf = check_conf_keys(tree, known_conf_opts=('name', 'node', 'args'))
if unknown_conf:
raise ConfigurationError("Unknown configuration option for predicate '%s' in flow '%s': %s"
% (tree['name'], flow.name, unknown_conf.keys()))
predicate = LeafPredicate.create(tree['name'], node, flow, tree.get('args'))
if not can_inspect_results and predicate.requires_message():
raise ConfigurationError("Cannot inspect results of tasks '%s' in predicate '%s' in flow '%s'"
% (nodes_from, tree['name'], flow.name))
return predicate
if 'or' in tree:
return OrPredicate.create(tree['or'], nodes_from, flow, can_inspect_results)
if 'not' in tree:
return NotPredicate.create(tree['not'], nodes_from, flow, can_inspect_results)
if 'and' in tree:
return AndPredicate.create(tree['and'], nodes_from, flow, can_inspect_results)
raise ConfigurationError("Unknown predicate:\n%s" % dict2json(tree))
[docs] @staticmethod
def construct_condition_name(flow_name, idx):
"""Create condition name for a dump.
:param flow_name: flow name
:type flow_name: str
:param idx: index of condition within the flow
:type idx: int
:return: condition function representation
"""
assert idx >= 0 # nosec
return '_condition_{}_{}_cond'.format(flow_name, idx)
[docs] def to_source(self):
"""Construct predicate source code.
:return: predicate source code
"""
return codegen.to_source(self.ast())
[docs] @abc.abstractmethod
def check(self):
"""Recursively/transitively check predicate correctness.
:raises ValueError: if predicate is not correct
"""
[docs] @abc.abstractmethod
def requires_message(self):
"""Recursively check if one of the predicates require message from storage (result of previous task).
:return: True if a result from storage is required
"""