spiff-arena/SpiffWorkflow/operators.py

373 lines
10 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (C) 2007 Samuel Abels
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA
import logging
import re
logger = logging.getLogger('spiff')
class Term(object):
"""
Abstract base class for all operators and expressions.
"""
pass
class DotDict(dict):
"""dot.notation access to dictionary attributes"""
def __getattr__(*args):
val = dict.get(*args)
return DotDict(val) if type(val) is dict else val
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
class Attrib(Term):
"""
Used for marking a value such that it is recognized to be an
attribute name by valueof().
"""
def __init__(self, name):
self.name = name
def serialize(self, serializer):
"""
Serializes the instance using the provided serializer.
:type serializer: :class:`SpiffWorkflow.serializer.base.Serializer`
:param serializer: The serializer to use.
:rtype: object
:returns: The serialized object.
"""
return serializer.serialize_attrib(self)
@classmethod
def deserialize(cls, serializer, s_state):
"""
Serializes the instance using the provided serializer.
:type serializer: :class:`SpiffWorkflow.serializer.base.Serializer`
:param serializer: The serializer to use.
:rtype: object
:returns: The serialized object.
"""
return serializer.deserialize_attrib(s_state)
class PathAttrib(Term):
"""
Used for marking a value such that it is recognized to be an
attribute obtained by evaluating a path by valueof().
"""
def __init__(self, path):
self.path = path
self.name = path
def serialize(self, serializer):
"""
Serializes the instance using the provided serializer.
:type serializer: :class:`SpiffWorkflow.serializer.base.Serializer`
:param serializer: The serializer to use.
:rtype: object
:returns: The serialized object.
"""
return serializer.serialize_pathattrib(self)
@classmethod
def deserialize(cls, serializer, s_state):
"""
Serializes the instance using the provided serializer.
:type serializer: :class:`SpiffWorkflow.serializer.base.Serializer`
:param serializer: The serializer to use.
:rtype: object
:returns: The serialized object.
"""
return serializer.deserialize_pathattrib(s_state)
class Assign(Term):
"""
Assigns a new value to an attribute. The source may be either
a static value, or another attribute.
"""
def __init__(self,
left_attribute,
right_attribute=None,
right=None,
**kwargs):
"""
Constructor.
:type left_attribute: str
:param left_attribute: The name of the attribute to which the value
is assigned.
:type right: object
:param right: A static value that, when given, is assigned to
left_attribute.
:type right_attribute: str
:param right_attribute: When given, the attribute with the given
name is used as the source (instead of the
static value).
:type kwargs: dict
:param kwargs: See :class:`SpiffWorkflow.specs.TaskSpec`.
"""
if not right_attribute and not right:
raise ValueError('require argument: right_attribute or right')
assert left_attribute is not None
self.left_attribute = left_attribute
self.right_attribute = right_attribute
self.right = right
def assign(self, from_obj, to_obj):
# Fetch the value of the right expression.
if self.right is not None:
right = self.right
else:
right = from_obj.get_data(self.right_attribute)
to_obj.set_data(**{str(self.left_attribute): right})
def serialize(self, serializer):
"""
Serializes the instance using the provided serializer.
:type serializer: :class:`SpiffWorkflow.serializer.base.Serializer`
:param serializer: The serializer to use.
:rtype: object
:returns: The serialized object.
"""
return serializer.serialize_assign(self)
@classmethod
def deserialize(cls, serializer, s_state):
"""
Serializes the instance using the provided serializer.
:type serializer: :class:`SpiffWorkflow.serializer.base.Serializer`
:param serializer: The serializer to use.
:rtype: object
:returns: The serialized object.
"""
return serializer.deserialize_assign(s_state)
def valueof(scope, op, default=None):
if op is None:
return default
elif isinstance(op, Attrib):
if op.name not in scope.data:
logger.debug("Attrib('{op.name}') not present in task data", extra=scope.log_info({'data': scope.data}))
return scope.get_data(op.name, default)
elif isinstance(op, PathAttrib):
if not op.path:
return default
parts = op.path.split('/')
data = scope.data
for part in parts:
if part not in data:
logger.debug(f"PathAttrib('{op.name}') not present in task data", extra=scope.log_info({'data': scope.data}))
return default
data = data[part] # move down the path
return data
else:
return op
def is_number(text):
try:
x = int(text)
except:
return False
return True
class Operator(Term):
"""
Abstract base class for all operators.
"""
def __init__(self, *args):
"""
Constructor.
"""
if len(args) == 0:
raise TypeError("Too few arguments")
self.args = args
def _get_values(self, task):
values = []
for arg in self.args:
values.append(str(valueof(task, arg)))
return values
def _matches(self, task):
raise Exception("Abstract class, do not call")
def serialize(self, serializer):
"""
Serializes the instance using the provided serializer.
:type serializer: :class:`SpiffWorkflow.serializer.base.Serializer`
:param serializer: The serializer to use.
:rtype: object
:returns: The serialized object.
"""
return serializer.serialize_operator(self)
@classmethod
def deserialize(cls, serializer, s_state):
"""
Serializes the instance using the provided serializer.
:type serializer: :class:`SpiffWorkflow.serializer.base.Serializer`
:param serializer: The serializer to use.
:rtype: object
:returns: The serialized object.
"""
return serializer.deserialize_operator(s_state)
class Equal(Operator):
"""
This class represents the EQUAL operator.
"""
def _matches(self, task):
values = self._get_values(task)
last = values[0]
for value in values:
if value != last:
return False
last = value
return True
def serialize(self, serializer):
return serializer.serialize_operator_equal(self)
@classmethod
def deserialize(cls, serializer, s_state):
return serializer.deserialize_operator_equal(s_state)
class NotEqual(Operator):
"""
This class represents the NOT EQUAL operator.
"""
def _matches(self, task):
values = self._get_values(task)
last = values[0]
for value in values:
if value != last:
return True
last = value
return False
def serialize(self, serializer):
return serializer.serialize_operator_not_equal(self)
@classmethod
def deserialize(cls, serializer, s_state):
return serializer.deserialize_operator_not_equal(s_state)
class GreaterThan(Operator):
"""
This class represents the GREATER THAN operator.
"""
def __init__(self, left, right):
"""
Constructor.
"""
Operator.__init__(self, left, right)
def _matches(self, task):
left, right = self._get_values(task)
return int(left) > int(right)
def serialize(self, serializer):
return serializer.serialize_operator_greater_than(self)
@classmethod
def deserialize(cls, serializer, s_state):
return serializer.deserialize_operator_greater_than(s_state)
class LessThan(Operator):
"""
This class represents the LESS THAN operator.
"""
def __init__(self, left, right):
"""
Constructor.
"""
Operator.__init__(self, left, right)
def _matches(self, task):
left, right = self._get_values(task)
return int(left) < int(right)
def serialize(self, serializer):
return serializer.serialize_operator_less_than(self)
@classmethod
def deserialize(cls, serializer, s_state):
return serializer.deserialize_operator_less_than(s_state)
class Match(Operator):
"""
This class represents the regular expression match operator.
"""
def __init__(self, regex, *args):
"""
Constructor.
"""
Operator.__init__(self, *args)
self.regex = re.compile(regex)
def _matches(self, task):
for value in self._get_values(task):
if not self.regex.search(value):
return False
return True
def serialize(self, serializer):
return serializer.serialize_operator_match(self)
@classmethod
def deserialize(cls, serializer, s_state):
return serializer.deserialize_operator_match(s_state)