6996768 - DataObjectCache should provide a method for importing all DataObjects from a given module
--- a/usr/src/lib/install_doc/data_object/__init__.py Thu Nov 04 09:15:13 2010 -0700
+++ b/usr/src/lib/install_doc/data_object/__init__.py Fri Nov 05 11:26:05 2010 +0000
@@ -36,6 +36,7 @@
from abc import ABCMeta, abstractmethod
from lxml import etree
+from solaris_install.logger import INSTALL_LOGGER_NAME
# Define various Data Object specific exceptions
@@ -92,6 +93,9 @@
# Define regular expression for extracting paths from strings.
__STRING_REPLACEMENT_RE = re.compile("%{([^}]+)}")
+ # Reference for Install Logger
+ __logger = None
+
def __init__(self, name):
self._name = name
self._parent = None
@@ -102,6 +106,30 @@
# MutatableSequence sub-class
self._children = []
+ @classmethod
+ def get_logger(cls):
+ ''' Returns reference to logger class
+
+ Use a class method and variable instead of an instance variable as
+ pickle has problems when it tries to pickle the reference to the
+ logger.
+
+ Mainly used for logging from class methods, so most will just use
+ self.logger property if it's an object instance.
+ '''
+ if cls.__logger is None:
+ cls.__logger = logging.getLogger(INSTALL_LOGGER_NAME)
+
+ return cls.__logger
+
+ @property
+ def logger(self):
+ '''Instance accessor for the logger.
+
+ The use of a property is a nicer interface sub-classes to use.
+ '''
+ return DataObjectBase.get_logger()
+
# Abstract class methods.
# These methods must be implemented by DataObject sub-classes or an
# Exception will be raised when they are instantiated.
@@ -440,7 +468,7 @@
if not isinstance(obj, DataObjectBase):
msg = "Invalid Child Type: %s" % (obj.__class__.__name__)
- logging.error(msg)
+ DataObjectBase.get_logger().error(msg)
raise TypeError(msg)
# Methods for cloning / duplication objects
@@ -756,7 +784,7 @@
else:
value_str += "%s%s" % (value_separator, repr(value))
- logging.debug("Replacing reference to '%s' with '%s'" %
+ self.logger.debug("Replacing reference to '%s' with '%s'" %
(matches.group(0), value_str))
new_string = new_string.replace(matches.group(0),
value_str, 1)
@@ -880,7 +908,7 @@
if before is not None and after is not None:
msg = "Both 'before' and 'after' should not be specified."
- logging.error(msg)
+ self.logger.error(msg)
raise ValueError(msg)
elif before is not None:
self._check_object_type(before)
@@ -888,7 +916,7 @@
insert_at = self._children.index(before)
except ValueError:
msg = "Invalid value for 'before' while inserting children"
- logging.error(msg)
+ self.logger.error(msg)
raise ObjectNotFoundError(msg)
elif after is not None:
self._check_object_type(after)
@@ -896,7 +924,7 @@
insert_at = self._children.index(after) + 1
except ValueError:
msg = "Invalid value for 'after' while inserting children"
- logging.error(msg)
+ self.logger.error(msg)
raise ObjectNotFoundError(msg)
else:
insert_at = len(self._children)
--- a/usr/src/lib/install_doc/data_object/cache.py Thu Nov 04 09:15:13 2010 -0700
+++ b/usr/src/lib/install_doc/data_object/cache.py Fri Nov 05 11:26:05 2010 +0000
@@ -25,7 +25,7 @@
"""Mechanism for providing a central store of in-memory data in the installer.
"""
-import logging
+import inspect
import pickle
from lxml import etree
@@ -155,7 +155,7 @@
child.delete_children()
msg = "DataObjectCache cleared!"
- logging.info(msg)
+ self.logger.info(msg)
@property
def is_empty(self):
@@ -269,8 +269,12 @@
importing XML to find classes that support the handling of XML
snippets by calling the 'can_handle()' and 'from_xml()' methods.
- 'new_class_obj' may be either a single class object, or an iterable
- object containing class objects.
+ 'new_class_obj' may be either any of:
+ - a single class object or module
+ - an iterable object containing class objects and/or modules
+
+ Modules to be registered should contain classes that are sub-classes
+ of DataObjectBase.
The priority defines the order in which classes are checked, with
lower numbers being checked first - the default is for all registered
@@ -279,8 +283,8 @@
Exceptions:
TypeError
- - thrown if class_obj is not a sub-class of DataObject or
- an iterable object.
+ - thrown if class_obj is not a sub-class of DataObjectBase,
+ a Module, or an iterable object containing either of them.
ValueError
- thrown if priority is not in the range 0-100 inclusive.
@@ -299,7 +303,25 @@
# Should have iterable at this point, so loop through.
for class_ref in class_list:
- if issubclass(class_ref, DataObjectBase):
+ if inspect.ismodule(class_ref):
+ # It's not really a class, it's a module, so get it's classes
+ to_register = []
+ for name, value in \
+ inspect.getmembers(class_ref, inspect.isclass):
+
+ if inspect.getmodule(value) != class_ref:
+ # Skip anything that didn't originate from the module
+ # itself (e.g. DataObject imported into the module)
+ continue
+ if issubclass(value, DataObjectBase):
+ to_register.append(value)
+
+ if len(to_register) > 0:
+ # Do a recursive call to self to register found classes
+ DataObjectCache.register_class(to_register, priority)
+
+ elif issubclass(class_ref, DataObjectBase):
+ # It's definitely a valid class to register it.
_CACHE_CLASS_REGISTRY.setdefault(priority, [])\
.append(class_ref)
else:
--- a/usr/src/lib/install_doc/test/test_data_object_cache_registration.py Thu Nov 04 09:15:13 2010 -0700
+++ b/usr/src/lib/install_doc/test/test_data_object_cache_registration.py Fri Nov 05 11:26:05 2010 +0000
@@ -33,6 +33,7 @@
import solaris_install.data_object.cache as DOC
from simple_data_object import SimpleDataObject, SimpleDataObject2, \
SimpleDataObject3, SimpleDataObject4
+import simple_data_object
# Define two classes that both handle the same tag, but have
# different priorities.
@@ -168,6 +169,59 @@
"EXPECTED:\n%s\nGOT:\n%s\n" %
(expected_registered_classes_str, txt))
+ def test_doc_registration_classes_from_module_same_prio(self):
+ '''Validate registration of classes in a module at same priority'''
+ # Compensate for indent
+ indentation = '''\
+ '''
+ expected_registered_classes_str = '''\
+ ============================
+ Registered Classes:
+ [Priority = 30]
+ <class 'simple_data_object.SimpleDataObject'>
+ <class 'simple_data_object.SimpleDataObject2'>
+ <class 'simple_data_object.SimpleDataObject3'>
+ <class 'simple_data_object.SimpleDataObject4'>
+ <class 'simple_data_object.SimpleDataObject5'>
+ <class 'simple_data_object.SimpleDataObjectHandlesChildren'>
+ <class 'simple_data_object.SimpleDataObjectNoXml'>
+ ============================
+ '''.replace(indentation, "")
+ DataObjectCache.register_class(simple_data_object, priority=30)
+
+ txt = self.doc.get_registered_classes_str()
+
+ self.assertEquals(expected_registered_classes_str, txt,
+ "EXPECTED:\n%s\nGOT:\n%s\n" %
+ (expected_registered_classes_str, txt))
+
+ def test_doc_registration_classes_from_module_in_list_same_prio(self):
+ '''Validate registration of classes in a module list at same priority
+ '''
+ # Compensate for indent
+ indentation = '''\
+ '''
+ expected_registered_classes_str = '''\
+ ============================
+ Registered Classes:
+ [Priority = 30]
+ <class 'simple_data_object.SimpleDataObject'>
+ <class 'simple_data_object.SimpleDataObject2'>
+ <class 'simple_data_object.SimpleDataObject3'>
+ <class 'simple_data_object.SimpleDataObject4'>
+ <class 'simple_data_object.SimpleDataObject5'>
+ <class 'simple_data_object.SimpleDataObjectHandlesChildren'>
+ <class 'simple_data_object.SimpleDataObjectNoXml'>
+ ============================
+ '''.replace(indentation, "")
+ DataObjectCache.register_class([simple_data_object], priority=30)
+
+ txt = self.doc.get_registered_classes_str()
+
+ self.assertEquals(expected_registered_classes_str, txt,
+ "EXPECTED:\n%s\nGOT:\n%s\n" %
+ (expected_registered_classes_str, txt))
+
def test_doc_registration_multiple_classes_same_prio(self):
'''Validate registration of multiple classes at same priority'''
# Compensate for indent