Merge pull request #296 from sputnik13/refactor_config
diff --git a/lib/muchos/config/base.py b/lib/muchos/config/base.py
index d487a7f..b7a2e2a 100644
--- a/lib/muchos/config/base.py
+++ b/lib/muchos/config/base.py
@@ -164,7 +164,7 @@
# the cluster specific config
filter(lambda t: t.class_name.lower() in f,
# get all ansible vars of var_type
- get_ansible_vars(var_type))}
+ get_ansible_vars(var_type, type(self)))}
@abstractmethod
def verify_config(self, action):
diff --git a/lib/muchos/config/decorators.py b/lib/muchos/config/decorators.py
index e3a709f..3fcd6fb 100644
--- a/lib/muchos/config/decorators.py
+++ b/lib/muchos/config/decorators.py
@@ -17,6 +17,7 @@
from collections.abc import Iterable
from functools import wraps
+from pydoc import locate
# struct to hold information about ansible vars defined via decorators.
@@ -25,10 +26,16 @@
# property_name indicates the class property/function where the variable was
# defined
class _ansible_var(object):
- def __init__(self, var_name, class_name, property_name):
+ def __init__(self, var_name, class_name, property_name, module_name):
self.var_name = var_name
self.class_name = class_name
self.property_name = property_name
+ self.module_name = module_name
+
+ def __str__(self):
+ return 'var_name={}, class_name={}, property_name={}, module_name={}'.format(
+ self.var_name, self.class_name, self.property_name, self.module_name
+ )
# each entry of _ansible_vars will contain a list of _ansible_var instances
_ansible_vars = dict(
@@ -37,8 +44,11 @@
extra=[]
)
-def get_ansible_vars(var_type):
- return _ansible_vars.get(var_type)
+def get_ansible_vars(var_type, class_in_scope):
+ # return variables for the complete class hierarchy
+ return list(filter(lambda v:
+ issubclass(class_in_scope, locate(v.module_name + "." + v.class_name)),
+ _ansible_vars.get(var_type)))
# ansible hosts inventory variables
def ansible_host_var(name=None):
@@ -57,7 +67,8 @@
ansible_var = _ansible_var(
var_name=name if isinstance(name, str) else func.__name__,
class_name=func.__qualname__.split('.')[0],
- property_name=func.__name__)
+ property_name=func.__name__,
+ module_name=func.__module__)
_ansible_vars[var_type].append(ansible_var)
return func
@@ -108,4 +119,3 @@
class ConfigMissingError(Exception):
def __init__(self, name):
super(ConfigMissingError, self).__init__("{} is missing from the configuration".format(name))
-
diff --git a/lib/tests/test_decorators.py b/lib/tests/test_decorators.py
new file mode 100644
index 0000000..95a17cb
--- /dev/null
+++ b/lib/tests/test_decorators.py
@@ -0,0 +1,132 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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 unittest import TestCase
+
+from muchos.config import decorators
+
+
+class DecoratedThing(object):
+ @property
+ @decorators.ansible_host_var
+ def host_var1(self):
+ return 'host_var1'
+
+ @property
+ @decorators.ansible_host_var(name='named_host_var2')
+ def host_var2(self):
+ return 'named_host_var2'
+
+ @property
+ @decorators.ansible_play_var
+ def play_var1(self):
+ return 'play_var1'
+
+ @property
+ @decorators.ansible_play_var(name='named_play_var2')
+ def play_var2(self):
+ return 'named_play_var2'
+
+ @property
+ @decorators.ansible_extra_var
+ def extra_var1(self):
+ return 'extra_var1'
+
+ @property
+ @decorators.ansible_extra_var(name='named_extra_var2')
+ def extra_var2(self):
+ return 'named_extra_var2'
+
+ @property
+ @decorators.default('default_val')
+ def default_val(self):
+ return None
+
+ @property
+ @decorators.default(True)
+ def default_boolean_val_True(self):
+ return True
+
+ @property
+ @decorators.default(True)
+ def default_boolean_val_False(self):
+ return False
+
+ @property
+ @decorators.default(True)
+ def default_missing_boolean_val(self):
+ return None
+
+ @property
+ @decorators.required
+ def required_val(self):
+ return 'required_val'
+
+ @property
+ @decorators.required
+ def missing_required_val(self):
+ return None
+
+
+class DecoratorTests(TestCase):
+ def _flatten_dict(d):
+ return {(k, v) for k, v in d.items()}
+
+ def test_decorators(self):
+ thing = DecoratedThing()
+
+ actual_host_vars = decorators.get_ansible_vars('host', type(thing))
+ actual_play_vars = decorators.get_ansible_vars('play', type(thing))
+ actual_extra_vars = decorators.get_ansible_vars('extra', type(thing))
+
+ expected_host_vars = [
+ decorators._ansible_var('host_var1', 'DecoratedThing', 'host_var1', 'tests.test_decorators'),
+ decorators._ansible_var('named_host_var2', 'DecoratedThing', 'host_var2', 'tests.test_decorators')
+ ]
+
+ expected_play_vars = [
+ decorators._ansible_var('play_var1', 'DecoratedThing', 'play_var1', 'tests.test_decorators'),
+ decorators._ansible_var('named_play_var2', 'DecoratedThing', 'play_var2', 'tests.test_decorators')
+ ]
+
+ expected_extra_vars = [
+ decorators._ansible_var('extra_var1', 'DecoratedThing', 'extra_var1', 'tests.test_decorators'),
+ decorators._ansible_var('named_extra_var2', 'DecoratedThing', 'extra_var2', 'tests.test_decorators')
+ ]
+
+ self.assertEquals(
+ set([str(v) for v in expected_host_vars]),
+ set([str(v) for v in actual_host_vars])
+ )
+
+ self.assertEquals(
+ set([str(v) for v in expected_play_vars]),
+ set([str(v) for v in actual_play_vars])
+ )
+
+ self.assertEquals(
+ set([str(v) for v in expected_extra_vars]),
+ set([str(v) for v in actual_extra_vars])
+ )
+
+ self.assertEquals(thing.default_val, 'default_val')
+ self.assertEquals(thing.default_boolean_val_True, True)
+ self.assertEquals(thing.default_boolean_val_False, False)
+ self.assertEquals(thing.default_missing_boolean_val, True)
+ self.assertEquals(thing.required_val, 'required_val')
+ with self.assertRaises(decorators.ConfigMissingError):
+ thing.missing_required_val
\ No newline at end of file
diff --git a/lib/tests/test_validators.py b/lib/tests/test_validators.py
new file mode 100644
index 0000000..0785717
--- /dev/null
+++ b/lib/tests/test_validators.py
@@ -0,0 +1,150 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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 unittest import TestCase
+
+from muchos.config import validators
+from muchos.config.decorators import is_valid
+
+
+class ValidateThis(object):
+ @property
+ @is_valid(validators.greater_than(5))
+ def fourNotGreaterThanFive(self):
+ return 4
+
+ @property
+ @is_valid(validators.greater_than(5))
+ def fiveNotGreaterThanFive(self):
+ return 5
+
+ @property
+ @is_valid(validators.greater_than(5))
+ def sixGreaterThanFive(self):
+ return 6
+
+ @property
+ @is_valid(validators.less_than(5))
+ def fourLessThanFive(self):
+ return 4
+
+ @property
+ @is_valid(validators.less_than(5))
+ def fiveNotLessThanFive(self):
+ return 5
+
+ @property
+ @is_valid(validators.less_than(5))
+ def sixNotLessThanFive(self):
+ return 6
+
+ @property
+ @is_valid(validators.equals(5))
+ def fourNotEqualFive(self):
+ return 4
+
+ @property
+ @is_valid(validators.equals(5))
+ def fiveEqualFive(self):
+ return 5
+
+ @property
+ @is_valid(validators.equals(5))
+ def sixeNotEqualFive(self):
+ return 6
+
+ @property
+ @is_valid(validators.contains(5))
+ def containsFive(self):
+ return [4,5,6]
+
+ @property
+ @is_valid(validators.contains(5))
+ def notContainsFive(self):
+ return []
+
+ @property
+ @is_valid(validators.is_in([5]))
+ def fourNotInListOfFive(self):
+ return 4
+
+ @property
+ @is_valid(validators.is_in([5]))
+ def fiveInListOfFive(self):
+ return 5
+
+ @property
+ @is_valid(validators.is_in([5]))
+ def sixNotInListOfFive(self):
+ return 6
+
+ @property
+ @is_valid(validators.is_type(str))
+ def intIsNotString(self):
+ return 5
+
+ @property
+ @is_valid(validators.is_type(str))
+ def stringIsString(self):
+ return 'some string'
+
+
+class ValidationTests(TestCase):
+ def test_validators(self):
+ thing = ValidateThis()
+
+ with self.assertRaises(Exception):
+ thing.fourNotGreaterThanFive
+
+ with self.assertRaises(Exception):
+ thing.fiveNotGreaterThanFive
+
+ self.assertEqual(thing.sixGreaterThanFive, 6)
+
+ self.assertEqual(thing.fourLessThanFive, 4)
+
+ with self.assertRaises(Exception):
+ thing.fiveNotLessThanFive
+
+ with self.assertRaises(Exception):
+ thing.sixNotLessThanFive
+
+ with self.assertRaises(Exception):
+ thing.fourNotEqualFive
+
+ self.assertEqual(thing.fiveEqualFive, 5)
+
+ with self.assertRaises(Exception):
+ thing.sixeNotEqualFive
+
+ self.assertEqual(thing.containsFive, [4,5,6])
+
+ with self.assertRaises(Exception):
+ thing.notContainsFive
+
+ with self.assertRaises(Exception):
+ thing.fourNotInListOfFive
+
+ self.assertEqual(thing.fiveInListOfFive, 5)
+
+ with self.assertRaises(Exception):
+ thing.sixNotInListOfFive
+
+ with self.assertRaises(Exception):
+ thing.intIsNotString
+
+ self.assertEqual(thing.stringIsString, 'some string')
\ No newline at end of file