Home | Trees | Indices | Help |
---|
|
1 ################################################################################ 2 # Ganga Project. http://cern.ch/ganga 3 # 4 # $Id: Schema.py,v 1.3 2009-05-20 13:40:22 moscicki Exp $ 5 ################################################################################ 6 7 import Ganga.Utility.logging 8 logger = Ganga.Utility.logging.getLogger() 9 10 from Ganga.Utility.logic import implies 11 12 # 13 # Ganga Public Interface Schema 14 # 15 16 # Version of the schema. 17 3133 return 'defaults_'+name #_Properties34 35 # Schema defines the logical model of the Ganga Public Interface (GPI) 36 # for jobs and pluggable job components such as applications and 37 # submission backends. 38 39 # Schema contains the data description (properties) which is the basis 40 # of the efficient management of the persistent data in a generic way. 41 # Any Ganga component is a container of properties (a property 42 # may be a component as well). Properties may be protected (read-only 43 # by the users). Persistent properties are stored with the job 44 # whereas transient are not. This additional information is contained 45 # in metaproperties. Large fractions of Ganga GPI may be generated 46 # automatically based on the metaproperties definition in a schema. 47 48 # Each component belongs to a certain category (for example 49 # 'application'). There may be many different types of applications, 50 # each one defined by a separate component. 51 52 # Schema of each component provides a version information (it is 53 # important for persistent storage) and compatibility of the scripts. 54 55 # Internal Note: Schema object must be additionally initialized with 56 # the _pluginclass object which is used as the source of information 57 # about name and category of the schema deepcopy of Schema objects is 58 # possible however the _pluginclass objects are shared unless 59 # overriden explicitly. 6062 # Schema constructor is used by Ganga plugin developers. 63 # Ganga will automatically set a reference to the plugin class which corresponds to this schema, hence 64 # defining the schema's name and category. 65 # 66 # datadict: dictionary of properties (schema items) 67 # version: the version information 7217774 return self.datadict[name]75 76 category = property(lambda self: self._pluginclass._category) 77 name = property(lambda self: self._pluginclass._name) 78 81 84 87 90 9395 r = [] 96 97 for n,c in zip(self.datadict.keys(),self.datadict.values()): 98 if issubclass(c.__class__,klass): 99 r.append((n,c)) 100 return r101 104 105 ## # NOT TESTED 106 ## def isCompatible(self,schema): 107 ## if len(self.datadict.keys()) != len(schema.datadict.keys()): 108 ## return 0 109 110 ## for k in self.datadict.keys(): 111 ## if not self[k].isA(schema[k]) 112 ## return 0 113 ## return 1 114 117 118 # make a schema copy for a derived class, does not copy the pluginclass 122124 import Ganga.Utility.Config 125 # create a configuration unit for default values of object properties 126 # take the defaults from schema defaults 127 config = Ganga.Utility.Config.makeConfig( defaultConfigSectionName(self.name), "default attribute values for %s objects"%self.name) #self._pluginclass._proxyClass.__doc__ ) 128 129 for name,item in self.allItems(): 130 if not item['protected'] and not item['hidden']: #and not item['sequence']: #FIXME: do we need it or not?? 131 try: 132 types = item['typelist'] 133 if types == []: 134 types = None 135 except: 136 types = None 137 if item['sequence']: 138 if not types is None: 139 types.append('list') # bugfix 36398: allow to assign a list in the configuration 140 if type(item['defvalue']) is type({}): 141 if not types is None: 142 types.append('dict') 143 config.addOption(name,item['defvalue'],item['doc'],override=False,typelist=types) 144 145 146 def prehook(name,x): 147 errmsg = "Cannot set %s=%s in [%s]: "%(name,repr(x),config.name) 148 149 try: 150 item = self.getItem(name) 151 except KeyError,x: 152 raise Ganga.Utility.Config.ConfigError(errmsg+"attribute not defined in the schema") 153 except Exception,x: 154 raise Ganga.Utility.Config.ConfigError(errmsg+str(x)) 155 156 if item.isA(ComponentItem): 157 if not type(x) is type('') and not x is None: 158 raise Ganga.Utility.Config.ConfigError(errmsg+"only strings and None allowed as a default value of Component Item.") 159 try: 160 self._getDefaultValueInternal(name,x,check=True) 161 except: 162 raise Ganga.Utility.Config.ConfigError(errmsg+str(x)) 163 164 if item['protected'] or item['hidden']: 165 raise Ganga.Utility.Config.ConfigError(errmsg+"protected or hidden property") 166 167 # FIXME: File() == 'x' triggers AttributeError 168 #try: 169 # if x == '': x = None 170 #except AttributeError: 171 # pass 172 173 return x174 175 config.attachUserHandler(prehook,None) 176 config.attachSessionHandler(prehook,None)179 """ Get the default value of a schema item, both simple and component. 180 """ 181 return self._getDefaultValueInternal(attr)182184 """ Get the default value of a schema item, both simple and component. 185 If check is True then val is used instead of default value: this is used to check if the val may be used as a default value (e.g. if it is OK to use it as a value in the config file) 186 """ 187 import Ganga.Utility.Config 188 config = Ganga.Utility.Config.getConfig( defaultConfigSectionName(self.name) ) 189 190 item = self.getItem(attr) 191 192 # hidden, protected and sequence values are not represented in config 193 try: 194 defvalue = config[attr] 195 except Ganga.Utility.Config.ConfigError,x: 196 defvalue = item['defvalue'] 197 198 # in the checking mode, use the provided value instead 199 if check: 200 defvalue = val 201 202 if item.isA(ComponentItem): 203 204 # FIXME: limited support for initializing non-empty sequences (i.e. apps => ['DaVinci','Executable'] is NOT correctly initialized) 205 206 if not item['sequence']: 207 if defvalue is None: 208 if not item['load_default']: 209 assert(item['optional']) 210 return None 211 212 # if a defvalue of a component item is an object (not string) just process it as for SimpleItems (useful for FileItems) 213 # otherwise do a lookup via plugin registry 214 215 if type(defvalue) is type('') or defvalue is None: 216 from Ganga.Utility.Plugin import allPlugins 217 return allPlugins.find(item['category'],defvalue)() 218 219 # make a copy of the default value (to avoid strange effects if the original modified) 220 import copy 221 return copy.deepcopy(defvalue)222 223 224 225 # Items in schema may be either Components,Simples, Files or BindingItems. 226 # 227 # Component, Simple, File are data items and have implicit storage. 228 # 229 # Metaproperties of all Items: 230 # 231 # transient : never stored on persistent media 232 # protected : not modifiable via GPI 233 # hidden : not visible in the GPI 234 # comparable: taken into account for equality checks 235 # sequence: an item is a sequence (algorithms traversing the component tree dive into sequences as well) 236 # strict_sequence: if not strict sequence then assignment of a single item will be automatically converted to a 1-element sequence, i.e. obj.x = v => obj.x = [v] 237 # defvalue : default value, if item is a sequence the defvalue must be a list 238 # for component this must be either None or a string with a name of the component in the same category 239 # copyable : if 0 then the property value will not be copied and the default value from schema will be set in the destination 240 # implied initialization rule (unless the "copyable" metaproperty is explicitly set) 241 # protected == 1 => copyable == 0 242 # protected == 0 => copyable == 1 243 # doc : a docstring 244 # checkset : a bound checkset method, restrict write access at the object level (for example job.status 245 # may not be modified directly in backend handlers, instead updateStatus() method should be used) 246 # 247 # filter : a bound method can be used to change the values of an attribute. The attribute will be set to the return value 248 # 249 # visitable : if false then all algorithms based on the visitor patter will not accept this item [true] 250 # this is needed in certain cases (such as job.master) to avoid infinite recursion (and loops) 251 # 252 # summary_print: An bound method name (string). Will be passed an attribute value and a verbosity_level. Should 253 # return a (Python parsable) string summarising the state of the value. 254 # 255 # summary_sequence_maxlen: An integer longer than which a sequence will be summerised when doing a summary 256 # print. If the value is -1, the sequence will never be summerised. 257 # 258 # changable_at_resubmit: if true than the value may be changed at job resubmission, see Job.resubmit() 259 # 260 # 261 # Metaproperties of SimpleItems 262 # 263 # typelist : a list of type names (strings) indicating allowed types of the property (e.g. ["str","int","Ganga.GPIDev.Lib.File.File.File"]), see: http://twiki.cern.ch/twiki/bin/view/ArdaGrid/GangaTypes 264 # 265 # 266 # Metaproperties of ComponentItems: 267 # 268 # category : category of the component ('applications','backends',...) 269 # optional : if true then None may be used as a legal value of the item, [false] 270 # load_default: if true and defvalue is None then load default plugin, [true] 271 # 272 # defvalue is None load_default==true => load default plugin 273 # defvalue is None load_default==false => use None (optional MUST be true, otherwise error) 274 # 275 # defvalue is 'x' => load 'x' 276 # 277 # getter : a bound getter method, this implies that the component does not have associated storage and cannot be neither set nor deleted [None] 278 # getter implies: transient=1, protected=1, sequence=0, defvalue=None, load_default=0, optional=1, copyable=0 279 # proxy_get: a bound getter method for proxy decoration, allows to customize completely the creation of the proxy 280 # 281 # 282 # Metaproperties of FileItems 283 # 284 # FIXME: under development 285 # in/out : direction wrt to the job submission operation 286 # ref/copy : reference vs copy semantics for file contents 287 # 288 289 ## # BindingItems enable to hook arbitrary getter methods and do not have any implicit storage. Therefore 290 ## # the BindingItems are always transient, cannot be copied, cannot be sequences and have None default value. 291293 # default values of common metaproperties 294 _metaproperties = {'transient' : 0, 'protected' : 0, 'hidden' : 0, 'comparable' : 1, 'sequence' : 0, 'defvalue' : None, 'copyable' : 1, 'doc' : '','visitable':1, 'checkset':None, 'filter':None,'strict_sequence':1, 'summary_print':None, 'summary_sequence_maxlen':5,'proxy_get':None,'getter':None, 'changable_at_resubmit':0} 295 298436 437300 return self._meta[key]301 302 # compare the kind of item: 303 # all calls are equivalent: 304 # item.isA('SimpleItem') 305 # item.isA(SimpleItem) 306 # item.isA(SimpleItem())308 # for backwards compatibility with Ganga3 CLIP: if a string -- first convert to the class name 309 if type(what) is type(''): 310 import Schema # get access to all Item classes defined in this module (schema) 311 try: 312 what = getattr(Schema,what) 313 except AttributeError: 314 # class not found 315 return 0 316 317 import types 318 if type(what) is types.InstanceType: 319 what = what.__class__ 320 321 return issubclass(self.__class__,what)322324 """ Add new metaproperties/override old values. To be used by derived contructors only. 325 'forced' is an (optional) dictionary containing all values which cannot be modified by 326 the user of the derived contructor. 327 """ 328 if forced: 329 # find intersection 330 forbidden = [k for k in forced if k in kwds] 331 if len(forbidden) > 0: 332 raise TypeError('%s received forbidden (forced) keyword arguments %s' %(str(self.__class__),str(forbidden))) 333 self._meta.update(forced) 334 335 self._meta.update(kwds) 336 337 # conditional initial value logic... 338 # unless 'copyable' explicitly set (or forced), protected==1 => copyable==0 339 if not kwds.has_key('copyable') and (not forced or not forced.has_key('copyable')): 340 if self._meta['protected']: 341 logger.debug('applied implicit conditional rule: protected==1 => copyable==0') 342 self._meta['copyable'] = 0343 344 # return a description of the property including a docstring346 347 specdoc = '('+self._describe() + ')' 348 349 if self['doc']: 350 s = "%s. %s"% (self['doc'],specdoc) 351 else: 352 s = specdoc 353 354 return s355 356 # return a meta description of the property358 first = ',' 359 #txt = ' [' 360 txt = '' 361 for m in ['transient','protected','comparable','optional']: 362 try: 363 if self[m]: 364 txt += '%s%s'%(first,m) 365 first = ',' 366 except KeyError: 367 pass 368 #txt += ']' 369 txt = " default="+repr(self['defvalue'])+txt 370 if self['sequence']: txt = " list," + txt 371 return txt372374 from Ganga.Core import GangaAttributeError,ProtectedAttributeError,ReadOnlyObjectError,TypeMismatchError,SchemaError 375 376 if enableGangaList: 377 from Ganga.GPIDev.Lib.GangaList.GangaList import GangaList 378 else: 379 GangaList = list 380 381 item = self 382 383 # type checking does not make too much sense for Component Items because component items are 384 # always checked at the object (_impl) level for category compatibility 385 386 if item.isA(ComponentItem): 387 return 388 389 validTypes = item._meta['typelist'] 390 391 # setting typelist explicitly to None results in disabling the type checking completely 392 if validTypes is None: 393 return 394 395 def check(isAllowedType): 396 if not isAllowedType: 397 raise TypeMismatchError( 'Attribute "%s" expects a value of the following types: %s' % ( name, validTypes ) )398 399 if item._meta['sequence']: 400 if not isinstance( item._meta['defvalue'], (list,GangaList) ): 401 raise SchemaError( 'Attribute "%s" defined as a sequence but defvalue is not a list.' % name ) 402 403 if not isinstance( val, (GangaList,list) ): 404 raise TypeMismatchError( 'Attribute "%s" expects a list.' % name ) 405 406 if validTypes: 407 if item._meta[ 'sequence' ]: 408 409 for valItem in val: 410 if not valueTypeAllowed( valItem, validTypes ): 411 raise TypeMismatchError( 'List entry %s for %s is invalid. Valid types: %s' % ( valItem, name, validTypes ) ) 412 413 414 return 415 else: # Non-sequence 416 if isinstance( item._meta['defvalue'], dict ): 417 if isinstance( val, dict ): 418 for dKey, dVal in val.iteritems(): 419 if not valueTypeAllowed( dKey, validTypes ) or not valueTypeAllowed( dVal, validTypes ): 420 raise TypeMismatchError( 'Dictionary entry %s:%s for attribute %s is invalid. Valid types for key/value pairs: %s' % ( dKey, dVal, name, validTypes ) ) 421 else: # new value is not a dict 422 raise TypeMismatchError( 'Attribute "%s" expects a dictionary.' % name ) 423 return 424 else: # a 'simple' (i.e. non-dictionary) non-sequence value 425 check(valueTypeAllowed( val, validTypes )) 426 return 427 428 # typelist is not defined, use the type of the default value 429 if item._meta['defvalue'] is None: 430 logger.warning('type-checking disabled: type information not provided for %s, contact plugin developer',name) 431 else: 432 if item._meta['sequence']: 433 logger.warning('type-checking is incomplete: type information not provided for a sequence %s, contact plugin developer',name) 434 else: 435 check(isinstance( val, type(item._meta['defvalue']) ))439 # schema user of a ComponentItem cannot change the forced values below 440 _forced = {} 441455 456 from Ganga.GPIDev.TypeCheck import _valueTypeAllowed 457 valueTypeAllowed = lambda val,valTypeList: _valueTypeAllowed(val,valTypeList,logger) 458 459 460443 Item.__init__(self) 444 kwds['category'] = category 445 kwds['optional'] = optional 446 kwds['load_default'] = load_default 447 #kwds['getter'] = getter 448 self._update(kwds,forced=ComponentItem._forced) 449 assert(implies(self['defvalue'] is None and not self['load_default'], self['optional'])) 450 451 assert(implies(self['getter'], self['transient'] and self['defvalue'] is None and self['protected'] and not self['sequence'] and not self['copyable']))452470 471 472 # def type_match(self,v): 473 # return (self['sequence'] and v == []) or \ 474 # (not self['typelist'] or valueTypeAllowed( v, self['typelist'])) 475 476 ## class BindingItem(Item): 477 ## _forced = {'transient' : 1, 'sequence' : 0, 'defvalue' : None, 'copyable' : 0} 478 479 ## def __init__(self,getter,**kwds): 480 ## Item.__init__(self) 481 ## assert(not getter is None) 482 ## kwds['getter'] = getter 483 ## ## kwds['setter'] = setter 484 ## ## assert(not setter is None) 485 ## self._update(kwds,forced=BindingItem._forced) 486 487 488 489 # Files are important and common enough to merit a special support for defining their metaproperties 497 498 # a helper class which gives a human readible representation of schema items 499 # for example suitable for python interactive help() 504 return SchemaItemHelper() 505 506 if __name__=='__main__': 507 508 # a simple test 509 510 dd = { 511 'application' : ComponentItem(category='applications'), 512 'backend' : ComponentItem(category='backends'), 513 'name' : SimpleItem('',comparable=0), 514 'workdir' : SimpleItem(defvalue=None,type='string',transient=1,protected=1,comparable=0), 515 'status' : SimpleItem(defvalue='new', protected=1, comparable=0), 516 'id': SimpleItem(defvalue=None,type='string',protected=1, comparable=0), 517 'inputbox': FileItem(defvalue=[],sequence=1), 518 'outputbox': FileItem(defvalue=[],sequence=1), 519 'overriden_copyable' : SimpleItem(defvalue=None,protected=1,copyable=1), 520 'plain_copyable' : SimpleItem(defvalue=None,copyable=0) 521 } 522 523 schema = Schema(Version(1,0),dd) 524 525 # NOT a public interface: emulate the Ganga Plugin object for test purposes 526 # Note that pclass MUST be a new-style class in order to support deepcopy 530 schema._pluginclass = pclass 531 # end of emulating code 532 #allSchemas.add(schema) 533 534 assert(schema.name == 'Job') 535 assert(schema.category == 'jobs') 536 537 assert(schema.allItems() == dd.items()) 538 539 cc = (schema.componentItems() + schema.simpleItems()).sort() 540 cc2 = dd.items().sort() 541 assert(cc == cc2) 542 543 for i in schema.allItems(): 544 assert(schema[i[0]] == schema.getItem(i[0])) 545 546 assert(schema['id'].isA(SimpleItem)) 547 assert(schema['application'].isA(ComponentItem)) 548 assert(schema['inputbox'].isA(ComponentItem)) 549 assert(schema['inputbox'].isA(FileItem)) 550 551 assert(schema['id']['protected']) 552 assert(not schema['id']['comparable']) 553 assert(schema['id']['type'] == 'string') 554 555 print schema['application']['category'], schema['application']['defvalue'] 556 557 import copy 558 schema2 = copy.deepcopy(schema) 559 560 assert(schema2 is not schema) 561 assert(schema.datadict is not schema2.datadict) 562 assert(schema._pluginclass is schema2._pluginclass) 563 564 for i in schema.allItems(): 565 assert(schema.getItem(i[0]) is not schema2.getItem(i[0])) 566 567 # check the implied rules 568 569 assert(schema['overriden_copyable']['copyable'] == 1) 570 assert(schema['plain_copyable']['copyable'] == 0) 571 assert(schema['id']['copyable'] == 0) 572 assert(schema['application']['copyable'] == 1) 573 574 print 'Schema tested OK.' 575 576 577 # 578 # 579 # $Log: not supported by cvs2svn $ 580 # Revision 1.2 2008/09/09 14:37:16 moscicki 581 # bugfix #40220: Ensure that default values satisfy the declared types in the schema 582 # 583 # factored out type checking into schema module, fixed a number of wrongly declared schema items in the core 584 # 585 # Revision 1.1 2008/07/17 16:40:55 moscicki 586 # migration of 5.0.2 to HEAD 587 # 588 # the doc and release/tools have been taken from HEAD 589 # 590 # Revision 1.15.26.8 2008/05/15 06:31:38 moscicki 591 # # bugfix 36398: allow to assign a list in the configuration 592 # 593 # Revision 1.15.26.7 2008/04/18 13:42:18 moscicki 594 # remove obsolete printout 595 # 596 # Revision 1.15.26.6 2008/04/18 10:52:13 moscicki 597 # 1) typechecking fix 598 # 2) Ganga/test/GPI/ConfigSetEmptyVOMSString.gpi fix 599 # 600 # Revision 1.15.26.5 2008/04/18 08:14:38 moscicki 601 # bugfix 18272 (reintroduced in Ganga 5): add typelist information to the configuration option 602 # 603 # Revision 1.15.26.4 2007/12/18 09:07:06 moscicki 604 # integrated typesystem from Alvin 605 # 606 # Revision 1.15.26.3 2007/11/07 17:02:13 moscicki 607 # merged against Ganga-4-4-0-dev-branch-kuba-slices with a lot of manual merging 608 # 609 # Revision 1.15.26.2 2007/11/07 15:10:03 moscicki 610 # merged in pretty print and GangaList support from ganga-5-dev-branch-4-4-1-will-print branch 611 # 612 # 613 # Revision 1.15.26.1 2007/10/12 13:56:24 moscicki 614 # merged with the new configuration subsystem 615 # 616 # Revision 1.15.28.1 2007/09/25 09:45:11 moscicki 617 # merged from old config branch 618 # 619 # Revision 1.15.6.1 2007/06/18 07:44:55 moscicki 620 # config prototype 621 # 622 # Revision 1.15.30.1 2007/10/30 12:12:08 wreece 623 # First version of the new print_summary functionality. Lots of changes, but some known limitations. Will address in next version. 624 # 625 # Revision 1.15.8.1 2007/06/18 10:16:36 moscicki 626 # slices prototype 627 # 628 # Revision 1.15 2007/03/05 12:04:18 moscicki 629 # explicit switch for strict_sequence (default is True), if the sequence is non-strict then a single value v will be converted to [v] on assignment, for example non-strict File sequence yields obj.x = 'a' <=> obj.x = [File('a')] <=> obj.x = File('a') 630 # 631 # Revision 1.14 2006/10/02 13:11:18 moscicki 632 # added extra check when setting default values: try to apply the value and raise ConfigError if it fails (for example unknown plugin) 633 # 634 # Revision 1.13 2006/07/28 12:53:36 moscicki 635 # fixed default value for ComponentItem (was broken) 636 # 637 # Revision 1.12 2006/07/28 08:26:23 moscicki 638 # allow defvalue to be an object for component items (not only a string or None) 639 # 640 # Revision 1.11 2006/07/27 20:15:35 moscicki 641 # createDefaultConfig() 642 # getDefaultValue() 643 # "checkset" metaproperty 644 # 645 # Revision 1.10 2005/12/02 15:35:06 moscicki 646 # visitable and getter metaproperties 647 # 648 # Revision 1.9 2005/08/24 15:42:21 moscicki 649 # automatically generated help for properties, disabled the SchemaHelper and few other improvements to the help system 650 # 651 # 652 # 653463 Item.__init__(self) 464 kwds['defvalue'] = defvalue 465 kwds['typelist'] = typelist 466 self._update(kwds)467
Home | Trees | Indices | Help |
---|
Generated by Epydoc 3.0.1 on Mon Jun 25 10:35:31 2012 | http://epydoc.sourceforge.net |