Exemple #1
0
 def create_parser(self, prog_name, subcommand):
     cfg = pulsar.Config(apps=['socket', 'pulse'],
                         exclude=['debug'],
                         description=self.help,
                         version=self.get_version())
     parser = cfg.parser()
     for option in self.option_list:
         flags = []
         if option._short_opts:
             flags.extend(option._short_opts)
         if option._long_opts:
             flags.extend(option._long_opts)
         type = option.type
         if type == 'choice':
             type = None
         s = Setting(option.dest,
                     flags=flags,
                     choices=option.choices,
                     default=option.default,
                     action=option.action,
                     type=type,
                     nargs=option.nargs,
                     desc=option.help)
         s.add_argument(parser)
     return parser
Exemple #2
0
class Command(startproject.Command):
    option_list = (Setting('name', nargs=1, desc='Extension name.'),
                   Setting('target', ['--target'],
                           desc='directory containing the extension.'))
    help = ('Creates a Lux project directory structure for the given '
            'project name in the current directory or optionally in the '
            'given directory.')

    template_type = "extension"
Exemple #3
0
class OdmCommand(lux.Command):
    option_list = (
        Setting('dry_run', ('--dry-run',),
                action='store_true',
                default=False,
                desc='run the command but it doesnt do anything'),
        Setting('force', ('--force',),
                action='store_true',
                default=False,
                desc='remove pre-existing databases if required'),
        Setting('apps', nargs='*',
                desc='appname appname.ModelName ...'),
    )
    args = '[appname appname.ModelName ...]'
Exemple #4
0
 def get_parser(self, **params):
     parser = argparse.ArgumentParser(**params)
     parser.add_argument('--version',
                         action='version',
                         version=self.get_version(),
                         help="Show version number and exit")
     config = Setting('config', ('-c', '--config'),
                      default=self.config_module,
                      desc=('python dotted path to a Lux/Pulsar config '
                            ' file, where settings can be specified.'))
     config.add_argument(parser, True)
     for opt in self.default_option_list:
         opt.add_argument(parser, True)
     for opt in self.option_list:
         opt.add_argument(parser, True)
     return parser
Exemple #5
0
class Command(lux.Command):
    option_list = (Setting('apps', nargs='*', desc='app app.modelname ...'), )
    help = "Flush models in the data server."

    def __call__(self, argv, **params):
        return self.run_async(argv, **params)

    def run(self, argv, dump=True):
        options = self.options(argv)
        models = self.app.models
        if not models:
            self.logger.info('No model registered')
        apps = options.apps or None
        managers = models.flush(include=apps, dryrun=True)
        if managers:
            print('')
            print('Are you sure you want to remove these models?')
            print('')
            for manager in sorted(managers, key=lambda x: x._meta.modelkey):
                backend = manager.backend
                print('%s from %s' % (manager._meta, backend))
            print('')
            yn = input('yes/no : ')
            if yn.lower() == 'yes':
                result = yield models.flush(include=apps)
                for manager, N in zip(managers, result):
                    print('{0} - removed {1} models'.format(manager._meta, N))
            else:
                print('Nothing done.')
        else:
            print('Nothing done. No models selected')
Exemple #6
0
 def get_parser(self, **params):
     parser = argparse.ArgumentParser(**params)
     parser.add_argument('--version',
                         action='version',
                         version=self.get_version(),
                         help="Show version number and exit")
     config = Setting('config',
                      ('-c', '--config'),
                      default=self.config_module,
                      desc=('python dotted path to a Lux/Pulsar config '
                            ' file, where settings can be specified.'))
     config.add_argument(parser, True)
     for opt in self.default_option_list:
         opt.add_argument(parser, True)
     for opt in self.option_list:
         opt.add_argument(parser, True)
     return parser
Exemple #7
0
class Command(lux.Command):
    help = "Generate a secret key."
    option_list = (
        Setting('length', ('--length', ),
                default=50,
                type=int,
                desc=('Secret key length')),
        Setting('hex', ('--hex', ),
                default=False,
                action='store_true',
                desc=('Hexadecimal string')),
    )

    def run(self, options, **params):
        key = generate_secret(options.length, options.hex)
        self.write('Secret key:')
        self.write(key)
        self.write('-----------------------------------------------------')
        return key
Exemple #8
0
 def create_parser(self, prog_name, subcommand):
     cfg = pulsar.Config(apps=['socket', 'pulse'],
                         exclude=['debug'],
                         description=self.help,
                         version=self.get_version())
     parser = cfg.parser()
     for option in self.option_list:
         flags = []
         if option._short_opts:
             flags.extend(option._short_opts)
         if option._long_opts:
             flags.extend(option._long_opts)
         type = option.type
         if type == 'choice':
             type = None
         s = Setting(option.dest, flags=flags, choices=option.choices,
                     default=option.default, action=option.action,
                     type=type, nargs=option.nargs, desc=option.help)
         s.add_argument(parser)
     return parser
Exemple #9
0
class Command(lux.Command):
    option_list = (Setting('relative_url',
                           ['--relative-url'],
                           action="store_true",
                           default=False,
                           desc='Use relative urls rather than absolute ones. '
                                'Useful during development.'),
                   Setting('nominify',
                           ['--nominify'],
                           action="store_true",
                           default=False,
                           desc="Don't use minified media files"))

    help = "create the static site"

    def run(self, options):
        if options.relative_url:
            self.app.config['SITE_URL'] = ''
        if options.nominify:
            self.app.config['MINIFIED_MEDIA'] = False
        return self.app.extensions['static'].build(self.app)
Exemple #10
0
class Command(lux.Command):
    option_list = (
        Setting('dryrun', ('--dryrun',),
                action='store_true',
                default=False,
                desc=("It does not remove any data, instead it displays "
                      "the number of models which could be removed")),
        Setting('apps', nargs='*', desc='app app.modelname ...')
    )
    help = "Flush models in the data server."

    def run(self, options, interactive=True, **params):
        dryrun = options.dryrun
        mapper = self.app.mapper()
        self.write('\nFlush model data\n')
        if not mapper:
            return self.write('No model registered')
        apps = options.apps or None
        managers_count = yield from mapper.flush(include=apps, dryrun=True)
        if not managers_count:
            return self.write('Nothing done. No models selected')
        if not dryrun:
            self.write('\nAre you sure you want to remove these models?\n')
        for manager, N in sorted(managers_count,
                                 key=lambda x: x[0]._meta.table_name):
            self.write('%s - %s' % (manager, plural(N, 'model')))
        #
        if dryrun:
            self.write('\nNothing done. Dry run')
        else:
            self.write('')
            yn = input('yes/no : ') if interactive else 'yes'
            if yn.lower() == 'yes':
                managers_count = yield from mapper.flush(include=apps)
                for manager, removed in sorted(
                        managers_count, key=lambda x: x[0]._meta.table_name):
                    N = plural(removed, 'model')
                    self.write('{0} - removed {1}'.format(manager, N))
            else:
                self.write('Nothing done.')
Exemple #11
0
class Command(lux.Command):
    option_list = (Setting('relative_url', ['--relative-url'],
                           action="store_true",
                           default=False,
                           desc='Use relative urls rather than absolute ones. '
                           'Useful during development.'), )

    help = "create the static site"

    def run(self, options):
        if options.relative_url:
            self.app.config['SITE_URL'] = ''
        return self.app.extensions['lux.extensions.static'].build(self.app)
Exemple #12
0
class ConsoleParser(object):
    '''A class for parsing the console inputs.'''
    help = None
    option_list = ()
    default_option_list = (Setting('loglevel', ('--log-level', ),
                                   meta='LEVEL',
                                   default='info',
                                   desc='Set the overall log level.',
                                   choices=('debug', 'info', 'warning',
                                            'error', 'critical')),
                           Setting('debug', ('--debug', ),
                                   action="store_true",
                                   default=False,
                                   desc='Run in debug mode.'))

    @property
    def config_module(self):
        raise NotImplementedError

    def get_version(self):
        raise NotImplementedError

    def get_parser(self, **params):
        parser = argparse.ArgumentParser(**params)
        parser.add_argument('--version',
                            action='version',
                            version=self.get_version(),
                            help="Show version number and exit")
        config = Setting('config', ('-c', '--config'),
                         default=self.config_module,
                         desc=('python dotted path to a Lux/Pulsar config '
                               ' file, where settings can be specified.'))
        config.add_argument(parser, True)
        for opt in self.default_option_list:
            opt.add_argument(parser, True)
        for opt in self.option_list:
            opt.add_argument(parser, True)
        return parser
Exemple #13
0
class Command(lux.Command):
    option_list = (
        Setting('nginx', ('-n', '--nginx'),
                default='http://{0}'.format(platform.node()),
                desc='Nginx server as http://host or https://host.'),
        Setting('port', ('-p', '--nginx-port'),
                type=int,
                default=80,
                desc='Nginx server port.'),
        Setting('server', ('-s', '--server'),
                default='http://127.0.0.1:8060',
                desc='Python server as http://host:port.'),
        Setting('target', ('-t', '--target'),
                default='',
                desc='Target path of nginx config file.'),
        Setting('enabled', ('-e', '--enabled'),
                action='store_true',
                default=False,
                desc=('Save the file in the nginx '
                      '/etc/nginx/sites-enabled directory.')),
    )
    help = "Creates a nginx config file for a reverse-proxy configuration."

    def run(self, args, target=None):
        options = self.options(args)
        target = target if target is not None else options.target
        nginx_server = '{0}:{1}'.format(options.nginx, options.port)
        path = None
        if options.enabled and os.name == 'posix':
            path = '/etc/nginx/sites-enabled'
        target = nginx_reverse_proxy_config(self.app,
                                            nginx_server,
                                            proxy_server=options.server,
                                            target=target,
                                            path=path).save()
        self.write('Created nginx configuration file "%s"', target)
        self.write('You need to restart nginx.')
        self.write('in linux /etc/init.d/nginx restart')
Exemple #14
0
class Command(lux.Command):
    help = "Show parameters."
    option_list = (Setting('extensions',
                           nargs='*',
                           desc='Extensions to display parameters from.'), )

    def run(self, options, **params):
        display = options.extensions
        config = self.app.config
        extensions = self.app.extensions
        for ext in chain([self.app], extensions.values()):
            if display and ext.meta.name not in display:
                continue
            if ext.meta.config:
                self.write('\n%s' % ext.meta.name)
                self.write('#=====================================')
            for key, value in ext.sorted_config():
                self.write('%s: %s' % (key, config[key]))
Exemple #15
0
class Command(lux.Command):
    help = "Starts a fully-functional Web server using pulsar"
    option_list = (Setting('nominify',
                           ['--nominify'],
                           action="store_true",
                           default=False,
                           desc="Don't use minified media files"),)

    def __call__(self, argv, start=True):
        app = self.app
        server = self.pulsar_app(argv, wsgi.WSGIServer)
        if server.cfg.nominify:
            app.params['MINIFIED_MEDIA'] = False

        if start and not server.logger:   # pragma    nocover
            if not pulsar.get_actor():
                clear_logger()
            app._started = server()
            app.on_start(server)
            arbiter = pulsar.arbiter()
            arbiter.start()
        return app
Exemple #16
0
class Command(lux.Command):
    option_list = (
        Setting('apps', nargs='*', desc='appname appname.ModelName ...'),
        Setting('indent', ('--indent', ),
                default=-1,
                type=int,
                desc=('Specifies the indent level to use when'
                      ' using json output.')),
        Setting('format', ('-f', '--format'),
                default='json',
                desc='The data format.'),
        Setting('target', ('-t', '--target'),
                default='stdnetdump',
                desc='Filename.'),
        Setting('listall', ('-l', '--list-all'),
                action='store_true',
                default=False,
                desc='List all supported formats and exit.'),
    )
    help = ("Output the content of registered models in the data-server into "
            "a file given an output format. If no application is passed to "
            "the positional argument list, it looks if the config dictionary "
            "contains the DUMPDB_EXTENSIONS list, otherwise it dumps all "
            "registered models.")

    def __call__(self, argv, **params):
        return self.run_until_complete(argv, **params)

    def run(self, options, dump=True):
        apps = options.apps or self.app.config.get('DUMPDB_EXTENSIONS')
        models = self.app.extensions['api'].get_router()
        if not models:
            self.logger.info('No model registered')
        else:
            fname = '%s.%s' % (options.target, options.format)
            self.logger.info('Aggregating %s data into "%s".' %
                             (options.format, fname))
            stream = StringIO()
            for model in models:
                # If the model is marked as not serializable, skip it.
                if not getattr(model, 'serializable', True):
                    continue
                model.write(stream, options.format)
        if dump:
            with open(file_name, 'w') as stream:
                serializer.write(stream)
        return serializer

    def get_models(self, apps):
        models = self.app.models()
        if apps:
            pass
        # if not getattr(model, 'serializable', True):
        #     continue
        return models

    def preprocess(self, options, queries):
        return queries

    def listall(self):
        indent = options.indent
        if indent == -1:
            indent = None
        serializer = odm.get_serializer(options.format, indent=indent)
        if options.listall:
            for name in odm.all_serializers():
                print(name)
            exit(0)
Exemple #17
0
class Command(lux.Command):
    help = 'Create a superuser.'
    option_list = (Setting('username', ('--username', ), desc='Username'),
                   Setting('password', ('--password', ), desc='Password'))

    def run(self, options, interactive=False):
        username = options.username
        password = options.password
        if not username or not password:
            interactive = True
        request = self.app.wsgi_request()
        auth_backend = self.app.extensions['lux.extensions.auth']
        def_username = get_def_username(request, auth_backend)
        input_msg = 'Username'
        if def_username:
            input_msg += ' (Leave blank to use %s)' % def_username
        if interactive:  # pragma    nocover
            username = None
            password = None
            try:
                # Get a username
                while not username:
                    username = input(input_msg + ': ')
                    if def_username and username == '':
                        username = def_username
                    if not RE_VALID_USERNAME.match(username):
                        self.write_err('Error: That username is invalid. Use '
                                       'only letters, digits and underscores.')
                        username = None
                    else:
                        user = auth_backend.get_user(request,
                                                     username=username)
                        if user is not None:
                            self.write_err(
                                "Error: That username is already taken.\n")
                            username = None
                # Get a password
                while 1:
                    if not password:
                        password = getpass.getpass()
                        password2 = getpass.getpass('Password (again): ')
                        if password != password2:
                            self.write_err(
                                "Error: Your passwords didn't match.")
                            password = None
                            continue
                    if password.strip() == '':
                        self.write_err(
                            "Error: Blank passwords aren't allowed.")
                        password = None
                        continue
                    break
            except KeyboardInterrupt:
                self.write_err('\nOperation cancelled.')
                sys.exit(1)
        user = auth_backend.create_superuser(request,
                                             username=username,
                                             password=password)
        if user:
            self.write("Superuser %s created successfully.\n" % user)
        else:
            self.write_err("ERROR: could not create superuser")
Exemple #18
0
class Command(lux.Command):
    help = "Build the style-sheet file from installed applications"
    option_list = (Setting('theme', ('--theme', ),
                           default='',
                           desc='Theme to use. Default is lux.'),
                   Setting('variables', ('--variables', ),
                           action='store_true',
                           default=False,
                           desc=('Dump the theme variables as json'
                                 ' file for the theme specified')),
                   Setting('cssfile', ('--cssfile', ),
                           default='',
                           desc=('Target path of css file. For example '
                                 '"media/site/site.css". If not provided, '
                                 'a file called {{ STYLE }}.css will '
                                 'be created and put in "media/<sitename>" '
                                 'directory, if available, '
                                 'otherwise in the local directory.')),
                   Setting('minify', ('--minify', ),
                           action='store_true',
                           default=False,
                           desc='Also create a minified file'))

    def run(self, options, dump=True, **params):
        target = options.cssfile
        app = self.app
        name = app.meta.name
        self.theme = options.theme or name
        if not target and not options.variables:
            target = self.theme
            mdir = os.path.join(self.app.meta.path, 'media', name)
            if os.path.isdir(mdir):
                target = os.path.join(mdir, target)
        data = self.render(self.theme, options.variables)
        if dump:
            if target:
                targets = ['%s.css' % target]
                if options.minify:
                    targets.append('%s.min.css' % target)
                for minify, target in enumerate(targets):
                    if minify:
                        data = self.minify(options, data)
                    with open(target, 'w') as f:
                        f.write(data)
                    b = convert_bytes(len(data))
                    self.write('Created %s file. Size %s.' % (target, b))
                return targets
            else:
                self.write(data)
        return data

    def render(self, theme, dump_variables):
        self.write('Building theme "%s".' % theme)
        css = Css(app=self.app)
        return css.dump(theme, dump_variables=dump_variables)

    def minify(self, options, data):
        b = convert_bytes(len(data))
        self.write('Minimise %s css file via http://cssminifier.com' % b)
        http = HttpClient(loop=new_event_loop())
        response = http.post('http://cssminifier.com/raw',
                             encode_multipart=False,
                             data={'input': data})
        if response.status_code == 200:
            return native_str(response.get_content())
        else:
            response.raise_for_status()
Exemple #19
0
class Command(lux.Command):
    option_list = (Setting('name', nargs=1, desc='Name of the project.'),
                   Setting('target', ['--target'],
                           desc='directory containing the project.'))
    help = ('Creates a Lux project directory structure for the given '
            'project name in the current directory or optionally in the '
            'given directory.')

    template_type = "project"

    def run(self, argv, name=None, target=None):
        options = self.options(argv)
        name = options.name[0]
        validate_name(name, self.template_type)
        # Check that the name cannot be imported.
        try:
            import_module(name)
        except ImportError:
            pass
        else:
            raise lux.CommandError("%r conflicts with the name of an existing "
                                   "Python module and cannot be used as a "
                                   "%s name.\nPlease try another name." %
                                   (name, self.template_type))
        #
        # if some directory is given, make sure it's nicely expanded
        if target is None:
            top_dir = path.join(os.getcwd(), name)
            try:
                os.makedirs(top_dir)
            except OSError as e:
                if e.errno == errno.EEXIST:
                    message = "'%s' already exists" % top_dir
                else:
                    message = e
                raise lux.CommandError(message)
        else:
            top_dir = path.abspath(path.expanduser(target))
            if not path.exists(top_dir):
                raise lux.CommandError("Destination directory '%s' does not "
                                       "exist, please create it first." %
                                       top_dir)
        self.build(name, top_dir)
        self.write('%s "%s" created' % (self.template_type, name))

    def add_context(self, context):
        # Create a random SECRET_KEY hash to put it in the main settings.
        chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
        context['secret_key'] = random_string(chars, 50)

    def build(self, name, top_dir):
        base_name = '%s_name' % self.template_type
        template_dir = path.join(path.dirname(__file__), 'templates',
                                 self.template_type)
        context = {base_name: name}
        self.add_context(context)
        prefix_length = len(template_dir) + 1

        for root, dirs, files in os.walk(template_dir):
            path_rest = root[prefix_length:]
            relative_dir = path_rest.replace(base_name, name)

            if relative_dir:
                target_dir = path.join(top_dir, relative_dir)
                if not path.exists(target_dir):
                    os.mkdir(target_dir)

            for dirname in dirs[:]:
                if dirname.startswith('.') or dirname == '__pycache__':
                    dirs.remove(dirname)

            for filename in files:
                if filename.endswith(('.pyo', '.pyc', '.py.class')):
                    continue

                old_path = path.join(root, filename)

                with open(old_path, 'r') as template_file:
                    content = template_file.read()

                content = Template(content).substitute(context)

                new_path = path.join(top_dir, relative_dir,
                                     filename.replace(base_name, name))

                with open(new_path, 'w') as new_file:
                    new_file.write(content)
Exemple #20
0
class Command(lux.Command):
    option_list = (Setting('luxname',
                           nargs='?',
                           desc='Name of the project.'),
                   Setting('template', ('--template',),
                           default='default',
                           desc='Site template'),
                   Setting('template_list', ('--template-list',),
                           default=False,
                           action='store_true',
                           desc='List of available site templates')
                   )
    help = ('Creates a Lux project directory structure for the given '
            'project name in the current directory or optionally in the '
            'given directory.')

    def run(self, options):
        self.template_dir = path.join(path.dirname(__file__), 'templates')
        if options.template_list:
            for name in os.listdir(self.template_dir):
                dir = os.path.join(self.template_dir, name)
                if os.path.isdir(dir) and not name.startswith('_'):
                    self.write(name)
        else:
            template = options.template
            template_dir = path.join(self.template_dir, template)
            if not os.path.isdir(template_dir):
                raise lux.CommandError('Unknown template project "%s"'
                                       % template)
            if not options.luxname:
                raise lux.CommandError("A project name is required")
            name = options.luxname
            validate_name(name)
            self.target = path.join(os.getcwd(), '%s-project' % name)
            if path.exists(self.target):
                raise lux.CommandError("%r conflicts with an existing path"
                                       % self.target)

            # Check that the name cannot be imported.
            try:
                import_module(name)
            except ImportError:
                pass
            else:
                raise lux.CommandError("%r conflicts with the name of an "
                                       "existing Python module and cannot be "
                                       "used as a %s name.\n"
                                       "Please try another name." %
                                       (name, self.template_type))
            #
            # if some directory is given, make sure it's nicely expanded
            try:
                os.makedirs(self.target)
            except OSError as e:
                raise lux.CommandError(str(e))

            self.build(options.template, name)
            self.write('Project "%s" created' % name)

    def build(self, template, name):
        base_name = 'project_name'
        template_dir = path.join(self.template_dir, template)
        context = {base_name: name,
                   'secret_key': generate_secret(50)}

        if template != 'default':
            default_dir = path.join(self.template_dir, 'default')
            self._write(name, base_name, default_dir, context)
        self._write(name, base_name, template_dir, context)

    def _write(self, name, base_name, template_dir, context):
        prefix_length = len(template_dir) + 1

        for root, dirs, files in os.walk(template_dir):
            path_rest = root[prefix_length:]
            relative_dir = path_rest.replace(base_name, name)

            if relative_dir:
                target_dir = path.join(self.target, relative_dir)
                if not path.exists(target_dir):
                    os.mkdir(target_dir)

            for dirname in dirs[:]:
                if dirname.startswith('.') or dirname == '__pycache__':
                    dirs.remove(dirname)

            for filename in files:
                if filename.endswith(('.pyo', '.pyc', '.py.class')):
                    continue

                old_path = path.join(root, filename)

                with open(old_path, 'r') as template_file:
                    content = template_file.read()

                if not filename.endswith('.html'):
                    content = Template(content).substitute(context)

                new_path = path.join(self.target, relative_dir,
                                     filename.replace(base_name, name))

                with open(new_path, 'w') as new_file:
                    new_file.write(content)
Exemple #21
0
class Command(lux.Command):
    help = 'Alembic commands for migrating database.'

    commands = [
        'auto', 'branches', 'current', 'downgrade', 'heads', 'history', 'init',
        'merge', 'revision', 'show', 'stamp', 'upgrade'
    ]

    option_list = (
        Setting('command', nargs='*', default=None, desc='Alembic command'),
        Setting('branch', ('-b', '--branch'),
                default=None,
                nargs='?',
                desc='Branch label for auto, revision and merge command',
                meta='LABEL'),
        Setting('list', ('--commands', ),
                default=None,
                action='store_true',
                desc='List available Alembic commands'),
        Setting('msg', ('-m', '--message'),
                nargs='?',
                default=None,
                desc='Message for auto, revision and merge command'),
    )

    def run(self, opt):
        '''
        Run obvious commands and validate more complex.
        '''
        if opt.list:
            available = 'Available commands:\n%s' % ', '.join(self.commands)
            self.write(available)
            return available
        if opt.command:
            cmd = opt.command[0]
            if cmd not in self.commands:
                raise CommandError('Unrecognized command %s' % opt.command[0])
            if cmd in ('auto', 'revision', 'merge') and not opt.msg:
                raise CommandError('Missing [-m] parameter for %s' % cmd)
            self.run_alembic_cmd(opt)
            return True
        raise CommandError('Pass [--commands] for available commands')

    def get_lux_template_directory(self):
        return os.path.join(os.path.dirname(os.path.realpath(__file__)),
                            'template')

    def get_config(self):
        '''
        Programmatically create Alembic config. To determine databases,
        DATASTORE from project's config file is used. To customize Alembic
        use MIGRATIONS in you config file.

        Example:
        MIGRATIONS = {
            'alembic': {
                'script_location': '<path>',
                'databases': '<db_name1>,<db_name2>',
            },
            '<db_name1>': {
                'sqlalchemy.url': 'driver://*****:*****@localhost/dbname',
            },
            '<bd_name2>': {
                'sqlalchemy.url': 'driver://*****:*****@localhost/dbname',
            },
            'logging': {
                'path': '<path_to_logging_config>',
            }
        }

        For more information about possible options, please visit Alembic
        documentation:
        https://alembic.readthedocs.org/en/latest/index.html
        '''
        from alembic.config import Config
        # Because we are using custom template, we need to change default
        # implementation of get_template_directory() to make it pointing
        # to template stored in lux, we need to do this hack
        Config.get_template_directory = self.get_lux_template_directory

        # put migrations in main project dir
        migration_dir = os.path.join(self.app.meta.path, 'migrations')
        # set default settings in case where MIGRATIONS is not set
        alembic_cfg = Config()
        # where to place alembic env
        alembic_cfg.set_main_option('script_location', migration_dir)
        # get database(s) name(s) and location(s)
        odm = self.app.odm()
        databases = []
        # set section for each found database
        for name, engine in odm.keys_engines():
            if not name:
                name = 'default'
            databases.append(name)
            # url = str(engine.url).replace('+green', '')
            url = str(engine.url)
            alembic_cfg.set_section_option(name, 'sqlalchemy.url', url)
        # put databases in main options
        alembic_cfg.set_main_option("databases", ','.join(databases))
        # create empty logging section to avoid raising errors in env.py
        alembic_cfg.set_section_option('logging', 'path', '')
        # obtain the metadata required for `auto` command
        self.get_metadata(alembic_cfg)

        # get rest of settings from project config. This may overwrite
        # already existing options (especially if different migration dir
        # is provided)
        cfg = self.app.config.get('MIGRATIONS')
        if cfg:
            for section in cfg.keys():
                for key, value in cfg[section].items():
                    if section == 'alembic':
                        alembic_cfg.set_main_option(key, value)
                    else:
                        alembic_cfg.set_section_option(section, key, value)

        return alembic_cfg

    def run_alembic_cmd(self, opt):
        '''
        Logic for running different Alembic commands.
        '''
        from alembic import command as alembic_cmd

        config = self.get_config()
        # command consume any number of parameters but first is command name
        cmd = opt.command.pop(0)
        # init command needs to point to lux template, not alembic default
        if cmd == 'init':
            dirname = config.get_main_option('script_location')
            # line 63 will be executed in:
            # https://github.com/zzzeek/alembic/blob/master/alembic/command.py
            # since we do not use any *.ini file, we simply silence error
            # about referenced before assignment as it have no negative impact.
            try:
                alembic_cmd.init(config, dirname, template='lux')
            except UnboundLocalError:  # pragma nocover
                pass
        # merge required two revision name
        elif cmd == 'merge':
            if len(opt.command) != 2:
                raise CommandError('Command %s required revisions id.' % cmd)
            alembic_cmd.merge(config,
                              opt.command,
                              message=opt.msg,
                              branch_label=opt.branch)
        elif cmd == 'revision':
            alembic_cmd.revision(config,
                                 message=opt.msg,
                                 branch_label=opt.branch)
        # auto command is a shortcut for `revision --autogenerate`
        elif cmd == 'auto':
            alembic_cmd.revision(config,
                                 autogenerate=True,
                                 message=opt.msg,
                                 branch_label=opt.branch)
        # this commands required revision name, but do not take any message or
        # branch labels
        elif cmd in ('show', 'stamp', 'upgrade', 'downgrade'):
            if len(opt.command) != 1:
                raise CommandError('Command %s required revision id' % cmd)
            getattr(alembic_cmd, cmd)(config, *opt.command)
        else:
            # execute commands without any additional params
            getattr(alembic_cmd, cmd)(config)

    def get_metadata(self, config):
        '''
        MetaData object stored in odm extension contains aggregated data
        from all databases defined in project. This function splits the data
        to correspond with related database only.
        '''
        from sqlalchemy import MetaData
        odm = self.app.odm()
        metadata = {}

        for key, db_engine in odm.keys_engines():
            if not key:
                key = 'default'
            metadata[key] = meta = MetaData()
            for table, engine in odm.binds.items():
                if engine == db_engine:
                    table.tometadata(meta)

        config.metadata = metadata