Spaces:
Sleeping
Sleeping
| # Copyright 2017 The Abseil Authors. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """Generic entry point for Abseil Python applications. | |
| To use this module, define a ``main`` function with a single ``argv`` argument | |
| and call ``app.run(main)``. For example:: | |
| def main(argv): | |
| if len(argv) > 1: | |
| raise app.UsageError('Too many command-line arguments.') | |
| if __name__ == '__main__': | |
| app.run(main) | |
| """ | |
| import collections | |
| import errno | |
| import os | |
| import pdb | |
| import sys | |
| import textwrap | |
| import traceback | |
| from absl import command_name | |
| from absl import flags | |
| from absl import logging | |
| try: | |
| import faulthandler | |
| except ImportError: | |
| faulthandler = None | |
| FLAGS = flags.FLAGS | |
| flags.DEFINE_boolean('run_with_pdb', False, 'Set to true for PDB debug mode') | |
| flags.DEFINE_boolean('pdb_post_mortem', False, | |
| 'Set to true to handle uncaught exceptions with PDB ' | |
| 'post mortem.') | |
| flags.DEFINE_alias('pdb', 'pdb_post_mortem') | |
| flags.DEFINE_boolean('run_with_profiling', False, | |
| 'Set to true for profiling the script. ' | |
| 'Execution will be slower, and the output format might ' | |
| 'change over time.') | |
| flags.DEFINE_string('profile_file', None, | |
| 'Dump profile information to a file (for python -m ' | |
| 'pstats). Implies --run_with_profiling.') | |
| flags.DEFINE_boolean('use_cprofile_for_profiling', True, | |
| 'Use cProfile instead of the profile module for ' | |
| 'profiling. This has no effect unless ' | |
| '--run_with_profiling is set.') | |
| flags.DEFINE_boolean('only_check_args', False, | |
| 'Set to true to validate args and exit.', | |
| allow_hide_cpp=True) | |
| # If main() exits via an abnormal exception, call into these | |
| # handlers before exiting. | |
| EXCEPTION_HANDLERS = [] | |
| class Error(Exception): | |
| pass | |
| class UsageError(Error): | |
| """Exception raised when the arguments supplied by the user are invalid. | |
| Raise this when the arguments supplied are invalid from the point of | |
| view of the application. For example when two mutually exclusive | |
| flags have been supplied or when there are not enough non-flag | |
| arguments. It is distinct from flags.Error which covers the lower | |
| level of parsing and validating individual flags. | |
| """ | |
| def __init__(self, message, exitcode=1): | |
| super(UsageError, self).__init__(message) | |
| self.exitcode = exitcode | |
| class HelpFlag(flags.BooleanFlag): | |
| """Special boolean flag that displays usage and raises SystemExit.""" | |
| NAME = 'help' | |
| SHORT_NAME = '?' | |
| def __init__(self): | |
| super(HelpFlag, self).__init__( | |
| self.NAME, False, 'show this help', | |
| short_name=self.SHORT_NAME, allow_hide_cpp=True) | |
| def parse(self, arg): | |
| if self._parse(arg): | |
| usage(shorthelp=True, writeto_stdout=True) | |
| # Advertise --helpfull on stdout, since usage() was on stdout. | |
| print() | |
| print('Try --helpfull to get a list of all flags.') | |
| sys.exit(1) | |
| class HelpshortFlag(HelpFlag): | |
| """--helpshort is an alias for --help.""" | |
| NAME = 'helpshort' | |
| SHORT_NAME = None | |
| class HelpfullFlag(flags.BooleanFlag): | |
| """Display help for flags in the main module and all dependent modules.""" | |
| def __init__(self): | |
| super(HelpfullFlag, self).__init__( | |
| 'helpfull', False, 'show full help', allow_hide_cpp=True) | |
| def parse(self, arg): | |
| if self._parse(arg): | |
| usage(writeto_stdout=True) | |
| sys.exit(1) | |
| class HelpXMLFlag(flags.BooleanFlag): | |
| """Similar to HelpfullFlag, but generates output in XML format.""" | |
| def __init__(self): | |
| super(HelpXMLFlag, self).__init__( | |
| 'helpxml', False, 'like --helpfull, but generates XML output', | |
| allow_hide_cpp=True) | |
| def parse(self, arg): | |
| if self._parse(arg): | |
| flags.FLAGS.write_help_in_xml_format(sys.stdout) | |
| sys.exit(1) | |
| def parse_flags_with_usage(args): | |
| """Tries to parse the flags, print usage, and exit if unparsable. | |
| Args: | |
| args: [str], a non-empty list of the command line arguments including | |
| program name. | |
| Returns: | |
| [str], a non-empty list of remaining command line arguments after parsing | |
| flags, including program name. | |
| """ | |
| try: | |
| return FLAGS(args) | |
| except flags.Error as error: | |
| message = str(error) | |
| if '\n' in message: | |
| final_message = 'FATAL Flags parsing error:\n%s\n' % textwrap.indent( | |
| message, ' ') | |
| else: | |
| final_message = 'FATAL Flags parsing error: %s\n' % message | |
| sys.stderr.write(final_message) | |
| sys.stderr.write('Pass --helpshort or --helpfull to see help on flags.\n') | |
| sys.exit(1) | |
| _define_help_flags_called = False | |
| def define_help_flags(): | |
| """Registers help flags. Idempotent.""" | |
| # Use a global to ensure idempotence. | |
| global _define_help_flags_called | |
| if not _define_help_flags_called: | |
| flags.DEFINE_flag(HelpFlag()) | |
| flags.DEFINE_flag(HelpshortFlag()) # alias for --help | |
| flags.DEFINE_flag(HelpfullFlag()) | |
| flags.DEFINE_flag(HelpXMLFlag()) | |
| _define_help_flags_called = True | |
| def _register_and_parse_flags_with_usage( | |
| argv=None, | |
| flags_parser=parse_flags_with_usage, | |
| ): | |
| """Registers help flags, parses arguments and shows usage if appropriate. | |
| This also calls sys.exit(0) if flag --only_check_args is True. | |
| Args: | |
| argv: [str], a non-empty list of the command line arguments including | |
| program name, sys.argv is used if None. | |
| flags_parser: Callable[[List[Text]], Any], the function used to parse flags. | |
| The return value of this function is passed to `main` untouched. | |
| It must guarantee FLAGS is parsed after this function is called. | |
| Returns: | |
| The return value of `flags_parser`. When using the default `flags_parser`, | |
| it returns the following: | |
| [str], a non-empty list of remaining command line arguments after parsing | |
| flags, including program name. | |
| Raises: | |
| Error: Raised when flags_parser is called, but FLAGS is not parsed. | |
| SystemError: Raised when it's called more than once. | |
| """ | |
| if _register_and_parse_flags_with_usage.done: | |
| raise SystemError('Flag registration can be done only once.') | |
| define_help_flags() | |
| original_argv = sys.argv if argv is None else argv | |
| args_to_main = flags_parser(original_argv) | |
| if not FLAGS.is_parsed(): | |
| raise Error('FLAGS must be parsed after flags_parser is called.') | |
| # Exit when told so. | |
| if FLAGS.only_check_args: | |
| sys.exit(0) | |
| # Immediately after flags are parsed, bump verbosity to INFO if the flag has | |
| # not been set. | |
| if FLAGS['verbosity'].using_default_value: | |
| FLAGS.verbosity = 0 | |
| _register_and_parse_flags_with_usage.done = True | |
| return args_to_main | |
| _register_and_parse_flags_with_usage.done = False | |
| def _run_main(main, argv): | |
| """Calls main, optionally with pdb or profiler.""" | |
| if FLAGS.run_with_pdb: | |
| sys.exit(pdb.runcall(main, argv)) | |
| elif FLAGS.run_with_profiling or FLAGS.profile_file: | |
| # Avoid import overhead since most apps (including performance-sensitive | |
| # ones) won't be run with profiling. | |
| # pylint: disable=g-import-not-at-top | |
| import atexit | |
| if FLAGS.use_cprofile_for_profiling: | |
| import cProfile as profile | |
| else: | |
| import profile | |
| profiler = profile.Profile() | |
| if FLAGS.profile_file: | |
| atexit.register(profiler.dump_stats, FLAGS.profile_file) | |
| else: | |
| atexit.register(profiler.print_stats) | |
| sys.exit(profiler.runcall(main, argv)) | |
| else: | |
| sys.exit(main(argv)) | |
| def _call_exception_handlers(exception): | |
| """Calls any installed exception handlers.""" | |
| for handler in EXCEPTION_HANDLERS: | |
| try: | |
| if handler.wants(exception): | |
| handler.handle(exception) | |
| except: # pylint: disable=bare-except | |
| try: | |
| # We don't want to stop for exceptions in the exception handlers but | |
| # we shouldn't hide them either. | |
| logging.error(traceback.format_exc()) | |
| except: # pylint: disable=bare-except | |
| # In case even the logging statement fails, ignore. | |
| pass | |
| def run( | |
| main, | |
| argv=None, | |
| flags_parser=parse_flags_with_usage, | |
| ): | |
| """Begins executing the program. | |
| Args: | |
| main: The main function to execute. It takes an single argument "argv", | |
| which is a list of command line arguments with parsed flags removed. | |
| The return value is passed to `sys.exit`, and so for example | |
| a return value of 0 or None results in a successful termination, whereas | |
| a return value of 1 results in abnormal termination. | |
| For more details, see https://docs.python.org/3/library/sys#sys.exit | |
| argv: A non-empty list of the command line arguments including program name, | |
| sys.argv is used if None. | |
| flags_parser: Callable[[List[Text]], Any], the function used to parse flags. | |
| The return value of this function is passed to `main` untouched. | |
| It must guarantee FLAGS is parsed after this function is called. | |
| Should be passed as a keyword-only arg which will become mandatory in a | |
| future release. | |
| - Parses command line flags with the flag module. | |
| - If there are any errors, prints usage(). | |
| - Calls main() with the remaining arguments. | |
| - If main() raises a UsageError, prints usage and the error message. | |
| """ | |
| try: | |
| args = _run_init( | |
| sys.argv if argv is None else argv, | |
| flags_parser, | |
| ) | |
| while _init_callbacks: | |
| callback = _init_callbacks.popleft() | |
| callback() | |
| try: | |
| _run_main(main, args) | |
| except UsageError as error: | |
| usage(shorthelp=True, detailed_error=error, exitcode=error.exitcode) | |
| except: | |
| exc = sys.exc_info()[1] | |
| # Don't try to post-mortem debug successful SystemExits, since those | |
| # mean there wasn't actually an error. In particular, the test framework | |
| # raises SystemExit(False) even if all tests passed. | |
| if isinstance(exc, SystemExit) and not exc.code: | |
| raise | |
| # Check the tty so that we don't hang waiting for input in an | |
| # non-interactive scenario. | |
| if FLAGS.pdb_post_mortem and sys.stdout.isatty(): | |
| traceback.print_exc() | |
| print() | |
| print(' *** Entering post-mortem debugging ***') | |
| print() | |
| pdb.post_mortem() | |
| raise | |
| except Exception as e: | |
| _call_exception_handlers(e) | |
| raise | |
| # Callbacks which have been deferred until after _run_init has been called. | |
| _init_callbacks = collections.deque() | |
| def call_after_init(callback): | |
| """Calls the given callback only once ABSL has finished initialization. | |
| If ABSL has already finished initialization when ``call_after_init`` is | |
| called then the callback is executed immediately, otherwise `callback` is | |
| stored to be executed after ``app.run`` has finished initializing (aka. just | |
| before the main function is called). | |
| If called after ``app.run``, this is equivalent to calling ``callback()`` in | |
| the caller thread. If called before ``app.run``, callbacks are run | |
| sequentially (in an undefined order) in the same thread as ``app.run``. | |
| Args: | |
| callback: a callable to be called once ABSL has finished initialization. | |
| This may be immediate if initialization has already finished. It | |
| takes no arguments and returns nothing. | |
| """ | |
| if _run_init.done: | |
| callback() | |
| else: | |
| _init_callbacks.append(callback) | |
| def _run_init( | |
| argv, | |
| flags_parser, | |
| ): | |
| """Does one-time initialization and re-parses flags on rerun.""" | |
| if _run_init.done: | |
| return flags_parser(argv) | |
| command_name.make_process_name_useful() | |
| # Set up absl logging handler. | |
| logging.use_absl_handler() | |
| args = _register_and_parse_flags_with_usage( | |
| argv=argv, | |
| flags_parser=flags_parser, | |
| ) | |
| if faulthandler: | |
| try: | |
| faulthandler.enable() | |
| except Exception: # pylint: disable=broad-except | |
| # Some tests verify stderr output very closely, so don't print anything. | |
| # Disabled faulthandler is a low-impact error. | |
| pass | |
| _run_init.done = True | |
| return args | |
| _run_init.done = False | |
| def usage(shorthelp=False, writeto_stdout=False, detailed_error=None, | |
| exitcode=None): | |
| """Writes __main__'s docstring to stderr with some help text. | |
| Args: | |
| shorthelp: bool, if True, prints only flags from the main module, | |
| rather than all flags. | |
| writeto_stdout: bool, if True, writes help message to stdout, | |
| rather than to stderr. | |
| detailed_error: str, additional detail about why usage info was presented. | |
| exitcode: optional integer, if set, exits with this status code after | |
| writing help. | |
| """ | |
| if writeto_stdout: | |
| stdfile = sys.stdout | |
| else: | |
| stdfile = sys.stderr | |
| doc = sys.modules['__main__'].__doc__ | |
| if not doc: | |
| doc = '\nUSAGE: %s [flags]\n' % sys.argv[0] | |
| doc = flags.text_wrap(doc, indent=' ', firstline_indent='') | |
| else: | |
| # Replace all '%s' with sys.argv[0], and all '%%' with '%'. | |
| num_specifiers = doc.count('%') - 2 * doc.count('%%') | |
| try: | |
| doc %= (sys.argv[0],) * num_specifiers | |
| except (OverflowError, TypeError, ValueError): | |
| # Just display the docstring as-is. | |
| pass | |
| if shorthelp: | |
| flag_str = FLAGS.main_module_help() | |
| else: | |
| flag_str = FLAGS.get_help() | |
| try: | |
| stdfile.write(doc) | |
| if flag_str: | |
| stdfile.write('\nflags:\n') | |
| stdfile.write(flag_str) | |
| stdfile.write('\n') | |
| if detailed_error is not None: | |
| stdfile.write('\n%s\n' % detailed_error) | |
| except IOError as e: | |
| # We avoid printing a huge backtrace if we get EPIPE, because | |
| # "foo.par --help | less" is a frequent use case. | |
| if e.errno != errno.EPIPE: | |
| raise | |
| if exitcode is not None: | |
| sys.exit(exitcode) | |
| class ExceptionHandler(object): | |
| """Base exception handler from which other may inherit.""" | |
| def wants(self, exc): | |
| """Returns whether this handler wants to handle the exception or not. | |
| This base class returns True for all exceptions by default. Override in | |
| subclass if it wants to be more selective. | |
| Args: | |
| exc: Exception, the current exception. | |
| """ | |
| del exc # Unused. | |
| return True | |
| def handle(self, exc): | |
| """Do something with the current exception. | |
| Args: | |
| exc: Exception, the current exception | |
| This method must be overridden. | |
| """ | |
| raise NotImplementedError() | |
| def install_exception_handler(handler): | |
| """Installs an exception handler. | |
| Args: | |
| handler: ExceptionHandler, the exception handler to install. | |
| Raises: | |
| TypeError: Raised when the handler was not of the correct type. | |
| All installed exception handlers will be called if main() exits via | |
| an abnormal exception, i.e. not one of SystemExit, KeyboardInterrupt, | |
| FlagsError or UsageError. | |
| """ | |
| if not isinstance(handler, ExceptionHandler): | |
| raise TypeError('handler of type %s does not inherit from ExceptionHandler' | |
| % type(handler)) | |
| EXCEPTION_HANDLERS.append(handler) | |