Skip to content
Snippets Groups Projects
Unverified Commit f985caaf authored by Stefano Borini's avatar Stefano Borini Committed by GitHub
Browse files

Merge pull request #120 from force-h2020/remove-kpi

Allow KPIs to be specified as datasource outputs.
parents 1e55a245 31e32338
No related branches found
No related tags found
No related merge requests found
Showing
with 12 additions and 465 deletions
force\_bdss\.kpi package
========================
Subpackages
-----------
.. toctree::
force_bdss.kpi.tests
Submodules
----------
force\_bdss\.kpi\.base\_kpi\_calculator module
----------------------------------------------
.. automodule:: force_bdss.kpi.base_kpi_calculator
:members:
:undoc-members:
:show-inheritance:
force\_bdss\.kpi\.base\_kpi\_calculator\_factory module
-------------------------------------------------------
.. automodule:: force_bdss.kpi.base_kpi_calculator_factory
:members:
:undoc-members:
:show-inheritance:
force\_bdss\.kpi\.base\_kpi\_calculator\_model module
-----------------------------------------------------
.. automodule:: force_bdss.kpi.base_kpi_calculator_model
:members:
:undoc-members:
:show-inheritance:
force\_bdss\.kpi\.i\_kpi\_calculator\_factory module
----------------------------------------------------
.. automodule:: force_bdss.kpi.i_kpi_calculator_factory
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: force_bdss.kpi
:members:
:undoc-members:
:show-inheritance:
force\_bdss\.kpi\.tests package
===============================
Submodules
----------
force\_bdss\.kpi\.tests\.test\_base\_kpi\_calculator module
-----------------------------------------------------------
.. automodule:: force_bdss.kpi.tests.test_base_kpi_calculator
:members:
:undoc-members:
:show-inheritance:
force\_bdss\.kpi\.tests\.test\_base\_kpi\_calculator\_factory module
--------------------------------------------------------------------
.. automodule:: force_bdss.kpi.tests.test_base_kpi_calculator_factory
:members:
:undoc-members:
:show-inheritance:
force\_bdss\.kpi\.tests\.test\_base\_kpi\_calculator\_model module
------------------------------------------------------------------
.. automodule:: force_bdss.kpi.tests.test_base_kpi_calculator_model
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: force_bdss.kpi.tests
:members:
:undoc-members:
:show-inheritance:
...@@ -11,7 +11,6 @@ Subpackages ...@@ -11,7 +11,6 @@ Subpackages
force_bdss.core_plugins force_bdss.core_plugins
force_bdss.data_sources force_bdss.data_sources
force_bdss.io force_bdss.io
force_bdss.kpi
force_bdss.mco force_bdss.mco
force_bdss.notification_listeners force_bdss.notification_listeners
force_bdss.tests force_bdss.tests
......
...@@ -28,14 +28,6 @@ force\_bdss\.tests\.probe\_classes\.factory\_registry\_plugin module ...@@ -28,14 +28,6 @@ force\_bdss\.tests\.probe\_classes\.factory\_registry\_plugin module
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
force\_bdss\.tests\.probe\_classes\.kpi\_calculator module
----------------------------------------------------------
.. automodule:: force_bdss.tests.probe_classes.kpi_calculator
:members:
:undoc-members:
:show-inheritance:
force\_bdss\.tests\.probe\_classes\.mco module force\_bdss\.tests\.probe\_classes\.mco module
---------------------------------------------- ----------------------------------------------
......
...@@ -10,11 +10,6 @@ from .data_sources.base_data_source import BaseDataSource # noqa ...@@ -10,11 +10,6 @@ from .data_sources.base_data_source import BaseDataSource # noqa
from .data_sources.base_data_source_factory import BaseDataSourceFactory # noqa from .data_sources.base_data_source_factory import BaseDataSourceFactory # noqa
from .data_sources.i_data_source_factory import IDataSourceFactory # noqa from .data_sources.i_data_source_factory import IDataSourceFactory # noqa
from .kpi.base_kpi_calculator import BaseKPICalculator # noqa
from .kpi.base_kpi_calculator_model import BaseKPICalculatorModel # noqa
from .kpi.base_kpi_calculator_factory import BaseKPICalculatorFactory # noqa
from .kpi.i_kpi_calculator_factory import IKPICalculatorFactory # noqa
from .mco.base_mco_model import BaseMCOModel # noqa from .mco.base_mco_model import BaseMCOModel # noqa
from .mco.base_mco_communicator import BaseMCOCommunicator # noqa from .mco.base_mco_communicator import BaseMCOCommunicator # noqa
from .mco.base_mco import BaseMCO # noqa from .mco.base_mco import BaseMCO # noqa
......
...@@ -5,7 +5,6 @@ from .notification_listeners.i_notification_listener_factory import \ ...@@ -5,7 +5,6 @@ from .notification_listeners.i_notification_listener_factory import \
INotificationListenerFactory INotificationListenerFactory
from .ids import ExtensionPointID from .ids import ExtensionPointID
from .data_sources.i_data_source_factory import IDataSourceFactory from .data_sources.i_data_source_factory import IDataSourceFactory
from .kpi.i_kpi_calculator_factory import IKPICalculatorFactory
from .mco.i_mco_factory import IMCOFactory from .mco.i_mco_factory import IMCOFactory
from .ui_hooks.i_ui_hooks_factory import IUIHooksFactory from .ui_hooks.i_ui_hooks_factory import IUIHooksFactory
...@@ -37,12 +36,6 @@ class BaseExtensionPlugin(Plugin): ...@@ -37,12 +36,6 @@ class BaseExtensionPlugin(Plugin):
contributes_to=ExtensionPointID.DATA_SOURCE_FACTORIES contributes_to=ExtensionPointID.DATA_SOURCE_FACTORIES
) )
#: A list of the available KPI calculators this plugin exports.
kpi_calculator_factories = List(
IKPICalculatorFactory,
contributes_to=ExtensionPointID.KPI_CALCULATOR_FACTORIES
)
notification_listener_factories = List( notification_listener_factories = List(
INotificationListenerFactory, INotificationListenerFactory,
contributes_to=ExtensionPointID.NOTIFICATION_LISTENER_FACTORIES contributes_to=ExtensionPointID.NOTIFICATION_LISTENER_FACTORIES
......
from traits.api import HasStrictTraits, Instance, List from traits.api import HasStrictTraits, Instance, List
from force_bdss.data_sources.base_data_source_model import BaseDataSourceModel from force_bdss.data_sources.base_data_source_model import BaseDataSourceModel
from force_bdss.kpi.base_kpi_calculator_model import BaseKPICalculatorModel
from force_bdss.mco.base_mco_model import BaseMCOModel from force_bdss.mco.base_mco_model import BaseMCOModel
from force_bdss.notification_listeners.base_notification_listener_model \ from force_bdss.notification_listeners.base_notification_listener_model \
import BaseNotificationListenerModel import BaseNotificationListenerModel
...@@ -20,9 +19,5 @@ class Workflow(HasStrictTraits): ...@@ -20,9 +19,5 @@ class Workflow(HasStrictTraits):
#: will go away when we remove the KPI calculators. #: will go away when we remove the KPI calculators.
execution_layers = List(List(BaseDataSourceModel)) execution_layers = List(List(BaseDataSourceModel))
#: Contains the factory-specific KPI Calculator Model objects.
#: The list can be empty
kpi_calculators = List(BaseKPICalculatorModel)
#: Contains information about the listeners to be setup #: Contains information about the listeners to be setup
notification_listeners = List(BaseNotificationListenerModel) notification_listeners = List(BaseNotificationListenerModel)
...@@ -57,23 +57,17 @@ def execute_workflow(workflow, data_values): ...@@ -57,23 +57,17 @@ def execute_workflow(workflow, data_values):
ds_results = _compute_layer_results( ds_results = _compute_layer_results(
available_data_values, available_data_values,
layer, layer,
"create_data_source"
) )
available_data_values += ds_results available_data_values += ds_results
log.info("Computing KPI layer") log.info("Computing KPI layer")
kpi_results = _compute_layer_results( kpi_results = [dv for dv in available_data_values if dv.is_kpi]
available_data_values,
workflow.kpi_calculators,
"create_kpi_calculator"
)
return kpi_results return kpi_results
def _compute_layer_results(environment_data_values, def _compute_layer_results(environment_data_values,
evaluator_models, evaluator_models,
creator_method_name
): ):
"""Helper routine. """Helper routine.
Performs the evaluation of a single layer. Performs the evaluation of a single layer.
...@@ -86,12 +80,7 @@ def _compute_layer_results(environment_data_values, ...@@ -86,12 +80,7 @@ def _compute_layer_results(environment_data_values,
A list of data values to submit to the evaluators. A list of data values to submit to the evaluators.
evaluator_models: list evaluator_models: list
A list of the models for all the evaluators (data source A list of the models for all the data sources
or kpi calculator)
creator_method_name: str
A string of the creator method for the evaluator on the
factory (e.g. create_kpi_calculator)
NOTE: The above parameter is going to go away as soon as we move NOTE: The above parameter is going to go away as soon as we move
to unlimited layers and remove the distinction between data sources to unlimited layers and remove the distinction between data sources
...@@ -101,12 +90,12 @@ def _compute_layer_results(environment_data_values, ...@@ -101,12 +90,12 @@ def _compute_layer_results(environment_data_values,
for model in evaluator_models: for model in evaluator_models:
factory = model.factory factory = model.factory
evaluator = getattr(factory, creator_method_name)() data_source = factory.create_data_source()
# Get the slots for this data source. These must be matched to # Get the slots for this data source. These must be matched to
# the appropriate values in the environment data values. # the appropriate values in the environment data values.
# Matching is by position. # Matching is by position.
in_slots, out_slots = evaluator.slots(model) in_slots, out_slots = data_source.slots(model)
# Binding performs the extraction of the specified data values # Binding performs the extraction of the specified data values
# satisfying the above input slots from the environment data values # satisfying the above input slots from the environment data values
...@@ -125,7 +114,7 @@ def _compute_layer_results(environment_data_values, ...@@ -125,7 +114,7 @@ def _compute_layer_results(environment_data_values,
factory.name)) factory.name))
try: try:
res = evaluator.run(model, passed_data_values) res = data_source.run(model, passed_data_values)
except Exception: except Exception:
log.error("Evaluation could not be performed. Run method raised" log.error("Evaluation could not be performed. Run method raised"
"exception", exc_info=True) "exception", exc_info=True)
...@@ -162,6 +151,7 @@ def _compute_layer_results(environment_data_values, ...@@ -162,6 +151,7 @@ def _compute_layer_results(environment_data_values,
# Add the names as specified by the user. # Add the names as specified by the user.
for dv, output_slot_info in zip(res, model.output_slot_info): for dv, output_slot_info in zip(res, model.output_slot_info):
dv.name = output_slot_info.name dv.name = output_slot_info.name
dv.is_kpi = output_slot_info.is_kpi
# If the name was not specified, simply discard the value, # If the name was not specified, simply discard the value,
# because apparently the user is not interested in it. # because apparently the user is not interested in it.
......
...@@ -56,8 +56,12 @@ class CoreMCODriver(BaseCoreDriver): ...@@ -56,8 +56,12 @@ class CoreMCODriver(BaseCoreDriver):
@on_trait_change("mco:started") @on_trait_change("mco:started")
def _deliver_start_event(self): def _deliver_start_event(self):
output_names = [] output_names = []
for kpi in self.workflow.kpi_calculators: for layer in self.workflow.execution_layers:
output_names.extend(kpi.output_slot_info) for data_source in layer:
output_names.extend(info.name
for info in data_source.output_slot_info
if info.is_kpi
)
self._deliver_event(MCOStartEvent( self._deliver_event(MCOStartEvent(
input_names=tuple(p.name for p in self.workflow.mco.parameters), input_names=tuple(p.name for p in self.workflow.mco.parameters),
......
...@@ -7,7 +7,6 @@ from force_bdss.notification_listeners.i_notification_listener_factory import \ ...@@ -7,7 +7,6 @@ from force_bdss.notification_listeners.i_notification_listener_factory import \
INotificationListenerFactory INotificationListenerFactory
from .data_sources.i_data_source_factory import ( from .data_sources.i_data_source_factory import (
IDataSourceFactory) IDataSourceFactory)
from .kpi.i_kpi_calculator_factory import IKPICalculatorFactory
from .mco.i_mco_factory import IMCOFactory from .mco.i_mco_factory import IMCOFactory
from .ui_hooks.i_ui_hooks_factory import IUIHooksFactory from .ui_hooks.i_ui_hooks_factory import IUIHooksFactory
...@@ -56,12 +55,6 @@ class FactoryRegistryPlugin(Plugin): ...@@ -56,12 +55,6 @@ class FactoryRegistryPlugin(Plugin):
List(IDataSourceFactory), List(IDataSourceFactory),
id=ExtensionPointID.DATA_SOURCE_FACTORIES) id=ExtensionPointID.DATA_SOURCE_FACTORIES)
#: A list of the available Key Performance Indicator calculators.
#: It will be populated by plugins.
kpi_calculator_factories = ExtensionPoint(
List(IKPICalculatorFactory),
id=ExtensionPointID.KPI_CALCULATOR_FACTORIES)
#: Notification listeners are pluggable entities that will listen #: Notification listeners are pluggable entities that will listen
#: to MCO events and act accordingly. #: to MCO events and act accordingly.
notification_listener_factories = ExtensionPoint( notification_listener_factories = ExtensionPoint(
...@@ -97,26 +90,6 @@ class FactoryRegistryPlugin(Plugin): ...@@ -97,26 +90,6 @@ class FactoryRegistryPlugin(Plugin):
raise KeyError(id) raise KeyError(id)
def kpi_calculator_factory_by_id(self, id):
"""Finds a given kpi factory by means of its id.
The ID is as obtained by the function factory_id() in the
plugin api.
Parameters
----------
id: str
The identifier returned by the factory_id() function.
Raises
------
KeyError: if the entry is not found.
"""
for kpic in self.kpi_calculator_factories:
if kpic.id == id:
return kpic
raise KeyError(id)
def mco_factory_by_id(self, id): def mco_factory_by_id(self, id):
"""Finds a given Multi Criteria Optimizer (MCO) factory by means of """Finds a given Multi Criteria Optimizer (MCO) factory by means of
its id. The ID is as obtained by the function factory_id() in the its id. The ID is as obtained by the function factory_id() in the
......
...@@ -57,7 +57,6 @@ class TestWorkflowWriter(unittest.TestCase): ...@@ -57,7 +57,6 @@ class TestWorkflowWriter(unittest.TestCase):
self.assertIn("workflow", result) self.assertIn("workflow", result)
self.assertIn("mco", result["workflow"]) self.assertIn("mco", result["workflow"])
self.assertIn("execution_layers", result["workflow"]) self.assertIn("execution_layers", result["workflow"])
self.assertIn("kpi_calculators", result["workflow"])
def test_write_and_read(self): def test_write_and_read(self):
wfwriter = WorkflowWriter() wfwriter = WorkflowWriter()
......
...@@ -91,7 +91,6 @@ class WorkflowReader(HasStrictTraits): ...@@ -91,7 +91,6 @@ class WorkflowReader(HasStrictTraits):
wf_data = json_data["workflow"] wf_data = json_data["workflow"]
wf.mco = self._extract_mco(wf_data) wf.mco = self._extract_mco(wf_data)
wf.execution_layers[:] = self._extract_execution_layers(wf_data) wf.execution_layers[:] = self._extract_execution_layers(wf_data)
wf.kpi_calculators[:] = self._extract_kpi_calculators(wf_data)
wf.notification_listeners[:] = \ wf.notification_listeners[:] = \
self._extract_notification_listeners(wf_data) self._extract_notification_listeners(wf_data)
except KeyError as e: except KeyError as e:
...@@ -167,37 +166,6 @@ class WorkflowReader(HasStrictTraits): ...@@ -167,37 +166,6 @@ class WorkflowReader(HasStrictTraits):
return layers return layers
def _extract_kpi_calculators(self, wf_data):
"""Extracts the KPI calculators from the workflow dictionary data.
Parameters
----------
wf_data: dict
the content of the workflow key in the top level dictionary data.
Returns
-------
list of BaseKPICalculatorModel instances. Each BaseKPICalculatorModel
is an instance of the specific model class. The list can be
empty.
"""
registry = self.factory_registry
kpi_calculators = []
for kpic_entry in wf_data["kpi_calculators"]:
kpic_id = kpic_entry["id"]
kpic_factory = registry.kpi_calculator_factory_by_id(kpic_id)
model_data = kpic_entry["model_data"]
model_data["input_slot_info"] = self._extract_input_slot_info(
model_data["input_slot_info"]
)
kpi_calculators.append(
kpic_factory.create_model(model_data)
)
return kpi_calculators
def _extract_mco_parameters(self, mco_id, parameters_data): def _extract_mco_parameters(self, mco_id, parameters_data):
"""Extracts the MCO parameters from the data as dictionary. """Extracts the MCO parameters from the data as dictionary.
......
...@@ -25,9 +25,6 @@ class WorkflowWriter(HasStrictTraits): ...@@ -25,9 +25,6 @@ class WorkflowWriter(HasStrictTraits):
def _workflow_data(self, workflow): def _workflow_data(self, workflow):
workflow_data = { workflow_data = {
"mco": self._mco_data(workflow.mco), "mco": self._mco_data(workflow.mco),
"kpi_calculators": [
self._model_data(kpic)
for kpic in workflow.kpi_calculators],
"execution_layers": [ "execution_layers": [
self._execution_layer_data(el) self._execution_layer_data(el)
for el in workflow.execution_layers], for el in workflow.execution_layers],
......
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_info 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)
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