Skip to content
Snippets Groups Projects
Commit 65c67b99 authored by Stefano Borini's avatar Stefano Borini
Browse files

Removed KPI submodule

parent 725ec246
No related branches found
No related tags found
1 merge request!120Allow KPIs to be specified as datasource outputs.
import abc
from traits.api import ABCHasStrictTraits, Instance
from .i_kpi_calculator_factory import IKPICalculatorFactory
class BaseKPICalculator(ABCHasStrictTraits):
"""Base class for the KPICalculators.
Inherit this class for your KPI calculator.
"""
#: A reference to the factory
factory = Instance(IKPICalculatorFactory)
def __init__(self, factory, *args, **kwargs):
self.factory = factory
super(BaseKPICalculator, self).__init__(*args, **kwargs)
@abc.abstractmethod
def run(self, model, data_values):
"""
Executes the KPI evaluation and returns the results it computes.
Reimplement this method in your specific KPI calculator.
Parameters
----------
model: BaseKPICalculatorModel
The model of the KPI Calculator, instantiated through
create_model()
data_values:
a list of DataValue instances containing data from the
MCO and DataSources.
Returns
-------
List[DataValue]:
The result of this KPI evaluation, as a list of DataValues.
"""
@abc.abstractmethod
def slots(self, model):
"""Returns the input (and output) slots of the KPI Calculator.
Slots are the entities that are needed (and produced) by this
KPI Calculator.
The slots may depend on the configuration options, and thus the model.
This allows, for example, to change the slots depending if an option
is enabled or not.
Parameters
----------
model: BaseKPICalculatorModel
The model of the KPICalculator, instantiated through create_model()
Returns
-------
(input_slots, output_slots): tuple[tuple, tuple]
A tuple containing two tuples.
The first element is the input slots, the second element is
the output slots. Each slot must be an instance of the Slot class.
It is possible for each of the two inside tuples to be empty.
The case of an empty input slot is common: the KPICalculator does
not need any information from the MCO to operate.
The case of an empty output slot is uncommon, but supported:
the KPICalculator does not produce any output and is therefore
useless.
"""
import logging
from envisage.plugin import Plugin
from traits.api import ABCHasStrictTraits, provides, String, Instance, Type
from force_bdss.kpi.base_kpi_calculator import BaseKPICalculator
from force_bdss.kpi.base_kpi_calculator_model import BaseKPICalculatorModel
from .i_kpi_calculator_factory import IKPICalculatorFactory
log = logging.getLogger(__name__)
@provides(IKPICalculatorFactory)
class BaseKPICalculatorFactory(ABCHasStrictTraits):
"""Base class for the Key Performance Indicator calculator factories.
Inherit from this class to create a factory, and reimplement the abstract
methods.
"""
# NOTE: any changes in this interface must be ported to
# IKPICalculatorFactory
#: A unique ID generated with factory_id() routine
id = String()
#: A UI friendly name for the factory. Can contain spaces.
name = String()
#: The KPI calculator to be instantiated. Define this to your KPICalculator
kpi_calculator_class = Type(BaseKPICalculator)
#: The model associated to the KPI calculator.
#: Define this to your KPICalculatorModel
model_class = Type(BaseKPICalculatorModel)
#: A reference to the plugin that holds this factory.
plugin = Instance(Plugin)
def __init__(self, plugin, *args, **kwargs):
"""Initializes the instance.
Parameters
----------
plugin: Plugin
The plugin that holds this factory.
"""
self.plugin = plugin
super(BaseKPICalculatorFactory, self).__init__(*args, **kwargs)
def create_kpi_calculator(self):
"""Factory method.
Creates and returns an instance of a KPI Calculator, associated
to the given application and model.
Returns
-------
BaseKPICalculator
The specific instance of the generated KPICalculator
"""
if self.kpi_calculator_class is None:
msg = ("kpi_calculator_class cannot be None in {}. Either define "
"kpi_calculator_class or reimplement create_kpi_calculator "
"on your factory class.".format(self.__class__.__name__))
log.error(msg)
raise RuntimeError(msg)
return self.kpi_calculator_class(self)
def create_model(self, model_data=None):
"""Factory method.
Creates the model object (or network of model objects) of the KPI
calculator. The model can provide a traits UI View according to
traitsui specifications, so that a UI can be provided automatically.
Parameters
----------
model_data: dict or None
A dictionary containing the information to recreate the model.
If None, an empty (with defaults) model will be returned.
Returns
-------
BaseKPICalculatorModel
The model
"""
if model_data is None:
model_data = {}
if self.model_class is None:
msg = ("model_class cannot be None in {}. Either define "
"model_class or reimplement create_model on your "
"factory class.".format(self.__class__.__name__))
log.error(msg)
raise RuntimeError(msg)
return self.model_class(self, **model_data)
from traits.api import ABCHasStrictTraits, Instance, List, Event
from force_bdss.core.output_slot_info import OutputSlotInfo
from ..core.input_slot_map import InputSlotInfo
from .i_kpi_calculator_factory import IKPICalculatorFactory
class BaseKPICalculatorModel(ABCHasStrictTraits):
"""Base class for the factory specific KPI calculator models.
This model will also provide, through traits/traitsui magic the View
that will appear in the workflow manager UI.
In your factory definition, your factory-specific model must reimplement
this class.
"""
#: A reference to the creating factory, so that we can
#: retrieve it as the originating factory.
factory = Instance(IKPICalculatorFactory, visible=False, transient=True)
#: Specifies binding between input slots and source for that value.
#: Each InputSlotMap instance specifies this information for each of the
#: slots.
input_slot_info = List(Instance(InputSlotInfo), visible=False)
#: Allows to assign names to the output slots, so that they can be
#: referenced somewhere else (e.g. the KPICalculators).
#: If the name is the empty string, it means that the user is not
#: interested in preserving the information, and should therefore be
#: discarded and not propagated further.
output_slot_info = List(Instance(OutputSlotInfo), visible=False)
#: This event claims that a change in the model influences the slots
#: (either input or output). It must be triggered every time a specific
#: option in your model implies a change in the slots. The UI will detect
#: this and adapt the visual entries.
changes_slots = Event()
def __init__(self, factory, *args, **kwargs):
self.factory = factory
super(BaseKPICalculatorModel, self).__init__(*args, **kwargs)
def __getstate__(self):
state = super(BaseKPICalculatorModel, self).__getstate__()
state["input_slot_info"] = [
x.__getstate__() for x in self.input_slot_info
]
state["output_slot_info"] = [
x.__getstate__() for x in self.output_slot_info
]
return state
from traits.api import Interface, String, Instance, Type
from envisage.plugin import Plugin
class IKPICalculatorFactory(Interface):
"""Envisage required interface for the BaseKPICalculatorFactory.
You should not need to use this directly.
Refer to the BaseKPICalculatorFactory for documentation.
"""
id = String()
name = String()
kpi_calculator_class = Type(
"force_bdss.kpi.base_kpi_calculator.BaseKPICalculator"
)
model_class = Type(
"force_bdss.kpi.base_kpi_calculator_model.BaseKPICalculatorModel"
)
plugin = Instance(Plugin)
def create_kpi_calculator(self):
""""""
def create_model(self, model_data=None):
""""""
import unittest
try:
import mock
except ImportError:
from unittest import mock
from force_bdss.kpi.base_kpi_calculator import BaseKPICalculator
from force_bdss.kpi.i_kpi_calculator_factory import IKPICalculatorFactory
class DummyKPICalculator(BaseKPICalculator):
def run(self, *args, **kwargs):
pass
def slots(self, model):
return (), ()
class TestBaseKPICalculator(unittest.TestCase):
def test_initialization(self):
factory = mock.Mock(spec=IKPICalculatorFactory)
kpic = DummyKPICalculator(factory)
self.assertEqual(kpic.factory, factory)
import unittest
import testfixtures
from envisage.plugin import Plugin
from force_bdss.kpi.tests.test_base_kpi_calculator import DummyKPICalculator
from force_bdss.kpi.tests.test_base_kpi_calculator_model import \
DummyKPICalculatorModel
try:
import mock
except ImportError:
from unittest import mock
from force_bdss.kpi.base_kpi_calculator_factory import \
BaseKPICalculatorFactory
class DummyKPICalculatorFactory(BaseKPICalculatorFactory):
id = "foo"
name = "bar"
def create_kpi_calculator(self):
pass
def create_model(self, model_data=None):
pass
class DummyKPICalculatorFactoryFast(BaseKPICalculatorFactory):
id = "foo"
name = "bar"
kpi_calculator_class = DummyKPICalculator
model_class = DummyKPICalculatorModel
class TestBaseKPICalculatorFactory(unittest.TestCase):
def test_initialization(self):
factory = DummyKPICalculatorFactory(mock.Mock(spec=Plugin))
self.assertEqual(factory.id, 'foo')
self.assertEqual(factory.name, 'bar')
def test_fast_definition(self):
factory = DummyKPICalculatorFactoryFast(mock.Mock(spec=Plugin))
self.assertIsInstance(factory.create_kpi_calculator(),
DummyKPICalculator)
self.assertIsInstance(factory.create_model(),
DummyKPICalculatorModel)
def test_fast_definition_errors(self):
factory = DummyKPICalculatorFactoryFast(mock.Mock(spec=Plugin))
factory.kpi_calculator_class = None
factory.model_class = None
with testfixtures.LogCapture():
with self.assertRaises(RuntimeError):
factory.create_kpi_calculator()
with self.assertRaises(RuntimeError):
factory.create_model()
import unittest
from force_bdss.core.input_slot_map import InputSlotInfo
from force_bdss.core.output_slot_info import OutputSlotInfo
from force_bdss.kpi.base_kpi_calculator_factory import BaseKPICalculatorFactory
from force_bdss.kpi.base_kpi_calculator_model import BaseKPICalculatorModel
try:
import mock
except ImportError:
from unittest import mock
class DummyKPICalculatorModel(BaseKPICalculatorModel):
pass
class TestBaseKPICalculatorModel(unittest.TestCase):
def test_getstate(self):
model = DummyKPICalculatorModel(
mock.Mock(spec=BaseKPICalculatorFactory))
self.assertEqual(
model.__getstate__(),
{
"__traits_version__": "4.6.0",
"input_slot_info": [],
"output_slot_info": []
})
model.input_slot_info = [
InputSlotInfo(name="foo"),
InputSlotInfo(name="bar")
]
model.output_slot_info = [
OutputSlotInfo(name="baz"),
OutputSlotInfo(name="quux")
]
self.assertEqual(
model.__getstate__(),
{
"__traits_version__": "4.6.0",
"input_slot_info": [
{
"__traits_version__": "4.6.0",
"source": "Environment",
"name": "foo"
},
{
"__traits_version__": "4.6.0",
"source": "Environment",
"name": "bar"
}
],
"output_slot_info": [
{
"__traits_version__": "4.6.0",
"name": "baz",
"kpi": False,
},
{
"__traits_version__": "4.6.0",
"name": "quux",
"kpi": False,
}
]
})
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment