1#====================== BEGIN GPL LICENSE BLOCK ====================== 2# 3# This program is free software; you can redistribute it and/or 4# modify it under the terms of the GNU General Public License 5# as published by the Free Software Foundation; either version 2 6# of the License, or (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software Foundation, 15# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16# 17#======================= END GPL LICENSE BLOCK ======================== 18 19# <pep8 compliant> 20 21import collections 22 23from types import FunctionType 24from itertools import chain 25 26 27#============================================= 28# Class With Stages 29#============================================= 30 31 32def rigify_stage(stage): 33 """Decorates the method with the specified stage.""" 34 def process(method): 35 if not isinstance(method, FunctionType): 36 raise ValueError("Stage decorator must be applied to a method definition") 37 method._rigify_stage = stage 38 return method 39 return process 40 41 42class StagedMetaclass(type): 43 """ 44 Metaclass for rigs that manages assignment of methods to stages via @stage.* decorators. 45 46 Using define_stages=True in the class definition will register all non-system 47 method names from that definition as valid stages. After that, subclasses can 48 register methods to those stages, to be called via rigify_invoke_stage. 49 """ 50 def __new__(metacls, class_name, bases, namespace, define_stages=None, **kwds): 51 # suppress keyword args to avoid issues with __init_subclass__ 52 return super().__new__(metacls, class_name, bases, namespace, **kwds) 53 54 def __init__(self, class_name, bases, namespace, define_stages=None, **kwds): 55 super().__init__(class_name, bases, namespace, **kwds) 56 57 # Compute the set of stages defined by this class 58 if not define_stages: 59 define_stages = [] 60 61 elif define_stages is True: 62 define_stages = [ 63 name for name, item in namespace.items() 64 if name[0] != '_' and isinstance(item, FunctionType) 65 ] 66 67 self.rigify_own_stages = frozenset(define_stages) 68 69 # Compute complete set of inherited stages 70 staged_bases = [ cls for cls in reversed(self.__mro__) if isinstance(cls, StagedMetaclass) ] 71 72 self.rigify_stages = stages = frozenset(chain.from_iterable( 73 cls.rigify_own_stages for cls in staged_bases 74 )) 75 76 # Compute the inherited stage to method mapping 77 stage_map = collections.defaultdict(collections.OrderedDict) 78 own_stage_map = collections.defaultdict(collections.OrderedDict) 79 method_map = {} 80 81 self.rigify_own_stage_map = own_stage_map 82 83 for base in staged_bases: 84 for stage_name, methods in base.rigify_own_stage_map.items(): 85 for method_name, method_class in methods.items(): 86 if method_name in stages: 87 raise ValueError("Stage method '%s' inherited @stage.%s in class %s (%s)" % 88 (method_name, stage_name, class_name, self.__module__)) 89 90 # Check consistency of inherited stage assignment to methods 91 if method_name in method_map: 92 if method_map[method_name] != stage_name: 93 print("RIGIFY CLASS %s (%s): method '%s' has inherited both @stage.%s and @stage.%s\n" % 94 (class_name, self.__module__, method_name, method_map[method_name], stage_name)) 95 else: 96 method_map[method_name] = stage_name 97 98 stage_map[stage_name][method_name] = method_class 99 100 # Scan newly defined methods for stage decorations 101 for method_name, item in namespace.items(): 102 if isinstance(item, FunctionType): 103 stage = getattr(item, '_rigify_stage', None) 104 105 if stage and method_name in stages: 106 print("RIGIFY CLASS %s (%s): cannot use stage decorator on the stage method '%s' (@stage.%s ignored)" % 107 (class_name, self.__module__, method_name, stage)) 108 continue 109 110 # Ensure that decorators aren't lost when redefining methods 111 if method_name in method_map: 112 if not stage: 113 stage = method_map[method_name] 114 print("RIGIFY CLASS %s (%s): missing stage decorator on method '%s' (should be @stage.%s)" % 115 (class_name, self.__module__, method_name, stage)) 116 # Check that the method is assigned to only one stage 117 elif stage != method_map[method_name]: 118 print("RIGIFY CLASS %s (%s): method '%s' has decorator @stage.%s, but inherited base has @stage.%s" % 119 (class_name, self.__module__, method_name, stage, method_map[method_name])) 120 121 # Assign the method to the stage, verifying that it's valid 122 if stage: 123 if stage not in stages: 124 raise ValueError("Invalid stage name '%s' for method '%s' in class %s (%s)" % 125 (stage, method_name, class_name, self.__module__)) 126 else: 127 stage_map[stage][method_name] = self 128 own_stage_map[stage][method_name] = self 129 130 self.rigify_stage_map = stage_map 131 132 def make_stage_decorators(self): 133 return [(name, rigify_stage(name)) for name in self.rigify_stages] 134 135 136class BaseStagedClass(object, metaclass=StagedMetaclass): 137 rigify_sub_objects = tuple() 138 139 def rigify_invoke_stage(self, stage): 140 """Call all methods decorated with the given stage, followed by the callback.""" 141 cls = self.__class__ 142 assert isinstance(cls, StagedMetaclass) 143 assert stage in cls.rigify_stages 144 145 getattr(self, stage)() 146 147 for sub in self.rigify_sub_objects: 148 sub.rigify_invoke_stage(stage) 149 150 for method_name in cls.rigify_stage_map[stage]: 151 getattr(self, method_name)() 152 153 154#============================================= 155# Per-owner singleton class 156#============================================= 157 158 159class SingletonPluginMetaclass(StagedMetaclass): 160 """Metaclass for maintaining one instance per owner object per constructor arg set.""" 161 def __call__(cls, owner, *constructor_args): 162 key = (cls, *constructor_args) 163 try: 164 return owner.plugin_map[key] 165 except KeyError: 166 new_obj = super().__call__(owner, *constructor_args) 167 owner.plugin_map[key] = new_obj 168 owner.plugin_list.append(new_obj) 169 owner.plugin_list.sort(key=lambda obj: obj.priority, reverse=True) 170 return new_obj 171