PageRenderTime 60ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/betse/util/io/error/errwarning.py

https://gitlab.com/betse/betse
Python | 139 lines | 40 code | 15 blank | 84 comment | 8 complexity | 99b148e685467842110c9488c0aaceb6 MD5 | raw file
  1. #!/usr/bin/env python3
  2. # --------------------( LICENSE )--------------------
  3. # Copyright 2014-2022 by Alexis Pietak & Cecil Curry.
  4. # See "LICENSE" for further details.
  5. '''
  6. Low-level **warning** (i.e., non-fatal errors with associated types emitted by
  7. the standard :mod:`warnings` module) facilities.
  8. '''
  9. # ....................{ IMPORTS }....................
  10. import sys, warnings
  11. from betse.util.io.log import logs
  12. from betse.util.type.types import type_check, ClassType, GeneratorType
  13. from contextlib import contextmanager
  14. # ....................{ INITIALIZERS }....................
  15. def init() -> None:
  16. '''
  17. Initialize this submodule.
  18. Specifically, this function conditionally establishes a default warning
  19. filter as follows:
  20. * If the end user explicitly passed the ``-W`` option to the external
  21. command forking the active Python interpreter (e.g., via ``python3 -W -m
  22. betse``) and hence expressed one or more warning preferences, defer to
  23. these preferences as is.
  24. * Else if this application has a Git-based working tree and is thus likely
  25. to be under active development, unconditionally log *all* warnings
  26. (including those ignored by default).
  27. * Else, preserve the default warning filter defined by the standard
  28. :mod:`warnings` module.
  29. '''
  30. # Avoid circular import dependencies.
  31. from betse.util.app.meta import appmetaone
  32. from betse.util.test import tsttest
  33. # Log this initialization.
  34. logs.log_debug('Initializing warning policy...')
  35. # If the end user explicitly passed the "-W" option to the external command
  36. # forking the active Python interpreter (and hence expressed a warning
  37. # preference), defer to these preferences.
  38. if sys.warnoptions:
  39. logs.log_debug(
  40. 'Deferring warning policy to '
  41. '"-W" option passed to Python interpreter.')
  42. # Else if the active Python interpreter is currently exercising tests,
  43. # defer to the test harness supervising these tests. Most harnesses
  44. # (including pytest) define sane default warnings filters as well as
  45. # enabling users to externally configure warnings filters from project-wide
  46. # configuration files. Ergo, the current harness knows better than we do.
  47. elif tsttest.is_testing():
  48. logs.log_debug(
  49. 'Deferring warning policy to that of the parent test harness.')
  50. # Else...
  51. else:
  52. # If this application has a Git-based working tree and is thus likely
  53. # to be under active development...
  54. if appmetaone.get_app_meta().is_git_worktree:
  55. # Log this preference.
  56. logs.log_debug(
  57. 'Setting warning policy to '
  58. 'unconditionally log all warnings '
  59. '(i.e., due to detecting developer environment).')
  60. # Discard all previously registered warnings filter *BEFORE*
  61. # registering a warnings filter.
  62. warnings.resetwarnings()
  63. # Registering a warnings filter unconditionally logging *ALL*
  64. # warnings, including those Python ignores by default.
  65. warnings.simplefilter('default')
  66. # Else, preserve Python's default warning filter as is.
  67. else:
  68. logs.log_debug('Deferring to default warning policy.')
  69. # ....................{ MANAGERS }....................
  70. def ignoring_deprecations() -> GeneratorType:
  71. '''
  72. Single-shot context manager temporarily ignoring all **deprecation
  73. warnings** (i.e., instances of the :class:`DeprecationWarning`,
  74. :class:`PendingDeprecationWarning`, and :class:`FutureWarning` exception
  75. base classes) emitted by the :mod:`warnings` module for the duration of
  76. this context.
  77. See Also
  78. -----------
  79. :class:`ignoring_warnings`
  80. Further details.
  81. '''
  82. return ignoring_warnings(
  83. DeprecationWarning,
  84. PendingDeprecationWarning,
  85. FutureWarning,
  86. )
  87. @contextmanager
  88. @type_check
  89. def ignoring_warnings(*warning_clses: ClassType) -> GeneratorType:
  90. '''
  91. Single-shot context manager temporarily ignoring *all* warnings of *all*
  92. passed warning types emitted by the :mod:`warnings` module for the duration
  93. of this context.
  94. Caveats
  95. -----------
  96. This context manager is single-shot and hence *not* safely reusable across
  97. multiple ``with`` blocks. Why? Because the low-level
  98. :class:`warnings.catch_warnings` class to which this context manager defers
  99. is itself single-shot, explicitly raising exceptions on reuse. While
  100. lamentable, this overhead appears to be unavoidable.
  101. Parameters
  102. -----------
  103. warning_clses : Tuple[ClassType]
  104. Tuple of zero or more warning classes to be ignored by this context
  105. manager. Defaults to a tuple containing only the :class:`Warning`
  106. superclass of all warning subclasses (both builtin and user-defined),
  107. in which case this context manager ignores *all* warnings.
  108. '''
  109. # If no types were passed, default to the "Warning" superclass.
  110. if not warning_clses:
  111. warning_clses = (Warning,)
  112. # Isolate all side effects produced by the "warnings" module to this block.
  113. with warnings.catch_warnings():
  114. # For each class of warning to ignore...
  115. for warning_cls in warning_clses:
  116. # Temporarily ignore all instances of this warning class.
  117. warnings.simplefilter(action='ignore', category=warning_cls)
  118. # Yield control to the body of the caller's "with" block.
  119. yield