Example #1
0
def run_system_tests(options):
    """
    Runs unittests for gluon.tests
    """
    # see "python -m unittest -h" for unittest options help
    # NOTE: someone might be interested either in using the
    #       -f (--failfast) option to stop testing on first failure, or
    #       in customizing the test selection, for example to run only
    #       'gluon.tests.<module>', 'gluon.tests.<module>.<class>' (this
    #       could be shortened as 'gluon.tests.<class>'), or even
    #       'gluon.tests.<module>.<class>.<method>' (or
    #       the shorter 'gluon.tests.<class>.<method>')
    call_args = ['-m', 'unittest', '-c', 'gluon.tests']
    if options.verbose:
        call_args.insert(-1, '-v')
    if options.with_coverage:
        try:
            import coverage
        except:
            die('Coverage not installed')
    if not PY2:
        sys.stderr.write('Experimental ')
    sys.stderr.write("Python %s\n" % sys.version)
    if options.with_coverage:
        coverage_exec = 'coverage2' if PY2 else 'coverage3'
        coverage_config_file = os.path.join('gluon', 'tests', 'coverage.ini')
        coverage_config = os.environ.setdefault("COVERAGE_PROCESS_START",
                                                coverage_config_file)
        run_args = [coverage_exec, 'run', '--rcfile=%s' % coverage_config]
        # replace the current process
        os.execvpe(run_args[0], run_args + call_args, os.environ)
    else:
        run_args = [sys.executable]
        # replace the current process
        os.execv(run_args[0], run_args + call_args)
Example #2
0
def run_system_tests(options):
    """
    Runs unittests for gluon.tests
    """
    # see "python -m unittest -h" for unittest options help
    # NOTE: someone might be interested either in using the
    #       -f (--failfast) option to stop testing on first failure, or
    #       in customizing the test selection, for example to run only
    #       'gluon.tests.<module>', 'gluon.tests.<module>.<class>' (this
    #       could be shortened as 'gluon.tests.<class>'), or even
    #       'gluon.tests.<module>.<class>.<method>' (or
    #       the shorter 'gluon.tests.<class>.<method>')
    call_args = ['-m', 'unittest', '-c', 'gluon.tests']
    if options.verbose:
        call_args.insert(-1, '-v')
    if options.with_coverage:
        try:
            import coverage
        except:
            die('Coverage not installed')
    if not PY2:
        sys.stderr.write('Experimental ')
    sys.stderr.write("Python %s\n" % sys.version)
    if options.with_coverage:
        coverage_exec = 'coverage2' if PY2 else 'coverage3'
        coverage_config_file = os.path.join('gluon', 'tests', 'coverage.ini')
        coverage_config = os.environ.setdefault("COVERAGE_PROCESS_START",
                                                coverage_config_file)
        run_args = [coverage_exec, 'run', '--rcfile=%s' % coverage_config]
        # replace the current process
        os.execvpe(run_args[0], run_args + call_args, os.environ)
    else:
        run_args = [sys.executable]
        # replace the current process
        os.execv(run_args[0], run_args + call_args)
Example #3
0
def load_config(config_file, opt_map):
    """
    Load options from config file (a Python script).

    config_file(str): file name
    opt_map(dict): mapping fom option name (key) to callable (val),
        used to post-process parsed value for the option

    Notice that the configuring Python script is never executed/imported,
    instead the ast library is used to evaluate each option assignment,
    provided that it is written on a single line.

    Returns an OrderedDict with sourced options.
    """
    REGEX_ASSIGN_EXP = re.compile(r'\s*=\s*(.+)')
    map_items = opt_map.items()
    # preserve the order of loaded options even though this is not needed
    pl = OrderedDict()
    config_encoding = get_pep263_encoding(config_file)
    # NOTE: assume 'ascii' encoding when not explicitly stated (Python 2),
    #       this is not correct for Python 3 where the default is 'utf-8'
    open_kwargs = dict() if PY2 else dict(encoding=config_encoding or 'ascii')
    with open(config_file, 'r', **open_kwargs) as cfil:
        for linenum, clin in enumerate(cfil, start=1):
            if PY2 and config_encoding:
                clin = unicode(clin, config_encoding)
            clin = clin.strip()
            for opt, mapr in map_items:
                if clin.startswith(opt):
                    m = REGEX_ASSIGN_EXP.match(clin[len(opt):])
                    if m is None: continue
                    try:
                        val = opt_map[opt](ast.literal_eval(m.group(1)))
                    except:
                        die("cannot parse config file %r at line %d" %
                            (config_file, linenum))
                    if val is not IGNORE:
                        pl[opt] = val
    return pl
Example #4
0
def load_config(config_file, opt_map):
    """
    Load options from config file (a Python script).

    config_file(str): file name
    opt_map(dict): mapping fom option name (key) to callable (val),
        used to post-process parsed value for the option

    Notice that the configuring Python script is never executed/imported,
    instead the ast library is used to evaluate each option assignment,
    provided that it is written on a single line.

    Returns an OrderedDict with sourced options.
    """
    REGEX_ASSIGN_EXP = re.compile(r'\s*=\s*(.+)')
    map_items = opt_map.items()
    # preserve the order of loaded options even though this is not needed
    pl = OrderedDict()
    config_encoding = get_pep263_encoding(config_file)
    # NOTE: assume 'ascii' encoding when not explicitly stated (Python 2),
    #       this is not correct for Python 3 where the default is 'utf-8'
    open_kwargs = dict() if PY2 else dict(encoding=config_encoding or 'ascii')
    with open(config_file, 'r', **open_kwargs) as cfil:
        for linenum, clin in enumerate(cfil, start=1):
            if PY2 and config_encoding:
                clin = unicode(clin, config_encoding)
            clin = clin.strip()
            for opt, mapr in map_items:
                if clin.startswith(opt):
                    m = REGEX_ASSIGN_EXP.match(clin[len(opt):])
                    if m is None: continue
                    try:
                        val = opt_map[opt](ast.literal_eval(m.group(1)))
                    except:
                        die("cannot parse config file %r at line %d" % (config_file, linenum))
                    if val is not IGNORE:
                        pl[opt] = val
    return pl
Example #5
0
def parse_args(parser,
               cli_args,
               deprecated_opts,
               integer_log_level,
               namespace=None):

    #print('PARSING ARGS:', cli_args)
    del integer_log_level[:]
    options = parser.parse_args(cli_args, namespace)
    #print('PARSED OPTIONS:', options)

    # warn for deprecated options
    deprecated_args = [a for a in cli_args if a in deprecated_opts]
    for da in deprecated_args:
        # verify if it was a real option by looking into
        # parsed values for the actual destination
        hint = deprecated_opts[da]
        dest = (hint or da).lstrip('-')
        default = parser.get_default(dest)
        if da == '--interfaces':
            hint = '--interface'
        if getattr(options, dest) is not default:
            # the option has been specified
            msg = "%s is deprecated" % da
            if hint:
                msg += ", use %s instead" % hint
            warn(msg)
    # warn for deprecated values
    if integer_log_level and '--debug' not in deprecated_args:
        warn('integer argument for -D/--log_level is deprecated, '
             'use label instead')
    # fix schedulers and die if all were skipped
    if None in options.schedulers:
        options.schedulers = [i for i in options.schedulers if i is not None]
        if not options.schedulers:
            die('no scheduler left')
    # fix crontabs and die if all were skipped
    if None in options.crontabs:
        options.crontabs = [i for i in options.crontabs if i is not None]
        if not options.crontabs:
            die('no crontab left')
    # taskbar
    if options.taskbar and os.name != 'nt':
        warn('--taskbar not supported on this platform, skipped')
        options.taskbar = False
    # options consistency checkings
    if options.run and not options.shell:
        die('-R/--run requires -S/--shell', exit_status=2)
    if options.args and not options.run:
        die('-A/--args requires -R/--run', exit_status=2)
    if options.with_scheduler and not options.schedulers:
        die('-X/--with_scheduler requires -K/--scheduler', exit_status=2)
    if options.soft_cron and not options.with_cron:
        die('--soft_cron requires -Y/--with_cron', exit_status=2)
    if options.shell:
        for o, os in dict(with_scheduler='-X/--with_scheduler',
                          schedulers='-K/--scheduler',
                          with_cron='-Y/--with_cron',
                          cron_run='-C/--cron_run',
                          run_doctests='-T/--run_doctests',
                          run_system_tests='--run_system_tests').items():
            if getattr(options, o):
                die("-S/--shell and %s are conflicting options" % os,
                    exit_status=2)
    if options.bpython and options.plain:
        die('-B/--bpython and -P/--plain are conflicting options',
            exit_status=2)
    if options.cron_run:
        for o, os in dict(with_cron='-Y/--with_cron',
                          run_doctests='-T/--run_doctests',
                          run_system_tests='--run_system_tests').items():
            if getattr(options, o):
                die("-C/--cron_run and %s are conflicting options" % os,
                    exit_status=2)
    if options.run_doctests and options.run_system_tests:
        die('-T/--run_doctests and --run_system_tests are conflicting options',
            exit_status=2)

    if options.config:
        # load options from file,
        # all options sourced from file that evaluates to False
        # are skipped, the special IGNORE value is used for this
        store_true = lambda v: True if v else IGNORE
        str_or_default = lambda v: str(v) if v else IGNORE
        list_or_default = lambda v : (
            [str(i) for i in v] if isinstance(v, list) else [str(v)]) if v \
            else IGNORE
        # NOTE: 'help', 'version', 'folder', 'cron_job' and 'GAE' are not
        #       sourced from file, the same applies to deprecated options
        opt_map = {
            # global options
            'config': str_or_default,
            'add_options': store_true,
            'password': str_or_default,
            'errors_to_console': store_true,
            'no_banner': store_true,
            'quiet': store_true,
            'log_level': str_or_default,
            # GUI options
            'no_gui': store_true,
            'taskbar': store_true,
            # console options
            'shell': str_or_default,
            'bpython': store_true,
            'plain': store_true,
            'import_models': store_true,
            'run': str_or_default,
            'args': list_or_default,
            # web server options
            'server_name': str_or_default,
            'ip': str_or_default,
            'port': str_or_default,
            'server_key': str_or_default,
            'server_cert': str_or_default,
            'ca_cert': str_or_default,
            'interface': list_or_default,
            'pid_filename': str_or_default,
            'log_filename': str_or_default,
            'min_threads': str_or_default,
            'max_threads': str_or_default,
            'request_queue_size': str_or_default,
            'timeout': str_or_default,
            'socket_timeout': str_or_default,
            'profiler_dir': str_or_default,
            # scheduler options
            'with_scheduler': store_true,
            'scheduler': list_or_default,
            # cron options
            'with_cron': store_true,
            'crontab': list_or_default,
            'soft_cron': store_true,
            'cron_run': store_true,
            # test options
            'verbose': store_true,
            'run_doctests': str_or_default,
            'run_system_tests': store_true,
            'with_coverage': store_true,
        }
        od = load_config(options.config, opt_map)
        #print("LOADED FROM %s:" % options.config, od)
        # convert loaded options dict as retuned by load_config
        # into a list of arguments for further parsing by parse_args
        file_args = []
        args_args = []  # '--args' must be the last
        for key, val in od.items():
            if key != 'args':
                file_args.append('--' + key)
                if isinstance(val, list): file_args.extend(val)
                elif not isinstance(val, bool): file_args.append(val)
            else:
                args_args = ['--args'] + val
        file_args += args_args

        if options.add_options:
            # add options to existing ones,
            # must clear config to avoid infinite recursion
            options.config = options.add_options = None
            return parse_args(parser, file_args, deprecated_opts,
                              integer_log_level, options)
        return parse_args(parser, file_args, deprecated_opts,
                          integer_log_level)

    return options
Example #6
0
def start():
    """ Starts server and other services """

    # get command line arguments
    options = console(version=ProgramVersion)

    if options.with_scheduler or len(options.schedulers) > 1:
        try:
            from multiprocessing import Process
        except:
            die('Sorry, -K/--scheduler only supported for Python 2.6+')

    if options.gae:
        # write app.yaml, gaehandler.py, and exit
        if not os.path.exists('app.yaml'):
            name = options.gae
            # for backward compatibility
            if name == 'configure':
                if PY2: input = raw_input
                name = input("Your GAE app name: ")
            content = open(os.path.join('examples', 'app.example.yaml'), 'rb').read()
            open('app.yaml', 'wb').write(content.replace("yourappname", name))
        else:
            print("app.yaml alreday exists in the web2py folder")
        if not os.path.exists('gaehandler.py'):
            content = open(os.path.join('handlers', 'gaehandler.py'), 'rb').read()
            open('gaehandler.py', 'wb').write(content)
        else:
            print("gaehandler.py alreday exists in the web2py folder")
        return

    logger = logging.getLogger("web2py")
    logger.setLevel(options.log_level)
    logging.getLogger().setLevel(options.log_level) # root logger

    # on new installation build the scaffolding app
    create_welcome_w2p()

    if options.run_system_tests:
        # run system test and exit
        run_system_tests(options)

    if options.quiet:
        # to prevent writes on stdout set a null stream
        class NullFile(object):
            def write(self, x):
                pass
        sys.stdout = NullFile()
        # but still has to mute existing loggers, to do that iterate
        # over all existing loggers (root logger included) and remove
        # all attached logging.StreamHandler instances currently
        # streaming on sys.stdout or sys.stderr
        loggers = [logging.getLogger()]
        loggers.extend(logging.Logger.manager.loggerDict.values())
        for l in loggers:
            if isinstance(l, logging.PlaceHolder): continue
            for h in l.handlers[:]:
                if isinstance(h, logging.StreamHandler) and \
                    h.stream in (sys.stdout, sys.stderr):
                    l.removeHandler(h)
        # NOTE: stderr.write() is still working

    if not options.no_banner:
        # banner
        print(ProgramName)
        print(ProgramAuthor)
        print(ProgramVersion)
        from pydal.drivers import DRIVERS
        print('Database drivers available: %s' % ', '.join(DRIVERS))

    if options.run_doctests:
        # run doctests and exit
        test(options.run_doctests, verbose=options.verbose)
        return

    if options.shell:
        # run interactive shell and exit
        sys.argv = [options.run or ''] + options.args
        run(options.shell, plain=options.plain, bpython=options.bpython,
            import_models=options.import_models, startfile=options.run,
            cron_job=options.cron_job, force_migrate=options.force_migrate,
            fake_migrate=options.fake_migrate)
        return

    # set size of cron thread pools
    newcron.dancer_size(options.min_threads)
    newcron.launcher_size(options.cron_threads)

    if options.cron_run:
        # run cron (extcron) and exit
        logger.debug('Running extcron...')
        global_settings.web2py_crontype = 'external'
        newcron.extcron(options.folder, apps=options.crontabs)
        return

    if not options.with_scheduler and options.schedulers:
        # run schedulers and exit
        try:
            start_schedulers(options)
        except KeyboardInterrupt:
            pass
        return

    if options.with_cron:
        if options.soft_cron:
            print('Using cron software emulation (but this is not very efficient)')
            global_settings.web2py_crontype = 'soft'
        else:
            # start hardcron thread
            logger.debug('Starting hardcron...')
            global_settings.web2py_crontype = 'hard'
            newcron.hardcron(options.folder, apps=options.crontabs).start()

    # if no password provided and have Tk library start GUI (when not
    # explicitly disabled), we also need a GUI to put in taskbar (system tray)
    # when requested
    root = None

    if (not options.no_gui and options.password == '<ask>') or options.taskbar:
        try:
            if PY2:
                import Tkinter as tkinter
            else:
                import tkinter
            root = tkinter.Tk()
        except (ImportError, OSError):
            logger.warn(
                'GUI not available because Tk library is not installed')
            options.no_gui = True
        except:
            logger.exception('cannot get Tk root window, GUI disabled')
            options.no_gui = True

    if root:
        # run GUI and exit
        root.focus_force()

        # Mac OS X - make the GUI window rise to the top
        if os.path.exists("/usr/bin/osascript"):
            applescript = """
tell application "System Events"
    set proc to first process whose unix id is %d
    set frontmost of proc to true
end tell
""" % (os.getpid())
            os.system("/usr/bin/osascript -e '%s'" % applescript)

        # web2pyDialog takes care of schedulers
        master = web2pyDialog(root, options)
        signal.signal(signal.SIGTERM, lambda a, b: master.quit())

        try:
            root.mainloop()
        except:
            master.quit()

        sys.exit()

    spt = None

    if options.with_scheduler and options.schedulers:
        # start schedulers in a separate thread
        spt = threading.Thread(target=start_schedulers, args=(options,))
        spt.start()

    # start server

    if options.password == '<ask>':
        options.password = getpass.getpass('choose a password:'******'no password, no web admin interface')

    # Use first interface IP and port if interfaces specified, since the
    # interfaces option overrides the IP (and related) options.
    if not options.interfaces:
        ip = options.ip
        port = options.port
    else:
        first_if = options.interfaces[0]
        ip = first_if[0]
        port = first_if[1]

    if options.server_key and options.server_cert:
        proto = 'https'
    else:
        proto = 'http'

    url = get_url(ip, proto=proto, port=port)

    if not options.no_banner:
        message = '\nplease visit:\n\t%s\n'
        if sys.platform.startswith('win'):
            message += 'use "taskkill /f /pid %i" to shutdown the web2py server\n\n'
        else:
            message += 'use "kill -SIGTERM %i" to shutdown the web2py server\n\n'
        print(message % (url, os.getpid()))

    # enhance linecache.getline (used by debugger) to look at the source file
    # if the line was not found (under py2exe & when file was modified)
    import linecache
    py2exe_getline = linecache.getline

    def getline(filename, lineno, *args, **kwargs):
        line = py2exe_getline(filename, lineno, *args, **kwargs)
        if not line:
            try:
                with open(filename, "rb") as f:
                    for i, line in enumerate(f):
                        line = line.decode('utf-8')
                        if lineno == i + 1:
                            break
                    else:
                        line = ''
            except (IOError, OSError):
                line = ''
        return line
    linecache.getline = getline

    server = main.HttpServer(ip=ip,
                             port=port,
                             password=options.password,
                             pid_filename=options.pid_filename,
                             log_filename=options.log_filename,
                             profiler_dir=options.profiler_dir,
                             ssl_certificate=options.server_cert,
                             ssl_private_key=options.server_key,
                             ssl_ca_certificate=options.ca_cert,
                             min_threads=options.min_threads,
                             max_threads=options.max_threads,
                             server_name=options.server_name,
                             request_queue_size=options.request_queue_size,
                             timeout=options.timeout,
                             socket_timeout=options.socket_timeout,
                             shutdown_timeout=options.shutdown_timeout,
                             path=options.folder,
                             interfaces=options.interfaces)

    try:
        server.start()
    except KeyboardInterrupt:
        server.stop()
        if spt is not None:
            try:
                spt.join()
            except:
                logger.exception('error terminating schedulers')
    logging.shutdown()
Example #7
0
def parse_args(parser, cli_args, deprecated_opts, integer_log_level,
               namespace=None):

    #print('PARSING ARGS:', cli_args)
    del integer_log_level[:]
    options = parser.parse_args(cli_args, namespace)
    #print('PARSED OPTIONS:', options)

    # warn for deprecated options
    deprecated_args = [a for a in cli_args if a in deprecated_opts]
    for da in deprecated_args:
        # verify if it was a real option by looking into
        # parsed values for the actual destination
        hint = deprecated_opts[da]
        dest = (hint or da).lstrip('-')
        default = parser.get_default(dest)
        if da == '--interfaces':
            hint = '--interface'
        if getattr(options, dest) is not default:
            # the option has been specified
            msg = "%s is deprecated" % da
            if hint:
                msg += ", use %s instead" % hint
            warn(msg)
    # warn for deprecated values
    if integer_log_level and '--debug' not in deprecated_args:
        warn('integer argument for -D/--log_level is deprecated, '
             'use label instead')
    # fix schedulers and die if all were skipped
    if None in options.schedulers:
        options.schedulers = [i for i in options.schedulers if i is not None]
        if not options.schedulers:
            die('no scheduler left')
    # fix crontabs and die if all were skipped
    if None in options.crontabs:
        options.crontabs = [i for i in options.crontabs if i is not None]
        if not options.crontabs:
            die('no crontab left')
    # taskbar
    if options.taskbar and os.name != 'nt':
        warn('--taskbar not supported on this platform, skipped')
        options.taskbar = False
    # options consistency checkings
    if options.run and not options.shell:
        die('-R/--run requires -S/--shell', exit_status=2)
    if options.args and not options.run:
        die('-A/--args requires -R/--run', exit_status=2)
    if options.with_scheduler and not options.schedulers:
        die('-X/--with_scheduler requires -K/--scheduler', exit_status=2)
    if options.soft_cron and not options.with_cron:
        die('--soft_cron requires -Y/--with_cron', exit_status=2)
    if options.shell:
        for o, os in dict(with_scheduler='-X/--with_scheduler',
                          schedulers='-K/--scheduler',
                          with_cron='-Y/--with_cron',
                          cron_run='-C/--cron_run',
                          run_doctests='-T/--run_doctests',
                          run_system_tests='--run_system_tests').items():
            if getattr(options, o):
                die("-S/--shell and %s are conflicting options" % os,
                    exit_status=2)
    if options.bpython and options.plain:
        die('-B/--bpython and -P/--plain are conflicting options',
            exit_status=2)
    if options.cron_run:
        for o, os in dict(with_cron='-Y/--with_cron',
                          run_doctests='-T/--run_doctests',
                          run_system_tests='--run_system_tests').items():
            if getattr(options, o):
                die("-C/--cron_run and %s are conflicting options" % os,
                    exit_status=2)
    if options.run_doctests and options.run_system_tests:
        die('-T/--run_doctests and --run_system_tests are conflicting options',
            exit_status=2)

    if options.config:
        # load options from file,
        # all options sourced from file that evaluates to False
        # are skipped, the special IGNORE value is used for this
        store_true = lambda v: True if v else IGNORE
        str_or_default = lambda v : str(v) if v else IGNORE
        list_or_default = lambda v : (
            [str(i) for i in v] if isinstance(v, list) else [str(v)]) if v \
            else IGNORE
        # NOTE: 'help', 'version', 'folder', 'cron_job' and 'GAE' are not
        #       sourced from file, the same applies to deprecated options
        opt_map = {
            # global options
            'config': str_or_default,
            'add_options': store_true,
            'password': str_or_default,
            'errors_to_console': store_true,
            'no_banner': store_true,
            'quiet': store_true,
            'log_level': str_or_default,
            # GUI options
            'no_gui': store_true,
            'taskbar': store_true,
            # console options
            'shell': str_or_default,
            'bpython': store_true,
            'plain': store_true,
            'import_models': store_true,
            'run': str_or_default,
            'args': list_or_default,
            # web server options
            'server_name': str_or_default,
            'ip': str_or_default,
            'port': str_or_default,
            'server_key': str_or_default,
            'server_cert': str_or_default,
            'ca_cert': str_or_default,
            'interface': list_or_default,
            'pid_filename': str_or_default,
            'log_filename': str_or_default,
            'min_threads': str_or_default,
            'max_threads': str_or_default,
            'request_queue_size': str_or_default,
            'timeout': str_or_default,
            'socket_timeout': str_or_default,
            'profiler_dir': str_or_default,
            # scheduler options
            'with_scheduler': store_true,
            'scheduler': list_or_default,
            # cron options
            'with_cron': store_true,
            'crontab': list_or_default,
            'soft_cron': store_true,
            'cron_run': store_true,
            # test options
            'verbose': store_true,
            'run_doctests': str_or_default,
            'run_system_tests': store_true,
            'with_coverage': store_true,
        }
        od = load_config(options.config, opt_map)
        #print("LOADED FROM %s:" % options.config, od)
        # convert loaded options dict as retuned by load_config
        # into a list of arguments for further parsing by parse_args
        file_args = []; args_args = [] # '--args' must be the last
        for key, val in od.items():
            if key != 'args':
                file_args.append('--' + key)
                if isinstance(val, list): file_args.extend(val)
                elif not isinstance(val, bool): file_args.append(val)
            else:
                args_args = ['--args'] + val
        file_args += args_args

        if options.add_options:
            # add options to existing ones,
            # must clear config to avoid infinite recursion
            options.config = options.add_options = None
            return parse_args(parser, file_args,
                deprecated_opts, integer_log_level, options)
        return parse_args(parser, file_args,
            deprecated_opts, integer_log_level)

    return options
Example #8
0
def console():
    """ Defines the behavior of the console web2py execution """
    import optparse

    parser = optparse.OptionParser(
        usage='python %prog [options]',
        version=ProgramVersion,
        description='web2py Web Framework startup script.',
        epilog='''NOTE: unless a password is specified (-a 'passwd')
web2py will attempt to run a GUI to ask for it when starting the web server
(if not disabled with --nogui).''')

    parser.add_option('-i', '--ip',
                      default='127.0.0.1',
                      metavar='IP_ADDR', help=\
        'IP address of the server (e.g., 127.0.0.1 or ::1); ' \
        'Note: This value is ignored when using the --interfaces option')

    parser.add_option('-p', '--port',
                      default=8000,
                      type='int', metavar='NUM', help=\
        'port of server (%default); ' \
        'Note: This value is ignored when using the --interfaces option')

    parser.add_option('-G', '--GAE', dest='gae',
                      default=None,
                      metavar='APP_NAME', help=\
        'will create app.yaml and gaehandler.py and exit')

    parser.add_option('-a', '--password',
                      default='<ask>',
                      help=\
        'password to be used for administration ' \
        '(use "<recycle>" to reuse the last password), ' \
        'when no password is available the administrative ' \
        'interface will be disabled')

    parser.add_option('-c',
                      '--ssl_certificate',
                      default=None,
                      metavar='FILE',
                      help='server certificate file')

    parser.add_option('-k',
                      '--ssl_private_key',
                      default=None,
                      metavar='FILE',
                      help='server private key file')

    parser.add_option(
        '--ca-cert',
        dest='ca_cert',  # not needed
        default=None,
        metavar='FILE',
        help='CA certificate file')

    parser.add_option('-d',
                      '--pid_filename',
                      default='httpserver.pid',
                      metavar='FILE',
                      help='server pid file (%default)')

    parser.add_option('-l',
                      '--log_filename',
                      default='httpserver.log',
                      metavar='FILE',
                      help='server log file (%default)')

    parser.add_option('-n',
                      '--numthreads',
                      default=None,
                      type='int',
                      metavar='NUM',
                      help='number of threads (deprecated)')

    parser.add_option('--minthreads',
                      default=None,
                      type='int',
                      metavar='NUM',
                      help='minimum number of server threads')

    parser.add_option('--maxthreads',
                      default=None,
                      type='int',
                      metavar='NUM',
                      help='maximum number of server threads')

    parser.add_option('-s',
                      '--server_name',
                      default=socket.gethostname(),
                      help='web server name (%default)')

    parser.add_option('-q', '--request_queue_size',
                      default=5,
                      type='int', metavar='NUM',
                      help=\
        'max number of queued requests when server unavailable (%default)')

    parser.add_option('-o',
                      '--timeout',
                      default=10,
                      type='int',
                      metavar='SECONDS',
                      help='timeout for individual request (%default seconds)')

    parser.add_option('-z', '--shutdown_timeout',
                      default=None,
                      type='int', metavar='SECONDS',
                      help=\
        'timeout on server shutdown; this value is not used by ' \
        'Rocket web server')

    parser.add_option(
        '--socket-timeout',
        dest='socket_timeout',  # not needed
        default=5,
        type='int',
        metavar='SECONDS',
        help='timeout for socket (%default seconds)')

    parser.add_option('-f',
                      '--folder',
                      default=os.getcwd(),
                      metavar='WEB2PY_DIR',
                      help='folder from which to run web2py')

    parser.add_option('-v',
                      '--verbose',
                      default=False,
                      action='store_true',
                      help='increase --test and --run_system_tests verbosity')

    parser.add_option('-Q',
                      '--quiet',
                      default=False,
                      action='store_true',
                      help='disable all output')

    parser.add_option('-e',
                      '--errors_to_console',
                      default=False,
                      action='store_true',
                      help='log all errors to console')

    parser.add_option('-D', '--debug', dest='debuglevel',
                      default=30,
                      type='int',
                      metavar='LOG_LEVEL', help=\
        'set log level (0-100, 0 means all, 100 means none; ' \
        'default is %default)')

    parser.add_option('-S', '--shell',
                      default=None,
                      metavar='APPNAME', help=\
        'run web2py in interactive shell or IPython (if installed) with ' \
        'specified appname (if app does not exist it will be created). ' \
        'APPNAME like a/c/f?x=y (c, f and vars optional)')

    parser.add_option('-B', '--bpython',
                      default=False,
                      action='store_true',
                      help=\
        'run web2py in interactive shell or bpython (if installed) with ' \
        'specified appname (if app does not exist it will be created). ' \
        'Use combined with --shell')

    parser.add_option('-P', '--plain',
                      default=False,
                      action='store_true',
                      help=\
        'only use plain python shell; should be used with --shell option')

    parser.add_option('-M', '--import_models',
                      default=False,
                      action='store_true',
                      help=\
        'auto import model files (default is %default); should be used ' \
        'with --shell option')

    parser.add_option('-R', '--run',
                      default='', # NOTE: used for sys.argv[0] if --shell
                      metavar='PYTHON_FILE', help=\
        'run PYTHON_FILE in web2py environment; ' \
        'should be used with --shell option')

    parser.add_option('-K', '--scheduler',
                      default=None,
                      metavar='APP_LIST', help=\
        'run scheduled tasks for the specified apps: expects a list of ' \
        'app names as app1,app2,app3 ' \
        'or a list of app:groups as app1:group1:group2,app2:group1 ' \
        '(only strings, no spaces allowed). NOTE: ' \
        'Requires a scheduler defined in the models')

    parser.add_option('-X', '--with-scheduler', dest='with_scheduler', # not needed
                      default=False,
                      action='store_true',
                      help=\
        'run schedulers alongside webserver, needs -K')

    parser.add_option('-T', '--test',
                      default=None,
                      metavar='TEST_PATH', help=\
        'run doctests in web2py environment; ' \
        'TEST_PATH like a/c/f (c, f optional)')

    parser.add_option('-C', '--cron', dest='extcron',
                      default=False,
                      action='store_true',
                      help=\
        'trigger a cron run and exit; usually used when invoked ' \
        'from a system crontab')

    parser.add_option('--softcron',
                      default=False,
                      action='store_true',
                      help=\
        'use software cron emulation instead of separate cron process, '\
        'needs -Y; NOTE: use of software cron emulation is strongly '
        'discouraged')

    parser.add_option('-Y',
                      '--run-cron',
                      dest='runcron',
                      default=False,
                      action='store_true',
                      help='start the background cron process')

    parser.add_option(
        '-J',
        '--cronjob',
        default=False,
        action='store_true',
        # NOTE: help suppressed because this option is
        #       intended for internal use only
        help=optparse.SUPPRESS_HELP)

    parser.add_option('-L', '--config', default='', help='config file')

    parser.add_option('-F',
                      '--profiler',
                      dest='profiler_dir',
                      default=None,
                      help='profiler dir')

    parser.add_option('-t',
                      '--taskbar',
                      default=False,
                      action='store_true',
                      help='use web2py GUI and run in taskbar (system tray)')

    parser.add_option('--nogui',
                      default=False,
                      action='store_true',
                      help='do not run GUI')

    parser.add_option('-A', '--args',
                      default=None,
                      help=\
        'should be followed by a list of arguments to be passed to script, ' \
        'to be used with -S; NOTE: must be the last option because eat all ' \
        'remaining arguments')

    parser.add_option(
        '--no-banner',
        dest='no_banner',  # not needed
        default=False,
        action='store_true',
        help='do not print header banner')

    parser.add_option('--interfaces',
                      default=None,
                      help=\
        'listen on multiple addresses: ' \
        '"ip1:port1:key1:cert1:ca_cert1;ip2:port2:key2:cert2:ca_cert2;..." ' \
        '(:key:cert:ca_cert optional; no spaces; IPv6 addresses must be in ' \
        'square [] brackets)')

    parser.add_option('--run_system_tests',
                      default=False,
                      action='store_true',
                      help='run web2py tests')

    parser.add_option('--with_coverage',
                      default=False,
                      action='store_true',
                      help=\
        'collect coverage data when used with --run_system_tests; ' \
        'require Python 2.7+ and the coverage module installed')

    if '-A' in sys.argv:
        k = sys.argv.index('-A')
    elif '--args' in sys.argv:
        k = sys.argv.index('--args')
    else:
        k = len(sys.argv)
    sys.argv, other_args = sys.argv[:k], sys.argv[k + 1:]
    (options, args) = parser.parse_args()
    # TODO: warn or error if args (should be no unparsed arguments)
    options.args = other_args

    if options.taskbar and os.name != 'nt':
        # TODO: warn and disable taskbar instead of exit
        die('taskbar not supported on this platform')

    if options.config.endswith('.py'):
        options.config = options.config[:-3]
    if options.config:
        # import options from options.config file
        try:
            # FIXME: avoid __import__
            options2 = __import__(options.config)
        except:
            die("cannot import config file %s" % options.config)
        for key in dir(options2):
            if hasattr(options, key):
                setattr(options, key, getattr(options2, key))

    # transform options.interfaces, in the form
    # "ip1:port1:key1:cert1:ca_cert1;[ip2]:port2;ip3:port3:key3:cert3"
    # (no spaces; optional key:cert:ca_cert indicate SSL), into
    # a list of tuples
    if options.interfaces:
        interfaces = options.interfaces.split(';')
        options.interfaces = []
        for interface in interfaces:
            if interface.startswith('['):
                # IPv6
                ip, if_remainder = interface.split(']', 1)
                ip = ip[1:]
                interface = if_remainder[1:].split(':')
                interface.insert(0, ip)
            else:
                # IPv4
                interface = interface.split(':')
            interface[1] = int(interface[1])  # numeric port
            options.interfaces.append(tuple(interface))

    if options.numthreads is not None and options.minthreads is None:
        options.minthreads = options.numthreads  # legacy

    copy_options = copy.deepcopy(options)
    copy_options.password = '******'
    global_settings.cmd_options = copy_options

    return options, args
Example #9
0
def start(cron=True):
    """ Starts server and other services """

    # get command line arguments
    (options, args) = console()

    if options.gae:
        # write app.yaml, gaehandler.py, and exit
        if not os.path.exists('app.yaml'):
            name = options.gae
            # for backward compatibility
            if name == 'configure':
                if PY2: input = raw_input
                name = input("Your GAE app name: ")
            content = open(os.path.join('examples', 'app.example.yaml'), 'rb').read()
            open('app.yaml', 'wb').write(content.replace("yourappname", name))
        else:
            print("app.yaml alreday exists in the web2py folder")
        if not os.path.exists('gaehandler.py'):
            content = open(os.path.join('handlers', 'gaehandler.py'), 'rb').read()
            open('gaehandler.py', 'wb').write(content)
        else:
            print("gaehandler.py alreday exists in the web2py folder")
        return

    create_welcome_w2p()

    if options.run_system_tests:
        # run system test and exit
        run_system_tests(options)

    if options.quiet:
        # to prevent writes on stdout set a null stream
        class NullFile(object):
            def write(self, x):
                pass
        sys.stdout = NullFile()
        # but still has to mute existing loggers, to do that iterate
        # over all existing loggers (root logger included) and remove
        # all attached logging.StreamHandler instances currently
        # streaming on sys.stdout or sys.stderr
        loggers = [logging.getLogger()]
        loggers.extend(logging.Logger.manager.loggerDict.values())
        for l in loggers:
            if isinstance(l, logging.PlaceHolder): continue
            for h in l.handlers[:]:
                if isinstance(h, logging.StreamHandler) and \
                    h.stream in (sys.stdout, sys.stderr):
                    l.removeHandler(h)
        # NOTE: stderr.write() is still working

    logger.setLevel(options.debuglevel)

    if not options.nobanner:
        # banner
        print(ProgramName)
        print(ProgramAuthor)
        print(ProgramVersion)
        from pydal.drivers import DRIVERS
        print('Database drivers available: %s' % ', '.join(DRIVERS))

    if options.test:
        # run doctests and exit
        test(options.test, verbose=options.verbose)
        return

    if options.shell:
        # run interactive shell and exit
        sys.argv = [options.run] + options.args
        run(options.shell, plain=options.plain, bpython=options.bpython,
            import_models=options.import_models, startfile=options.run,
            cronjob=options.cronjob)
        return

    if options.extcron:
        # run cron (extcron) and exit
        logger.debug('Starting extcron...')
        global_settings.web2py_crontype = 'external'
        if options.scheduler:
            # run cron for applications listed with --scheduler (-K)
            apps = [app.strip() for app in options.scheduler.split(
                ',') if check_existent_app(options, app.strip())]
        else:
            apps = None
        extcron = newcron.extcron(options.folder, apps=apps)
        extcron.start()
        extcron.join()
        return

    if options.scheduler and not options.with_scheduler:
        # run schedulers and exit
        try:
            start_schedulers(options)
        except KeyboardInterrupt:
            pass
        return

    if cron and options.runcron:
        if options.softcron:
            print('Using softcron (but this is not very efficient)')
            global_settings.web2py_crontype = 'soft'
        else:
            # start hardcron thread
            logger.debug('Starting hardcron...')
            global_settings.web2py_crontype = 'hard'
            newcron.hardcron(options.folder).start()

    # if no password provided and have Tk library start GUI (when not
    # explicitly disabled), we also need a GUI to put in taskbar (system tray)
    # when requested

    # FIXME: this check should be done first
    if options.taskbar and os.name != 'nt':
        die('taskbar not supported on this platform')

    root = None

    if (not options.nogui and options.password == '<ask>') or options.taskbar:
        try:
            if PY2:
                import Tkinter as tkinter
            else:
                import tkinter
            root = tkinter.Tk()
        except (ImportError, OSError):
            logger.warn(
                'GUI not available because Tk library is not installed')
            options.nogui = True
        except:
            logger.exception('cannot get Tk root window, GUI disabled')
            options.nogui = True

    if root:
        # run GUI and exit
        root.focus_force()

        # Mac OS X - make the GUI window rise to the top
        if os.path.exists("/usr/bin/osascript"):
            applescript = """
tell application "System Events"
    set proc to first process whose unix id is %d
    set frontmost of proc to true
end tell
""" % (os.getpid())
            os.system("/usr/bin/osascript -e '%s'" % applescript)

        # web2pyDialog takes care of schedulers
        master = web2pyDialog(root, options)
        signal.signal(signal.SIGTERM, lambda a, b: master.quit())

        try:
            root.mainloop()
        except:
            master.quit()

        sys.exit()

    if options.password == '<ask>':
        options.password = getpass.getpass('choose a password:'******'no password, disable admin interface')

    spt = None

    if options.scheduler and options.with_scheduler:
        # start schedulers in a separate thread
        spt = threading.Thread(target=start_schedulers, args=(options,))
        spt.start()

    # start server

    # Use first interface IP and port if interfaces specified, since the
    # interfaces option overrides the IP (and related) options.
    if not options.interfaces:
        ip = options.ip
        port = options.port
    else:
        first_if = options.interfaces[0]
        ip = first_if[0]
        port = first_if[1]

    if options.ssl_certificate and options.ssl_private_key:
        proto = 'https'
    else:
        proto = 'http'

    url = get_url(ip, proto=proto, port=port)

    if not options.nobanner:
        message = '\nplease visit:\n\t%s\n'
        if sys.platform.startswith('win'):
            message += 'use "taskkill /f /pid %i" to shutdown the web2py server\n\n'
        else:
            message += 'use "kill -SIGTERM %i" to shutdown the web2py server\n\n'
        print(message % (url, os.getpid()))

    # enhance linecache.getline (used by debugger) to look at the source file
    # if the line was not found (under py2exe & when file was modified)
    import linecache
    py2exe_getline = linecache.getline

    def getline(filename, lineno, *args, **kwargs):
        line = py2exe_getline(filename, lineno, *args, **kwargs)
        if not line:
            try:
                with open(filename, "rb") as f:
                    for i, line in enumerate(f):
                        line = line.decode('utf-8')
                        if lineno == i + 1:
                            break
                    else:
                        line = ''
            except (IOError, OSError):
                line = ''
        return line
    linecache.getline = getline

    server = main.HttpServer(ip=ip,
                             port=port,
                             password=options.password,
                             pid_filename=options.pid_filename,
                             log_filename=options.log_filename,
                             profiler_dir=options.profiler_dir,
                             ssl_certificate=options.ssl_certificate,
                             ssl_private_key=options.ssl_private_key,
                             ssl_ca_certificate=options.ssl_ca_certificate,
                             min_threads=options.minthreads,
                             max_threads=options.maxthreads,
                             server_name=options.server_name,
                             request_queue_size=options.request_queue_size,
                             timeout=options.timeout,
                             socket_timeout=options.socket_timeout,
                             shutdown_timeout=options.shutdown_timeout,
                             path=options.folder,
                             interfaces=options.interfaces)

    try:
        server.start()
    except KeyboardInterrupt:
        server.stop()
        if spt is not None:
            try:
                spt.join()
            except:
                logger.exception('error terminating schedulers')
                pass
    logging.shutdown()
Example #10
0
def console():
    """ Defines the behavior of the console web2py execution """
    import optparse

    parser = optparse.OptionParser(
        usage='python %prog [options]',
        version=ProgramVersion,
        description='web2py Web Framework startup script.',
        epilog='''NOTE: unless a password is specified (-a 'passwd')
web2py will attempt to run a GUI to ask for it when starting the web server
(if not disabled with --nogui).''')

    parser.add_option('-i', '--ip',
                      default='127.0.0.1',
                      metavar='IP_ADDR', help=\
        'IP address of the server (e.g., 127.0.0.1 or ::1); ' \
        'Note: This value is ignored when using the --interfaces option')

    parser.add_option('-p', '--port',
                      default=8000,
                      type='int', metavar='NUM', help=\
        'port of server (%default); ' \
        'Note: This value is ignored when using the --interfaces option')

    parser.add_option('-G', '--GAE', dest='gae',
                      default=None,
                      metavar='APP_NAME', help=\
        'will create app.yaml and gaehandler.py and exit')

    parser.add_option('-a', '--password',
                      default='<ask>',
                      help=\
        'password to be used for administration ' \
        '(use "<recycle>" to reuse the last password), ' \
        'when no password is available the administrative ' \
        'interface will be disabled')

    parser.add_option('-c', '--ssl_certificate',
                      default=None,
                      metavar='FILE', help='server certificate file')

    parser.add_option('-k', '--ssl_private_key',
                      default=None,
                      metavar='FILE', help='server private key file')

    parser.add_option('--ca-cert', dest='ssl_ca_certificate',
                      default=None,
                      metavar='FILE', help='CA certificate file')

    parser.add_option('-d', '--pid_filename',
                      default='httpserver.pid',
                      metavar='FILE', help='server pid file (%default)')

    parser.add_option('-l', '--log_filename',
                      default='httpserver.log',
                      metavar='FILE', help='server log file (%default)')

    parser.add_option('-n', '--numthreads',
                      default=None,
                      type='int', metavar='NUM',
                      help='number of threads (deprecated)')

    parser.add_option('--minthreads',
                      default=None,
                      type='int', metavar='NUM',
                      help='minimum number of server threads')

    parser.add_option('--maxthreads',
                      default=None,
                      type='int', metavar='NUM',
                      help='maximum number of server threads')

    parser.add_option('-s', '--server_name',
                      default=socket.gethostname(),
                      help='web server name (%default)')

    parser.add_option('-q', '--request_queue_size',
                      default=5,
                      type='int', metavar='NUM',
                      help=\
        'max number of queued requests when server unavailable (%default)')

    parser.add_option('-o', '--timeout',
                      default=10,
                      type='int', metavar='SECONDS',
                      help='timeout for individual request (%default seconds)')

    parser.add_option('-z', '--shutdown_timeout',
                      default=None,
                      type='int', metavar='SECONDS',
                      help=\
        'timeout on server shutdown; this value is not used by ' \
        'Rocket web server')

    parser.add_option('--socket-timeout', dest='socket_timeout', # not needed
                      default=5,
                      type='int', metavar='SECONDS',
                      help='timeout for socket (%default seconds)')

    parser.add_option('-f', '--folder',
                      default=os.getcwd(), metavar='WEB2PY_DIR',
                      help='folder from which to run web2py')

    parser.add_option('-v', '--verbose',
                      default=False,
                      action='store_true',
                      help='increase --test and --run_system_tests verbosity')

    parser.add_option('-Q', '--quiet',
                      default=False,
                      action='store_true',
                      help='disable all output')

    parser.add_option('-e', '--errors_to_console', dest='print_errors',
                      default=False,
                      action='store_true',
                      help='log all errors to console')

    parser.add_option('-D', '--debug', dest='debuglevel',
                      default=30,
                      type='int',
                      metavar='LOG_LEVEL', help=\
        'set log level (0-100, 0 means all, 100 means none; ' \
        'default is %default)')

    parser.add_option('-S', '--shell',
                      default=None,
                      metavar='APPNAME', help=\
        'run web2py in interactive shell or IPython (if installed) with ' \
        'specified appname (if app does not exist it will be created). ' \
        'APPNAME like a/c/f?x=y (c,f and vars x,y optional)')

    parser.add_option('-B', '--bpython',
                      default=False,
                      action='store_true',
                      help=\
        'run web2py in interactive shell or bpython (if installed) with ' \
        'specified appname (if app does not exist it will be created). ' \
        'Use combined with --shell')

    parser.add_option('-P', '--plain',
                      default=False,
                      action='store_true',
                      help=\
        'only use plain python shell; should be used with --shell option')

    parser.add_option('-M', '--import_models',
                      default=False,
                      action='store_true',
                      help=\
        'auto import model files (default is %default); should be used ' \
        'with --shell option')

    parser.add_option('-R', '--run',
                      default='', # NOTE: used for sys.argv[0] if --shell
                      metavar='PYTHON_FILE', help=\
        'run PYTHON_FILE in web2py environment; ' \
        'should be used with --shell option')

    parser.add_option('-K', '--scheduler',
                      default=None,
                      metavar='APP_LIST', help=\
        'run scheduled tasks for the specified apps: expects a list of ' \
        'app names as app1,app2,app3 ' \
        'or a list of app:groups as app1:group1:group2,app2:group1 ' \
        '(only strings, no spaces allowed). NOTE: ' \
        'Requires a scheduler defined in the models')

    parser.add_option('-X', '--with-scheduler', dest='with_scheduler', # not needed
                      default=False,
                      action='store_true',
                      help=\
        'run schedulers alongside webserver, needs -K')

    parser.add_option('-T', '--test',
                      default=None,
                      metavar='TEST_PATH', help=\
        'run doctests in web2py environment; ' \
        'TEST_PATH like a/c/f (c,f optional)')

    parser.add_option('-C', '--cron', dest='extcron',
                      default=False,
                      action='store_true',
                      help=\
        'trigger a cron run and exit; usually used when invoked ' \
        'from a system crontab')

    parser.add_option('--softcron',
                      default=False,
                      action='store_true',
                      help=\
        'use software cron emulation instead of separate cron process, '\
        'needs -Y; NOTE: use of software cron emulation is strongly '
        'discouraged')

    parser.add_option('-Y', '--run-cron', dest='runcron',
                      default=False,
                      action='store_true',
                      help='start the background cron process')

    parser.add_option('-J', '--cronjob',
                      default=False,
                      action='store_true',
                      help='identify cron-initiated command')

    parser.add_option('-L', '--config',
                      default='',
                      help='config file')

    parser.add_option('-F', '--profiler', dest='profiler_dir',
                      default=None,
                      help='profiler dir')

    parser.add_option('-t', '--taskbar',
                      default=False,
                      action='store_true',
                      help='use web2py GUI and run in taskbar (system tray)')

    parser.add_option('--nogui',
                      default=False,
                      action='store_true',
                      help='do not run GUI')

    parser.add_option('-A', '--args',
                      default=None,
                      help=\
        'should be followed by a list of arguments to be passed to script, ' \
        'to be used with -S; NOTE: must be the last option because eat all ' \
        'remaining arguments')

    parser.add_option('--no-banner', dest='nobanner',
                      default=False,
                      action='store_true',
                      help='do not print header banner')

    parser.add_option('--interfaces',
                      default=None,
                      help=\
        'listen on multiple addresses: ' \
        '"ip1:port1:key1:cert1:ca_cert1;ip2:port2:key2:cert2:ca_cert2;..." ' \
        '(:key:cert:ca_cert optional; no spaces; IPv6 addresses must be in ' \
        'square [] brackets)')

    parser.add_option('--run_system_tests',
                      default=False,
                      action='store_true',
                      help='run web2py tests')

    parser.add_option('--with_coverage',
                      default=False,
                      action='store_true',
                      help=\
        'adds coverage reporting (should be used with --run_system_tests), ' \
        'needs Python 2.7+ and the coverage module installed. ' \
        'You can alter the default path setting the environment ' \
        'variable "COVERAGE_PROCESS_START" ' \
        '(by default it takes gluon/tests/coverage.ini)')

    if '-A' in sys.argv:
        k = sys.argv.index('-A')
    elif '--args' in sys.argv:
        k = sys.argv.index('--args')
    else:
        k = len(sys.argv)
    sys.argv, other_args = sys.argv[:k], sys.argv[k + 1:]
    (options, args) = parser.parse_args()
    # TODO: warn or error if args (should be no unparsed arguments)
    options.args = other_args

    if options.config.endswith('.py'):
        options.config = options.config[:-3]
    if options.config:
        # import options from options.config file
        try:
            # FIXME: avoid __import__
            options2 = __import__(options.config)
        except:
            die("cannot import config file %s" % options.config)
        for key in dir(options2):
            if hasattr(options, key):
                setattr(options, key, getattr(options2, key))

    # store in options.ips the list of server IP addresses
    try:
        options.ips = list(set(  # no duplicates
            [addrinfo[4][0] for addrinfo in getipaddrinfo(socket.getfqdn())
             if not is_loopback_ip_address(addrinfo=addrinfo)]))
    except socket.gaierror:
        options.ips = []

    if options.cronjob:
        global_settings.cronjob = True  # tell the world
        options.plain = True    # cronjobs use a plain shell
        options.nobanner = True
        options.nogui = True

    # transform options.interfaces, in the form
    # "ip1:port1:key1:cert1:ca_cert1;[ip2]:port2;ip3:port3:key3:cert3"
    # (no spaces; optional key:cert:ca_cert indicate SSL), into
    # a list of tuples
    if options.interfaces:
        interfaces = options.interfaces.split(';')
        options.interfaces = []
        for interface in interfaces:
            if interface.startswith('['):
                # IPv6
                ip, if_remainder = interface.split(']', 1)
                ip = ip[1:]
                interface = if_remainder[1:].split(':')
                interface.insert(0, ip)
            else:
                # IPv4
                interface = interface.split(':')
            interface[1] = int(interface[1])  # numeric port
            options.interfaces.append(tuple(interface))

    # strip group infos from options.scheduler, in the form
    # "app:group1:group2,app2:group1", and put into a list of lists
    # in options.scheduler_groups
    if options.scheduler and ':' in options.scheduler:
        sg = options.scheduler_groups = []
        for awg in options.scheduler.split(','):
            sg.append(awg.split(':'))
        options.scheduler = ','.join([app[0] for app in sg])
    else:
        options.scheduler_groups = None

    if options.numthreads is not None and options.minthreads is None:
        options.minthreads = options.numthreads  # legacy

    copy_options = copy.deepcopy(options)
    copy_options.password = '******'
    global_settings.cmd_options = copy_options

    return options, args
Example #11
0
def console():
    """ Defines the behavior of the console web2py execution """
    import optparse

    parser = optparse.OptionParser(
        usage='python %prog [options]',
        version=ProgramVersion,
        description='web2py Web Framework startup script.',
        epilog='''NOTE: unless a password is specified (-a 'passwd')
web2py will attempt to run a GUI to ask for it
(if not disabled with --nogui).''')

    parser.add_option('-i', '--ip',
                      default='127.0.0.1',
                      help=\
        'IP address of the server (e.g., 127.0.0.1 or ::1); ' \
        'Note: This value is ignored when using the --interfaces option')

    parser.add_option('-p', '--port',
                      default=8000,
                      type='int', help=\
        'port of server (%default); ' \
        'Note: This value is ignored when using the --interfaces option')

    parser.add_option('-G', '--GAE', dest='gae',
                      default=None,
                      metavar='APP_NAME', help=\
        'will create app.yaml and gaehandler.py and exit')

    parser.add_option('-a', '--password',
                      default='<ask>',
                      help=\
        'password to be used for administration ' \
        '(use -a "<recycle>" to reuse the last password))')

    parser.add_option('-c',
                      '--ssl_certificate',
                      default='',
                      help='file that contains ssl certificate')

    parser.add_option('-k',
                      '--ssl_private_key',
                      default='',
                      help='file that contains ssl private key')

    parser.add_option('--ca-cert', dest='ssl_ca_certificate',
                      default=None,
                      help=\
        'use this file containing the CA certificate to validate X509 ' \
        'certificates from clients')

    parser.add_option('-d',
                      '--pid_filename',
                      default='httpserver.pid',
                      help='file to store the pid of the server')

    parser.add_option('-l',
                      '--log_filename',
                      default='httpserver.log',
                      help='name for the server log file')

    parser.add_option('-n',
                      '--numthreads',
                      default=None,
                      type='int',
                      help='number of threads (deprecated)')

    parser.add_option('--minthreads',
                      default=None,
                      type='int',
                      help='minimum number of server threads')

    parser.add_option('--maxthreads',
                      default=None,
                      type='int',
                      help='maximum number of server threads')

    parser.add_option('-s',
                      '--server_name',
                      default=socket.gethostname(),
                      help='web server name (%default)')

    parser.add_option('-q', '--request_queue_size',
                      default='5',
                      type='int',
                      help=\
        'max number of queued requests when server unavailable')

    parser.add_option('-o',
                      '--timeout',
                      default='10',
                      type='int',
                      help='timeout for individual request (%default seconds)')

    parser.add_option('-z',
                      '--shutdown_timeout',
                      default='5',
                      type='int',
                      help='timeout on shutdown of server (%default seconds)')

    parser.add_option(
        '--socket-timeout',
        dest='socket_timeout',  # not needed
        default=5,
        type='int',
        help='timeout for socket (%default seconds)')

    parser.add_option('-f',
                      '--folder',
                      default=os.getcwd(),
                      help='folder from which to run web2py')

    parser.add_option('-v',
                      '--verbose',
                      default=False,
                      action='store_true',
                      help='increase --test and --run_system_tests verbosity')

    parser.add_option('-Q',
                      '--quiet',
                      default=False,
                      action='store_true',
                      help='disable all output')

    parser.add_option('-e',
                      '--errors_to_console',
                      dest='print_errors',
                      default=False,
                      action='store_true',
                      help='log all errors to console')

    parser.add_option('-D', '--debug', dest='debuglevel',
                      default=30,
                      type='int',
                      help=\
        'set debug output level (0-100, 0 means all, 100 means none; ' \
        'default is %default)')

    parser.add_option('-S', '--shell',
                      default=None,
                      metavar='APPNAME', help=\
        'run web2py in interactive shell or IPython (if installed) with ' \
        'specified appname (if app does not exist it will be created). ' \
        'APPNAME like a/c/f?x=y (c,f and vars x,y optional)')

    parser.add_option('-B', '--bpython',
                      default=False,
                      action='store_true',
                      help=\
        'run web2py in interactive shell or bpython (if installed) with ' \
        'specified appname (if app does not exist it will be created). ' \
        'Use combined with --shell')

    parser.add_option('-P', '--plain',
                      default=False,
                      action='store_true',
                      help=\
        'only use plain python shell; should be used with --shell option')

    parser.add_option('-M', '--import_models',
                      default=False,
                      action='store_true',
                      help=\
        'auto import model files; default is %default; should be used ' \
        'with --shell option')

    parser.add_option('-R', '--run',
                      default='', # NOTE: used for sys.argv[0] if --shell
                      metavar='PYTHON_FILE', help=\
        'run PYTHON_FILE in web2py environment; ' \
        'should be used with --shell option')

    parser.add_option('-K', '--scheduler',
                      default=None,
                      help=\
        'run scheduled tasks for the specified apps: expects a list of ' \
        'app names as -K app1,app2,app3 ' \
        'or a list of app:groups as -K app1:group1:group2,app2:group1 ' \
        'to override specific group_names. (only strings, no spaces ' \
        'allowed. Requires a scheduler defined in the models')

    parser.add_option('-X', '--with-scheduler', dest='with_scheduler', # not needed
                      default=False,
                      action='store_true',
                      help=\
        'run schedulers alongside webserver, needs -K app1 and -a too')

    parser.add_option('-T', '--test',
                      default=None,
                      metavar='TEST_PATH', help=\
        'run doctests in web2py environment; ' \
        'TEST_PATH like a/c/f (c,f optional)')

    parser.add_option('-C', '--cron', dest='extcron',
                      default=False,
                      action='store_true',
                      help=\
        'trigger a cron run manually; usually invoked from a system crontab')

    parser.add_option('--softcron',
                      default=False,
                      action='store_true',
                      help='triggers the use of softcron')

    parser.add_option('-Y',
                      '--run-cron',
                      dest='runcron',
                      default=False,
                      action='store_true',
                      help='start the background cron process')

    parser.add_option('-J',
                      '--cronjob',
                      default=False,
                      action='store_true',
                      help='identify cron-initiated command')

    parser.add_option('-L', '--config', default='', help='config file')

    parser.add_option('-F',
                      '--profiler',
                      dest='profiler_dir',
                      default=None,
                      help='profiler dir')

    parser.add_option('-t',
                      '--taskbar',
                      default=False,
                      action='store_true',
                      help='use web2py GUI and run in taskbar (system tray)')

    parser.add_option('--nogui',
                      default=False,
                      action='store_true',
                      help='do not run GUI')

    parser.add_option('-A', '--args',
                      default=None,
                      help=\
        'should be followed by a list of arguments to be passed to script, ' \
        'to be used with -S, -A must be the last option')

    parser.add_option('--no-banner',
                      dest='nobanner',
                      default=False,
                      action='store_true',
                      help='do not print header banner')

    parser.add_option('--interfaces',
                      default=None,
                      help=\
        'listen on multiple addresses: ' \
        '"ip1:port1:key1:cert1:ca_cert1;ip2:port2:key2:cert2:ca_cert2;..." ' \
        '(:key:cert:ca_cert optional; no spaces; IPv6 addresses must be in ' \
        'square [] brackets)')

    parser.add_option('--run_system_tests',
                      default=False,
                      action='store_true',
                      help='run web2py tests')

    parser.add_option('--with_coverage',
                      default=False,
                      action='store_true',
                      help=\
        'adds coverage reporting (needs --run_system_tests), ' \
        'python 2.7 and the coverage module installed. ' \
        'You can alter the default path setting the environment ' \
        'variable "COVERAGE_PROCESS_START" ' \
        '(by default it takes gluon/tests/coverage.ini)')

    if '-A' in sys.argv:
        k = sys.argv.index('-A')
    elif '--args' in sys.argv:
        k = sys.argv.index('--args')
    else:
        k = len(sys.argv)
    sys.argv, other_args = sys.argv[:k], sys.argv[k + 1:]
    (options, args) = parser.parse_args()
    # TODO: warn or error if args (should be no unparsed arguments)
    options.args = other_args

    if options.config.endswith('.py'):
        options.config = options.config[:-3]
    if options.config:
        # import options from options.config file
        try:
            # FIXME: avoid __import__
            options2 = __import__(options.config)
        except:
            die("cannot import config file %s" % options.config)
        for key in dir(options2):
            if hasattr(options, key):
                setattr(options, key, getattr(options2, key))

    try:
        options.ips = list(
            set(  # no duplicates
                [
                    addrinfo[4][0]
                    for addrinfo in getipaddrinfo(socket.getfqdn())
                    if not is_loopback_ip_address(addrinfo=addrinfo)
                ]))
    except socket.gaierror:
        options.ips = []

    if options.cronjob:
        global_settings.cronjob = True  # tell the world
        options.plain = True  # cronjobs use a plain shell
        options.nobanner = True
        options.nogui = True

    #  accept --interfaces in the form
    #  "ip1:port1:key1:cert1:ca_cert1;[ip2]:port2;ip3:port3:key3:cert3"
    #  (no spaces; optional key:cert:ca_cert indicate SSL)
    if isinstance(options.interfaces, str):
        interfaces = options.interfaces.split(';')
        options.interfaces = []
        for interface in interfaces:
            if interface.startswith('['):
                # IPv6
                ip, if_remainder = interface.split(']', 1)
                ip = ip[1:]
                interface = if_remainder[1:].split(':')
                interface.insert(0, ip)
            else:
                # IPv4
                interface = interface.split(':')
            interface[1] = int(interface[1])  # numeric port
            options.interfaces.append(tuple(interface))

    #  accepts --scheduler in the form
    #  "app:group1:group2,app2:group1"
    scheduler = []
    options.scheduler_groups = None
    if isinstance(options.scheduler, str):
        if ':' in options.scheduler:
            for opt in options.scheduler.split(','):
                scheduler.append(opt.split(':'))
            options.scheduler = ','.join([app[0] for app in scheduler])
            options.scheduler_groups = scheduler

    if options.numthreads is not None and options.minthreads is None:
        options.minthreads = options.numthreads  # legacy

    copy_options = copy.deepcopy(options)
    copy_options.password = '******'
    global_settings.cmd_options = copy_options
    # FIXME: do we still really need this?
    global_settings.cmd_args = args

    return options, args
Example #12
0
def start(cron=True):
    """ Starts server and other services """

    # get command line arguments
    (options, args) = console()

    if options.gae:
        # write app.yaml, gaehandler.py, and exit
        if not os.path.exists('app.yaml'):
            name = options.gae
            # for backward compatibility
            if name == 'configure':
                if PY2: input = raw_input
                name = input("Your GAE app name: ")
            content = open(os.path.join('examples', 'app.example.yaml'),
                           'rb').read()
            open('app.yaml', 'wb').write(content.replace("yourappname", name))
        else:
            print("app.yaml alreday exists in the web2py folder")
        if not os.path.exists('gaehandler.py'):
            content = open(os.path.join('handlers', 'gaehandler.py'),
                           'rb').read()
            open('gaehandler.py', 'wb').write(content)
        else:
            print("gaehandler.py alreday exists in the web2py folder")
        return

    create_welcome_w2p()

    if options.run_system_tests:
        # run system test and exit
        run_system_tests(options)

    if options.quiet:
        capture = StringIO()
        sys.stdout = capture
        logger.setLevel(logging.CRITICAL + 1)
    else:
        logger.setLevel(options.debuglevel)

    if not options.nobanner:
        # banner
        print(ProgramName)
        print(ProgramAuthor)
        print(ProgramVersion)
        from pydal.drivers import DRIVERS
        print('Database drivers available: %s' % ', '.join(DRIVERS))

    if options.test:
        # run doctests and exit
        test(options.test, verbose=options.verbose)
        return

    if options.shell:
        # run interactive shell and exit
        if options.folder:
            os.chdir(options.folder)
        sys.argv = [options.run] + options.args
        run(options.shell,
            plain=options.plain,
            bpython=options.bpython,
            import_models=options.import_models,
            startfile=options.run,
            cronjob=options.cronjob)
        return

    if options.extcron:
        # run cron (extcron) and exit
        logger.debug('Starting extcron...')
        global_settings.web2py_crontype = 'external'
        if options.scheduler:
            # run cron for applications listed with --scheduler (-K)
            apps = [
                app.strip() for app in options.scheduler.split(',')
                if check_existent_app(options, app.strip())
            ]
        else:
            apps = None
        extcron = newcron.extcron(options.folder, apps=apps)
        extcron.start()
        extcron.join()
        return

    if options.scheduler and not options.with_scheduler:
        # run schedulers and exit
        try:
            start_schedulers(options)
        except KeyboardInterrupt:
            pass
        return

    if cron and options.runcron:
        if options.softcron:
            print('Using softcron (but this is not very efficient)')
            global_settings.web2py_crontype = 'soft'
        else:
            # start hardcron thread
            logger.debug('Starting hardcron...')
            global_settings.web2py_crontype = 'hard'
            newcron.hardcron(options.folder).start()

    # if no password provided and have Tk library start GUI (when not
    # explicitly disabled), we also need a GUI to put in taskbar (system tray)
    # when requested

    # FIXME: this check should be done first
    if options.taskbar and os.name != 'nt':
        die('taskbar not supported on this platform')

    root = None

    if (not options.nogui and options.password == '<ask>') or options.taskbar:
        try:
            if PY2:
                import Tkinter as tkinter
            else:
                import tkinter
            root = tkinter.Tk()
        except (ImportError, OSError):
            logger.warn(
                'GUI not available because Tk library is not installed')
            options.nogui = True
        except:
            logger.exception('cannot get Tk root window, GUI disabled')
            options.nogui = True

    if root:
        # run GUI and exit
        root.focus_force()

        # Mac OS X - make the GUI window rise to the top
        if os.path.exists("/usr/bin/osascript"):
            applescript = """
tell application "System Events"
    set proc to first process whose unix id is %d
    set frontmost of proc to true
end tell
""" % (os.getpid())
            os.system("/usr/bin/osascript -e '%s'" % applescript)

        # web2pyDialog takes care of schedulers
        master = web2pyDialog(root, options)
        signal.signal(signal.SIGTERM, lambda a, b: master.quit())

        try:
            root.mainloop()
        except:
            master.quit()

        sys.exit()

    if options.password == '<ask>':
        options.password = getpass.getpass('choose a password:'******'no password, disable admin interface')

    spt = None

    if options.scheduler and options.with_scheduler:
        # start schedulers in a separate thread
        spt = threading.Thread(target=start_schedulers, args=(options, ))
        spt.start()

    # start server

    # Use first interface IP and port if interfaces specified, since the
    # interfaces option overrides the IP (and related) options.
    if not options.interfaces:
        ip = options.ip
        port = options.port
    else:
        first_if = options.interfaces[0]
        ip = first_if[0]
        port = first_if[1]

    if options.ssl_certificate or options.ssl_private_key:
        proto = 'https'
    else:
        proto = 'http'

    url = get_url(ip, proto=proto, port=port)

    if not options.nobanner:
        message = '\nplease visit:\n\t%s\n'
        if sys.platform.startswith('win'):
            message += 'use "taskkill /f /pid %i" to shutdown the web2py server\n\n'
        else:
            message += 'use "kill -SIGTERM %i" to shutdown the web2py server\n\n'
        print(message % (url, os.getpid()))

    # enhance linecache.getline (used by debugger) to look at the source file
    # if the line was not found (under py2exe & when file was modified)
    import linecache
    py2exe_getline = linecache.getline

    def getline(filename, lineno, *args, **kwargs):
        line = py2exe_getline(filename, lineno, *args, **kwargs)
        if not line:
            try:
                with open(filename, "rb") as f:
                    for i, line in enumerate(f):
                        line = line.decode('utf-8')
                        if lineno == i + 1:
                            break
                    else:
                        line = ''
            except (IOError, OSError):
                line = ''
        return line

    linecache.getline = getline

    server = main.HttpServer(ip=ip,
                             port=port,
                             password=options.password,
                             pid_filename=options.pid_filename,
                             log_filename=options.log_filename,
                             profiler_dir=options.profiler_dir,
                             ssl_certificate=options.ssl_certificate,
                             ssl_private_key=options.ssl_private_key,
                             ssl_ca_certificate=options.ssl_ca_certificate,
                             min_threads=options.minthreads,
                             max_threads=options.maxthreads,
                             server_name=options.server_name,
                             request_queue_size=options.request_queue_size,
                             timeout=options.timeout,
                             socket_timeout=options.socket_timeout,
                             shutdown_timeout=options.shutdown_timeout,
                             path=options.folder,
                             interfaces=options.interfaces)

    try:
        server.start()
    except KeyboardInterrupt:
        server.stop()
        if spt is not None:
            try:
                spt.join()
            except:
                logger.exception('error terminating schedulers')
                pass
    logging.shutdown()