Coverage for nova/loadables.py: 97%

50 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-17 15:08 +0000

1# Copyright (c) 2011-2012 OpenStack Foundation 

2# All Rights Reserved. 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); you may 

5# not use this file except in compliance with the License. You may obtain 

6# a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16""" 

17Generic Loadable class support. 

18 

19Meant to be used by such things as scheduler filters and weights where we 

20want to load modules from certain directories and find certain types of 

21classes within those modules. Note that this is quite different than 

22generic plugins and the pluginmanager code that exists elsewhere. 

23 

24Usage: 

25 

26Create a directory with an __init__.py with code such as: 

27 

28class SomeLoadableClass(object): 

29 pass 

30 

31 

32class MyLoader(nova.loadables.BaseLoader) 

33 def __init__(self): 

34 super(MyLoader, self).__init__(SomeLoadableClass) 

35 

36If you create modules in the same directory and subclass SomeLoadableClass 

37within them, MyLoader().get_all_classes() will return a list 

38of such classes. 

39""" 

40 

41import inspect 

42import os 

43import sys 

44 

45from oslo_utils import importutils 

46 

47from nova import exception 

48 

49 

50class BaseLoader(object): 

51 def __init__(self, loadable_cls_type): 

52 mod = sys.modules[self.__class__.__module__] 

53 self.path = os.path.abspath(mod.__path__[0]) 

54 self.package = mod.__package__ 

55 self.loadable_cls_type = loadable_cls_type 

56 

57 def _is_correct_class(self, obj): 

58 """Return whether an object is a class of the correct type and 

59 is not prefixed with an underscore. 

60 """ 

61 return (inspect.isclass(obj) and 

62 (not obj.__name__.startswith('_')) and 

63 issubclass(obj, self.loadable_cls_type)) 

64 

65 def _get_classes_from_module(self, module_name): 

66 """Get the classes from a module that match the type we want.""" 

67 classes = [] 

68 module = importutils.import_module(module_name) 

69 for obj_name in dir(module): 

70 # Skip objects that are meant to be private. 

71 if obj_name.startswith('_'): 

72 continue 

73 itm = getattr(module, obj_name) 

74 if self._is_correct_class(itm): 

75 classes.append(itm) 

76 return classes 

77 

78 def get_all_classes(self): 

79 """Get the classes of the type we want from all modules found 

80 in the directory that defines this class. 

81 """ 

82 classes = [] 

83 for dirpath, _, filenames in os.walk(self.path): 

84 relpath = os.path.relpath(dirpath, self.path) 

85 if relpath == '.': 85 ↛ 88line 85 didn't jump to line 88 because the condition on line 85 was always true

86 relpkg = '' 

87 else: 

88 relpkg = '.%s' % '.'.join(relpath.split(os.sep)) 

89 for fname in filenames: 

90 root, ext = os.path.splitext(fname) 

91 if ext != '.py' or root == '__init__': 

92 continue 

93 module_name = "%s%s.%s" % (self.package, relpkg, root) 

94 mod_classes = self._get_classes_from_module(module_name) 

95 classes.extend(mod_classes) 

96 return classes 

97 

98 def get_matching_classes(self, loadable_class_names): 

99 """Get loadable classes from a list of names. Each name can be 

100 a full module path or the full path to a method that returns 

101 classes to use. The latter behavior is useful to specify a method 

102 that returns a list of classes to use in a default case. 

103 """ 

104 classes = [] 

105 for cls_name in loadable_class_names: 

106 obj = importutils.import_class(cls_name) 

107 if self._is_correct_class(obj): 

108 classes.append(obj) 

109 elif inspect.isfunction(obj): 

110 # Get list of classes from a function 

111 for cls in obj(): 

112 classes.append(cls) 

113 else: 

114 error_str = 'Not a class of the correct type' 

115 raise exception.ClassNotFound(class_name=cls_name, 

116 exception=error_str) 

117 return classes