Merge "Improve default meta implementation"

This commit is contained in:
Jenkins 2017-08-10 19:23:07 +00:00 committed by Gerrit Code Review
commit fe5d98048d
5 changed files with 164 additions and 110 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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"))

View File

@ -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):