--- a/common/i18n.py Tue Dec 08 09:45:07 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-"""Some i18n/gettext utilities.
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-import re
-import os
-import sys
-from os.path import join, basename, splitext, exists
-from glob import glob
-
-from cubicweb.toolsutils import create_dir
-
-def extract_from_tal(files, output_file):
- """extract i18n strings from tal and write them into the given output file
- using standard python gettext marker (_)
- """
- output = open(output_file, 'w')
- for filepath in files:
- for match in re.finditer('i18n:(content|replace)="([^"]+)"', open(filepath).read()):
- print >> output, '_("%s")' % match.group(2)
- output.close()
-
-
-def add_msg(w, msgid, msgctx=None):
- """write an empty pot msgid definition"""
- if isinstance(msgid, unicode):
- msgid = msgid.encode('utf-8')
- if msgctx:
- if isinstance(msgctx, unicode):
- msgctx = msgctx.encode('utf-8')
- w('msgctxt "%s"\n' % msgctx)
- msgid = msgid.replace('"', r'\"').splitlines()
- if len(msgid) > 1:
- w('msgid ""\n')
- for line in msgid:
- w('"%s"' % line.replace('"', r'\"'))
- else:
- w('msgid "%s"\n' % msgid[0])
- w('msgstr ""\n\n')
-
-
-def execute(cmd):
- """display the command, execute it and raise an Exception if returned
- status != 0
- """
- from subprocess import call
- print cmd.replace(os.getcwd() + os.sep, '')
- status = call(cmd, shell=True)
- if status != 0:
- raise Exception('status = %s' % status)
-
-
-def available_catalogs(i18ndir=None):
- if i18ndir is None:
- wildcard = '*.po'
- else:
- wildcard = join(i18ndir, '*.po')
- for popath in glob(wildcard):
- lang = splitext(basename(popath))[0]
- yield lang, popath
-
-
-def compile_i18n_catalogs(sourcedirs, destdir, langs):
- """generate .mo files for a set of languages into the `destdir` i18n directory
- """
- from logilab.common.fileutils import ensure_fs_mode
- print '-> compiling %s catalogs...' % destdir
- errors = []
- for lang in langs:
- langdir = join(destdir, lang, 'LC_MESSAGES')
- if not exists(langdir):
- create_dir(langdir)
- pofiles = [join(path, '%s.po' % lang) for path in sourcedirs]
- pofiles = [pof for pof in pofiles if exists(pof)]
- mergedpo = join(destdir, '%s_merged.po' % lang)
- try:
- # merge instance/cubes messages catalogs with the stdlib's one
- execute('msgcat --use-first --sort-output --strict -o "%s" %s'
- % (mergedpo, ' '.join('"%s"' % f for f in pofiles)))
- # make sure the .mo file is writeable and compiles with *msgfmt*
- applmo = join(destdir, lang, 'LC_MESSAGES', 'cubicweb.mo')
- try:
- ensure_fs_mode(applmo)
- except OSError:
- pass # suppose not exists
- execute('msgfmt "%s" -o "%s"' % (mergedpo, applmo))
- except Exception, ex:
- errors.append('while handling language %s: %s' % (lang, ex))
- try:
- # clean everything
- os.unlink(mergedpo)
- except Exception:
- continue
- return errors
--- a/common/migration.py Tue Dec 08 09:45:07 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,373 +0,0 @@
-"""utilities for instances migration
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-import sys
-import os
-import logging
-import tempfile
-from os.path import exists, join, basename, splitext
-
-from logilab.common.decorators import cached
-from logilab.common.configuration import REQUIRED, read_old_config
-from logilab.common.shellutils import ASK
-
-from cubicweb import ConfigurationError
-
-
-def filter_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
- """
- from logilab.common.changelog import Version # doesn't work with appengine
- assert fromversion
- assert toversion
- assert isinstance(fromversion, tuple), fromversion.__class__
- assert isinstance(toversion, tuple), toversion.__class__
- assert fromversion <= toversion, (fromversion, toversion)
- if not exists(directory):
- if not quiet:
- print directory, "doesn't exists, no migration path"
- return []
- if fromversion == toversion:
- return []
- result = []
- for fname in os.listdir(directory):
- if fname.endswith('.pyc') or fname.endswith('.pyo') \
- or fname.endswith('~'):
- continue
- fpath = join(directory, fname)
- try:
- tver, mode = fname.split('_', 1)
- except ValueError:
- continue
- mode = mode.split('.', 1)[0]
- if not config.accept_mode(mode):
- continue
- try:
- tver = Version(tver)
- except ValueError:
- continue
- if tver <= fromversion:
- continue
- if tver > toversion:
- continue
- result.append((tver, fpath))
- # be sure scripts are executed in order
- return sorted(result)
-
-
-IGNORED_EXTENSIONS = ('.swp', '~')
-
-
-def execscript_confirm(scriptpath):
- """asks for confirmation before executing a script and provides the
- ability to show the script's content
- """
- while True:
- answer = ASK.ask('Execute %r ?' % scriptpath, ('Y','n','show'), 'Y')
- if answer == 'n':
- return False
- elif answer == 'show':
- stream = open(scriptpath)
- scriptcontent = stream.read()
- stream.close()
- print
- print scriptcontent
- print
- else:
- return True
-
-def yes(*args, **kwargs):
- return True
-
-
-class MigrationHelper(object):
- """class holding CubicWeb Migration Actions used by migration scripts"""
-
- def __init__(self, config, interactive=True, verbosity=1):
- self.config = config
- if config:
- # no config on shell to a remote instance
- self.config.init_log(logthreshold=logging.ERROR, debug=True)
- # 0: no confirmation, 1: only main commands confirmed, 2 ask for everything
- self.verbosity = verbosity
- self.need_wrap = True
- if not interactive or not verbosity:
- self.confirm = yes
- self.execscript_confirm = yes
- else:
- self.execscript_confirm = execscript_confirm
- self._option_changes = []
- self.__context = {'confirm': self.confirm,
- 'config': self.config,
- 'interactive_mode': interactive,
- }
-
- def __getattribute__(self, name):
- try:
- return object.__getattribute__(self, name)
- except AttributeError:
- cmd = 'cmd_%s' % name
- if hasattr(self, cmd):
- meth = getattr(self, cmd)
- return lambda *args, **kwargs: self.interact(args, kwargs,
- meth=meth)
- raise
- raise AttributeError(name)
-
- def repo_connect(self):
- return self.config.repository()
-
- def migrate(self, vcconf, toupgrade, options):
- """upgrade the given set of cubes
-
- `cubes` is an ordered list of 3-uple:
- (cube, fromversion, toversion)
- """
- if options.fs_only:
- # monkey path configuration.accept_mode so database mode (e.g. Any)
- # won't be accepted
- orig_accept_mode = self.config.accept_mode
- def accept_mode(mode):
- if mode == 'Any':
- return False
- return orig_accept_mode(mode)
- self.config.accept_mode = accept_mode
- # may be an iterator
- toupgrade = tuple(toupgrade)
- vmap = dict( (cube, (fromver, tover)) for cube, fromver, tover in toupgrade)
- ctx = self.__context
- ctx['versions_map'] = vmap
- if self.config.accept_mode('Any') and 'cubicweb' in vmap:
- migrdir = self.config.migration_scripts_dir()
- self.cmd_process_script(join(migrdir, 'bootstrapmigration_repository.py'))
- for cube, fromversion, toversion in toupgrade:
- if cube == 'cubicweb':
- migrdir = self.config.migration_scripts_dir()
- else:
- migrdir = self.config.cube_migration_scripts_dir(cube)
- scripts = filter_scripts(self.config, migrdir, fromversion, toversion)
- if scripts:
- prevversion = None
- for version, script in scripts:
- # take care to X.Y.Z_Any.py / X.Y.Z_common.py: we've to call
- # cube_upgraded once all script of X.Y.Z have been executed
- if prevversion is not None and version != prevversion:
- self.cube_upgraded(cube, prevversion)
- prevversion = version
- self.cmd_process_script(script)
- self.cube_upgraded(cube, toversion)
- else:
- self.cube_upgraded(cube, toversion)
-
- def cube_upgraded(self, cube, version):
- pass
-
- def shutdown(self):
- pass
-
- def interact(self, args, kwargs, meth):
- """execute the given method according to user's confirmation"""
- msg = 'Execute command: %s(%s) ?' % (
- meth.__name__[4:],
- ', '.join([repr(arg) for arg in args] +
- ['%s=%r' % (n,v) for n,v in kwargs.items()]))
- if 'ask_confirm' in kwargs:
- ask_confirm = kwargs.pop('ask_confirm')
- else:
- ask_confirm = True
- if not ask_confirm or self.confirm(msg):
- return meth(*args, **kwargs)
-
- def confirm(self, question, shell=True, abort=True, retry=False, default='y'):
- """ask for confirmation and return true on positive answer
-
- if `retry` is true the r[etry] answer may return 2
- """
- possibleanswers = ['y','n']
- if abort:
- possibleanswers.append('abort')
- if shell:
- possibleanswers.append('shell')
- if retry:
- possibleanswers.append('retry')
- try:
- answer = ASK.ask(question, possibleanswers, default)
- except (EOFError, KeyboardInterrupt):
- answer = 'abort'
- if answer == 'n':
- return False
- if answer == 'retry':
- return 2
- if answer == 'abort':
- raise SystemExit(1)
- if shell and answer == 'shell':
- self.interactive_shell()
- return self.confirm(question)
- return True
-
- def interactive_shell(self):
- self.confirm = yes
- self.need_wrap = False
- # avoid '_' to be added to builtins by sys.display_hook
- def do_not_add___to_builtins(obj):
- if obj is not None:
- print repr(obj)
- sys.displayhook = do_not_add___to_builtins
- local_ctx = self._create_context()
- try:
- import readline
- from rlcompleter import Completer
- except ImportError:
- # readline not available
- pass
- else:
- readline.set_completer(Completer(local_ctx).complete)
- readline.parse_and_bind('tab: complete')
- home_key = 'HOME'
- if sys.platform == 'win32':
- home_key = 'USERPROFILE'
- histfile = os.path.join(os.environ[home_key], ".eshellhist")
- try:
- readline.read_history_file(histfile)
- except IOError:
- pass
- from code import interact
- banner = """entering the migration python shell
-just type migration commands or arbitrary python code and type ENTER to execute it
-type "exit" or Ctrl-D to quit the shell and resume operation"""
- # give custom readfunc to avoid http://bugs.python.org/issue1288615
- def unicode_raw_input(prompt):
- return unicode(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 questions
- del self.confirm
- self.need_wrap = True
-
- @cached
- def _create_context(self):
- """return a dictionary to use as migration script execution context"""
- context = self.__context
- for attr in dir(self):
- if attr.startswith('cmd_'):
- if self.need_wrap:
- context[attr[4:]] = getattr(self, attr[4:])
- else:
- context[attr[4:]] = getattr(self, attr)
- return context
-
- def cmd_process_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
- """
- migrscript = os.path.normpath(migrscript)
- if migrscript.endswith('.py'):
- script_mode = 'python'
- elif migrscript.endswith('.txt') or migrscript.endswith('.rst'):
- script_mode = 'doctest'
- else:
- raise Exception('This is not a valid cubicweb shell input')
- if not self.execscript_confirm(migrscript):
- return
- scriptlocals = self._create_context().copy()
- if script_mode == 'python':
- if funcname is None:
- pyname = '__main__'
- else:
- pyname = splitext(basename(migrscript))[0]
- scriptlocals.update({'__file__': migrscript, '__name__': pyname})
- execfile(migrscript, scriptlocals)
- if funcname is not None:
- try:
- func = scriptlocals[funcname]
- self.info('found %s in locals', funcname)
- assert callable(func), '%s (%s) is not callable' % (func, funcname)
- except KeyError:
- self.critical('no %s in script %s', funcname, migrscript)
- return None
- return func(*args, **kwargs)
- else: # script_mode == 'doctest'
- import doctest
- doctest.testfile(migrscript, module_relative=False,
- optionflags=doctest.ELLIPSIS, globs=scriptlocals)
-
- def cmd_option_renamed(self, oldname, newname):
- """a configuration option has been renamed"""
- self._option_changes.append(('renamed', oldname, newname))
-
- def cmd_option_group_change(self, option, oldgroup, newgroup):
- """a configuration option has been moved in another group"""
- self._option_changes.append(('moved', option, oldgroup, newgroup))
-
- def cmd_option_added(self, optname):
- """a configuration option has been added"""
- self._option_changes.append(('added', optname))
-
- def cmd_option_removed(self, optname):
- """a configuration option has been removed"""
- # can safely be ignored
- #self._option_changes.append(('removed', optname))
-
- def cmd_option_type_changed(self, optname, oldtype, newvalue):
- """a configuration option's type has changed"""
- self._option_changes.append(('typechanged', optname, oldtype, newvalue))
-
- def cmd_add_cubes(self, cubes):
- """modify the list of used cubes in the in-memory config
- returns newly inserted cubes, including dependencies
- """
- if isinstance(cubes, basestring):
- cubes = (cubes,)
- origcubes = self.config.cubes()
- newcubes = [p for p in self.config.expand_cubes(cubes)
- if not p in origcubes]
- if newcubes:
- for cube in cubes:
- assert cube in newcubes
- self.config.add_cubes(newcubes)
- return newcubes
-
- def cmd_remove_cube(self, cube, removedeps=False):
- if removedeps:
- toremove = self.config.expand_cubes([cube])
- else:
- toremove = (cube,)
- origcubes = self.config._cubes
- basecubes = [c for c in origcubes if not c in toremove]
- self.config._cubes = tuple(self.config.expand_cubes(basecubes))
- removed = [p for p in origcubes if not p in self.config._cubes]
- if not cube in removed:
- raise ConfigurationError("can't remove cube %s, "
- "used as a dependency" % cube)
- return removed
-
- def rewrite_configuration(self):
- # import locally, show_diffs unavailable in gae environment
- from cubicweb.toolsutils import show_diffs
- configfile = self.config.main_config_file()
- if self._option_changes:
- read_old_config(self.config, self._option_changes, configfile)
- fd, newconfig = tempfile.mkstemp()
- for optdescr in self._option_changes:
- if optdescr[0] == 'added':
- optdict = self.config.get_option_def(optdescr[1])
- if optdict.get('default') is REQUIRED:
- self.config.input_option(optdescr[1], optdict)
- self.config.generate_config(open(newconfig, 'w'))
- show_diffs(configfile, newconfig)
- os.close(fd)
- if exists(newconfig):
- os.unlink(newconfig)
-
-
-from logging import getLogger
-from cubicweb import set_log_methods
-set_log_methods(MigrationHelper, getLogger('cubicweb.migration'))
--- a/common/test/data/migration/0.0.3_Any.py Tue Dec 08 09:45:07 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-coucou
--- a/common/test/data/migration/0.0.4_Any.py Tue Dec 08 09:45:07 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-coucou
--- a/common/test/data/migration/0.1.0_Any.py Tue Dec 08 09:45:07 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-coucou
--- a/common/test/data/migration/0.1.0_common.py Tue Dec 08 09:45:07 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-"""common to all configuration
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
--- a/common/test/data/migration/0.1.0_repository.py Tue Dec 08 09:45:07 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-"""repository specific
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
--- a/common/test/data/migration/0.1.0_web.py Tue Dec 08 09:45:07 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-"""web only
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
--- a/common/test/data/migration/0.1.2_Any.py Tue Dec 08 09:45:07 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-coucou
--- a/common/test/data/migration/depends.map Tue Dec 08 09:45:07 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-0.0.2: 2.3.0
-0.0.3: 2.4.0
-# missing 0.0.4 entry, that's alright
-0.1.0: 2.6.0
-0.1.2: 2.10.0
--- a/common/test/data/server_migration/bootstrapmigration_repository.py Tue Dec 08 09:45:07 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-"""allways executed before all others in server migration
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
--- a/common/test/unittest_migration.py Tue Dec 08 09:45:07 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-"""cubicweb.common.migration unit tests
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-from os.path import abspath
-from logilab.common.testlib import TestCase, unittest_main
-
-from cubicweb.devtools import TestServerConfiguration
-from cubicweb.cwconfig import CubicWebConfiguration
-from cubicweb.common.migration import MigrationHelper, filter_scripts
-from cubicweb.server.migractions import ServerMigrationHelper
-
-
-class Schema(dict):
- def has_entity(self, e_type):
- return self.has_key(e_type)
-
-SMIGRDIR = abspath('data/server_migration') + '/'
-TMIGRDIR = abspath('data/migration') + '/'
-
-class MigrTestConfig(TestServerConfiguration):
- verbosity = 0
- def migration_scripts_dir(cls):
- return SMIGRDIR
-
- def cube_migration_scripts_dir(cls, cube):
- return TMIGRDIR
-
-class MigrationToolsTC(TestCase):
- def setUp(self):
- self.config = MigrTestConfig('data')
- from yams.schema import Schema
- self.config.load_schema = lambda expand_cubes=False: Schema('test')
- self.config.__class__.cubicweb_appobject_path = frozenset()
- self.config.__class__.cube_appobject_path = frozenset()
-
- def test_filter_scripts_base(self):
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,3,0), (2,4,0)),
- [])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,5,0)),
- [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql')])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,6,0)),
- [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,6,0)),
- [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql'),
- ((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,5,1)),
- [])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,10,2)),
- [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'),
- ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,6,0)),
- [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
-
- self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,3)),
- [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py')])
- self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,4)),
- [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py'),
- ((0, 0, 4), TMIGRDIR+'0.0.4_Any.py')])
-
- def test_filter_scripts_for_mode(self):
- config = CubicWebConfiguration('data')
- config.verbosity = 0
- self.assert_(not isinstance(config.migration_handler(), ServerMigrationHelper))
- self.assertIsInstance(config.migration_handler(), MigrationHelper)
- config = self.config
- config.__class__.name = 'twisted'
- self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
- [((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
- ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')])
- config.__class__.name = 'repository'
- self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
- [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'),
- ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
- ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py')])
- config.__class__.name = 'all-in-one'
- self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
- [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'),
- ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
- ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py'),
- ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')])
- config.__class__.name = 'repository'
-
-
-from cubicweb.devtools import ApptestConfiguration, init_test_database, cleanup_sqlite
-
-class BaseCreationTC(TestCase):
-
- def test_db_creation(self):
- """make sure database can be created"""
- config = ApptestConfiguration('data')
- source = config.sources()['system']
- self.assertEquals(source['db-driver'], 'sqlite')
- cleanup_sqlite(source['db-name'], removetemplate=True)
- init_test_database(config=config)
-
-
-if __name__ == '__main__':
- unittest_main()
--- a/cwconfig.py Tue Dec 08 09:45:07 2009 +0100
+++ b/cwconfig.py Tue Dec 08 10:40:12 2009 +0100
@@ -926,11 +926,11 @@
def migration_handler(self):
"""return a migration handler instance"""
- from cubicweb.common.migration import MigrationHelper
+ from cubicweb.migration import MigrationHelper
return MigrationHelper(self, verbosity=self.verbosity)
def i18ncompile(self, langs=None):
- from cubicweb.common import i18n
+ from cubicweb import i18n
if langs is None:
langs = self.available_languages()
i18ndir = join(self.apphome, 'i18n')
--- a/cwctl.py Tue Dec 08 09:45:07 2009 +0100
+++ b/cwctl.py Tue Dec 08 10:40:12 2009 +0100
@@ -311,7 +311,7 @@
# handle i18n files structure
# in the first cube given
print '-> preparing i18n catalogs'
- from cubicweb.common import i18n
+ from cubicweb import i18n
langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))]
errors = config.i18ncompile(langs)
if errors:
@@ -666,7 +666,7 @@
# * install new languages
# * recompile catalogs
# in the first componant given
- from cubicweb.common import i18n
+ from cubicweb import i18n
templdir = cwcfg.cube_dir(config.cubes()[0])
langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))]
errors = config.i18ncompile(langs)
--- a/devtools/devctl.py Tue Dec 08 09:45:07 2009 +0100
+++ b/devtools/devctl.py Tue Dec 08 10:40:12 2009 +0100
@@ -113,7 +113,7 @@
def _generate_schema_pot(w, vreg, schema, libconfig=None, cube=None):
- from cubicweb.common.i18n import add_msg
+ from cubicweb.i18n import add_msg
from cubicweb.web import uicfg
from cubicweb.schema import META_RTYPES, SYSTEM_RTYPES
no_context_rtypes = META_RTYPES | SYSTEM_RTYPES
@@ -286,7 +286,7 @@
import yams
from logilab.common.fileutils import ensure_fs_mode
from logilab.common.shellutils import globfind, find, rm
- from cubicweb.common.i18n import extract_from_tal, execute
+ from cubicweb.i18n import extract_from_tal, execute
tempdir = tempfile.mkdtemp()
potfiles = [join(I18NDIR, 'static-messages.pot')]
print '-> extract schema messages.'
@@ -379,7 +379,7 @@
import tempfile
from logilab.common.fileutils import ensure_fs_mode
from logilab.common.shellutils import find, rm
- from cubicweb.common.i18n import extract_from_tal, execute
+ from cubicweb.i18n import extract_from_tal, execute
toedit = []
cube = basename(normpath(cubedir))
tempdir = tempfile.mkdtemp()
--- a/goa/goactl.py Tue Dec 08 09:45:07 2009 +0100
+++ b/goa/goactl.py Tue Dec 08 10:40:12 2009 +0100
@@ -59,6 +59,8 @@
'cwconfig.py',
'entity.py',
'interfaces.py',
+ 'i18n.py',
+ 'migration.py',
'rqlrewrite.py',
'rset.py',
'schema.py',
@@ -69,7 +71,6 @@
'view.py',
'common/mail.py',
- 'common/migration.py',
'common/mixins.py',
'common/mttransforms.py',
'common/uilib.py',
@@ -224,7 +225,7 @@
join(packagesdir, include))
# generate sample config
from cubicweb.goa.goaconfig import GAEConfiguration
- from cubicweb.common.migration import MigrationHelper
+ from cubicweb.migration import MigrationHelper
config = GAEConfiguration(appid, appldir)
if exists(config.main_config_file()):
mih = MigrationHelper(config)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/i18n.py Tue Dec 08 10:40:12 2009 +0100
@@ -0,0 +1,99 @@
+"""Some i18n/gettext utilities.
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+import re
+import os
+import sys
+from os.path import join, basename, splitext, exists
+from glob import glob
+
+from cubicweb.toolsutils import create_dir
+
+def extract_from_tal(files, output_file):
+ """extract i18n strings from tal and write them into the given output file
+ using standard python gettext marker (_)
+ """
+ output = open(output_file, 'w')
+ for filepath in files:
+ for match in re.finditer('i18n:(content|replace)="([^"]+)"', open(filepath).read()):
+ print >> output, '_("%s")' % match.group(2)
+ output.close()
+
+
+def add_msg(w, msgid, msgctx=None):
+ """write an empty pot msgid definition"""
+ if isinstance(msgid, unicode):
+ msgid = msgid.encode('utf-8')
+ if msgctx:
+ if isinstance(msgctx, unicode):
+ msgctx = msgctx.encode('utf-8')
+ w('msgctxt "%s"\n' % msgctx)
+ msgid = msgid.replace('"', r'\"').splitlines()
+ if len(msgid) > 1:
+ w('msgid ""\n')
+ for line in msgid:
+ w('"%s"' % line.replace('"', r'\"'))
+ else:
+ w('msgid "%s"\n' % msgid[0])
+ w('msgstr ""\n\n')
+
+
+def execute(cmd):
+ """display the command, execute it and raise an Exception if returned
+ status != 0
+ """
+ from subprocess import call
+ print cmd.replace(os.getcwd() + os.sep, '')
+ status = call(cmd, shell=True)
+ if status != 0:
+ raise Exception('status = %s' % status)
+
+
+def available_catalogs(i18ndir=None):
+ if i18ndir is None:
+ wildcard = '*.po'
+ else:
+ wildcard = join(i18ndir, '*.po')
+ for popath in glob(wildcard):
+ lang = splitext(basename(popath))[0]
+ yield lang, popath
+
+
+def compile_i18n_catalogs(sourcedirs, destdir, langs):
+ """generate .mo files for a set of languages into the `destdir` i18n directory
+ """
+ from logilab.common.fileutils import ensure_fs_mode
+ print '-> compiling %s catalogs...' % destdir
+ errors = []
+ for lang in langs:
+ langdir = join(destdir, lang, 'LC_MESSAGES')
+ if not exists(langdir):
+ create_dir(langdir)
+ pofiles = [join(path, '%s.po' % lang) for path in sourcedirs]
+ pofiles = [pof for pof in pofiles if exists(pof)]
+ mergedpo = join(destdir, '%s_merged.po' % lang)
+ try:
+ # merge instance/cubes messages catalogs with the stdlib's one
+ execute('msgcat --use-first --sort-output --strict -o "%s" %s'
+ % (mergedpo, ' '.join('"%s"' % f for f in pofiles)))
+ # make sure the .mo file is writeable and compiles with *msgfmt*
+ applmo = join(destdir, lang, 'LC_MESSAGES', 'cubicweb.mo')
+ try:
+ ensure_fs_mode(applmo)
+ except OSError:
+ pass # suppose not exists
+ execute('msgfmt "%s" -o "%s"' % (mergedpo, applmo))
+ except Exception, ex:
+ errors.append('while handling language %s: %s' % (lang, ex))
+ try:
+ # clean everything
+ os.unlink(mergedpo)
+ except Exception:
+ continue
+ return errors
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/migration.py Tue Dec 08 10:40:12 2009 +0100
@@ -0,0 +1,373 @@
+"""utilities for instances migration
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+import sys
+import os
+import logging
+import tempfile
+from os.path import exists, join, basename, splitext
+
+from logilab.common.decorators import cached
+from logilab.common.configuration import REQUIRED, read_old_config
+from logilab.common.shellutils import ASK
+
+from cubicweb import ConfigurationError
+
+
+def filter_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
+ """
+ from logilab.common.changelog import Version # doesn't work with appengine
+ assert fromversion
+ assert toversion
+ assert isinstance(fromversion, tuple), fromversion.__class__
+ assert isinstance(toversion, tuple), toversion.__class__
+ assert fromversion <= toversion, (fromversion, toversion)
+ if not exists(directory):
+ if not quiet:
+ print directory, "doesn't exists, no migration path"
+ return []
+ if fromversion == toversion:
+ return []
+ result = []
+ for fname in os.listdir(directory):
+ if fname.endswith('.pyc') or fname.endswith('.pyo') \
+ or fname.endswith('~'):
+ continue
+ fpath = join(directory, fname)
+ try:
+ tver, mode = fname.split('_', 1)
+ except ValueError:
+ continue
+ mode = mode.split('.', 1)[0]
+ if not config.accept_mode(mode):
+ continue
+ try:
+ tver = Version(tver)
+ except ValueError:
+ continue
+ if tver <= fromversion:
+ continue
+ if tver > toversion:
+ continue
+ result.append((tver, fpath))
+ # be sure scripts are executed in order
+ return sorted(result)
+
+
+IGNORED_EXTENSIONS = ('.swp', '~')
+
+
+def execscript_confirm(scriptpath):
+ """asks for confirmation before executing a script and provides the
+ ability to show the script's content
+ """
+ while True:
+ answer = ASK.ask('Execute %r ?' % scriptpath, ('Y','n','show'), 'Y')
+ if answer == 'n':
+ return False
+ elif answer == 'show':
+ stream = open(scriptpath)
+ scriptcontent = stream.read()
+ stream.close()
+ print
+ print scriptcontent
+ print
+ else:
+ return True
+
+def yes(*args, **kwargs):
+ return True
+
+
+class MigrationHelper(object):
+ """class holding CubicWeb Migration Actions used by migration scripts"""
+
+ def __init__(self, config, interactive=True, verbosity=1):
+ self.config = config
+ if config:
+ # no config on shell to a remote instance
+ self.config.init_log(logthreshold=logging.ERROR, debug=True)
+ # 0: no confirmation, 1: only main commands confirmed, 2 ask for everything
+ self.verbosity = verbosity
+ self.need_wrap = True
+ if not interactive or not verbosity:
+ self.confirm = yes
+ self.execscript_confirm = yes
+ else:
+ self.execscript_confirm = execscript_confirm
+ self._option_changes = []
+ self.__context = {'confirm': self.confirm,
+ 'config': self.config,
+ 'interactive_mode': interactive,
+ }
+
+ def __getattribute__(self, name):
+ try:
+ return object.__getattribute__(self, name)
+ except AttributeError:
+ cmd = 'cmd_%s' % name
+ if hasattr(self, cmd):
+ meth = getattr(self, cmd)
+ return lambda *args, **kwargs: self.interact(args, kwargs,
+ meth=meth)
+ raise
+ raise AttributeError(name)
+
+ def repo_connect(self):
+ return self.config.repository()
+
+ def migrate(self, vcconf, toupgrade, options):
+ """upgrade the given set of cubes
+
+ `cubes` is an ordered list of 3-uple:
+ (cube, fromversion, toversion)
+ """
+ if options.fs_only:
+ # monkey path configuration.accept_mode so database mode (e.g. Any)
+ # won't be accepted
+ orig_accept_mode = self.config.accept_mode
+ def accept_mode(mode):
+ if mode == 'Any':
+ return False
+ return orig_accept_mode(mode)
+ self.config.accept_mode = accept_mode
+ # may be an iterator
+ toupgrade = tuple(toupgrade)
+ vmap = dict( (cube, (fromver, tover)) for cube, fromver, tover in toupgrade)
+ ctx = self.__context
+ ctx['versions_map'] = vmap
+ if self.config.accept_mode('Any') and 'cubicweb' in vmap:
+ migrdir = self.config.migration_scripts_dir()
+ self.cmd_process_script(join(migrdir, 'bootstrapmigration_repository.py'))
+ for cube, fromversion, toversion in toupgrade:
+ if cube == 'cubicweb':
+ migrdir = self.config.migration_scripts_dir()
+ else:
+ migrdir = self.config.cube_migration_scripts_dir(cube)
+ scripts = filter_scripts(self.config, migrdir, fromversion, toversion)
+ if scripts:
+ prevversion = None
+ for version, script in scripts:
+ # take care to X.Y.Z_Any.py / X.Y.Z_common.py: we've to call
+ # cube_upgraded once all script of X.Y.Z have been executed
+ if prevversion is not None and version != prevversion:
+ self.cube_upgraded(cube, prevversion)
+ prevversion = version
+ self.cmd_process_script(script)
+ self.cube_upgraded(cube, toversion)
+ else:
+ self.cube_upgraded(cube, toversion)
+
+ def cube_upgraded(self, cube, version):
+ pass
+
+ def shutdown(self):
+ pass
+
+ def interact(self, args, kwargs, meth):
+ """execute the given method according to user's confirmation"""
+ msg = 'Execute command: %s(%s) ?' % (
+ meth.__name__[4:],
+ ', '.join([repr(arg) for arg in args] +
+ ['%s=%r' % (n,v) for n,v in kwargs.items()]))
+ if 'ask_confirm' in kwargs:
+ ask_confirm = kwargs.pop('ask_confirm')
+ else:
+ ask_confirm = True
+ if not ask_confirm or self.confirm(msg):
+ return meth(*args, **kwargs)
+
+ def confirm(self, question, shell=True, abort=True, retry=False, default='y'):
+ """ask for confirmation and return true on positive answer
+
+ if `retry` is true the r[etry] answer may return 2
+ """
+ possibleanswers = ['y','n']
+ if abort:
+ possibleanswers.append('abort')
+ if shell:
+ possibleanswers.append('shell')
+ if retry:
+ possibleanswers.append('retry')
+ try:
+ answer = ASK.ask(question, possibleanswers, default)
+ except (EOFError, KeyboardInterrupt):
+ answer = 'abort'
+ if answer == 'n':
+ return False
+ if answer == 'retry':
+ return 2
+ if answer == 'abort':
+ raise SystemExit(1)
+ if shell and answer == 'shell':
+ self.interactive_shell()
+ return self.confirm(question)
+ return True
+
+ def interactive_shell(self):
+ self.confirm = yes
+ self.need_wrap = False
+ # avoid '_' to be added to builtins by sys.display_hook
+ def do_not_add___to_builtins(obj):
+ if obj is not None:
+ print repr(obj)
+ sys.displayhook = do_not_add___to_builtins
+ local_ctx = self._create_context()
+ try:
+ import readline
+ from rlcompleter import Completer
+ except ImportError:
+ # readline not available
+ pass
+ else:
+ readline.set_completer(Completer(local_ctx).complete)
+ readline.parse_and_bind('tab: complete')
+ home_key = 'HOME'
+ if sys.platform == 'win32':
+ home_key = 'USERPROFILE'
+ histfile = os.path.join(os.environ[home_key], ".eshellhist")
+ try:
+ readline.read_history_file(histfile)
+ except IOError:
+ pass
+ from code import interact
+ banner = """entering the migration python shell
+just type migration commands or arbitrary python code and type ENTER to execute it
+type "exit" or Ctrl-D to quit the shell and resume operation"""
+ # give custom readfunc to avoid http://bugs.python.org/issue1288615
+ def unicode_raw_input(prompt):
+ return unicode(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 questions
+ del self.confirm
+ self.need_wrap = True
+
+ @cached
+ def _create_context(self):
+ """return a dictionary to use as migration script execution context"""
+ context = self.__context
+ for attr in dir(self):
+ if attr.startswith('cmd_'):
+ if self.need_wrap:
+ context[attr[4:]] = getattr(self, attr[4:])
+ else:
+ context[attr[4:]] = getattr(self, attr)
+ return context
+
+ def cmd_process_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
+ """
+ migrscript = os.path.normpath(migrscript)
+ if migrscript.endswith('.py'):
+ script_mode = 'python'
+ elif migrscript.endswith('.txt') or migrscript.endswith('.rst'):
+ script_mode = 'doctest'
+ else:
+ raise Exception('This is not a valid cubicweb shell input')
+ if not self.execscript_confirm(migrscript):
+ return
+ scriptlocals = self._create_context().copy()
+ if script_mode == 'python':
+ if funcname is None:
+ pyname = '__main__'
+ else:
+ pyname = splitext(basename(migrscript))[0]
+ scriptlocals.update({'__file__': migrscript, '__name__': pyname})
+ execfile(migrscript, scriptlocals)
+ if funcname is not None:
+ try:
+ func = scriptlocals[funcname]
+ self.info('found %s in locals', funcname)
+ assert callable(func), '%s (%s) is not callable' % (func, funcname)
+ except KeyError:
+ self.critical('no %s in script %s', funcname, migrscript)
+ return None
+ return func(*args, **kwargs)
+ else: # script_mode == 'doctest'
+ import doctest
+ doctest.testfile(migrscript, module_relative=False,
+ optionflags=doctest.ELLIPSIS, globs=scriptlocals)
+
+ def cmd_option_renamed(self, oldname, newname):
+ """a configuration option has been renamed"""
+ self._option_changes.append(('renamed', oldname, newname))
+
+ def cmd_option_group_change(self, option, oldgroup, newgroup):
+ """a configuration option has been moved in another group"""
+ self._option_changes.append(('moved', option, oldgroup, newgroup))
+
+ def cmd_option_added(self, optname):
+ """a configuration option has been added"""
+ self._option_changes.append(('added', optname))
+
+ def cmd_option_removed(self, optname):
+ """a configuration option has been removed"""
+ # can safely be ignored
+ #self._option_changes.append(('removed', optname))
+
+ def cmd_option_type_changed(self, optname, oldtype, newvalue):
+ """a configuration option's type has changed"""
+ self._option_changes.append(('typechanged', optname, oldtype, newvalue))
+
+ def cmd_add_cubes(self, cubes):
+ """modify the list of used cubes in the in-memory config
+ returns newly inserted cubes, including dependencies
+ """
+ if isinstance(cubes, basestring):
+ cubes = (cubes,)
+ origcubes = self.config.cubes()
+ newcubes = [p for p in self.config.expand_cubes(cubes)
+ if not p in origcubes]
+ if newcubes:
+ for cube in cubes:
+ assert cube in newcubes
+ self.config.add_cubes(newcubes)
+ return newcubes
+
+ def cmd_remove_cube(self, cube, removedeps=False):
+ if removedeps:
+ toremove = self.config.expand_cubes([cube])
+ else:
+ toremove = (cube,)
+ origcubes = self.config._cubes
+ basecubes = [c for c in origcubes if not c in toremove]
+ self.config._cubes = tuple(self.config.expand_cubes(basecubes))
+ removed = [p for p in origcubes if not p in self.config._cubes]
+ if not cube in removed:
+ raise ConfigurationError("can't remove cube %s, "
+ "used as a dependency" % cube)
+ return removed
+
+ def rewrite_configuration(self):
+ # import locally, show_diffs unavailable in gae environment
+ from cubicweb.toolsutils import show_diffs
+ configfile = self.config.main_config_file()
+ if self._option_changes:
+ read_old_config(self.config, self._option_changes, configfile)
+ fd, newconfig = tempfile.mkstemp()
+ for optdescr in self._option_changes:
+ if optdescr[0] == 'added':
+ optdict = self.config.get_option_def(optdescr[1])
+ if optdict.get('default') is REQUIRED:
+ self.config.input_option(optdescr[1], optdict)
+ self.config.generate_config(open(newconfig, 'w'))
+ show_diffs(configfile, newconfig)
+ os.close(fd)
+ if exists(newconfig):
+ os.unlink(newconfig)
+
+
+from logging import getLogger
+from cubicweb import set_log_methods
+set_log_methods(MigrationHelper, getLogger('cubicweb.migration'))
--- a/server/migractions.py Tue Dec 08 09:45:07 2009 +0100
+++ b/server/migractions.py Tue Dec 08 10:40:12 2009 +0100
@@ -38,7 +38,7 @@
from cubicweb.schema import (META_RTYPES, VIRTUAL_RTYPES,
CubicWebRelationSchema, order_eschemas)
from cubicweb.dbapi import get_repository, repo_connect
-from cubicweb.common.migration import MigrationHelper, yes
+from cubicweb.migration import MigrationHelper, yes
try:
from cubicweb.server import SOURCE_TYPES, schemaserial as ss
--- a/server/schemaserial.py Tue Dec 08 09:45:07 2009 +0100
+++ b/server/schemaserial.py Tue Dec 08 10:40:12 2009 +0100
@@ -510,7 +510,7 @@
def rdefrelations2rql(rschema, subjtype, objtype, props):
iterators = []
- for constraint in props['constraints']:
+ for constraint in props.constraints:
iterators.append(constraint2rql(rschema, subjtype, objtype, constraint))
return chain(*iterators)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/0.0.3_Any.py Tue Dec 08 10:40:12 2009 +0100
@@ -0,0 +1,8 @@
+"""
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+coucou
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/0.0.4_Any.py Tue Dec 08 10:40:12 2009 +0100
@@ -0,0 +1,8 @@
+"""
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+coucou
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/0.1.0_Any.py Tue Dec 08 10:40:12 2009 +0100
@@ -0,0 +1,8 @@
+"""
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+coucou
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/0.1.0_common.py Tue Dec 08 10:40:12 2009 +0100
@@ -0,0 +1,7 @@
+"""common to all configuration
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/0.1.0_repository.py Tue Dec 08 10:40:12 2009 +0100
@@ -0,0 +1,7 @@
+"""repository specific
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/0.1.0_web.py Tue Dec 08 10:40:12 2009 +0100
@@ -0,0 +1,7 @@
+"""web only
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/0.1.2_Any.py Tue Dec 08 10:40:12 2009 +0100
@@ -0,0 +1,8 @@
+"""
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+coucou
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/depends.map Tue Dec 08 10:40:12 2009 +0100
@@ -0,0 +1,5 @@
+0.0.2: 2.3.0
+0.0.3: 2.4.0
+# missing 0.0.4 entry, that's alright
+0.1.0: 2.6.0
+0.1.2: 2.10.0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/server_migration/bootstrapmigration_repository.py Tue Dec 08 10:40:12 2009 +0100
@@ -0,0 +1,7 @@
+"""allways executed before all others in server migration
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_migration.py Tue Dec 08 10:40:12 2009 +0100
@@ -0,0 +1,103 @@
+"""cubicweb.common.migration unit tests
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+
+from os.path import abspath
+from logilab.common.testlib import TestCase, unittest_main
+
+from cubicweb.devtools import TestServerConfiguration
+from cubicweb.cwconfig import CubicWebConfiguration
+from cubicweb.migration import MigrationHelper, filter_scripts
+from cubicweb.server.migractions import ServerMigrationHelper
+
+
+class Schema(dict):
+ def has_entity(self, e_type):
+ return self.has_key(e_type)
+
+SMIGRDIR = abspath('data/server_migration') + '/'
+TMIGRDIR = abspath('data/migration') + '/'
+
+class MigrTestConfig(TestServerConfiguration):
+ verbosity = 0
+ def migration_scripts_dir(cls):
+ return SMIGRDIR
+
+ def cube_migration_scripts_dir(cls, cube):
+ return TMIGRDIR
+
+class MigrationToolsTC(TestCase):
+ def setUp(self):
+ self.config = MigrTestConfig('data')
+ from yams.schema import Schema
+ self.config.load_schema = lambda expand_cubes=False: Schema('test')
+ self.config.__class__.cubicweb_appobject_path = frozenset()
+ self.config.__class__.cube_appobject_path = frozenset()
+
+ def test_filter_scripts_base(self):
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,3,0), (2,4,0)),
+ [])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,5,0)),
+ [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql')])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,6,0)),
+ [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,6,0)),
+ [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql'),
+ ((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,5,1)),
+ [])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,10,2)),
+ [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'),
+ ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,6,0)),
+ [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
+
+ self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,3)),
+ [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py')])
+ self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,4)),
+ [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py'),
+ ((0, 0, 4), TMIGRDIR+'0.0.4_Any.py')])
+
+ def test_filter_scripts_for_mode(self):
+ config = CubicWebConfiguration('data')
+ config.verbosity = 0
+ self.assert_(not isinstance(config.migration_handler(), ServerMigrationHelper))
+ self.assertIsInstance(config.migration_handler(), MigrationHelper)
+ config = self.config
+ config.__class__.name = 'twisted'
+ self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+ [((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')])
+ config.__class__.name = 'repository'
+ self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+ [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py')])
+ config.__class__.name = 'all-in-one'
+ self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+ [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')])
+ config.__class__.name = 'repository'
+
+
+from cubicweb.devtools import ApptestConfiguration, init_test_database, cleanup_sqlite
+
+class BaseCreationTC(TestCase):
+
+ def test_db_creation(self):
+ """make sure database can be created"""
+ config = ApptestConfiguration('data')
+ source = config.sources()['system']
+ self.assertEquals(source['db-driver'], 'sqlite')
+ cleanup_sqlite(source['db-name'], removetemplate=True)
+ init_test_database(config=config)
+
+
+if __name__ == '__main__':
+ unittest_main()