Merge "Improve default meta implementation"
This commit is contained in:
commit
fe5d98048d
@ -16,20 +16,17 @@
|
||||
import copy
|
||||
|
||||
|
||||
DEFAULT_META_CONCATENATION = {
|
||||
"context": "dict",
|
||||
"validators": "list",
|
||||
}
|
||||
|
||||
|
||||
class MetaMixin(object):
|
||||
"""Safe way to store meta information related to class object.
|
||||
|
||||
We are storing information in class object instead of the instance.
|
||||
Allows to store information in class object instead of the instance.
|
||||
|
||||
Information is stored in dict that is initialized only once during the
|
||||
load of module, it means that all subclasses of this class will point to
|
||||
the same dict object with the information.
|
||||
|
||||
Allows to protect children from using parents meta.
|
||||
|
||||
Sample that explains why it's important to use MetaMixin:
|
||||
|
||||
>>> # Using direct fields
|
||||
@ -64,24 +61,12 @@ class MetaMixin(object):
|
||||
>>> assert B._meta_get("a") == 20
|
||||
"""
|
||||
|
||||
_default_meta = (None, {})
|
||||
|
||||
@classmethod
|
||||
def _meta_init(cls):
|
||||
"""Initialize meta for this class."""
|
||||
cls._meta = {}
|
||||
|
||||
# set default values defined in all parent classes
|
||||
for class_ in reversed(cls.__mro__):
|
||||
default_meta = vars(class_).get("DEFAULT_META", {})
|
||||
for key, value in default_meta.items():
|
||||
if key in DEFAULT_META_CONCATENATION:
|
||||
if DEFAULT_META_CONCATENATION[key] == "list":
|
||||
cls._meta.setdefault(key, [])
|
||||
cls._meta[key].extend(value)
|
||||
elif DEFAULT_META_CONCATENATION[key] == "dict":
|
||||
cls._meta.setdefault(key, {})
|
||||
cls._meta[key].update(value)
|
||||
else:
|
||||
cls._meta[key] = copy.deepcopy(value)
|
||||
cls._meta = copy.deepcopy(cls._default_meta[1])
|
||||
|
||||
@classmethod
|
||||
def _meta_clear(cls):
|
||||
@ -120,3 +105,37 @@ class MetaMixin(object):
|
||||
"""Set default value for key in meta."""
|
||||
cls._meta_is_inited()
|
||||
cls._meta.setdefault(key, value)
|
||||
|
||||
@classmethod
|
||||
def _default_meta_init(cls, inherit=True):
|
||||
"""Initialize default meta.
|
||||
|
||||
Default Meta is used to change the behavior of _meta_init() method
|
||||
Meta is initialized with the copy of default meta instead of {}
|
||||
|
||||
:param inherit: initialize meta with copy of parent's default meta
|
||||
"""
|
||||
if inherit:
|
||||
cls._default_meta = (cls, copy.deepcopy(cls._default_meta[1]))
|
||||
else:
|
||||
cls._default_meta = (cls, {})
|
||||
|
||||
@classmethod
|
||||
def _default_meta_set(cls, key, value):
|
||||
if cls is not cls._default_meta[0]:
|
||||
raise ReferenceError(
|
||||
"Trying to update default meta from children class.")
|
||||
|
||||
cls._default_meta[1][key] = value
|
||||
|
||||
@classmethod
|
||||
def _default_meta_get(cls, key, default=None):
|
||||
return cls._default_meta[1].get(key, default)
|
||||
|
||||
@classmethod
|
||||
def _default_meta_setdefault(cls, key, value):
|
||||
if cls is not cls._default_meta[0]:
|
||||
raise ReferenceError(
|
||||
"Trying to update default meta from children class.")
|
||||
|
||||
cls._default_meta[1].setdefault(key, value)
|
||||
|
@ -26,14 +26,16 @@ from rally import exceptions
|
||||
def base():
|
||||
"""Mark Plugin as a base.
|
||||
|
||||
Base Plugins are used to have better organization of plugins.
|
||||
|
||||
It basically resolved to problems:
|
||||
Base Plugins are used to have better organization of plugins providing
|
||||
subtypes. Base Plugins may contain documentation and other base methods
|
||||
related to specific type of plugins, resolving next problems:
|
||||
|
||||
- Having different types of plugins (e.g. Sceanrio, Context, SLA, ...)
|
||||
- Auto generation of plugin reference with splitting plugins by their base
|
||||
- Plugin lookup - one can easily get all plugins from some base.
|
||||
|
||||
Plugin bases by default initialize _default_meta
|
||||
|
||||
.. warning:: This decorator should be added the line before
|
||||
six.add_metaclass if it is used.
|
||||
"""
|
||||
@ -51,6 +53,7 @@ def base():
|
||||
"parent": parent.__name__})
|
||||
|
||||
cls.base_ref = cls
|
||||
cls._default_meta_init(True)
|
||||
return cls
|
||||
return wrapper
|
||||
|
||||
@ -94,6 +97,22 @@ def configure(name, platform="default", hidden=False):
|
||||
return decorator
|
||||
|
||||
|
||||
def default_meta(inherit=True):
|
||||
"""Initialize default meta for particular plugin.
|
||||
|
||||
Default Meta is inherited by all children comparing to Meta which is unique
|
||||
per plugin.
|
||||
|
||||
:param inherit: Whatever to copy parents default meta
|
||||
"""
|
||||
|
||||
def decorator(plugin):
|
||||
plugin._default_meta_init(inherit)
|
||||
return plugin
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def deprecated(reason, rally_version):
|
||||
"""Mark plugin as deprecated.
|
||||
|
||||
|
@ -145,7 +145,7 @@ def add(name, **kwargs):
|
||||
def add_default(name, **kwargs):
|
||||
"""Add validator to the plugin class default meta.
|
||||
|
||||
Validator will be added to all subclasses by default
|
||||
Validator is added to all subclasses by default
|
||||
|
||||
:param name: str, name of the validator plugin
|
||||
:param kwargs: dict, arguments used to initialize validator class
|
||||
@ -153,10 +153,8 @@ def add_default(name, **kwargs):
|
||||
"""
|
||||
|
||||
def wrapper(plugin):
|
||||
if not hasattr(plugin, "DEFAULT_META"):
|
||||
plugin.DEFAULT_META = {}
|
||||
plugin.DEFAULT_META.setdefault("validators", [])
|
||||
plugin.DEFAULT_META["validators"].append((name, (), kwargs,))
|
||||
plugin._default_meta_setdefault("validators", [])
|
||||
plugin._default_meta_get("validators").append((name, (), kwargs,))
|
||||
return plugin
|
||||
return wrapper
|
||||
|
||||
|
@ -103,104 +103,79 @@ class TestMetaMixinTestCase(test.TestCase):
|
||||
Meta._meta_setdefault("any", 2)
|
||||
self.assertEqual(42, Meta._meta_get("any"))
|
||||
|
||||
def test_default_meta(self):
|
||||
def test_default_meta_init(self):
|
||||
|
||||
class Meta(meta.MetaMixin):
|
||||
DEFAULT_META = {"foo": "bar"}
|
||||
|
||||
class SubMeta(Meta):
|
||||
pass
|
||||
|
||||
class SubMetaWithDefault(Meta):
|
||||
DEFAULT_META = {"foo": "spam"}
|
||||
Meta._default_meta_init(False)
|
||||
self.assertIs(Meta, Meta._default_meta[0])
|
||||
self.assertEqual({}, Meta._default_meta[1])
|
||||
|
||||
class SubSubMeta(SubMeta):
|
||||
DEFAULT_META = {"baz": "eggs"}
|
||||
def test_default_meta_init_inherit(self):
|
||||
|
||||
Meta._meta_init()
|
||||
SubMeta._meta_init()
|
||||
SubMetaWithDefault._meta_init()
|
||||
SubSubMeta._meta_init()
|
||||
class MetaParent(meta.MetaMixin):
|
||||
pass
|
||||
|
||||
self.assertEqual("bar", Meta._meta_get("foo"))
|
||||
self.assertEqual("bar", SubMeta._meta_get("foo"))
|
||||
self.assertEqual("spam", SubMetaWithDefault._meta_get("foo"))
|
||||
self.assertEqual("bar", SubSubMeta._meta_get("foo"))
|
||||
self.assertEqual("eggs", SubSubMeta._meta_get("baz"))
|
||||
self.assertIsNone(Meta._meta_get("baz"))
|
||||
self.assertIsNone(SubMeta._meta_get("baz"))
|
||||
self.assertIsNone(SubMetaWithDefault._meta_get("baz"))
|
||||
MetaParent._default_meta_init(False)
|
||||
MetaParent._default_meta_set("a", 1)
|
||||
|
||||
def test_default_meta_change(self):
|
||||
class MetaChildInherit(MetaParent):
|
||||
pass
|
||||
|
||||
MetaChildInherit._default_meta_init(True)
|
||||
self.assertEqual(1, MetaChildInherit._default_meta_get("a", 5))
|
||||
|
||||
class MetaChildNoInherit(MetaParent):
|
||||
pass
|
||||
|
||||
MetaChildNoInherit._default_meta_init(False)
|
||||
self.assertEqual(5, MetaChildNoInherit._default_meta_get("a", 5))
|
||||
|
||||
def test_default_meta_set_and_get(self):
|
||||
class Meta(meta.MetaMixin):
|
||||
pass
|
||||
|
||||
Meta._default_meta_init(False)
|
||||
Meta._default_meta_set("b", 10)
|
||||
self.assertEqual(10, Meta._default_meta_get("b"))
|
||||
self.assertEqual(10, Meta._default_meta_get("b", 5))
|
||||
self.assertEqual(5, Meta._default_meta_get("c", 5))
|
||||
self.assertIsNone(Meta._default_meta_get("c"))
|
||||
|
||||
def test_default_meta_set_from_children(self):
|
||||
|
||||
class Meta(meta.MetaMixin):
|
||||
DEFAULT_META = {"foo": []}
|
||||
|
||||
class SubMeta(Meta):
|
||||
pass
|
||||
|
||||
Meta._meta_init()
|
||||
SubMeta._meta_init()
|
||||
self.assertRaises(ReferenceError,
|
||||
Meta._default_meta_set, "a", 1)
|
||||
self.assertRaises(ReferenceError,
|
||||
Meta._default_meta_setdefault, "a", 1)
|
||||
|
||||
self.assertEqual([], Meta._meta_get("foo"))
|
||||
self.assertEqual([], SubMeta._meta_get("foo"))
|
||||
def test_default_meta_set_default(self):
|
||||
|
||||
SubMeta._meta_get("foo").append("bar")
|
||||
class Meta(meta.MetaMixin):
|
||||
pass
|
||||
|
||||
self.assertEqual([], Meta._meta_get("foo"))
|
||||
self.assertEqual(["bar"], SubMeta._meta_get("foo"))
|
||||
Meta._default_meta_init(False)
|
||||
Meta._default_meta_setdefault("a", 1)
|
||||
Meta._default_meta_setdefault("a", 2)
|
||||
|
||||
Meta._meta_get("foo").append("baz")
|
||||
self.assertEqual(1, Meta._default_meta_get("a"))
|
||||
|
||||
self.assertEqual(["baz"], Meta._meta_get("foo"))
|
||||
self.assertEqual(["bar"], SubMeta._meta_get("foo"))
|
||||
def test_meta_init_with_default_meta(self):
|
||||
|
||||
def test_default_meta_validators(self):
|
||||
class Meta(meta.MetaMixin):
|
||||
pass
|
||||
|
||||
class A(meta.MetaMixin):
|
||||
DEFAULT_META = {"validators": ["a"]}
|
||||
Meta._default_meta_init()
|
||||
Meta._default_meta_set("a", 10)
|
||||
Meta._default_meta_set("b", 20)
|
||||
|
||||
class B(A):
|
||||
DEFAULT_META = {"validators": ["b", "foo"]}
|
||||
class MetaChild(Meta):
|
||||
pass
|
||||
|
||||
class C(A):
|
||||
DEFAULT_META = {"validators": ["c", "foo"]}
|
||||
|
||||
class D(B, C):
|
||||
DEFAULT_META = {"validators": ["d"]}
|
||||
|
||||
A._meta_init()
|
||||
B._meta_init()
|
||||
C._meta_init()
|
||||
D._meta_init()
|
||||
|
||||
self.assertEqual(["a"], A._meta_get("validators"))
|
||||
self.assertEqual(["a", "b", "foo"], B._meta_get("validators"))
|
||||
self.assertEqual(["a", "c", "foo"], C._meta_get("validators"))
|
||||
self.assertEqual(["a", "c", "foo", "b", "foo", "d"],
|
||||
D._meta_get("validators"))
|
||||
|
||||
def test_default_meta_context(self):
|
||||
|
||||
class A(meta.MetaMixin):
|
||||
DEFAULT_META = {"context": {"foo": "a"}}
|
||||
|
||||
class B(A):
|
||||
DEFAULT_META = {"context": {"foo": "b", "baz": "b"}}
|
||||
|
||||
class C(A):
|
||||
DEFAULT_META = {"context": {"foo": "c", "spam": "c"}}
|
||||
|
||||
class D(B, C):
|
||||
DEFAULT_META = {"context": {"bar": "d"}}
|
||||
|
||||
A._meta_init()
|
||||
B._meta_init()
|
||||
C._meta_init()
|
||||
D._meta_init()
|
||||
|
||||
self.assertEqual({"foo": "a"}, A._meta_get("context"))
|
||||
self.assertEqual({"foo": "b", "baz": "b"}, B._meta_get("context"))
|
||||
self.assertEqual({"foo": "c", "spam": "c"}, C._meta_get("context"))
|
||||
self.assertEqual({"foo": "b", "baz": "b", "spam": "c", "bar": "d"},
|
||||
D._meta_get("context"))
|
||||
MetaChild._meta_init()
|
||||
self.assertEqual(10, MetaChild._meta_get("a"))
|
||||
self.assertEqual(20, MetaChild._meta_get("b"))
|
||||
|
@ -27,6 +27,8 @@ class PluginModuleTestCase(test.TestCase):
|
||||
class MyPlugin(plugin.Plugin):
|
||||
pass
|
||||
|
||||
self.addCleanup(MyPlugin.unregister)
|
||||
|
||||
self.assertEqual({"reason": "God why?", "rally_version": "0.0.2"},
|
||||
MyPlugin.is_deprecated())
|
||||
|
||||
@ -36,6 +38,8 @@ class PluginModuleTestCase(test.TestCase):
|
||||
class MyPlugin(plugin.Plugin):
|
||||
pass
|
||||
|
||||
self.addCleanup(MyPlugin.unregister)
|
||||
|
||||
self.assertEqual("get_name_class_plugin", MyPlugin.get_name())
|
||||
|
||||
def test_configure_two_cls_with_same_names(self):
|
||||
@ -49,11 +53,15 @@ class PluginModuleTestCase(test.TestCase):
|
||||
class MyPlugin(FooBase):
|
||||
pass
|
||||
|
||||
self.addCleanup(MyPlugin.unregister)
|
||||
|
||||
try:
|
||||
@plugin.configure(name=name)
|
||||
class MyPlugin2(FooBase):
|
||||
pass
|
||||
|
||||
self.addCleanup(MyPlugin2.unregister)
|
||||
|
||||
except exceptions.PluginWithSuchNameExists:
|
||||
self.assertEqual([MyPlugin], FooBase.get_all())
|
||||
else:
|
||||
@ -75,10 +83,14 @@ class PluginModuleTestCase(test.TestCase):
|
||||
class A(OneBase):
|
||||
pass
|
||||
|
||||
self.addCleanup(A.unregister)
|
||||
|
||||
@plugin.configure(name, platform=name)
|
||||
class B(SecondBase):
|
||||
pass
|
||||
|
||||
self.addCleanup(B.unregister)
|
||||
|
||||
self.assertEqual(OneBase.get(name), A)
|
||||
self.assertEqual(SecondBase.get(name), B)
|
||||
|
||||
@ -97,13 +109,44 @@ class PluginModuleTestCase(test.TestCase):
|
||||
class A(OneBase):
|
||||
pass
|
||||
|
||||
self.addCleanup(A.unregister)
|
||||
|
||||
@plugin.configure(name, platform=name)
|
||||
class B(SecondBase):
|
||||
pass
|
||||
|
||||
self.addCleanup(B.unregister)
|
||||
|
||||
self.assertRaises(exceptions.MultipleMatchesFound, plugin.Plugin.get,
|
||||
name, name)
|
||||
|
||||
def test_default_meta_for_base(self):
|
||||
|
||||
@plugin.base()
|
||||
class CanUseDefaultMetaBase(plugin.Plugin):
|
||||
pass
|
||||
|
||||
CanUseDefaultMetaBase._default_meta_set("a", 10)
|
||||
|
||||
@plugin.configure("CantUseDefaultMetaPlugin")
|
||||
class CantUseDefaultMetaPlugin(CanUseDefaultMetaBase):
|
||||
pass
|
||||
|
||||
self.addCleanup(CantUseDefaultMetaPlugin.unregister)
|
||||
self.assertRaises(ReferenceError,
|
||||
CantUseDefaultMetaPlugin._default_meta_set, "a", 10)
|
||||
|
||||
self.assertEqual(10, CantUseDefaultMetaPlugin._meta_get("a"))
|
||||
|
||||
def test_default_meta_decorator(self):
|
||||
|
||||
@plugin.default_meta()
|
||||
class CanUseDefaultMetaPlugin(plugin.Plugin):
|
||||
pass
|
||||
|
||||
CanUseDefaultMetaPlugin._default_meta_set("a", 10)
|
||||
self.assertEqual(10, CanUseDefaultMetaPlugin._default_meta_get("a"))
|
||||
|
||||
|
||||
@plugin.configure(name="test_base_plugin")
|
||||
class BasePlugin(plugin.Plugin):
|
||||
|
Loading…
Reference in New Issue
Block a user