diff --git a/force_bdss/api.py b/force_bdss/api.py index fa2fddd8cf2847e007b6e89f4c410f7011edc862..39d081d78e4483948ae128e5cf5d892bdec36d46 100644 --- a/force_bdss/api.py +++ b/force_bdss/api.py @@ -28,4 +28,8 @@ from .notification_listeners.base_notification_listener import BaseNotificationL from .notification_listeners.base_notification_listener_factory import BaseNotificationListenerFactory # noqa from .notification_listeners.base_notification_listener_model import BaseNotificationListenerModel # noqa -from .local_traits import (ZMQSocketURL, Identifier) # noqa +from .ui_hooks.i_ui_hooks_factory import IUIHooksFactory # noqa +from .ui_hooks.base_ui_hooks_factory import BaseUIHooksFactory # noqa +from .ui_hooks.base_ui_hooks_manager import BaseUIHooksManager # noqa + +from .local_traits import Identifier # noqa diff --git a/force_bdss/base_extension_plugin.py b/force_bdss/base_extension_plugin.py index 692b9fa72898aafc938854d14042890cc50bed48..c94b622a6ac546a32610da49f70e600b13e99f04 100644 --- a/force_bdss/base_extension_plugin.py +++ b/force_bdss/base_extension_plugin.py @@ -7,6 +7,7 @@ from .ids import ExtensionPointID 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 .ui_hooks.i_ui_hooks_factory import IUIHooksFactory class BaseExtensionPlugin(Plugin): @@ -46,3 +47,8 @@ class BaseExtensionPlugin(Plugin): INotificationListenerFactory, contributes_to=ExtensionPointID.NOTIFICATION_LISTENER_FACTORIES ) + + ui_hooks_factories = List( + IUIHooksFactory, + contributes_to=ExtensionPointID.UI_HOOKS_FACTORIES + ) diff --git a/force_bdss/core_plugins/dummy/dummy_plugin.py b/force_bdss/core_plugins/dummy/dummy_plugin.py index c8020e198a858cfb312d0080c56a1f31b06c1d26..766186f03a3330002830dad4c180f9098a88bfe2 100644 --- a/force_bdss/core_plugins/dummy/dummy_plugin.py +++ b/force_bdss/core_plugins/dummy/dummy_plugin.py @@ -3,6 +3,7 @@ from .dummy_notification_listener.dummy_notification_listener_factory import ( DummyNotificationListenerFactory ) from .csv_extractor.csv_extractor_factory import CSVExtractorFactory +from .power_evaluator.power_evaluator_factory import PowerEvaluatorFactory from .kpi_adder.kpi_adder_factory import KPIAdderFactory from .dummy_dakota.dakota_factory import DummyDakotaFactory from .dummy_data_source.dummy_data_source_factory import DummyDataSourceFactory @@ -16,7 +17,8 @@ class DummyPlugin(BaseExtensionPlugin): def _data_source_factories_default(self): return [DummyDataSourceFactory(self), - CSVExtractorFactory(self)] + CSVExtractorFactory(self), + PowerEvaluatorFactory(self)] def _mco_factories_default(self): return [DummyDakotaFactory(self)] diff --git a/force_bdss/core_plugins/dummy/power_evaluator/__init__.py b/force_bdss/core_plugins/dummy/power_evaluator/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/force_bdss/core_plugins/dummy/power_evaluator/power_evaluator_data_source.py b/force_bdss/core_plugins/dummy/power_evaluator/power_evaluator_data_source.py new file mode 100644 index 0000000000000000000000000000000000000000..d8eb722ea6e3f485d3311d869a4709b2cdc5d3cb --- /dev/null +++ b/force_bdss/core_plugins/dummy/power_evaluator/power_evaluator_data_source.py @@ -0,0 +1,24 @@ +import math + +from force_bdss.api import BaseDataSource, DataValue +from force_bdss.core.slot import Slot + + +class PowerEvaluatorDataSource(BaseDataSource): + def run(self, model, parameters): + x = parameters[0].value + return [ + DataValue( + type=model.cuba_type_out, + value=math.pow(x, model.power) + )] + + def slots(self, model): + return ( + ( + Slot(type=model.cuba_type_in), + ), + ( + Slot(type=model.cuba_type_out), + ) + ) diff --git a/force_bdss/core_plugins/dummy/power_evaluator/power_evaluator_factory.py b/force_bdss/core_plugins/dummy/power_evaluator/power_evaluator_factory.py new file mode 100644 index 0000000000000000000000000000000000000000..ebd3aacf8fcd9b6a5e229756ad64a13ce9d97087 --- /dev/null +++ b/force_bdss/core_plugins/dummy/power_evaluator/power_evaluator_factory.py @@ -0,0 +1,21 @@ +from traits.api import String + +from force_bdss.api import factory_id, BaseDataSourceFactory + +from .power_evaluator_model import PowerEvaluatorModel +from .power_evaluator_data_source import PowerEvaluatorDataSource + + +class PowerEvaluatorFactory(BaseDataSourceFactory): + id = String(factory_id("enthought", "power_evaluator")) + + name = String("Power Evaluator") + + def create_model(self, model_data=None): + if model_data is None: + model_data = {} + + return PowerEvaluatorModel(self, **model_data) + + def create_data_source(self): + return PowerEvaluatorDataSource(self) diff --git a/force_bdss/core_plugins/dummy/power_evaluator/power_evaluator_model.py b/force_bdss/core_plugins/dummy/power_evaluator/power_evaluator_model.py new file mode 100644 index 0000000000000000000000000000000000000000..14a2efcdb09959339213906051933ce303f18850 --- /dev/null +++ b/force_bdss/core_plugins/dummy/power_evaluator/power_evaluator_model.py @@ -0,0 +1,13 @@ +from traits.api import Float, String, on_trait_change + +from force_bdss.api import BaseDataSourceModel + + +class PowerEvaluatorModel(BaseDataSourceModel): + power = Float(1.0) + cuba_type_in = String() + cuba_type_out = String() + + @on_trait_change("cuba_type_in,cuba_type_out") + def _notify_changes_slots(self): + self.changes_slots = True diff --git a/force_bdss/core_plugins/dummy/power_evaluator/tests/__init__.py b/force_bdss/core_plugins/dummy/power_evaluator/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/force_bdss/core_plugins/dummy/power_evaluator/tests/test_power_evaluator_data_source.py b/force_bdss/core_plugins/dummy/power_evaluator/tests/test_power_evaluator_data_source.py new file mode 100644 index 0000000000000000000000000000000000000000..f4d35ac929110d6a3ee513c62a1580f72aeaba04 --- /dev/null +++ b/force_bdss/core_plugins/dummy/power_evaluator/tests/test_power_evaluator_data_source.py @@ -0,0 +1,57 @@ +import unittest + +from force_bdss.core.data_value import DataValue +from force_bdss.core.slot import Slot +from force_bdss.core_plugins.dummy.power_evaluator.power_evaluator_data_source import PowerEvaluatorDataSource # noqa +from force_bdss.core_plugins.dummy.power_evaluator.power_evaluator_model import PowerEvaluatorModel # noqa +from force_bdss.data_sources.base_data_source_factory import \ + BaseDataSourceFactory + +try: + import mock +except ImportError: + from unittest import mock + + +class TestPowerEvaluatorDataSource(unittest.TestCase): + def setUp(self): + self.factory = mock.Mock(spec=BaseDataSourceFactory) + + def test_initialization(self): + ds = PowerEvaluatorDataSource(self.factory) + self.assertEqual(ds.factory, self.factory) + + def test_run(self): + ds = PowerEvaluatorDataSource(self.factory) + model = PowerEvaluatorModel(self.factory) + model.power = 2 + mock_params = [DataValue(value=5, type="METER")] + result = ds.run(model, mock_params) + self.assertIsInstance(result, list) + self.assertEqual(len(result), 1) + self.assertIsInstance(result[0], DataValue) + self.assertEqual(result[0].value, 25) + + def test_run_with_exception(self): + ds = PowerEvaluatorDataSource(self.factory) + model = PowerEvaluatorModel(self.factory) + mock_params = [] + model.power = 3 + with self.assertRaises(IndexError): + ds.run(model, mock_params) + + def test_slots(self): + ds = PowerEvaluatorDataSource(self.factory) + model = PowerEvaluatorModel(self.factory) + slots = ds.slots(model) + self.assertEqual(len(slots), 2) + self.assertEqual(len(slots[0]), 1) + self.assertEqual(len(slots[1]), 1) + self.assertIsInstance(slots[0][0], Slot) + self.assertIsInstance(slots[1][0], Slot) + + model.cuba_type_in = 'METER' + model.cuba_type_out = 'METER' + slots = ds.slots(model) + self.assertEqual(slots[0][0].type, 'METER') + self.assertEqual(slots[1][0].type, 'METER') diff --git a/force_bdss/core_plugins/dummy/power_evaluator/tests/test_power_evaluator_factory.py b/force_bdss/core_plugins/dummy/power_evaluator/tests/test_power_evaluator_factory.py new file mode 100644 index 0000000000000000000000000000000000000000..5df8c8f9e796042aaa8a8e0b8c4bd4500dfe4ea3 --- /dev/null +++ b/force_bdss/core_plugins/dummy/power_evaluator/tests/test_power_evaluator_factory.py @@ -0,0 +1,22 @@ +import unittest + +from force_bdss.core_plugins.dummy.tests.data_source_factory_test_mixin \ + import DataSourceFactoryTestMixin +from force_bdss.core_plugins.dummy.power_evaluator.power_evaluator_factory import PowerEvaluatorFactory # noqa +from force_bdss.core_plugins.dummy.power_evaluator.power_evaluator_data_source import PowerEvaluatorDataSource # noqa +from force_bdss.core_plugins.dummy.power_evaluator.power_evaluator_model import PowerEvaluatorModel # noqa + + +class TestPowerEvaluatorFactory(DataSourceFactoryTestMixin, + unittest.TestCase): + @property + def factory_class(self): + return PowerEvaluatorFactory + + @property + def model_class(self): + return PowerEvaluatorModel + + @property + def data_source_class(self): + return PowerEvaluatorDataSource diff --git a/force_bdss/factory_registry_plugin.py b/force_bdss/factory_registry_plugin.py index 67e785173c9986a4dccbd98a8d6aaa124dcaf250..64f41c24d13575789140a86220f04b1675259277 100644 --- a/force_bdss/factory_registry_plugin.py +++ b/force_bdss/factory_registry_plugin.py @@ -9,6 +9,7 @@ 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 .ui_hooks.i_ui_hooks_factory import IUIHooksFactory FACTORY_REGISTRY_PLUGIN_ID = "force.bdss.plugins.factory_registry" @@ -50,6 +51,14 @@ class FactoryRegistryPlugin(Plugin): id=ExtensionPointID.NOTIFICATION_LISTENER_FACTORIES ) + #: UI Hooks are pluggable entities holding methods that are called + #: at specific moments in the UI application lifetime. They can be used + #: to inject special behaviors at those moments. + ui_hooks_factories = ExtensionPoint( + List(IUIHooksFactory), + id=ExtensionPointID.UI_HOOKS_FACTORIES + ) + def data_source_factory_by_id(self, id): """Finds a given data source factory by means of its id. The ID is as obtained by the function factory_id() in the @@ -140,13 +149,13 @@ class FactoryRegistryPlugin(Plugin): def notification_listener_factory_by_id(self, id): """Finds a given notification listener by means of its id. - The ID is as obtained by the function bundle_id() in the + The ID is as obtained by the function factory_id() in the plugin api. Parameters ---------- id: str - The identifier returned by the bundle_id() function. + The identifier returned by the factory_id() function. Raises ------ diff --git a/force_bdss/ids.py b/force_bdss/ids.py index 336b64a90e0ee2a5b7be42e5d8b471a5d3c85915..c62d52b1de49cc0fb1eba50774f89dcebe84f9fb 100644 --- a/force_bdss/ids.py +++ b/force_bdss/ids.py @@ -14,6 +14,7 @@ class ExtensionPointID: KPI_CALCULATOR_FACTORIES = 'force.bdss.kpi_calculator.factories' NOTIFICATION_LISTENER_FACTORIES = \ 'force.bdss.notification_listener.factories' + UI_HOOKS_FACTORIES = 'force.bdss.ui_hooks.factories' def factory_id(producer, identifier): diff --git a/force_bdss/local_traits.py b/force_bdss/local_traits.py index f2aa3859b2c8488d50dce64996d0ce098ac0066a..f759aed8596558d9783bae4e0ce1f35671f3059c 100644 --- a/force_bdss/local_traits.py +++ b/force_bdss/local_traits.py @@ -1,32 +1,10 @@ -import re -from traits.api import Regex, BaseStr, String +from traits.api import Regex, String #: Used for variable names, but allow also empty string as it's the default #: case and it will be present if the workflow is saved before actually #: specifying the value. Identifier = Regex(regex="(^[^\d\W]\w*\Z|^\Z)") - -class ZMQSocketURL(BaseStr): - def validate(self, object, name, value): - super(ZMQSocketURL, self).validate(object, name, value) - m = re.match( - "tcp://(\\d{1,3})\.(\\d{1,3})\.(\\d{1,3})\.(\\d{1,3}):(\\d+)", - value) - if m is None: - self.error(object, name, value) - - a, b, c, d, port = m.groups() - - if not all(map(lambda x: 0 <= int(x) <= 255, (a, b, c, d))): - self.error(object, name, value) - - if not (1 <= int(port) <= 65535): - self.error(object, name, value) - - return value - - #: Identifies a CUBA type with its key. At the moment a String with #: no validation, but will come later. CUBAType = String() diff --git a/force_bdss/tests/test_local_traits.py b/force_bdss/tests/test_local_traits.py index d66a863206a887c90073feec2ce0b749cb4c83a9..d61e6d2c89520653d65431a0030ad89b362f18be 100644 --- a/force_bdss/tests/test_local_traits.py +++ b/force_bdss/tests/test_local_traits.py @@ -1,13 +1,12 @@ import unittest from traits.api import HasStrictTraits, TraitError -from force_bdss.local_traits import Identifier, CUBAType, ZMQSocketURL +from force_bdss.local_traits import Identifier, CUBAType class Traited(HasStrictTraits): val = Identifier() cuba = CUBAType() - socket_url = ZMQSocketURL() class TestLocalTraits(unittest.TestCase): @@ -26,22 +25,3 @@ class TestLocalTraits(unittest.TestCase): c = Traited() c.cuba = "PRESSURE" self.assertEqual(c.cuba, "PRESSURE") - - def test_zmq_socket_url(self): - c = Traited() - - for working in ["tcp://127.0.0.1:12345", - "tcp://255.255.255.255:65535", - "tcp://1.1.1.1:65535"]: - c.socket_url = working - self.assertEqual(c.socket_url, working) - - for broken in ["tcp://270.0.0.1:12345", - "tcp://0.270.0.1:12345", - "tcp://0.0.270.1:12345", - "tcp://0.0.0.270:12345", - "url://255.255.255.255:65535", - "whatever", - "tcp://1.1.1.1:100000"]: - with self.assertRaises(TraitError): - c.socket_url = broken diff --git a/force_bdss/ui_hooks/__init__.py b/force_bdss/ui_hooks/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/force_bdss/ui_hooks/base_ui_hooks_factory.py b/force_bdss/ui_hooks/base_ui_hooks_factory.py new file mode 100644 index 0000000000000000000000000000000000000000..b183489b441d12adcdbf8da751578ab6a4ac194c --- /dev/null +++ b/force_bdss/ui_hooks/base_ui_hooks_factory.py @@ -0,0 +1,44 @@ +import abc + +from traits.api import ABCHasStrictTraits, Instance, String, provides +from envisage.plugin import Plugin + +from .i_ui_hooks_factory import IUIHooksFactory + + +@provides(IUIHooksFactory) +class BaseUIHooksFactory(ABCHasStrictTraits): + """Base class for UIHooksFactory. + UI Hooks are extensions that perform actions associated to specific + moments of the UI lifetime. + """ + #: identifier of the factory + id = String() + + #: Name of the factory. User friendly for UI + name = String() + + #: A reference to the containing plugin + 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(BaseUIHooksFactory, self).__init__(*args, **kwargs) + + @abc.abstractmethod + def create_ui_hooks_manager(self): + """Creates an instance of the hook manager. + The hooks manager contains a set of methods that are applicable in + various moments of the UI application lifetime. + + Returns + ------- + BaseUIHooksManager + """ diff --git a/force_bdss/ui_hooks/base_ui_hooks_manager.py b/force_bdss/ui_hooks/base_ui_hooks_manager.py new file mode 100644 index 0000000000000000000000000000000000000000..a1c9228ce7299fb8106b99197ea81048f7082771 --- /dev/null +++ b/force_bdss/ui_hooks/base_ui_hooks_manager.py @@ -0,0 +1,51 @@ +from traits.api import HasStrictTraits, Instance + +from .i_ui_hooks_factory import IUIHooksFactory + + +class BaseUIHooksManager(HasStrictTraits): + #: A reference to the factory + factory = Instance(IUIHooksFactory) + + def __init__(self, factory, *args, **kwargs): + """Initializes the UI Hooks manager. + + Parameters + ---------- + factory: BaseUIHooksFactory + The factory this UI Hooks manager belongs to + """ + self.factory = factory + super(BaseUIHooksManager, self).__init__(*args, **kwargs) + + def before_execution(self, task): + """Hook that is called before execution of a given evaluation. + Gives a chance to perform operations before the temporary file is + created with its contents and the calculation invoked. + + Parameters + ---------- + task: + The pyface envisage task. + """ + + def after_execution(self, task): + """Hook that is called after execution of a given evaluation. + Gives a chance to perform operations after the calculation finished. + + Parameters + ---------- + task: + The pyface envisage task. + """ + + def before_save(self, task): + """Hook that is called just before saving a given model to disk + in response to a user action. This does not apply to saving of + temporary files before execution. + + Parameters + ---------- + task: + The pyface envisage task + """ diff --git a/force_bdss/ui_hooks/i_ui_hooks_factory.py b/force_bdss/ui_hooks/i_ui_hooks_factory.py new file mode 100644 index 0000000000000000000000000000000000000000..dc60ae017a6ab24d6d5e20891eea33e59a2e0f41 --- /dev/null +++ b/force_bdss/ui_hooks/i_ui_hooks_factory.py @@ -0,0 +1,18 @@ +from traits.api import Interface, String, Instance +from envisage.plugin import Plugin + + +class IUIHooksFactory(Interface): + """Envisage required interface for the BaseUIHooksFactory. + You should not need to use this directly. + + Refer to the BaseUIHooksFactory for documentation. + """ + id = String() + + name = String() + + plugin = Instance(Plugin) + + def create_hook_manager(self): + """""" diff --git a/force_bdss/ui_hooks/tests/__init__.py b/force_bdss/ui_hooks/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/force_bdss/ui_hooks/tests/test_base_ui_hooks_factory.py b/force_bdss/ui_hooks/tests/test_base_ui_hooks_factory.py new file mode 100644 index 0000000000000000000000000000000000000000..449fc91ee057c4a2bd522e0bed73a44c89c22358 --- /dev/null +++ b/force_bdss/ui_hooks/tests/test_base_ui_hooks_factory.py @@ -0,0 +1,21 @@ +import unittest + +try: + import mock +except ImportError: + from unittest import mock + +from envisage.api import Plugin +from ..base_ui_hooks_factory import BaseUIHooksFactory + + +class NullUIHooksFactory(BaseUIHooksFactory): + def create_ui_hooks_manager(self): + return None + + +class TestBaseUIHooksFactory(unittest.TestCase): + def test_initialize(self): + mock_plugin = mock.Mock(spec=Plugin) + factory = NullUIHooksFactory(plugin=mock_plugin) + self.assertEqual(factory.plugin, mock_plugin) diff --git a/force_bdss/ui_hooks/tests/test_base_ui_hooks_manager.py b/force_bdss/ui_hooks/tests/test_base_ui_hooks_manager.py new file mode 100644 index 0000000000000000000000000000000000000000..8724a19a1985682f577eee60af67235aef108e4b --- /dev/null +++ b/force_bdss/ui_hooks/tests/test_base_ui_hooks_manager.py @@ -0,0 +1,16 @@ +import unittest + +from ..base_ui_hooks_manager import BaseUIHooksManager +from ..base_ui_hooks_factory import BaseUIHooksFactory +try: + import mock +except ImportError: + from unittest import mock + + +class TestBaseUIHooksManager(unittest.TestCase): + def test_initialization(self): + mock_factory = mock.Mock(spec=BaseUIHooksFactory) + mgr = BaseUIHooksManager(mock_factory) + + self.assertEqual(mgr.factory, mock_factory)