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)
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
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
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()
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
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
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()
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
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
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()