source: OpenRLabs-Git/deploy/rlabs-docker/web2py-rlabs/gluon/console.py

main
Last change on this file was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

  • Property mode set to 100755
File size: 31.3 KB
Line 
1# -*- coding: utf-8 -*-
2# vim: set ts=4 sw=4 et ai:
3"""
4| This file is part of the web2py Web Framework
5| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
6| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
7
8Command line interface
9----------------------
10
11The processing of all command line arguments is done using
12the argparse library in the console function.
13
14The basic principle is to process and check for all options
15in a single place, this place is the parse_args function.
16Notice that when I say all options I mean really all,
17options sourced from a configuration file are included.
18
19A brief summary of options style follows,
20for the benefit of code maintainers/developers:
21
22- use the underscore to split words in long names (as in
23  '--run_system_tests')
24- remember to allow the '-' too as word separator (e.g.
25  '--run-system-tests') but do not use this form on help
26  (add the minus version of the option to _omitted_opts
27  to hide it in usage help)
28- prefer short names on help messages, instead use
29  all options names in warning/error messages (e.g.
30  '-R/--run requires -S/--shell')
31
32Notice that options must be included into opt_map dictionary
33(defined in parse_args function) to be available in
34configuration file.
35"""
36
37from __future__ import print_function
38
39__author__ = 'Paolo Pastori'
40
41import os.path
42import argparse
43import logging
44import socket
45import sys
46import re
47import ast
48from collections import OrderedDict
49import copy
50
51from gluon._compat import PY2
52from gluon.shell import die
53from gluon.utils import is_valid_ip_address
54from gluon.settings import global_settings
55
56
57def warn(msg):
58    print("%s: warning: %s" % (sys.argv[0], msg), file=sys.stderr)
59
60def is_appdir(applications_parent, app):
61    return os.path.isdir(os.path.join(applications_parent, 'applications', app))
62
63
64def console(version):
65    """
66    Load command line options.
67    Trivial -h/--help and --version options are also processed.
68
69    Returns a namespace object (in the sense of argparse)
70    with all options loaded.
71    """
72
73    # replacement hints for deprecated options
74    deprecated_opts = {
75        '--debug': '--log_level',
76        '--nogui': '--no_gui',
77        '--ssl_private_key': '--server_key',
78        '--ssl_certificate': '--server_cert',
79        '--interfaces': None, # dest is 'interfaces', hint is '--interface'
80        '-n': '--min_threads', '--numthreads': '--min_threads',
81        '--minthreads': '--min_threads',
82        '--maxthreads': '--max_threads',
83        '-z': None, '--shutdown_timeout': None,
84        '--profiler': '--profiler_dir',
85        '--run-cron': '--with_cron',
86        '--softcron': '--soft_cron',
87        '--cron': '--cron_run',
88        '--test': '--run_doctests'
89    }
90
91    class HelpFormatter2(argparse.HelpFormatter):
92        """Hides the options listed in _hidden_options in usage help."""
93
94        # NOTE: preferred style for long options name is to use '_'
95        #       between words (as in 'no_gui'), also accept the '-' in
96        #       most of the options but do not show both versions on help
97        _omitted_opts = ('--add-options', '--errors-to-console',
98            '--no-banner', '--log-level', '--no-gui', '--import-models',
99            '--force-migrate',
100            '--server-name', '--server-key', '--server-cert', '--ca-cert',
101            '--pid-filename', '--log-filename', '--min-threads',
102            '--max-threads', '--request-queue-size', '--socket-timeout',
103            '--profiler-dir', '--with-scheduler', '--with-cron',
104            '--cron-threads', '--soft-cron', '--cron-run',
105            '--run-doctests', '--run-system-tests', '--with-coverage')
106
107        _hidden_options = _omitted_opts + tuple(deprecated_opts.keys())
108
109        def _format_action_invocation(self, action):
110            if not action.option_strings:
111                return super(HelpFormatter2, self)._format_action_invocation(action)
112            parts = []
113            if action.nargs == 0:
114                parts.extend(filter(lambda o : o not in self._hidden_options,
115                                    action.option_strings))
116            else:
117                default = action.dest.upper()
118                args_string = self._format_args(action, default)
119                for option_string in action.option_strings:
120                    if option_string in self._hidden_options:
121                        continue
122                    parts.append('%s %s' % (option_string, args_string))
123            return ', '.join(parts)
124
125    class ExtendAction(argparse._AppendAction):
126        """Action to accumulate values in a flat list."""
127
128        def __call__(self, parser, namespace, values, option_string=None):
129            if isinstance(values, list):
130                # must copy to avoid altering the option default value
131                value = getattr(namespace, self.dest, None)
132                if value is None:
133                    value = []
134                    setattr(namespace, self.dest, value)
135                items = value[:]
136                # for options that allows multiple args (i.e. those declared
137                # with add_argument(..., nargs='+', ...)) the values are
138                # always placed into a list
139                while len(values) == 1 and isinstance(values[0], list):
140                    values = values[0]
141                items.extend(values)
142                setattr(namespace, self.dest, items)
143            else:
144                super(ExtendAction, self).__call__(parser, namespace, values, option_string)
145
146    parser = argparse.ArgumentParser(
147        usage='python %(prog)s [options]',
148        description='web2py Web Framework startup script.',
149        epilog='''NOTE: unless a password is specified (-a 'passwd')
150web2py will attempt to run a GUI to ask for it when starting the web server
151(if not disabled with --no_gui).''',
152        formatter_class=HelpFormatter2,
153        add_help=False) # do not add -h/--help option
154
155    # global options
156    g = parser.add_argument_group('global options')
157    g.add_argument('-h', '--help', action='help',
158                   help='show this help message and exit')
159    g.add_argument('--version', action='version',
160                   version=version,
161                   help="show program's version and exit")
162    folder = os.getcwd()
163    g.add_argument('-f', '--folder',
164                   default=folder, metavar='WEB2PY_DIR',
165                   help='web2py installation directory (%(default)s)')
166    def existing_file(v):
167        if not v:
168            raise argparse.ArgumentTypeError('empty argument')
169        if not os.path.exists(v):
170            raise argparse.ArgumentTypeError("file %r not found" % v)
171        return v
172    g.add_argument('-L', '--config',
173                   type=existing_file,
174                   metavar='PYTHON_FILE',
175                   help='read all options from PYTHON_FILE')
176    g.add_argument('--add_options', '--add-options',
177                   default=False,
178                   action='store_true', help=
179        'add options to existing ones, useful with -L only')
180    g.add_argument('-a', '--password',
181                   default='<ask>', help=
182        'password to be used for administration (use "<recycle>" '
183        'to reuse the last password), when no password is available '
184        'the administrative web interface will be disabled')
185    g.add_argument('-e', '--errors_to_console', '--errors-to-console',
186                   default=False,
187                   action='store_true',
188                   help='log application errors to console')
189    g.add_argument('--no_banner', '--no-banner',
190                   default=False,
191                   action='store_true',
192                   help='do not print header banner')
193    g.add_argument('-Q', '--quiet',
194                   default=False,
195                   action='store_true',
196                   help='disable all output')
197    integer_log_level = []
198    def log_level(v):
199        # try to convert a lgging level name to its numeric value,
200        # could use logging.getLevelName but not with
201        # 3.4 <= Python < 3.4.2, see
202        # https://docs.python.org/3/library/logging.html#logging.getLevelName)
203        try:
204            name2level = logging._levelNames
205        except AttributeError:
206            # logging._levelNames has gone with Python 3.4, see
207            # https://github.com/python/cpython/commit/3b84eae03ebd8122fdbdced3d85999dd9aedfc7e
208            name2level = logging._nameToLevel
209        try:
210            return name2level[v.upper()]
211        except KeyError:
212            pass
213        try:
214            ill = int(v)
215            # value deprecated: integer in range(101)
216            if 0 <= ill <= 100:
217                integer_log_level.append(ill)
218                return ill
219        except ValueError:
220            pass
221        raise argparse.ArgumentTypeError("bad level %r" % v)
222    g.add_argument('-D', '--log_level', '--log-level',
223                   '--debug', # deprecated
224                   default='WARNING',
225                   type=log_level,
226                   metavar='LOG_LEVEL', help=
227        'set log level, allowed values are: NOTSET, DEBUG, INFO, WARN, '
228        'WARNING, ERROR, and CRITICAL, also lowercase (default is '
229        '%(default)s)')
230
231    # GUI options
232    g = parser.add_argument_group('GUI options')
233    g.add_argument('--no_gui', '--no-gui',
234                   '--nogui', # deprecated
235                   default=False,
236                   action='store_true',
237                   help='do not run GUI')
238    g.add_argument('-t', '--taskbar',
239                   default=False,
240                   action='store_true',
241                   help='run in taskbar (system tray)')
242
243    # console options
244    g = parser.add_argument_group('console options')
245    g.add_argument('-S', '--shell',
246                   metavar='APP_ENV', help=
247        'run web2py in Python interactive shell or IPython (if installed) '
248        'with specified application environment (if application does not '
249        'exist it will be created). APP_ENV like a/c/f?x=y (c, f and vars '
250        'optional), if APP_ENV include the action f then after the '
251        'action execution the interpreter is exited')
252    g.add_argument('-B', '--bpython',
253                   default=False,
254                   action='store_true', help=
255        'use bpython (if installed) when running in interactive shell, '
256        'see -S above')
257    g.add_argument('-P', '--plain',
258                   default=False,
259                   action='store_true', help=
260        'use plain Python shell when running in interactive shell, '
261        'see -S above')
262    g.add_argument('-M', '--import_models', '--import-models',
263                   default=False,
264                   action='store_true', help=
265        'auto import model files when running in interactive shell '
266        '(default is %(default)s), see -S above. NOTE: when the APP_ENV '
267        'argument of -S include a controller c automatic import of '
268        'models is always enabled')
269    g.add_argument('--fake_migrate',
270                   default=False,
271                   action='store_true',
272                   help=
273                   'force DAL to fake migrate all tables; '
274                   'monkeypatch in the DAL class to force _fake_migrate=True')
275    g.add_argument('--force_migrate', '--force-migrate',
276                   default=False,
277                   action='store_true', help=
278        'force DAL to migrate all tables that should be migrated when enabled; '
279        'monkeypatch in the DAL class to force _migrate_enabled=True')
280    g.add_argument('-R', '--run',
281                   type=existing_file,
282                   metavar='PYTHON_FILE', help=
283        'run PYTHON_FILE in web2py environment; require -S')
284    g.add_argument('-A', '--args',
285                   default=[],
286                   nargs=argparse.REMAINDER, help=
287        'use this to pass arguments to the PYTHON_FILE above; require '
288        '-R. NOTE: must be the last option because eat all remaining '
289        'arguments')
290
291    # web server options
292    g = parser.add_argument_group('web server options')
293    g.add_argument('-s', '--server_name', '--server-name',
294                   default=socket.gethostname(),
295                   help='web server name (%(default)s)')
296    def ip_addr(v):
297        if not is_valid_ip_address(v):
298            raise argparse.ArgumentTypeError("bad IP address %s" % v)
299        return v
300    g.add_argument('-i', '--ip',
301                   default='127.0.0.1',
302                   type=ip_addr, metavar='IP_ADDR', help=
303        'IP address of the server (%(default)s), accept either IPv4 or '
304        'IPv6 (e.g. ::1) addresses. NOTE: this option is ignored if '
305        '--interface is specified')
306    def not_negative_int(v, err_label='value'):
307        try:
308            iv = int(v)
309            if iv < 0: raise ValueError()
310            return iv
311        except ValueError:
312            pass
313        raise argparse.ArgumentTypeError("bad %s %s" % (err_label, v))
314    def port(v):
315        return not_negative_int(v, err_label='port')
316    g.add_argument('-p', '--port',
317                   default=8000,
318                   type=port, metavar='NUM', help=
319        'port of server (%(default)d). '
320        'NOTE: this option is ignored if --interface is specified')
321    g.add_argument('-k', '--server_key', '--server-key',
322                   '--ssl_private_key', # deprecated
323                   type=existing_file,
324                   metavar='FILE', help='server private key')
325    g.add_argument('-c', '--server_cert', '--server-cert',
326                   '--ssl_certificate', # deprecated
327                   type=existing_file,
328                   metavar='FILE', help='server certificate')
329    g.add_argument('--ca_cert', '--ca-cert',
330                   type=existing_file,
331                   metavar='FILE', help='CA certificate')
332    def iface(v, sep=','):
333        if not v:
334            raise argparse.ArgumentTypeError('empty argument')
335        if sep == ':':
336            # deprecated --interfaces ip:port:key:cert:ca_cert
337            # IPv6 addresses in square brackets
338            if v.startswith('['):
339                # IPv6
340                ip, v_remainder = v.split(']', 1)
341                ip = ip[1:]
342                ifp = v_remainder[1:].split(':')
343                ifp.insert(0, ip)
344            else:
345                # IPv4
346                ifp = v.split(':')
347        else:
348            # --interface
349            ifp = v.split(sep, 5)
350        if not len(ifp) in (2, 4, 5):
351            raise argparse.ArgumentTypeError("bad interface %r" % v)
352        try:
353            ip_addr(ifp[0])
354            ifp[1] = port(ifp[1])
355            for fv in ifp[2:]:
356                existing_file(fv)
357        except argparse.ArgumentTypeError as ex:
358            raise argparse.ArgumentTypeError("bad interface %r (%s)" % (v, ex))
359        return tuple(ifp)
360    g.add_argument('--interface', dest='interfaces',
361                   default=[], action=ExtendAction,
362                   type=iface, nargs='+',
363                   metavar='IF_INFO', help=
364        'listen on specified interface, IF_INFO = '
365        'IP_ADDR,PORT[,KEY_FILE,CERT_FILE[,CA_CERT_FILE]].'
366        ' NOTE: this option can be used multiple times to provide additional '
367        'interfaces to choose from but you can choose which one to listen to '
368        'only using the GUI otherwise the first interface specified is used')
369    def ifaces(v):
370        # deprecated --interfaces 'if1;if2;...'
371        if not v:
372            raise argparse.ArgumentTypeError('empty argument')
373        return [iface(i, ':') for i in v.split(';')]
374    g.add_argument('--interfaces', # deprecated
375                   default=argparse.SUPPRESS, # do not set if absent
376                   action=ExtendAction,
377                   type=ifaces,
378                   help=argparse.SUPPRESS) # do not show on help
379    g.add_argument('-d', '--pid_filename', '--pid-filename',
380                   default='httpserver.pid',
381                   metavar='FILE', help='server pid file (%(default)s)')
382    g.add_argument('-l', '--log_filename', '--log-filename',
383                   default='httpserver.log',
384                   metavar='FILE', help='server log file (%(default)s)')
385    g.add_argument('--min_threads', '--min-threads',
386                   '--minthreads', '-n', '--numthreads', # deprecated
387                   type=not_negative_int, metavar='NUM',
388                   help='minimum number of server threads')
389    g.add_argument('--max_threads', '--max-threads',
390                   '--maxthreads', # deprecated
391                   type=not_negative_int, metavar='NUM',
392                   help='maximum number of server threads')
393    g.add_argument('-q', '--request_queue_size', '--request-queue-size',
394                   default=5,
395                   type=not_negative_int, metavar='NUM', help=
396        'max number of queued requests when server busy (%(default)d)')
397    g.add_argument('-o', '--timeout',
398                   default=10,
399                   type=not_negative_int, metavar='SECONDS',
400                   help='timeout for individual request (%(default)d seconds)')
401    g.add_argument('--socket_timeout', '--socket-timeout',
402                   default=5,
403                   type=not_negative_int, metavar='SECONDS',
404                   help='timeout for socket (%(default)d seconds)')
405    g.add_argument('-z', '--shutdown_timeout', # deprecated
406                   type=not_negative_int,
407                   help=argparse.SUPPRESS) # do not show on help
408    g.add_argument('-F', '--profiler_dir', '--profiler-dir',
409                   '--profiler', # deprecated
410                   help='profiler directory')
411
412    # scheduler options
413    g = parser.add_argument_group('scheduler options')
414    g.add_argument('-X', '--with_scheduler', '--with-scheduler',
415                   default=False,
416                   action='store_true', help=
417        'run schedulers alongside web server; require --K')
418    def is_app(app):
419        return is_appdir(folder, app)
420    def scheduler(v):
421        if not v:
422            raise argparse.ArgumentTypeError('empty argument')
423        if ',' in v:
424            # legacy "app1,..."
425            vl = [n.strip() for n in v.split(',')]
426            return [scheduler(iv) for iv in vl]
427        vp = [n.strip() for n in v.split(':')]
428        app = vp[0]
429        if not app:
430            raise argparse.ArgumentTypeError('empty application')
431        if not is_app(app):
432            warn("argument -K/--scheduler: bad application %r, skipped" % app)
433            return None
434        return ':'.join(filter(None, vp))
435    g.add_argument('-K', '--scheduler', dest='schedulers',
436                   default=[], action=ExtendAction,
437                   type=scheduler, nargs='+',
438                   metavar='APP_INFO', help=
439        'run scheduler for the specified application(s), APP_INFO = '
440        'APP_NAME[:GROUPS], that is an optional list of groups can follow '
441        'the application name (e.g. app:group1:group2); require a scheduler '
442        "to be defined in the application's models. NOTE: this option can "
443        'be used multiple times to add schedulers')
444
445    # cron options
446    g = parser.add_argument_group('cron options')
447    g.add_argument('-Y', '--with_cron', '--with-cron',
448                   '--run-cron', # deprecated
449                   default=False,
450                   action='store_true', help=
451        'run cron service alongside web server')
452    def crontab(v):
453        if not v:
454            raise argparse.ArgumentTypeError('empty argument')
455        if not is_app(v):
456            warn("argument --crontab: bad application %r, skipped" % v)
457            return None
458        return v
459    g.add_argument('--crontab', dest='crontabs',
460                   default=[], action=ExtendAction,
461                   type=crontab, nargs='+',
462                   metavar='APP_NAME', help=
463        'tell cron to read the crontab for the specified application(s) '
464        'only, the default behaviour is to read the crontab for all of the '
465        'installed applications. NOTE: this option can be used multiple '
466        'times to build the list of crontabs to be processed by cron')
467    def positive_int(v, err_label='value'):
468        try:
469            iv = int(v)
470            if iv <= 0: raise ValueError()
471            return iv
472        except ValueError:
473            pass
474        raise argparse.ArgumentTypeError("bad %s %s" % (err_label, v))
475    def cron_threads(v):
476        return positive_int(v, err_label='cron_threads')
477    g.add_argument('--cron_threads', '--cron-threads',
478                   type=cron_threads, metavar='NUM',
479                   help='maximum number of cron threads (5)')
480    g.add_argument('--soft_cron', '--soft-cron',
481                   '--softcron', # deprecated
482                   default=False,
483                   action='store_true', help=
484        'use cron software emulation instead of separate cron process; '
485        'require -Y. NOTE: use of cron software emulation is strongly '
486        'discouraged')
487    g.add_argument('-C', '--cron_run', '--cron-run',
488                   '--cron', # deprecated
489                   default=False,
490                   action='store_true', help=
491        'trigger a cron run and exit; usually used when invoked '
492        'from a system (external) crontab')
493    g.add_argument('--cron_job', # NOTE: this is intended for internal use only
494                   default=False,
495                   action='store_true',
496                   help=argparse.SUPPRESS) # do not show on help
497
498    # test options
499    g = parser.add_argument_group('test options')
500    g.add_argument('-v', '--verbose',
501                   default=False,
502                   action='store_true', help='increase verbosity')
503    g.add_argument('-T', '--run_doctests', '--run-doctests',
504                   '--test', # deprecated
505                   metavar='APP_ENV', help=
506        'run doctests in application environment. APP_ENV like a/c/f (c, f '
507        'optional)')
508    g.add_argument('--run_system_tests', '--run-system-tests',
509                   default=False,
510                   action='store_true', help='run web2py test suite')
511    g.add_argument('--with_coverage', '--with-coverage',
512                   default=False,
513                   action='store_true', help=
514        'collect coverage data when used with --run_system_tests; '
515        'require Python 2.7+ and the coverage module installed')
516
517    # other options
518    g = parser.add_argument_group('other options')
519    g.add_argument('-G', '--GAE', dest='gae',
520                   metavar='APP_NAME', help=
521        'will create app.yaml and gaehandler.py and exit')
522
523    options = parse_args(parser, sys.argv[1:],
524                         deprecated_opts, integer_log_level)
525
526    # make a copy of all options for global_settings
527    copy_options = copy.deepcopy(options)
528    copy_options.password = '******'
529    global_settings.cmd_options = copy_options
530
531    return options
532
533
534REGEX_PEP263 = r'^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)'
535
536def get_pep263_encoding(source):
537    """
538    Read python source file encoding, according to PEP 263, see
539    https://www.python.org/dev/peps/pep-0263/
540    """
541    with open(source, 'r') as sf:
542        l12 = (sf.readline(), sf.readline())
543    m12 = re.match(REGEX_PEP263, l12[0]) or re.match(REGEX_PEP263, l12[1])
544    return m12 and m12.group(1)
545
546
547IGNORE = lambda: None
548
549def load_config(config_file, opt_map):
550    """
551    Load options from config file (a Python script).
552
553    config_file(str): file name
554    opt_map(dict): mapping fom option name (key) to callable (val),
555        used to post-process parsed value for the option
556
557    Notice that the configuring Python script is never executed/imported,
558    instead the ast library is used to evaluate each option assignment,
559    provided that it is written on a single line.
560
561    Returns an OrderedDict with sourced options.
562    """
563    REGEX_ASSIGN_EXP = re.compile(r'\s*=\s*(.+)')
564    map_items = opt_map.items()
565    # preserve the order of loaded options even though this is not needed
566    pl = OrderedDict()
567    config_encoding = get_pep263_encoding(config_file)
568    # NOTE: assume 'ascii' encoding when not explicitly stated (Python 2),
569    #       this is not correct for Python 3 where the default is 'utf-8'
570    open_kwargs = dict() if PY2 else dict(encoding=config_encoding or 'ascii')
571    with open(config_file, 'r', **open_kwargs) as cfil:
572        for linenum, clin in enumerate(cfil, start=1):
573            if PY2 and config_encoding:
574                clin = unicode(clin, config_encoding)
575            clin = clin.strip()
576            for opt, mapr in map_items:
577                if clin.startswith(opt):
578                    m = REGEX_ASSIGN_EXP.match(clin[len(opt):])
579                    if m is None: continue
580                    try:
581                        val = opt_map[opt](ast.literal_eval(m.group(1)))
582                    except:
583                        die("cannot parse config file %r at line %d" % (config_file, linenum))
584                    if val is not IGNORE:
585                        pl[opt] = val
586    return pl
587
588
589def parse_args(parser, cli_args, deprecated_opts, integer_log_level,
590               namespace=None):
591
592    #print('PARSING ARGS:', cli_args)
593    del integer_log_level[:]
594    options = parser.parse_args(cli_args, namespace)
595    #print('PARSED OPTIONS:', options)
596
597    # warn for deprecated options
598    deprecated_args = [a for a in cli_args if a in deprecated_opts]
599    for da in deprecated_args:
600        # verify if it was a real option by looking into
601        # parsed values for the actual destination
602        hint = deprecated_opts[da]
603        dest = (hint or da).lstrip('-')
604        default = parser.get_default(dest)
605        if da == '--interfaces':
606            hint = '--interface'
607        if getattr(options, dest) is not default:
608            # the option has been specified
609            msg = "%s is deprecated" % da
610            if hint:
611                msg += ", use %s instead" % hint
612            warn(msg)
613    # warn for deprecated values
614    if integer_log_level and '--debug' not in deprecated_args:
615        warn('integer argument for -D/--log_level is deprecated, '
616             'use label instead')
617    # fix schedulers and die if all were skipped
618    if None in options.schedulers:
619        options.schedulers = [i for i in options.schedulers if i is not None]
620        if not options.schedulers:
621            die('no scheduler left')
622    # fix crontabs and die if all were skipped
623    if None in options.crontabs:
624        options.crontabs = [i for i in options.crontabs if i is not None]
625        if not options.crontabs:
626            die('no crontab left')
627    # taskbar
628    if options.taskbar and os.name != 'nt':
629        warn('--taskbar not supported on this platform, skipped')
630        options.taskbar = False
631    # options consistency checkings
632    if options.run and not options.shell:
633        die('-R/--run requires -S/--shell', exit_status=2)
634    if options.args and not options.run:
635        die('-A/--args requires -R/--run', exit_status=2)
636    if options.with_scheduler and not options.schedulers:
637        die('-X/--with_scheduler requires -K/--scheduler', exit_status=2)
638    if options.soft_cron and not options.with_cron:
639        die('--soft_cron requires -Y/--with_cron', exit_status=2)
640    if options.shell:
641        for o, os in dict(with_scheduler='-X/--with_scheduler',
642                          schedulers='-K/--scheduler',
643                          with_cron='-Y/--with_cron',
644                          cron_run='-C/--cron_run',
645                          run_doctests='-T/--run_doctests',
646                          run_system_tests='--run_system_tests').items():
647            if getattr(options, o):
648                die("-S/--shell and %s are conflicting options" % os,
649                    exit_status=2)
650    if options.bpython and options.plain:
651        die('-B/--bpython and -P/--plain are conflicting options',
652            exit_status=2)
653    if options.cron_run:
654        for o, os in dict(with_cron='-Y/--with_cron',
655                          run_doctests='-T/--run_doctests',
656                          run_system_tests='--run_system_tests').items():
657            if getattr(options, o):
658                die("-C/--cron_run and %s are conflicting options" % os,
659                    exit_status=2)
660    if options.run_doctests and options.run_system_tests:
661        die('-T/--run_doctests and --run_system_tests are conflicting options',
662            exit_status=2)
663
664    if options.config:
665        # load options from file,
666        # all options sourced from file that evaluates to False
667        # are skipped, the special IGNORE value is used for this
668        store_true = lambda v: True if v else IGNORE
669        str_or_default = lambda v : str(v) if v else IGNORE
670        list_or_default = lambda v : (
671            [str(i) for i in v] if isinstance(v, list) else [str(v)]) if v \
672            else IGNORE
673        # NOTE: 'help', 'version', 'folder', 'cron_job' and 'GAE' are not
674        #       sourced from file, the same applies to deprecated options
675        opt_map = {
676            # global options
677            'config': str_or_default,
678            'add_options': store_true,
679            'password': str_or_default,
680            'errors_to_console': store_true,
681            'no_banner': store_true,
682            'quiet': store_true,
683            'log_level': str_or_default,
684            # GUI options
685            'no_gui': store_true,
686            'taskbar': store_true,
687            # console options
688            'shell': str_or_default,
689            'bpython': store_true,
690            'plain': store_true,
691            'import_models': store_true,
692            'force_migrate': store_true,
693            'run': str_or_default,
694            'args': list_or_default,
695            # web server options
696            'server_name': str_or_default,
697            'ip': str_or_default,
698            'port': str_or_default,
699            'server_key': str_or_default,
700            'server_cert': str_or_default,
701            'ca_cert': str_or_default,
702            'interface': list_or_default,
703            'pid_filename': str_or_default,
704            'log_filename': str_or_default,
705            'min_threads': str_or_default,
706            'max_threads': str_or_default,
707            'request_queue_size': str_or_default,
708            'timeout': str_or_default,
709            'socket_timeout': str_or_default,
710            'profiler_dir': str_or_default,
711            # scheduler options
712            'with_scheduler': store_true,
713            'scheduler': list_or_default,
714            # cron options
715            'with_cron': store_true,
716            'crontab': list_or_default,
717            'cron_threads': str_or_default,
718            'soft_cron': store_true,
719            'cron_run': store_true,
720            # test options
721            'verbose': store_true,
722            'run_doctests': str_or_default,
723            'run_system_tests': store_true,
724            'with_coverage': store_true,
725        }
726        od = load_config(options.config, opt_map)
727        #print("LOADED FROM %s:" % options.config, od)
728        # convert loaded options dict as retuned by load_config
729        # into a list of arguments for further parsing by parse_args
730        file_args = []; args_args = [] # '--args' must be the last
731        for key, val in od.items():
732            if key != 'args':
733                file_args.append('--' + key)
734                if isinstance(val, list): file_args.extend(val)
735                elif not isinstance(val, bool): file_args.append(val)
736            else:
737                args_args = ['--args'] + val
738        file_args += args_args
739
740        if options.add_options:
741            # add options to existing ones,
742            # must clear config to avoid infinite recursion
743            options.config = options.add_options = None
744            return parse_args(parser, file_args,
745                deprecated_opts, integer_log_level, options)
746        return parse_args(parser, file_args,
747            deprecated_opts, integer_log_level)
748
749    return options
Note: See TracBrowser for help on using the repository browser.