new command line options
This commit is contained in:
@@ -1,3 +1,24 @@
|
||||
## 2.19.0
|
||||
- new command line options (Thanks Paolo Pastori)
|
||||
|
||||
OLD NAME NEW NAME
|
||||
================== ==================
|
||||
--debug --log_level
|
||||
--nogui --no_gui
|
||||
--ssl_private_key --server_key
|
||||
--ssl_certificate --server_cert
|
||||
--minthreads --min_threads
|
||||
--maxthreads --max_threads
|
||||
--profiler --profiler_dir
|
||||
--run-cron --with_cron
|
||||
--softcron --soft_cron
|
||||
--cron --cron_run
|
||||
--cronjob * --cron_job *
|
||||
--test --run_doctests
|
||||
--add_options
|
||||
--interface
|
||||
--crontab
|
||||
|
||||
## 2.18.1-2.18.5
|
||||
- pydal 19.04
|
||||
- made template its own module (Yet Another Template Language)
|
||||
|
||||
@@ -19,4 +19,4 @@ WORKDIR /web2py
|
||||
|
||||
EXPOSE 443
|
||||
|
||||
CMD python /web2py/web2py.py --nogui --no-banner -a 'a' -c web2py.crt -k web2py.key -i 0.0.0.0 -p 443
|
||||
CMD python /web2py/web2py.py --no_gui --no_banner -a 'a' -k web2py.key -c web2py.crt -i 0.0.0.0 -p 443
|
||||
|
||||
@@ -24,4 +24,4 @@ WORKDIR /home/web2py/web2py
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD . /home/web2py/bin/activate && python /home/web2py/web2py/web2py.py --nogui --no-banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
CMD . /home/web2py/bin/activate && python /home/web2py/web2py/web2py.py --no_gui --no_banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
|
||||
@@ -25,4 +25,4 @@ WORKDIR /home/web2py/web2py
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD . /home/web2py/bin/activate && python /home/web2py/web2py/web2py.py --nogui --no-banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
CMD . /home/web2py/bin/activate && python /home/web2py/web2py/web2py.py --no_gui --no_banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
|
||||
@@ -25,4 +25,4 @@ WORKDIR /home/web2py/web2py
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD . /home/web2py/bin/activate && python /home/web2py/web2py/web2py.py --nogui --no-banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
CMD . /home/web2py/bin/activate && python /home/web2py/web2py/web2py.py --no_gui --no_banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
|
||||
@@ -24,4 +24,4 @@ WORKDIR /home/web2py/web2py
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD . /home/web2py/bin/activate && python /home/web2py/web2py/web2py.py --nogui --no-banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
CMD . /home/web2py/bin/activate && python /home/web2py/web2py/web2py.py --no_gui --no_banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
|
||||
@@ -24,4 +24,4 @@ WORKDIR /home/web2py/web2py
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD . /home/web2py/bin/activate && python /home/web2py/web2py/web2py.py --nogui --no-banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
CMD . /home/web2py/bin/activate && python /home/web2py/web2py/web2py.py --no_gui --no_banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
|
||||
@@ -18,4 +18,4 @@ WORKDIR /web2py
|
||||
|
||||
EXPOSE 443
|
||||
|
||||
CMD python /web2py/web2py.py --nogui --no-banner -a 'a' -c web2py.crt -k web2py.key -i 0.0.0.0 -p 443
|
||||
CMD python /web2py/web2py.py --no_gui --no_banner -a 'a' -k web2py.key -c web2py.crt -i 0.0.0.0 -p 443
|
||||
|
||||
@@ -20,4 +20,4 @@ WORKDIR /home/web2py/web2py
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD python /home/web2py/web2py/web2py.py --nogui --no-banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
CMD python /home/web2py/web2py/web2py.py --no_gui --no_banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
|
||||
@@ -22,4 +22,4 @@ WORKDIR /home/web2py/web2py
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD . /home/web2py/bin/activate && python /home/web2py/web2py/web2py.py --nogui --no-banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
CMD . /home/web2py/bin/activate && python /home/web2py/web2py/web2py.py --no_gui --no_banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
|
||||
@@ -17,4 +17,4 @@ WORKDIR /web2py
|
||||
|
||||
EXPOSE 443
|
||||
|
||||
CMD python /web2py/web2py.py --nogui --no-banner -a 'a' -c web2py.crt -k web2py.key -i 0.0.0.0 -p 443
|
||||
CMD python /web2py/web2py.py --no_gui --no_banner -a 'a' -k web2py.key -c web2py.crt -i 0.0.0.0 -p 443
|
||||
|
||||
@@ -17,4 +17,4 @@ WORKDIR /web2py
|
||||
|
||||
EXPOSE 443
|
||||
|
||||
CMD python /web2py/web2py.py --nogui --no-banner -a 'a' -c web2py.crt -k web2py.key -i 0.0.0.0 -p 443
|
||||
CMD python /web2py/web2py.py --no_gui --no_banner -a 'a' -k web2py.key -c web2py.crt -i 0.0.0.0 -p 443
|
||||
|
||||
@@ -17,4 +17,4 @@ WORKDIR /web2py
|
||||
|
||||
EXPOSE 443
|
||||
|
||||
CMD python /web2py/web2py.py --nogui --no-banner -a 'a' -c web2py.crt -k web2py.key -i 0.0.0.0 -p 443
|
||||
CMD python /web2py/web2py.py --no_gui --no_banner -a 'a' -k web2py.key -c web2py.crt -i 0.0.0.0 -p 443
|
||||
|
||||
@@ -17,4 +17,4 @@ WORKDIR /web2py
|
||||
|
||||
EXPOSE 443
|
||||
|
||||
CMD python /web2py/web2py.py --nogui --no-banner -a 'a' -c web2py.crt -k web2py.key -i 0.0.0.0 -p 443
|
||||
CMD python /web2py/web2py.py --no_gui --no_banner -a 'a' -k web2py.key -c web2py.crt -i 0.0.0.0 -p 443
|
||||
|
||||
@@ -24,4 +24,4 @@ WORKDIR /home/web2py/web2py
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD . /home/web2py/bin/activate && python /home/web2py/web2py/web2py.py --nogui --no-banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
CMD . /home/web2py/bin/activate && python /home/web2py/web2py/web2py.py --no_gui --no_banner -a 'a' -i 0.0.0.0 -p 8000
|
||||
|
||||
@@ -0,0 +1,712 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: set ts=4 sw=4 et ai:
|
||||
"""
|
||||
| This file is part of the web2py Web Framework
|
||||
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
|
||||
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
Command line interface
|
||||
----------------------
|
||||
|
||||
The processing of all command line arguments is done using
|
||||
the argparse library in the console function.
|
||||
|
||||
The basic principle is to process and check for all options
|
||||
in a single place, this place is the parse_args function.
|
||||
Notice that when I say all options I mean really all,
|
||||
options sourced from a configuration file are included.
|
||||
|
||||
A brief summary of options style follows,
|
||||
for the benefit of code maintainers/developers:
|
||||
|
||||
- use the underscore to split words in long names (as in
|
||||
'--run_system_tests')
|
||||
- remember to allow the '-' too as word separator (e.g.
|
||||
'--run-system-tests') but do not use this form on help
|
||||
- prefer short names on help messages, instead use
|
||||
all options names in warning/error messages (e.g.
|
||||
'-R/--run requires -S/--shell')
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
__author__ = 'Paolo Pastori'
|
||||
|
||||
import os.path
|
||||
import argparse
|
||||
import logging
|
||||
import socket
|
||||
import sys
|
||||
import re
|
||||
import ast
|
||||
from collections import OrderedDict
|
||||
import copy
|
||||
|
||||
from gluon._compat import PY2
|
||||
from gluon.shell import die
|
||||
from gluon.utils import is_valid_ip_address
|
||||
from gluon.settings import global_settings
|
||||
|
||||
|
||||
def warn(msg):
|
||||
print("%s: warning: %s" % (sys.argv[0], msg), file=sys.stderr)
|
||||
|
||||
def is_appdir(applications_parent, app):
|
||||
return os.path.isdir(os.path.join(applications_parent, 'applications', app))
|
||||
|
||||
|
||||
def console(version):
|
||||
"""
|
||||
Load command line options.
|
||||
Trivial -h/--help and --version options are also processed.
|
||||
|
||||
Returns a namespace object (in the sense of argparse)
|
||||
with all options loaded.
|
||||
"""
|
||||
|
||||
# replacement hints for deprecated options
|
||||
deprecated_opts = {
|
||||
'--debug': '--log_level',
|
||||
'--nogui': '--no_gui',
|
||||
'--ssl_private_key': '--server_key',
|
||||
'--ssl_certificate': '--server_cert',
|
||||
'--interfaces': None, # dest is 'interfaces', hint is '--interface'
|
||||
'-n': '--min_threads', '--numthreads': '--min_threads',
|
||||
'--minthreads': '--min_threads',
|
||||
'--maxthreads': '--max_threads',
|
||||
'-z': None, '--shutdown_timeout': None,
|
||||
'--profiler': '--profiler_dir',
|
||||
'--run-cron': '--with_cron',
|
||||
'--softcron': '--soft_cron',
|
||||
'--cron': '--cron_run',
|
||||
'--test': '--run_doctests'
|
||||
}
|
||||
|
||||
class HelpFormatter2(argparse.HelpFormatter):
|
||||
"""Hides the options listed in _hidden_options in usage help."""
|
||||
|
||||
# NOTE: preferred style for long options name is to use '_'
|
||||
# between words (as in 'no_gui'), also accept the '-' in
|
||||
# most of the options but do not show both versions on help
|
||||
_omitted_opts = ('--add-options', '--errors-to-console',
|
||||
'--no-banner', '--log-level', '--no-gui', '--import-models',
|
||||
'--server-name', '--server-key', '--server-cert', '--ca-cert',
|
||||
'--pid-filename', '--log-filename', '--min-threads',
|
||||
'--max-threads', '--request-queue-size', '--socket-timeout',
|
||||
'--profiler-dir', '--with-scheduler', '--with-cron',
|
||||
'--soft-cron', '--cron-run',
|
||||
'--run-doctests', '--run-system-tests', '--with-coverage')
|
||||
|
||||
_hidden_options = _omitted_opts + tuple(deprecated_opts.keys())
|
||||
|
||||
def _format_action_invocation(self, action):
|
||||
if not action.option_strings:
|
||||
return super(HelpFormatter2, self)._format_action_invocation(action)
|
||||
parts = []
|
||||
if action.nargs == 0:
|
||||
parts.extend(filter(lambda o : o not in self._hidden_options,
|
||||
action.option_strings))
|
||||
else:
|
||||
default = action.dest.upper()
|
||||
args_string = self._format_args(action, default)
|
||||
for option_string in action.option_strings:
|
||||
if option_string in self._hidden_options:
|
||||
continue
|
||||
parts.append('%s %s' % (option_string, args_string))
|
||||
return ', '.join(parts)
|
||||
|
||||
class ExtendAction(argparse._AppendAction):
|
||||
"""Action to accumulate values in a flat list."""
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
if isinstance(values, list):
|
||||
# must copy to avoid altering the option default value
|
||||
items = argparse._ensure_value(namespace, self.dest, [])[:]
|
||||
# for options that allows multiple args (i.e. those declared
|
||||
# with add_argument(..., nargs='+', ...)) the values are
|
||||
# always placed into a list
|
||||
while len(values) == 1 and isinstance(values[0], list):
|
||||
values = values[0]
|
||||
items.extend(values)
|
||||
setattr(namespace, self.dest, items)
|
||||
else:
|
||||
super(ExtendAction, self).__call__(parser, namespace, values, option_string)
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
usage='python %(prog)s [options]',
|
||||
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 --no_gui).''',
|
||||
formatter_class=HelpFormatter2,
|
||||
add_help=False) # do not add -h/--help option
|
||||
|
||||
# global options
|
||||
g = parser.add_argument_group('global options')
|
||||
g.add_argument('-h', '--help', action='help',
|
||||
help='show this help message and exit')
|
||||
g.add_argument('--version', action='version',
|
||||
version=version,
|
||||
help="show program's version and exit")
|
||||
folder = os.getcwd()
|
||||
g.add_argument('-f', '--folder',
|
||||
default=folder, metavar='WEB2PY_DIR',
|
||||
help='web2py installation directory (%(default)s)')
|
||||
def existing_file(v):
|
||||
if not v:
|
||||
raise argparse.ArgumentTypeError('empty argument')
|
||||
if not os.path.exists(v):
|
||||
raise argparse.ArgumentTypeError("file %r not found" % v)
|
||||
return v
|
||||
g.add_argument('-L', '--config',
|
||||
type=existing_file,
|
||||
metavar='PYTHON_FILE',
|
||||
help='read all options from PYTHON_FILE')
|
||||
g.add_argument('--add_options', '--add-options',
|
||||
default=False,
|
||||
action='store_true', help=
|
||||
'add options to existing ones, useful with -L only')
|
||||
g.add_argument('-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 web interface will be disabled')
|
||||
g.add_argument('-e', '--errors_to_console', '--errors-to-console',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='log application errors to console')
|
||||
g.add_argument('--no_banner', '--no-banner',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='do not print header banner')
|
||||
g.add_argument('-Q', '--quiet',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='disable all output')
|
||||
integer_log_level = []
|
||||
def log_level(v):
|
||||
# try to convert a lgging level name to its numeric value,
|
||||
# could use logging.getLevelName but not with
|
||||
# 3.4 <= Python < 3.4.2, see
|
||||
# https://docs.python.org/3/library/logging.html#logging.getLevelName)
|
||||
try:
|
||||
name2level = logging._levelNames
|
||||
except AttributeError:
|
||||
# logging._levelNames has gone with Python 3.4, see
|
||||
# https://github.com/python/cpython/commit/3b84eae03ebd8122fdbdced3d85999dd9aedfc7e
|
||||
name2level = logging._nameToLevel
|
||||
try:
|
||||
return name2level[v.upper()]
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
ill = int(v)
|
||||
# value deprecated: integer in range(101)
|
||||
if 0 <= ill <= 100:
|
||||
integer_log_level.append(ill)
|
||||
return ill
|
||||
except ValueError:
|
||||
pass
|
||||
raise argparse.ArgumentTypeError("bad level %r" % v)
|
||||
g.add_argument('-D', '--log_level', '--log-level',
|
||||
'--debug', # deprecated
|
||||
default='WARNING',
|
||||
type=log_level,
|
||||
metavar='LOG_LEVEL', help=
|
||||
'set log level, allowed values are: NOTSET, DEBUG, INFO, WARN, '
|
||||
'WARNING, ERROR, and CRITICAL, also lowercase (default is '
|
||||
'%(default)s)')
|
||||
|
||||
# GUI options
|
||||
g = parser.add_argument_group('GUI options')
|
||||
g.add_argument('--no_gui', '--no-gui',
|
||||
'--nogui', # deprecated
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='do not run GUI')
|
||||
g.add_argument('-t', '--taskbar',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='run in taskbar (system tray)')
|
||||
|
||||
# console options
|
||||
g = parser.add_argument_group('console options')
|
||||
g.add_argument('-S', '--shell',
|
||||
metavar='APP_ENV', help=
|
||||
'run web2py in Python interactive shell or IPython (if installed) '
|
||||
'with specified application environment (if application does not '
|
||||
'exist it will be created). APP_ENV like a/c/f?x=y (c, f and vars '
|
||||
'optional), if APP_ENV include the action f then after the '
|
||||
'action execution the interpreter is exited')
|
||||
g.add_argument('-B', '--bpython',
|
||||
default=False,
|
||||
action='store_true', help=
|
||||
'use bpython (if installed) when running in interactive shell, '
|
||||
'see -S above')
|
||||
g.add_argument('-P', '--plain',
|
||||
default=False,
|
||||
action='store_true', help=
|
||||
'use plain Python shell when running in interactive shell, '
|
||||
'see -S above')
|
||||
g.add_argument('-M', '--import_models', '--import-models',
|
||||
default=False,
|
||||
action='store_true', help=
|
||||
'auto import model files when running in interactive shell '
|
||||
'(default is %(default)s), see -S above. NOTE: when the APP_ENV '
|
||||
'argument of -S include a controller c automatic import of '
|
||||
'models is always enabled')
|
||||
g.add_argument('-R', '--run',
|
||||
type=existing_file,
|
||||
metavar='PYTHON_FILE', help=
|
||||
'run PYTHON_FILE in web2py environment; require -S')
|
||||
g.add_argument('-A', '--args',
|
||||
default=[],
|
||||
nargs=argparse.REMAINDER, help=
|
||||
'use this to pass arguments to the PYTHON_FILE above; require '
|
||||
'-R. NOTE: must be the last option because eat all remaining '
|
||||
'arguments')
|
||||
|
||||
# web server options
|
||||
g = parser.add_argument_group('web server options')
|
||||
g.add_argument('-s', '--server_name', '--server-name',
|
||||
default=socket.gethostname(),
|
||||
help='web server name (%(default)s)')
|
||||
def ip_addr(v):
|
||||
if not is_valid_ip_address(v):
|
||||
raise argparse.ArgumentTypeError("bad IP address %s" % v)
|
||||
return v
|
||||
g.add_argument('-i', '--ip',
|
||||
default='127.0.0.1',
|
||||
type=ip_addr, metavar='IP_ADDR', help=
|
||||
'IP address of the server (%(default)s), accept either IPv4 or '
|
||||
'IPv6 (e.g. ::1) addresses. NOTE: this option is ignored if '
|
||||
'--interface is specified')
|
||||
def not_negative_int(v, err_label='value'):
|
||||
try:
|
||||
iv = int(v)
|
||||
if iv < 0: raise ValueError()
|
||||
return iv
|
||||
except ValueError:
|
||||
pass
|
||||
raise argparse.ArgumentTypeError("bad %s %s" % (err_label, v))
|
||||
def port(v):
|
||||
return not_negative_int(v, err_label='port')
|
||||
g.add_argument('-p', '--port',
|
||||
default=8000,
|
||||
type=port, metavar='NUM', help=
|
||||
'port of server (%(default)d). '
|
||||
'NOTE: this option is ignored if --interface is specified')
|
||||
g.add_argument('-k', '--server_key', '--server-key',
|
||||
'--ssl_private_key', # deprecated
|
||||
type=existing_file,
|
||||
metavar='FILE', help='server private key')
|
||||
g.add_argument('-c', '--server_cert', '--server-cert',
|
||||
'--ssl_certificate', # deprecated
|
||||
type=existing_file,
|
||||
metavar='FILE', help='server certificate')
|
||||
g.add_argument('--ca_cert', '--ca-cert',
|
||||
type=existing_file,
|
||||
metavar='FILE', help='CA certificate')
|
||||
def iface(v, sep=','):
|
||||
if not v:
|
||||
raise argparse.ArgumentTypeError('empty argument')
|
||||
if sep == ':':
|
||||
# deprecated --interfaces ip:port:key:cert:ca_cert
|
||||
# IPv6 addresses in square brackets
|
||||
if v.startswith('['):
|
||||
# IPv6
|
||||
ip, v_remainder = v.split(']', 1)
|
||||
ip = ip[1:]
|
||||
ifp = v_remainder[1:].split(':')
|
||||
ifp.insert(0, ip)
|
||||
else:
|
||||
# IPv4
|
||||
ifp = v.split(':')
|
||||
else:
|
||||
# --interface
|
||||
ifp = v.split(sep, 5)
|
||||
if not len(ifp) in (2, 4, 5):
|
||||
raise argparse.ArgumentTypeError("bad interface %r" % v)
|
||||
try:
|
||||
ip_addr(ifp[0])
|
||||
ifp[1] = port(ifp[1])
|
||||
for fv in ifp[2:]:
|
||||
existing_file(fv)
|
||||
except argparse.ArgumentTypeError as ex:
|
||||
raise argparse.ArgumentTypeError("bad interface %r (%s)" % (v, ex))
|
||||
return tuple(ifp)
|
||||
g.add_argument('--interface', dest='interfaces',
|
||||
default=[], action=ExtendAction,
|
||||
type=iface, nargs='+',
|
||||
metavar='IF_INFO', help=
|
||||
'listen on specified interface, IF_INFO = '
|
||||
'IP_ADDR,PORT[,KEY_FILE,CERT_FILE[,CA_CERT_FILE]].'
|
||||
' NOTE: this option can be used multiple times to provide additional '
|
||||
'interfaces to choose from but you can choose which one to listen to '
|
||||
'only using the GUI otherwise the first interface specified is used')
|
||||
def ifaces(v):
|
||||
# deprecated --interfaces 'if1;if2;...'
|
||||
if not v:
|
||||
raise argparse.ArgumentTypeError('empty argument')
|
||||
return [iface(i, ':') for i in v.split(';')]
|
||||
g.add_argument('--interfaces', # deprecated
|
||||
default=argparse.SUPPRESS, # do not set if absent
|
||||
action=ExtendAction,
|
||||
type=ifaces,
|
||||
help=argparse.SUPPRESS) # do not show on help
|
||||
g.add_argument('-d', '--pid_filename', '--pid-filename',
|
||||
default='httpserver.pid',
|
||||
metavar='FILE', help='server pid file (%(default)s)')
|
||||
g.add_argument('-l', '--log_filename', '--log-filename',
|
||||
default='httpserver.log',
|
||||
metavar='FILE', help='server log file (%(default)s)')
|
||||
g.add_argument('--min_threads', '--min-threads',
|
||||
'--minthreads', '-n', '--numthreads', # deprecated
|
||||
type=not_negative_int, metavar='NUM',
|
||||
help='minimum number of server threads')
|
||||
g.add_argument('--max_threads', '--max-threads',
|
||||
'--maxthreads', # deprecated
|
||||
type=not_negative_int, metavar='NUM',
|
||||
help='maximum number of server threads')
|
||||
g.add_argument('-q', '--request_queue_size', '--request-queue-size',
|
||||
default=5,
|
||||
type=not_negative_int, metavar='NUM', help=
|
||||
'max number of queued requests when server busy (%(default)d)')
|
||||
g.add_argument('-o', '--timeout',
|
||||
default=10,
|
||||
type=not_negative_int, metavar='SECONDS',
|
||||
help='timeout for individual request (%(default)d seconds)')
|
||||
g.add_argument('--socket_timeout', '--socket-timeout',
|
||||
default=5,
|
||||
type=not_negative_int, metavar='SECONDS',
|
||||
help='timeout for socket (%(default)d seconds)')
|
||||
g.add_argument('-z', '--shutdown_timeout', # deprecated
|
||||
type=not_negative_int,
|
||||
help=argparse.SUPPRESS) # do not show on help
|
||||
g.add_argument('-F', '--profiler_dir', '--profiler-dir',
|
||||
'--profiler', # deprecated
|
||||
help='profiler directory')
|
||||
|
||||
# scheduler options
|
||||
g = parser.add_argument_group('scheduler options')
|
||||
g.add_argument('-X', '--with_scheduler', '--with-scheduler',
|
||||
default=False,
|
||||
action='store_true', help=
|
||||
'run schedulers alongside web server; require --K')
|
||||
def is_app(app):
|
||||
return is_appdir(folder, app)
|
||||
def scheduler(v):
|
||||
if not v:
|
||||
raise argparse.ArgumentTypeError('empty argument')
|
||||
if ',' in v:
|
||||
# legacy "app1,..."
|
||||
vl = [n.strip() for n in v.split(',')]
|
||||
return [scheduler(iv) for iv in vl]
|
||||
vp = [n.strip() for n in v.split(':')]
|
||||
app = vp[0]
|
||||
if not app:
|
||||
raise argparse.ArgumentTypeError('empty application')
|
||||
if not is_app(app):
|
||||
warn("argument -K/--scheduler: bad application %r, skipped" % app)
|
||||
return None
|
||||
return ':'.join(filter(None, vp))
|
||||
g.add_argument('-K', '--scheduler', dest='schedulers',
|
||||
default=[], action=ExtendAction,
|
||||
type=scheduler, nargs='+',
|
||||
metavar='APP_INFO', help=
|
||||
'run scheduler for the specified application(s), APP_INFO = '
|
||||
'APP_NAME[:GROUPS], that is an optional list of groups can follow '
|
||||
'the application name (e.g. app:group1:group2); require a scheduler '
|
||||
"to be defined in the application's models. NOTE: this option can "
|
||||
'be used multiple times to add schedulers')
|
||||
|
||||
# cron options
|
||||
g = parser.add_argument_group('cron options')
|
||||
g.add_argument('-Y', '--with_cron', '--with-cron',
|
||||
'--run-cron', # deprecated
|
||||
default=False,
|
||||
action='store_true', help=
|
||||
'run cron service alongside web server')
|
||||
def crontab(v):
|
||||
if not v:
|
||||
raise argparse.ArgumentTypeError('empty argument')
|
||||
if not is_app(v):
|
||||
warn("argument --crontab: bad application %r, skipped" % v)
|
||||
return None
|
||||
return v
|
||||
g.add_argument('--crontab', dest='crontabs',
|
||||
default=[], action=ExtendAction,
|
||||
type=crontab, nargs='+',
|
||||
metavar='APP_NAME', help=
|
||||
'tell cron to read the crontab for the specified application(s) '
|
||||
'only, the default behaviour is to read the crontab for all of the '
|
||||
'installed applications. NOTE: this option can be used multiple '
|
||||
'times to build the list of crontabs to be processed by cron')
|
||||
g.add_argument('--soft_cron', '--soft-cron',
|
||||
'--softcron', # deprecated
|
||||
default=False,
|
||||
action='store_true', help=
|
||||
'use cron software emulation instead of separate cron process; '
|
||||
'require -Y. NOTE: use of cron software emulation is strongly '
|
||||
'discouraged')
|
||||
g.add_argument('-C', '--cron_run', '--cron-run',
|
||||
'--cron', # deprecated
|
||||
default=False,
|
||||
action='store_true', help=
|
||||
'trigger a cron run and exit; usually used when invoked '
|
||||
'from a system (external) crontab')
|
||||
g.add_argument('--cron_job', # NOTE: this is intended for internal use only
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS) # do not show on help
|
||||
|
||||
# test options
|
||||
g = parser.add_argument_group('test options')
|
||||
g.add_argument('-v', '--verbose',
|
||||
default=False,
|
||||
action='store_true', help='increase verbosity')
|
||||
g.add_argument('-T', '--run_doctests', '--run-doctests',
|
||||
'--test', # deprecated
|
||||
metavar='APP_ENV', help=
|
||||
'run doctests in application environment. APP_ENV like a/c/f (c, f '
|
||||
'optional)')
|
||||
g.add_argument('--run_system_tests', '--run-system-tests',
|
||||
default=False,
|
||||
action='store_true', help='run web2py test suite')
|
||||
g.add_argument('--with_coverage', '--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')
|
||||
|
||||
# other options
|
||||
g = parser.add_argument_group('other options')
|
||||
g.add_argument('-G', '--GAE', dest='gae',
|
||||
metavar='APP_NAME', help=
|
||||
'will create app.yaml and gaehandler.py and exit')
|
||||
|
||||
options = parse_args(parser, sys.argv[1:],
|
||||
deprecated_opts, integer_log_level)
|
||||
|
||||
# make a copy of all options for global_settings
|
||||
copy_options = copy.deepcopy(options)
|
||||
copy_options.password = '******'
|
||||
global_settings.cmd_options = copy_options
|
||||
|
||||
return options
|
||||
|
||||
|
||||
REGEX_PEP263 = r'^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)'
|
||||
|
||||
def get_pep263_encoding(source):
|
||||
"""
|
||||
Read python source file encoding, according to PEP 263, see
|
||||
https://www.python.org/dev/peps/pep-0263/
|
||||
"""
|
||||
with open(source, 'r') as sf:
|
||||
l12 = (sf.readline(), sf.readline())
|
||||
m12 = re.match(REGEX_PEP263, l12[0]) or re.match(REGEX_PEP263, l12[1])
|
||||
return m12 and m12.group(1)
|
||||
|
||||
|
||||
IGNORE = lambda: None
|
||||
|
||||
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 writen 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 if 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
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@@ -374,7 +373,7 @@ def get_session(request, other_application='admin'):
|
||||
if not os.path.exists(session_filename):
|
||||
session_filename = generate(session_filename)
|
||||
osession = storage.load_storage(session_filename)
|
||||
except:
|
||||
except Exception:
|
||||
osession = storage.Storage()
|
||||
return osession
|
||||
|
||||
|
||||
+3
-6
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@@ -355,11 +354,9 @@ class Request(Storage):
|
||||
"""
|
||||
cmd_opts = global_settings.cmd_options
|
||||
# checking if this is called within the scheduler or within the shell
|
||||
# in addition to checking if it's a cronjob
|
||||
# FIXME: cmd_opts.scheduler does not imply that
|
||||
# we are running in the scheduler
|
||||
if (self.is_https or cmd_opts and (
|
||||
cmd_opts.shell or cmd_opts.scheduler or cmd_opts.cronjob)):
|
||||
# in addition to checking if it's a cron job
|
||||
if (self.is_https or self.is_scheduler or cmd_opts and (
|
||||
cmd_opts.shell or cmd_opts.cron_job)):
|
||||
current.session.secure()
|
||||
else:
|
||||
current.session.forget()
|
||||
|
||||
+2
-2
@@ -1,4 +1,3 @@
|
||||
#!/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@@ -557,7 +556,8 @@ def wsgibase(environ, responder):
|
||||
return wsgibase(new_environ, responder)
|
||||
if global_settings.web2py_crontype == 'soft':
|
||||
cmd_opts = global_settings.cmd_options
|
||||
newcron.softcron(global_settings.applications_parent).start()
|
||||
newcron.softcron(global_settings.applications_parent,
|
||||
apps=cmd_opts and cmd_opts.crontabs).start()
|
||||
return http_response.to(responder, env=env)
|
||||
|
||||
|
||||
|
||||
+2
-5
@@ -287,7 +287,7 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None):
|
||||
('dom', now_s.tm_mday),
|
||||
('dow', (now_s.tm_wday + 1) % 7))
|
||||
|
||||
if apps is None:
|
||||
if not apps:
|
||||
apps = [x for x in os.listdir(apppath)
|
||||
if os.path.isdir(os.path.join(apppath, x))]
|
||||
|
||||
@@ -303,10 +303,7 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None):
|
||||
base_commands.append(w2p_path)
|
||||
if applications_parent != global_settings.gluon_parent:
|
||||
base_commands.extend(('-f', applications_parent))
|
||||
base_commands.extend(('--cronjob', '--no-banner', '--nogui', '--plain',
|
||||
# FIXME: this should not be needed since we are
|
||||
# not launching the web server
|
||||
'-a', '"<recycle>"'))
|
||||
base_commands.extend(('--cron_job', '--no_banner', '--no_gui', '--plain'))
|
||||
|
||||
for app in apps:
|
||||
if _cron_stopping:
|
||||
|
||||
+1
-1
Submodule gluon/packages/yatl updated: 8c6f1e1f17...3fb9abbac8
+5
-5
@@ -146,7 +146,7 @@ def env(
|
||||
request.is_shell = cmd_opts.shell is not None
|
||||
else:
|
||||
ip = '127.0.0.1'; port = 8000
|
||||
# FIXME: what about request.is_shell ?
|
||||
request.is_shell = False
|
||||
request.is_scheduler = False
|
||||
request.env.http_host = '%s:%s' % (ip, port)
|
||||
request.env.remote_addr = '127.0.0.1'
|
||||
@@ -213,7 +213,7 @@ def run(
|
||||
startfile=None,
|
||||
bpython=False,
|
||||
python_code=None,
|
||||
cronjob=False,
|
||||
cron_job=False,
|
||||
scheduler_job=False):
|
||||
"""
|
||||
Start interactive shell or run Python script (startfile) in web2py
|
||||
@@ -233,7 +233,7 @@ def run(
|
||||
adir = os.path.join('applications', a)
|
||||
|
||||
if not os.path.exists(adir):
|
||||
if not cronjob and not scheduler_job and \
|
||||
if not cron_job and not scheduler_job and \
|
||||
sys.stdin and not sys.stdin.name == '/dev/null':
|
||||
confirm = raw_input(
|
||||
'application %s does not exist, create (y/n)?' % a)
|
||||
@@ -259,7 +259,7 @@ def run(
|
||||
pyfile = os.path.join('applications', a, 'controllers', c + '.py')
|
||||
pycfile = os.path.join('applications', a, 'compiled',
|
||||
"controllers_%s_%s.pyc" % (c, f))
|
||||
if ((cronjob and os.path.isfile(pycfile))
|
||||
if ((cron_job and os.path.isfile(pycfile))
|
||||
or not os.path.isfile(pyfile)):
|
||||
exec(read_pyc(pycfile), _env)
|
||||
elif os.path.isfile(pyfile):
|
||||
@@ -380,7 +380,7 @@ def test(testpath, import_models=True, verbose=False):
|
||||
|
||||
import doctest
|
||||
if os.path.isfile(testpath):
|
||||
mo = re.match(r'(|.*/)applications/(?P<a>[^/]+)', testpath)
|
||||
mo = re.search('/?applications/(?P<a>[^/]+)', testpath)
|
||||
if not mo:
|
||||
die('test file is not in application directory: %s'
|
||||
% testpath)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@@ -636,7 +635,7 @@ def termination():
|
||||
|
||||
def exec_sched(self):
|
||||
import subprocess
|
||||
call_args = [sys.executable, 'web2py.py', '--no-banner', '-D', '20','-K', 'welcome']
|
||||
call_args = [sys.executable, 'web2py.py', '--no_banner', '-D', 'INFO','-K', 'welcome']
|
||||
ret = subprocess.call(call_args, env=dict(os.environ))
|
||||
return ret
|
||||
|
||||
|
||||
+46
-371
@@ -5,8 +5,8 @@
|
||||
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
|
||||
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
The widget is called from web2py
|
||||
----------------------------------
|
||||
GUI widget and services start function
|
||||
--------------------------------------
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
@@ -16,15 +16,15 @@ from gluon._compat import thread, xrange, PY2
|
||||
import time
|
||||
import threading
|
||||
import os
|
||||
import copy
|
||||
import socket
|
||||
import signal
|
||||
import math
|
||||
import logging
|
||||
import getpass
|
||||
from gluon import main, newcron
|
||||
|
||||
from gluon.fileutils import read_file, write_file, create_welcome_w2p
|
||||
from gluon import main, newcron
|
||||
from gluon.fileutils import read_file, create_welcome_w2p
|
||||
from gluon.console import console
|
||||
from gluon.settings import global_settings
|
||||
from gluon.shell import die, run, test
|
||||
from gluon.utils import is_valid_ip_address, is_loopback_ip_address, getipaddrinfo
|
||||
@@ -314,22 +314,20 @@ class web2pyDialog(object):
|
||||
self.tb = None
|
||||
|
||||
def update_schedulers(self, start=False):
|
||||
applications_folder = os.path.join(self.options.folder, 'applications')
|
||||
apps = []
|
||||
available_apps = [
|
||||
arq for arq in os.listdir(applications_folder)
|
||||
if os.path.isdir(os.path.join(applications_folder, arq))
|
||||
]
|
||||
if start:
|
||||
# the widget takes care of starting the scheduler
|
||||
if self.options.scheduler and self.options.with_scheduler:
|
||||
apps = [app for app
|
||||
in map(lambda ag : ag.split(':', 1)[0].strip(), self.options.scheduler.split(','))
|
||||
if app in available_apps]
|
||||
if start and self.options.with_scheduler and self.options.with_schedulers:
|
||||
# the widget takes care of starting the schedulers
|
||||
apps = [ag.split(':', 1)[0] for ag in self.options.with_schedulers]
|
||||
else:
|
||||
apps = []
|
||||
for app in apps:
|
||||
self.try_start_scheduler(app)
|
||||
|
||||
# reset the menu
|
||||
applications_folder = os.path.join(self.options.folder, 'applications')
|
||||
available_apps = [
|
||||
arq for arq in os.listdir(applications_folder)
|
||||
if os.path.isdir(os.path.join(applications_folder, arq))
|
||||
]
|
||||
self.schedmenu.delete(0, len(available_apps))
|
||||
|
||||
for arq in available_apps:
|
||||
@@ -351,7 +349,7 @@ class web2pyDialog(object):
|
||||
code = "from gluon.globals import current;current._scheduler.loop()"
|
||||
print('starting scheduler from widget for "%s"...' % app)
|
||||
args = (app, True, True, None, False, code, False, True)
|
||||
logging.getLogger().setLevel(self.options.debuglevel)
|
||||
logging.getLogger().setLevel(self.options.log_level)
|
||||
p = Process(target=run, args=args)
|
||||
self.scheduler_processes[app] = p
|
||||
self.update_schedulers()
|
||||
@@ -473,7 +471,7 @@ class web2pyDialog(object):
|
||||
except:
|
||||
return self.error('invalid port number')
|
||||
|
||||
if self.options.ssl_certificate and self.options.ssl_private_key:
|
||||
if self.options.server_key and self.options.server_cert:
|
||||
proto = 'https'
|
||||
else:
|
||||
proto = 'http'
|
||||
@@ -492,11 +490,11 @@ class web2pyDialog(object):
|
||||
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_certificate=options.server_cert,
|
||||
ssl_private_key=options.server_key,
|
||||
ssl_ca_certificate=options.ca_cert,
|
||||
min_threads=options.minthreads,
|
||||
max_threads=options.maxthreads,
|
||||
min_threads=options.min_threads,
|
||||
max_threads=options.max_threads,
|
||||
server_name=options.server_name,
|
||||
request_queue_size=req_queue_size,
|
||||
timeout=options.timeout,
|
||||
@@ -582,321 +580,6 @@ class web2pyDialog(object):
|
||||
self.canvas.after(1000, self.update_canvas)
|
||||
|
||||
|
||||
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 check_existent_app(options, appname):
|
||||
if os.path.isdir(os.path.join(options.folder, 'applications', appname)):
|
||||
return True
|
||||
@@ -921,11 +604,10 @@ def start_schedulers(options):
|
||||
except:
|
||||
sys.stderr.write('Sorry, -K only supported for Python 2.6+\n')
|
||||
return
|
||||
logging.getLogger().setLevel(options.debuglevel)
|
||||
logging.getLogger().setLevel(options.log_level)
|
||||
|
||||
apps = [[n.strip() for n in sched_app.split(':')]
|
||||
for sched_app in options.scheduler.split(',')]
|
||||
if len(apps) == 1 and not options.with_scheduler:
|
||||
apps = [ag.split(':') for ag in options.schedulers]
|
||||
if not options.with_scheduler and len(apps) == 1:
|
||||
app, code = get_code_for_scheduler(apps[0], options)
|
||||
if not app:
|
||||
return
|
||||
@@ -968,7 +650,7 @@ def start():
|
||||
""" Starts server and other services """
|
||||
|
||||
# get command line arguments
|
||||
(options, args) = console()
|
||||
options = console(version=ProgramVersion)
|
||||
|
||||
if options.gae:
|
||||
# write app.yaml, gaehandler.py, and exit
|
||||
@@ -990,7 +672,7 @@ def start():
|
||||
return
|
||||
|
||||
logger = logging.getLogger("web2py")
|
||||
logger.setLevel(options.debuglevel)
|
||||
logger.setLevel(options.log_level)
|
||||
|
||||
# on new installation build the scaffolding app
|
||||
create_welcome_w2p()
|
||||
@@ -1027,36 +709,29 @@ def start():
|
||||
from pydal.drivers import DRIVERS
|
||||
print('Database drivers available: %s' % ', '.join(DRIVERS))
|
||||
|
||||
if options.test:
|
||||
if options.run_doctests:
|
||||
# run doctests and exit
|
||||
test(options.test, verbose=options.verbose)
|
||||
test(options.run_doctests, verbose=options.verbose)
|
||||
return
|
||||
|
||||
if options.shell:
|
||||
# run interactive shell and exit
|
||||
sys.argv = [options.run] + options.args
|
||||
sys.argv = [options.run or ''] + options.args
|
||||
run(options.shell, plain=options.plain, bpython=options.bpython,
|
||||
import_models=options.import_models, startfile=options.run,
|
||||
cronjob=options.cronjob)
|
||||
cron_job=options.cron_job)
|
||||
return
|
||||
|
||||
if options.extcron:
|
||||
if options.cron_run:
|
||||
# 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 for app
|
||||
in map(lambda ag : ag.split(':', 1)[0].strip(), options.scheduler.split(','))
|
||||
if check_existent_app(options, app)]
|
||||
else:
|
||||
apps = None
|
||||
extcron = newcron.extcron(options.folder, apps=apps)
|
||||
extcron = newcron.extcron(options.folder, apps=options.crontabs)
|
||||
extcron.start()
|
||||
extcron.join()
|
||||
return
|
||||
|
||||
if options.scheduler and not options.with_scheduler:
|
||||
if not options.with_scheduler and options.schedulers:
|
||||
# run schedulers and exit
|
||||
try:
|
||||
start_schedulers(options)
|
||||
@@ -1064,22 +739,22 @@ def start():
|
||||
pass
|
||||
return
|
||||
|
||||
if options.runcron:
|
||||
if options.softcron:
|
||||
print('Using softcron (but this is not very efficient)')
|
||||
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).start()
|
||||
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.nogui and options.password == '<ask>') or options.taskbar:
|
||||
if (not options.no_gui and options.password == '<ask>') or options.taskbar:
|
||||
try:
|
||||
if PY2:
|
||||
import Tkinter as tkinter
|
||||
@@ -1089,10 +764,10 @@ def start():
|
||||
except (ImportError, OSError):
|
||||
logger.warn(
|
||||
'GUI not available because Tk library is not installed')
|
||||
options.nogui = True
|
||||
options.no_gui = True
|
||||
except:
|
||||
logger.exception('cannot get Tk root window, GUI disabled')
|
||||
options.nogui = True
|
||||
options.no_gui = True
|
||||
|
||||
if root:
|
||||
# run GUI and exit
|
||||
@@ -1121,7 +796,7 @@ end tell
|
||||
|
||||
spt = None
|
||||
|
||||
if options.scheduler and options.with_scheduler:
|
||||
if options.with_scheduler and options.schedulers:
|
||||
# start schedulers in a separate thread
|
||||
spt = threading.Thread(target=start_schedulers, args=(options,))
|
||||
spt.start()
|
||||
@@ -1144,7 +819,7 @@ end tell
|
||||
ip = first_if[0]
|
||||
port = first_if[1]
|
||||
|
||||
if options.ssl_certificate and options.ssl_private_key:
|
||||
if options.server_key and options.server_cert:
|
||||
proto = 'https'
|
||||
else:
|
||||
proto = 'http'
|
||||
@@ -1186,11 +861,11 @@ end tell
|
||||
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_certificate=options.server_cert,
|
||||
ssl_private_key=options.server_key,
|
||||
ssl_ca_certificate=options.ca_cert,
|
||||
min_threads=options.minthreads,
|
||||
max_threads=options.maxthreads,
|
||||
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,
|
||||
|
||||
@@ -29,7 +29,7 @@ cd $DAEMON_DIR
|
||||
|
||||
start() {
|
||||
echo -n $"Starting $DESC ($NAME): "
|
||||
daemon --check $NAME $PYTHON $DAEMON_DIR/web2py.py -Q --nogui -a $ADMINPASS -d $PIDFILE -p $PORT &
|
||||
daemon --check $NAME $PYTHON $DAEMON_DIR/web2py.py -Q --no_gui -a $ADMINPASS -d $PIDFILE -p $PORT &
|
||||
RETVAL=$?
|
||||
if [ $RETVAL -eq 0 ]; then
|
||||
touch /var/lock/subsys/$NAME
|
||||
|
||||
Reference in New Issue
Block a user