From 090e57724443904ce13f8ab95794f2a1524707b4 Mon Sep 17 00:00:00 2001 From: Boris Pavlovic Date: Wed, 2 Jul 2014 23:43:15 +0400 Subject: [PATCH] Add base for notifier plugins OSprofiler will support notifers for different collectors. So add base for plugins. In next patch we will add osprofiler oslo.messaging based notifier Change-Id: Iad14c67f1e7cdbd0f4bcc64200fecf048316e385 --- osprofiler/_notifiers/__init__.py | 0 osprofiler/_notifiers/base.py | 34 +++++++++++++++++++++ osprofiler/_notifiers/messaging.py | 14 +++++++++ osprofiler/_utils.py | 21 +++++++++++++ osprofiler/notifier.py | 14 +++++++++ tests/notifiers/__init__.py | 0 tests/notifiers/test_base.py | 48 ++++++++++++++++++++++++++++++ tests/test_notifier.py | 17 ++++++++++- tests/test_utils.py | 16 ++++++++++ 9 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 osprofiler/_notifiers/__init__.py create mode 100644 osprofiler/_notifiers/base.py create mode 100644 osprofiler/_notifiers/messaging.py create mode 100644 tests/notifiers/__init__.py create mode 100644 tests/notifiers/test_base.py diff --git a/osprofiler/_notifiers/__init__.py b/osprofiler/_notifiers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/osprofiler/_notifiers/base.py b/osprofiler/_notifiers/base.py new file mode 100644 index 0000000..a54ab8e --- /dev/null +++ b/osprofiler/_notifiers/base.py @@ -0,0 +1,34 @@ +# Copyright 2014 Mirantis Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from osprofiler import _utils as utils + + +class Notifier(object): + + def notify(self, info): + """This method will be called on each notifier.notify() call. + + To add new drivers you should, create new subclass of this class and + implement notify method. + """ + + @staticmethod + def factory(name, *args, **kwargs): + for driver in utils.itersubclasses(Notifier): + if name == driver.__name__: + return driver(*args, **kwargs).notify + + raise TypeError("There is no driver, with name: %s" % name) diff --git a/osprofiler/_notifiers/messaging.py b/osprofiler/_notifiers/messaging.py new file mode 100644 index 0000000..2d96b4b --- /dev/null +++ b/osprofiler/_notifiers/messaging.py @@ -0,0 +1,14 @@ +# Copyright 2014 Mirantis Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/osprofiler/_utils.py b/osprofiler/_utils.py index 4399ba8..678aa50 100644 --- a/osprofiler/_utils.py +++ b/osprofiler/_utils.py @@ -89,3 +89,24 @@ def signed_unpack(data, hmac_data, hmac_key): return json.loads(binary_decode(base64.urlsafe_b64decode(data))) except Exception: return None + + +def itersubclasses(cls, _seen=None): + """Generator over all subclasses of a given class in depth first order.""" + + if not isinstance(cls, type): + raise TypeError("itersubclasses must be called with " + "new-style classes, not %.100r" % cls) + + _seen = _seen or set() + try: + subs = cls.__subclasses__() + except TypeError: # fails only when cls is type + subs = cls.__subclasses__(cls) + subs = cls.__subclasses__() + for sub in subs: + if sub not in _seen: + _seen.add(sub) + yield sub + for sub in itersubclasses(sub, _seen): + yield sub diff --git a/osprofiler/notifier.py b/osprofiler/notifier.py index 6b52d5e..0d09be4 100644 --- a/osprofiler/notifier.py +++ b/osprofiler/notifier.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +from osprofiler._notifiers import base + def _noop_notifier(info): """Do nothing on notify().""" @@ -45,3 +47,15 @@ def set(notifier): """ global __notifier __notifier = notifier + + +def create(plugin_name, *args, **kwargs): + """Create notifier based on specified plugin_name + + :param plugin_name: Name of plugin that creates notifier + :param *args: args that will be passed to plugin init method + :param **kwargs: kwargs that will be passed to plugin init method + :returns: Callable notifier method + :raise TypeError: In case of invalid name of plugin raises TypeError + """ + return base.Notifier.factory(plugin_name, *args, **kwargs) diff --git a/tests/notifiers/__init__.py b/tests/notifiers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/notifiers/test_base.py b/tests/notifiers/test_base.py new file mode 100644 index 0000000..efe3c7f --- /dev/null +++ b/tests/notifiers/test_base.py @@ -0,0 +1,48 @@ +# Copyright 2014 Mirantis Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from osprofiler._notifiers import base +from tests import test + + +class NotifierBaseTestCase(test.TestCase): + + def test_factory(self): + + class A(base.Notifier): + + def notify(self, a): + return a + + self.assertEqual(base.Notifier.factory("A")(10), 10) + + def test_factory_with_args(self): + + class B(base.Notifier): + + def __init__(self, a, b=10): + self.a = a + self.b = b + + def notify(self, c): + return self.a + self.b + c + + self.assertEqual(base.Notifier.factory("B", 5, b=7)(10), 22) + + def test_factory_not_found(self): + self.assertRaises(TypeError, base.Notifier.factory, "non existing") + + def test_notify(self): + base.Notifier().notify("") diff --git a/tests/test_notifier.py b/tests/test_notifier.py index e1f6a29..27bb9ab 100644 --- a/tests/test_notifier.py +++ b/tests/test_notifier.py @@ -13,8 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. -from osprofiler import notifier +import mock +from osprofiler import notifier from tests import test @@ -34,3 +35,17 @@ class NotifierTestCase(test.TestCase): def test_get_default_notifier(self): self.assertEqual(notifier.get(), notifier._noop_notifier) + + def test_notify(self): + m = mock.MagicMock() + notifier.set(m) + notifier.notify(10) + + m.assert_called_once_with(10) + + @mock.patch("osprofiler.notifier.base.Notifier.factory") + def test_create(self, mock_factory): + + result = notifier.create("test", 10, b=20) + mock_factory.assert_called_once_with("test", 10, b=20) + self.assertEqual(mock_factory.return_value, result) diff --git a/tests/test_utils.py b/tests/test_utils.py index 790f469..001c361 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -76,3 +76,19 @@ class UtilsTestCase(test.TestCase): hmac_data = utils.generate_hmac(data, hmac) self.assertIsNone(utils.signed_unpack(data, hmac_data, hmac)) + + def test_itersubclasses(self): + + class A(object): + pass + + class B(A): + pass + + class C(A): + pass + + class D(C): + pass + + self.assertEqual([B, C, D], list(utils.itersubclasses(A)))