utils.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
  3. # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
  4. from __future__ import annotations
  5. import contextlib
  6. import sys
  7. import traceback
  8. import warnings
  9. from collections.abc import Iterator, Sequence
  10. from datetime import datetime
  11. from pathlib import Path
  12. from pylint.config import PYLINT_HOME
  13. from pylint.lint.expand_modules import discover_package_path
  14. def prepare_crash_report(ex: Exception, filepath: str, crash_file_path: str) -> Path:
  15. issue_template_path = (
  16. Path(PYLINT_HOME) / datetime.now().strftime(str(crash_file_path))
  17. ).resolve()
  18. with open(filepath, encoding="utf8") as f:
  19. file_content = f.read()
  20. template = ""
  21. if not issue_template_path.exists():
  22. template = """\
  23. First, please verify that the bug is not already filled:
  24. https://github.com/pylint-dev/pylint/issues/
  25. Then create a new issue:
  26. https://github.com/pylint-dev/pylint/issues/new?labels=Crash 💥%2CNeeds triage 📥
  27. """
  28. template += f"""\
  29. Issue title:
  30. Crash ``{ex}`` (if possible, be more specific about what made pylint crash)
  31. Content:
  32. When parsing the following file:
  33. <!--
  34. If sharing the code is not an option, please state so,
  35. but providing only the stacktrace would still be helpful.
  36. -->
  37. ```python
  38. {file_content}
  39. ```
  40. pylint crashed with a ``{ex.__class__.__name__}`` and with the following stacktrace:
  41. ```
  42. """
  43. template += traceback.format_exc()
  44. template += "```\n"
  45. try:
  46. with open(issue_template_path, "a", encoding="utf8") as f:
  47. f.write(template)
  48. except Exception as exc: # pylint: disable=broad-except
  49. print(
  50. f"Can't write the issue template for the crash in {issue_template_path} "
  51. f"because of: '{exc}'\nHere's the content anyway:\n{template}.",
  52. file=sys.stderr,
  53. )
  54. return issue_template_path
  55. def get_fatal_error_message(filepath: str, issue_template_path: Path) -> str:
  56. return (
  57. f"Fatal error while checking '{filepath}'. "
  58. f"Please open an issue in our bug tracker so we address this. "
  59. f"There is a pre-filled template that you can use in '{issue_template_path}'."
  60. )
  61. def _patch_sys_path(args: Sequence[str]) -> list[str]:
  62. # TODO: Remove deprecated function
  63. warnings.warn(
  64. "_patch_sys_path has been deprecated because it relies on auto-magic package path "
  65. "discovery which is implemented by get_python_path that is deprecated. "
  66. "Use _augment_sys_path and pass additional sys.path entries as an argument obtained from "
  67. "discover_package_path.",
  68. DeprecationWarning,
  69. stacklevel=2,
  70. )
  71. return _augment_sys_path([discover_package_path(arg, []) for arg in args])
  72. def _augment_sys_path(additional_paths: Sequence[str]) -> list[str]:
  73. original = list(sys.path)
  74. changes = []
  75. seen = set()
  76. for additional_path in additional_paths:
  77. if additional_path not in seen:
  78. changes.append(additional_path)
  79. seen.add(additional_path)
  80. sys.path[:] = changes + sys.path
  81. return original
  82. @contextlib.contextmanager
  83. def fix_import_path(args: Sequence[str]) -> Iterator[None]:
  84. """Prepare 'sys.path' for running the linter checks.
  85. Within this context, each of the given arguments is importable.
  86. Paths are added to 'sys.path' in corresponding order to the arguments.
  87. We avoid adding duplicate directories to sys.path.
  88. `sys.path` is reset to its original value upon exiting this context.
  89. """
  90. # TODO: Remove deprecated function
  91. warnings.warn(
  92. "fix_import_path has been deprecated because it relies on auto-magic package path "
  93. "discovery which is implemented by get_python_path that is deprecated. "
  94. "Use augmented_sys_path and pass additional sys.path entries as an argument obtained from "
  95. "discover_package_path.",
  96. DeprecationWarning,
  97. stacklevel=2,
  98. )
  99. with augmented_sys_path([discover_package_path(arg, []) for arg in args]):
  100. yield
  101. @contextlib.contextmanager
  102. def augmented_sys_path(additional_paths: Sequence[str]) -> Iterator[None]:
  103. """Augment 'sys.path' by adding non-existent entries from additional_paths."""
  104. original = _augment_sys_path(additional_paths)
  105. try:
  106. yield
  107. finally:
  108. sys.path[:] = original
  109. def _is_relative_to(self: Path, *other: Path) -> bool:
  110. """Checks if self is relative to other.
  111. Backport of pathlib.Path.is_relative_to for Python <3.9
  112. TODO: py39: Remove this backport and use stdlib function.
  113. """
  114. try:
  115. self.relative_to(*other)
  116. return True
  117. except ValueError:
  118. return False