Package turbogears :: Package widgets :: Module meta

Source Code for Module turbogears.widgets.meta

  1  import copy 
  2  import threading 
  3  import warnings 
  4   
  5  from inspect import isclass 
  6  from itertools import count, ifilter 
  7  from new import instancemethod 
  8   
  9  from turbogears import validators 
 10  from turbogears.util import setlike 
 11  from formencode.schema import Schema 
 12   
 13  # FIXME-dbr: we need to make kid-support pluggable or such. 
 14  try: 
 15      import kid 
 16  except ImportError: 
 17      pass 
 18   
 19  __all__ = ["MetaWidget", "load_kid_template"] 
 20   
 21  param_prefix = '_param_' 
 22   
23 -class MetaWidget(type):
24 """The meta class for widgets.""" 25
26 - def __new__(cls, name, bases, dct):
27 # Makes sure we get the union of params and member_widgets 28 # from all our bases. 29 params = setlike(dct.get('params', [])) 30 member_widgets = setlike(dct.get('member_widgets', [])) 31 compound = False 32 for base in bases: 33 params.add_all(getattr(base, 'params', [])) 34 if getattr(base, 'compound', False): 35 member_widgets.add_all(getattr(base, 'member_widgets', [])) 36 compound = True 37 for param in params: 38 # Swap all params listed at 'params' with a ParamDescriptor 39 try: 40 dct[param_prefix + param] = dct[param] 41 dct[param] = ParamDescriptor(param) 42 except KeyError: 43 for base in bases: 44 if hasattr(base, param): 45 break 46 else: 47 # not declared in any superclass, let default be None 48 dct[param_prefix + param] = None 49 dct[param] = ParamDescriptor(param) 50 params = list(params) 51 dct['params'] = params 52 if compound: 53 dct['member_widgets'] = list(member_widgets) 54 # Pick params_doc from all bases giving priority to the widget's own 55 params_doc = {} 56 for base in bases: 57 params_doc.update(getattr(base, 'params_doc', {})) 58 params_doc.update(dct.get('params_doc', {})) 59 dct['params_doc'] = params_doc 60 return super(MetaWidget, cls).__new__(cls, name, bases, dct)
61
62 - def __init__(cls, name, bases, dct):
63 if "__init__" in dct: 64 dct["__init__"] = _decorate_widget_init(dct["__init__"]) 65 cls.__init__ = dct["__init__"] 66 super(MetaWidget, cls).__init__(name, bases, dct) 67 if cls.template: 68 (cls.template_c, cls.template) = load_kid_template(cls.template) 69 cls._locked = False
70 71 ############################################################################# 72 # Method decorators and other MetaWidget helpers # 73 ############################################################################# 74
75 -class ParamDescriptor(object):
76 """Descriptor to support automatic callable support for widget params."""
77 - def __init__(self, param_name):
78 self.param_name = param_prefix + param_name
79
80 - def __get__(self, obj, typ=None):
81 if obj is None: 82 # return the original class attribute. This makes the descriptor 83 # almost invisible 84 return getattr(typ, self.param_name) 85 param = getattr(obj, self.param_name) 86 if callable(param): 87 return param() 88 return param
89
90 - def __set__(self, obj, value):
91 setattr(obj, self.param_name, value)
92
93 -def lockwidget(self, *args, **kw):
94 """Set this widget as locked the first time it's displayed.""" 95 gotlock = self._displaylock.acquire(False) 96 if gotlock: 97 del self.display 98 self._locked = True 99 output = self.__class__.display(self, *args, **kw) 100 if gotlock: 101 self._displaylock.release() 102 return output
103
104 -def _decorate_widget_init(func):
105 """Decorator for a widgets __init__ method. 106 107 Ensures that the display method for the instance is overridden by 108 lockwidget, that an eventual validator dict is applied to the widget 109 validator and that a validation schema is generated for compound widgets. 110 111 """ 112 def widget_init(self, *args, **kw): 113 self.display = instancemethod(lockwidget, self, self.__class__) 114 # We may want to move the InputWidget related logic to another 115 # decorator 116 input_widget = hasattr(self, "validator") 117 # Makes sure we only generate a schema once in the all __init__ 118 # cooperative calls from subclasses. Same for the displaylock 119 if not hasattr(self, '__initstack'): 120 self._displaylock = threading.Lock() 121 self.__initstack = [] 122 else: 123 self.__initstack.append(True) 124 # Manage an eventual validator dictionary that provides additional 125 # parameters to the default validator (if present). 126 # We remove it from kw and apply it after the execution of the 127 # decorated __init__ method since only at this point we can check 128 # for the presence of a default validator 129 if input_widget and ("validator" in kw) \ 130 and isinstance(kw["validator"], dict): 131 validator_dict = kw.pop("validator") 132 else: 133 validator_dict = None 134 # Execute the decorated __init__ method 135 func(self, *args, **kw) 136 try: 137 self.__initstack.pop() 138 except IndexError: 139 # We're the first __init__ called 140 del self.__initstack 141 if input_widget: 142 # Apply an eventual validator dictionary 143 if validator_dict: 144 if self.validator is not None: 145 class_validator = self.__class__.validator 146 if self.validator is class_validator: 147 # avoid modifying a validator that has been 148 # defined at class level and therefor is 149 # shared by all instances of this widget 150 self.validator = copy.deepcopy(class_validator) 151 self.validator.__dict__.update(validator_dict) 152 else: 153 raise ValueError, ("You can't use a dictionary to " 154 "provide additional parameters " 155 "as the widget doesn't provide a " 156 "default validator" ) 157 # Generate the validation Schema if we are a compound widget 158 if getattr(self, 'compound', False): 159 widgets = self.iter_member_widgets() 160 # generate the schema associated to our widgets 161 validator = generate_schema(self.validator, widgets) 162 if getattr(self, 'repeating', False): 163 self.validator = validators.ForEach(validator) 164 else: 165 self.validator = validator
166 return widget_init 167 168 ############################################################################# 169 # Widget template support. # 170 ############################################################################# 171 172 # Keeps a count of the widget instances which have overrided their template on 173 # the constructor so we can generate unique template-module names 174 widget_instance_serial = count() 175 176 # XXX: Maybe Widget should have a __del__ method to unload any template that 177 # has been loaded during the instances initialization? 178 # FIXME-dbr: This is returning nonsens in case KID isn't imported because 179 # we want to get rid of KID as a dependency - and as long as the widgets 180 # are in TG itself, that leads no not-so-beautiful compromises.
181 -def load_kid_template(t, modname=None):
182 """Load the given template into the given module name. 183 184 If modname is None, an unique one will be generated. 185 Returns a tuple (compiled_tmpl, template_text (or modulepath). 186 187 """ 188 # this is a dummy-value-return-thingy, see above comment 189 try: 190 kid 191 except NameError: 192 return None, None 193 194 if isinstance(t, basestring) and "<" in t: 195 if not modname: 196 modname = 'instance_template_%d' % widget_instance_serial.next() 197 return (kid.load_template(t, name=modname).Template, t) 198 else: 199 return (t, None)
200 201 ############################################################################# 202 # Schema generation support # 203 #############################################################################
204 -class NullValidator(validators.FancyValidator):
205 """A do-nothing validator. 206 207 Used as a placeholder for fields with no validator so they don't get 208 stripped by the Schema. 209 210 """ 211 if_missing = None
212
213 -def copy_schema(schema):
214 """Recursively copy a schema.""" 215 new_schema = copy.copy(schema) 216 new_schema.pre_validators = schema.pre_validators[:] 217 new_schema.chained_validators = schema.chained_validators[:] 218 fields = {} 219 for k, v in schema.fields.iteritems(): 220 if isinstance(v, Schema): 221 v = copy_schema(v) 222 fields[k] = v 223 new_schema.fields = fields 224 return new_schema
225
226 -def merge_schemas(to_schema, from_schema, inplace=False):
227 """Recursively merge from_schema into to_schema 228 229 Takes care of leaving to_schema intact if inplace is False (default). 230 Returns a new Schema instance if inplace is False or to_schema is a Schema 231 class not an instance or the changed to_schema. 232 233 """ 234 # Nested schemas may be Schema (sub-)classes instead of instances. 235 # Copying Schema classes does not work, so we just create an instance. 236 if isclass(to_schema) and issubclass(to_schema, validators.Schema): 237 to_schema = to_schema() 238 elif not inplace: 239 to_schema = copy_schema(to_schema) 240 241 # Recursively merge child schemas 242 is_schema = lambda f: isinstance(f[1], validators.Schema) 243 seen = set() 244 for k, v in ifilter(is_schema, to_schema.fields.iteritems()): 245 seen.add(k) 246 from_field = from_schema.fields.get(k) 247 if from_field: 248 v = merge_schemas(v, from_field) 249 to_schema.add_field(k, v) 250 251 # Add remaining fields if we can 252 can_add = lambda f: f[0] not in seen and can_add_field(to_schema, f[0]) 253 for field in ifilter(can_add, from_schema.fields.iteritems()): 254 to_schema.add_field(*field) 255 256 return to_schema
257
258 -def add_field_to_schema(schema, widget):
259 """Add widget's validator if any to the given schema.""" 260 name = widget.name 261 if widget.validator is not None: 262 if isinstance(widget.validator, validators.Schema): 263 # Schema instance or class, might need to merge 'em... 264 if widget.name in schema.fields: 265 assert (isinstance(schema.fields[name], validators.Schema) or 266 issubclass(schema.fields[name], validators.Schema) 267 ), "Validator for '%s' should be a Schema subclass" % name 268 v = merge_schemas(schema.fields[name], widget.validator) 269 else: 270 v = widget.validator 271 schema.add_field(name, v) 272 elif can_add_field(schema, name): 273 # Non-schema validator, add it if we can... 274 schema.add_field(name, widget.validator) 275 elif can_add_field(schema, name): 276 schema.add_field(name, NullValidator())
277
278 -def generate_schema(schema, widgets):
279 """Generate or extend a copy of schema with all validators of all widgets. 280 281 schema may be a schema instance or class, widgets is a list of widgets 282 instances. 283 Returns the new schema instance. 284 285 """ 286 if schema is None: 287 schema = validators.Schema() 288 elif isclass(schema) and issubclass(schema, validators.Schema): 289 # allows to attach Schema (sub-)classes to widgets. 290 schema = schema() 291 else: 292 schema = copy_schema(schema) 293 for widget in widgets: 294 if widget.is_named: 295 add_field_to_schema(schema, widget) 296 return schema
297
298 -def can_add_field(schema, field_name):
299 """Checks if we can safely add a field. 300 301 Makes sure we're not overriding any field in the Schema. NullValidators 302 are ok to override. 303 304 """ 305 current_field = schema.fields.get(field_name) 306 return bool(current_field is None or 307 isinstance(current_field, NullValidator))
308