fix for booelan attribute which have empty string as false value and didn't work if default value for this attribute was True.
"""utility to ease migration of application version to newly installedversion:organization: Logilab:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr"""__docformat__="restructuredtext en"importsysimportosimportloggingfromtempfileimportmktempfromos.pathimportexists,join,basename,splitextfromlogilab.common.decoratorsimportcachedfromlogilab.common.configurationimportREQUIRED,read_old_configdefmigration_files(config,toupgrade):"""return an orderer list of path of scripts to execute to upgrade an installed application according to installed cube and cubicweb versions """merged=[]forcube,fromversion,toversionintoupgrade:ifcube=='cubicweb':migrdir=config.migration_scripts_dir()else:migrdir=config.cube_migration_scripts_dir(cube)scripts=filter_scripts(config,migrdir,fromversion,toversion)merged+=[s[1]forsinscripts]ifconfig.accept_mode('Any'):migrdir=config.migration_scripts_dir()merged.insert(0,join(migrdir,'bootstrapmigration_repository.py'))returnmergeddeffilter_scripts(config,directory,fromversion,toversion,quiet=True):"""return a list of paths of migration files to consider to upgrade from a version to a greater one """fromlogilab.common.changelogimportVersion# doesn't work with appengineassertfromversionasserttoversionassertisinstance(fromversion,tuple),fromversion.__class__assertisinstance(toversion,tuple),toversion.__class__assertfromversion<=toversion,(fromversion,toversion)ifnotexists(directory):ifnotquiet:printdirectory,"doesn't exists, no migration path"return[]iffromversion==toversion:return[]result=[]forfnameinos.listdir(directory):iffname.endswith('.pyc')orfname.endswith('.pyo') \orfname.endswith('~'):continuefpath=join(directory,fname)try:tver,mode=fname.split('_',1)exceptValueError:continuemode=mode.split('.',1)[0]ifnotconfig.accept_mode(mode):continuetry:tver=Version(tver)exceptValueError:continueiftver<=fromversion:continueiftver>toversion:continueresult.append((tver,fpath))# be sure scripts are executed in orderreturnsorted(result)IGNORED_EXTENSIONS=('.swp','~')defexecscript_confirm(scriptpath):"""asks for confirmation before executing a script and provides the ability to show the script's content """whileTrue:confirm=raw_input('** execute %r (Y/n/s[how]) ?'%scriptpath)confirm=confirm.strip().lower()ifconfirmin('n','no'):returnFalseelifconfirmin('s','show'):stream=open(scriptpath)scriptcontent=stream.read()stream.close()printprintscriptcontentprintelse:returnTruedefyes(*args,**kwargs):returnTrueclassMigrationHelper(object):"""class holding CubicWeb Migration Actions used by migration scripts"""def__init__(self,config,interactive=True,verbosity=1):self.config=configself.config.init_log(logthreshold=logging.ERROR,debug=True)# 0: no confirmation, 1: only main commands confirmed, 2 ask for everythingself.verbosity=verbosityself.need_wrap=Trueifnotinteractiveornotverbosity:self.confirm=yesself.execscript_confirm=yeselse:self.execscript_confirm=execscript_confirmself._option_changes=[]self.__context={'confirm':self.confirm,'config':self.config,'interactive_mode':interactive,}defrepo_connect(self):returnself.config.repository()defmigrate(self,vcconf,toupgrade,options):"""upgrade the given set of cubes `cubes` is an ordered list of 3-uple: (cube, fromversion, toversion) """ifoptions.fs_only:# monkey path configuration.accept_mode so database mode (e.g. Any)# won't be acceptedorig_accept_mode=self.config.accept_modedefaccept_mode(mode):ifmode=='Any':returnFalsereturnorig_accept_mode(mode)self.config.accept_mode=accept_modescripts=migration_files(self.config,toupgrade)ifscripts:vmap=dict((pname,(fromver,tover))forpname,fromver,toverintoupgrade)self.__context.update({'applcubicwebversion':vcconf['cubicweb'],'cubicwebversion':self.config.cubicweb_version(),'versions_map':vmap})self.scripts_session(scripts)else:print'no migration script to execute'defshutdown(self):passdef__getattribute__(self,name):try:returnobject.__getattribute__(self,name)exceptAttributeError:cmd='cmd_%s'%nameifhasattr(self,cmd):meth=getattr(self,cmd)returnlambda*args,**kwargs:self.interact(args,kwargs,meth=meth)raiseraiseAttributeError(name)definteract(self,args,kwargs,meth):"""execute the given method according to user's confirmation"""msg='execute command: %s(%s) ?'%(meth.__name__[4:],', '.join([repr(arg)forarginargs]+['%s=%r'%(n,v)forn,vinkwargs.items()]))if'ask_confirm'inkwargs:ask_confirm=kwargs.pop('ask_confirm')else:ask_confirm=Trueifnotask_confirmorself.confirm(msg):returnmeth(*args,**kwargs)defconfirm(self,question,shell=True,abort=True,retry=False):"""ask for confirmation and return true on positive answer if `retry` is true the r[etry] answer may return 2 """printquestion,possibleanswers='Y/n'ifabort:possibleanswers+='/a[bort]'ifshell:possibleanswers+='/s[hell]'ifretry:possibleanswers+='/r[etry]'try:confirm=raw_input('(%s): '%(possibleanswers,))answer=confirm.strip().lower()except(EOFError,KeyboardInterrupt):answer='abort'ifanswerin('n','no'):returnFalseifanswerin('r','retry'):return2ifanswerin('a','abort'):self.rollback()raiseSystemExit(1)ifshellandanswerin('s','shell'):self.interactive_shell()returnself.confirm(question)returnTruedefinteractive_shell(self):self.confirm=yesself.need_wrap=False# avoid '_' to be added to builtins by sys.display_hookdefdo_not_add___to_builtins(obj):ifobjisnotNone:printrepr(obj)sys.displayhook=do_not_add___to_builtinslocal_ctx=self._create_context()try:importreadlinefromrlcompleterimportCompleterexceptImportError:# readline not availablepasselse:readline.set_completer(Completer(local_ctx).complete)readline.parse_and_bind('tab: complete')histfile=os.path.join(os.environ["HOME"],".eshellhist")try:readline.read_history_file(histfile)exceptIOError:passfromcodeimportinteractbanner="""entering the migration python shelljust type migration commands or arbitrary python code and type ENTER to execute ittype "exit" or Ctrl-D to quit the shell and resume operation"""# give custom readfunc to avoid http://bugs.python.org/issue1288615defunicode_raw_input(prompt):returnunicode(raw_input(prompt),sys.stdin.encoding)interact(banner,readfunc=unicode_raw_input,local=local_ctx)readline.write_history_file(histfile)# delete instance's confirm attribute to avoid questionsdelself.confirmself.need_wrap=True@cacheddef_create_context(self):"""return a dictionary to use as migration script execution context"""context=self.__contextforattrindir(self):ifattr.startswith('cmd_'):ifself.need_wrap:context[attr[4:]]=getattr(self,attr[4:])else:context[attr[4:]]=getattr(self,attr)returncontextdefprocess_script(self,migrscript,funcname=None,*args,**kwargs):"""execute a migration script in interactive mode, display the migration script path, ask for confirmation and execute it if confirmed """assertmigrscript.endswith('.py'),migrscriptifself.execscript_confirm(migrscript):scriptlocals=self._create_context().copy()iffuncnameisNone:pyname='__main__'else:pyname=splitext(basename(migrscript))[0]scriptlocals.update({'__file__':migrscript,'__name__':pyname})execfile(migrscript,scriptlocals)iffuncnameisnotNone:try:func=scriptlocals[funcname]self.info('found %s in locals',funcname)assertcallable(func),'%s (%s) is not callable'%(func,funcname)exceptKeyError:self.critical('no %s in script %s',funcname,migrscript)returnNonereturnfunc(*args,**kwargs)defscripts_session(self,migrscripts):"""execute some scripts in a transaction"""try:formigrscriptinmigrscripts:self.process_script(migrscript)self.commit()except:self.rollback()raisedefcmd_option_renamed(self,oldname,newname):"""a configuration option has been renamed"""self._option_changes.append(('renamed',oldname,newname))defcmd_option_group_change(self,option,oldgroup,newgroup):"""a configuration option has been moved in another group"""self._option_changes.append(('moved',option,oldgroup,newgroup))defcmd_option_added(self,optname):"""a configuration option has been added"""self._option_changes.append(('added',optname))defcmd_option_removed(self,optname):"""a configuration option has been removed"""# can safely be ignored#self._option_changes.append(('removed', optname))defcmd_option_type_changed(self,optname,oldtype,newvalue):"""a configuration option's type has changed"""self._option_changes.append(('typechanged',optname,oldtype,newvalue))defcmd_add_cubes(self,cubes):"""modify the list of used cubes in the in-memory config returns newly inserted cubes, including dependencies """ifisinstance(cubes,basestring):cubes=(cubes,)origcubes=self.config.cubes()newcubes=[pforpinself.config.expand_cubes(cubes)ifnotpinorigcubes]ifnewcubes:forcubeincubes:assertcubeinnewcubesself.config.add_cubes(newcubes)returnnewcubesdefcmd_remove_cube(self,cube):origcubes=self.config._cubesbasecubes=list(origcubes)forpkginself.config.expand_cubes([cube]):try:basecubes.remove(pkg)exceptValueError:continueself.config._cubes=tuple(self.config.expand_cubes(basecubes))removed=[pforpinorigcubesifnotpinself.config._cubes]assertcubeinremoved, \"can't remove cube %s, used as a dependancy"%cubereturnremoveddefrewrite_configuration(self):# import locally, show_diffs unavailable in gae environmentfromcubicweb.toolsutilsimportshow_diffsconfigfile=self.config.main_config_file()ifself._option_changes:read_old_config(self.config,self._option_changes,configfile)newconfig=mktemp()foroptdescrinself._option_changes:ifoptdescr[0]=='added':optdict=self.config.get_option_def(optdescr[1])ifoptdict.get('default')isREQUIRED:self.config.input_option(optdescr[1],optdict)self.config.generate_config(open(newconfig,'w'))show_diffs(configfile,newconfig)ifexists(newconfig):os.unlink(newconfig)fromloggingimportgetLoggerfromcubicwebimportset_log_methodsset_log_methods(MigrationHelper,getLogger('cubicweb.migration'))