application.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. """Module containing the application logic for Flake8."""
  2. from __future__ import annotations
  3. import argparse
  4. import json
  5. import logging
  6. import time
  7. from typing import Sequence
  8. import flake8
  9. from flake8 import checker
  10. from flake8 import defaults
  11. from flake8 import exceptions
  12. from flake8 import style_guide
  13. from flake8.formatting.base import BaseFormatter
  14. from flake8.main import debug
  15. from flake8.options.parse_args import parse_args
  16. from flake8.plugins import finder
  17. from flake8.plugins import reporter
  18. LOG = logging.getLogger(__name__)
  19. class Application:
  20. """Abstract our application into a class."""
  21. def __init__(self) -> None:
  22. """Initialize our application."""
  23. #: The timestamp when the Application instance was instantiated.
  24. self.start_time = time.time()
  25. #: The timestamp when the Application finished reported errors.
  26. self.end_time: float | None = None
  27. self.plugins: finder.Plugins | None = None
  28. #: The user-selected formatter from :attr:`formatting_plugins`
  29. self.formatter: BaseFormatter | None = None
  30. #: The :class:`flake8.style_guide.StyleGuideManager` built from the
  31. #: user's options
  32. self.guide: style_guide.StyleGuideManager | None = None
  33. #: The :class:`flake8.checker.Manager` that will handle running all of
  34. #: the checks selected by the user.
  35. self.file_checker_manager: checker.Manager | None = None
  36. #: The user-supplied options parsed into an instance of
  37. #: :class:`argparse.Namespace`
  38. self.options: argparse.Namespace | None = None
  39. #: The number of errors, warnings, and other messages after running
  40. #: flake8 and taking into account ignored errors and lines.
  41. self.result_count = 0
  42. #: The total number of errors before accounting for ignored errors and
  43. #: lines.
  44. self.total_result_count = 0
  45. #: Whether or not something catastrophic happened and we should exit
  46. #: with a non-zero status code
  47. self.catastrophic_failure = False
  48. def exit_code(self) -> int:
  49. """Return the program exit code."""
  50. if self.catastrophic_failure:
  51. return 1
  52. assert self.options is not None
  53. if self.options.exit_zero:
  54. return 0
  55. else:
  56. return int(self.result_count > 0)
  57. def make_formatter(self) -> None:
  58. """Initialize a formatter based on the parsed options."""
  59. assert self.plugins is not None
  60. assert self.options is not None
  61. self.formatter = reporter.make(self.plugins.reporters, self.options)
  62. def make_guide(self) -> None:
  63. """Initialize our StyleGuide."""
  64. assert self.formatter is not None
  65. assert self.options is not None
  66. self.guide = style_guide.StyleGuideManager(
  67. self.options, self.formatter
  68. )
  69. def make_file_checker_manager(self, argv: Sequence[str]) -> None:
  70. """Initialize our FileChecker Manager."""
  71. assert self.guide is not None
  72. assert self.plugins is not None
  73. self.file_checker_manager = checker.Manager(
  74. style_guide=self.guide,
  75. plugins=self.plugins.checkers,
  76. argv=argv,
  77. )
  78. def run_checks(self) -> None:
  79. """Run the actual checks with the FileChecker Manager.
  80. This method encapsulates the logic to make a
  81. :class:`~flake8.checker.Manger` instance run the checks it is
  82. managing.
  83. """
  84. assert self.file_checker_manager is not None
  85. self.file_checker_manager.start()
  86. try:
  87. self.file_checker_manager.run()
  88. except exceptions.PluginExecutionFailed as plugin_failed:
  89. print(str(plugin_failed))
  90. print("Run flake8 with greater verbosity to see more details")
  91. self.catastrophic_failure = True
  92. LOG.info("Finished running")
  93. self.file_checker_manager.stop()
  94. self.end_time = time.time()
  95. def report_benchmarks(self) -> None:
  96. """Aggregate, calculate, and report benchmarks for this run."""
  97. assert self.options is not None
  98. if not self.options.benchmark:
  99. return
  100. assert self.file_checker_manager is not None
  101. assert self.end_time is not None
  102. time_elapsed = self.end_time - self.start_time
  103. statistics = [("seconds elapsed", time_elapsed)]
  104. add_statistic = statistics.append
  105. for statistic in defaults.STATISTIC_NAMES + ("files",):
  106. value = self.file_checker_manager.statistics[statistic]
  107. total_description = f"total {statistic} processed"
  108. add_statistic((total_description, value))
  109. per_second_description = f"{statistic} processed per second"
  110. add_statistic((per_second_description, int(value / time_elapsed)))
  111. assert self.formatter is not None
  112. self.formatter.show_benchmarks(statistics)
  113. def report_errors(self) -> None:
  114. """Report all the errors found by flake8 3.0.
  115. This also updates the :attr:`result_count` attribute with the total
  116. number of errors, warnings, and other messages found.
  117. """
  118. LOG.info("Reporting errors")
  119. assert self.file_checker_manager is not None
  120. results = self.file_checker_manager.report()
  121. self.total_result_count, self.result_count = results
  122. LOG.info(
  123. "Found a total of %d violations and reported %d",
  124. self.total_result_count,
  125. self.result_count,
  126. )
  127. def report_statistics(self) -> None:
  128. """Aggregate and report statistics from this run."""
  129. assert self.options is not None
  130. if not self.options.statistics:
  131. return
  132. assert self.formatter is not None
  133. assert self.guide is not None
  134. self.formatter.show_statistics(self.guide.stats)
  135. def initialize(self, argv: Sequence[str]) -> None:
  136. """Initialize the application to be run.
  137. This finds the plugins, registers their options, and parses the
  138. command-line arguments.
  139. """
  140. self.plugins, self.options = parse_args(argv)
  141. if self.options.bug_report:
  142. info = debug.information(flake8.__version__, self.plugins)
  143. print(json.dumps(info, indent=2, sort_keys=True))
  144. raise SystemExit(0)
  145. self.make_formatter()
  146. self.make_guide()
  147. self.make_file_checker_manager(argv)
  148. def report(self) -> None:
  149. """Report errors, statistics, and benchmarks."""
  150. assert self.formatter is not None
  151. self.formatter.start()
  152. self.report_errors()
  153. self.report_statistics()
  154. self.report_benchmarks()
  155. self.formatter.stop()
  156. def _run(self, argv: Sequence[str]) -> None:
  157. self.initialize(argv)
  158. self.run_checks()
  159. self.report()
  160. def run(self, argv: Sequence[str]) -> None:
  161. """Run our application.
  162. This method will also handle KeyboardInterrupt exceptions for the
  163. entirety of the flake8 application. If it sees a KeyboardInterrupt it
  164. will forcibly clean up the :class:`~flake8.checker.Manager`.
  165. """
  166. try:
  167. self._run(argv)
  168. except KeyboardInterrupt as exc:
  169. print("... stopped")
  170. LOG.critical("Caught keyboard interrupt from user")
  171. LOG.exception(exc)
  172. self.catastrophic_failure = True
  173. except exceptions.ExecutionError as exc:
  174. print("There was a critical error during execution of Flake8:")
  175. print(exc)
  176. LOG.exception(exc)
  177. self.catastrophic_failure = True
  178. except exceptions.EarlyQuit:
  179. self.catastrophic_failure = True
  180. print("... stopped while processing files")
  181. else:
  182. assert self.options is not None
  183. if self.options.count:
  184. print(self.result_count)