find_default_config_files.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  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 configparser
  6. import os
  7. import sys
  8. from collections.abc import Iterator
  9. from pathlib import Path
  10. if sys.version_info >= (3, 11):
  11. import tomllib
  12. else:
  13. import tomli as tomllib
  14. RC_NAMES = (
  15. Path("pylintrc"),
  16. Path("pylintrc.toml"),
  17. Path(".pylintrc"),
  18. Path(".pylintrc.toml"),
  19. )
  20. PYPROJECT_NAME = Path("pyproject.toml")
  21. CONFIG_NAMES = (*RC_NAMES, PYPROJECT_NAME, Path("setup.cfg"), Path("tox.ini"))
  22. def _find_pyproject() -> Path:
  23. """Search for file pyproject.toml in the parent directories recursively.
  24. It resolves symlinks, so if there is any symlink up in the tree, it does not respect them
  25. """
  26. current_dir = Path.cwd().resolve()
  27. is_root = False
  28. while not is_root:
  29. if (current_dir / PYPROJECT_NAME).is_file():
  30. return current_dir / PYPROJECT_NAME
  31. is_root = (
  32. current_dir == current_dir.parent
  33. or (current_dir / ".git").is_dir()
  34. or (current_dir / ".hg").is_dir()
  35. )
  36. current_dir = current_dir.parent
  37. return current_dir
  38. def _toml_has_config(path: Path | str) -> bool:
  39. with open(path, mode="rb") as toml_handle:
  40. try:
  41. content = tomllib.load(toml_handle)
  42. except tomllib.TOMLDecodeError as error:
  43. print(f"Failed to load '{path}': {error}")
  44. return False
  45. return "pylint" in content.get("tool", [])
  46. def _cfg_or_ini_has_config(path: Path | str) -> bool:
  47. parser = configparser.ConfigParser()
  48. try:
  49. parser.read(path, encoding="utf-8")
  50. except configparser.Error:
  51. return False
  52. return any(
  53. section == "pylint" or section.startswith("pylint.")
  54. for section in parser.sections()
  55. )
  56. def _yield_default_files() -> Iterator[Path]:
  57. """Iterate over the default config file names and see if they exist."""
  58. for config_name in CONFIG_NAMES:
  59. try:
  60. if config_name.is_file():
  61. if config_name.suffix == ".toml" and not _toml_has_config(config_name):
  62. continue
  63. if config_name.suffix in {
  64. ".cfg",
  65. ".ini",
  66. } and not _cfg_or_ini_has_config(config_name):
  67. continue
  68. yield config_name.resolve()
  69. except OSError:
  70. pass
  71. def _find_project_config() -> Iterator[Path]:
  72. """Traverse up the directory tree to find a config file.
  73. Stop if no '__init__' is found and thus we are no longer in a package.
  74. """
  75. if Path("__init__.py").is_file():
  76. curdir = Path(os.getcwd()).resolve()
  77. while (curdir / "__init__.py").is_file():
  78. curdir = curdir.parent
  79. for rc_name in RC_NAMES:
  80. rc_path = curdir / rc_name
  81. if rc_path.is_file():
  82. yield rc_path.resolve()
  83. def _find_config_in_home_or_environment() -> Iterator[Path]:
  84. """Find a config file in the specified environment var or the home directory."""
  85. if "PYLINTRC" in os.environ and Path(os.environ["PYLINTRC"]).exists():
  86. if Path(os.environ["PYLINTRC"]).is_file():
  87. yield Path(os.environ["PYLINTRC"]).resolve()
  88. else:
  89. try:
  90. user_home = Path.home()
  91. except RuntimeError:
  92. # If the home directory does not exist a RuntimeError will be raised
  93. user_home = None
  94. if user_home is not None and str(user_home) not in ("~", "/root"):
  95. home_rc = user_home / ".pylintrc"
  96. if home_rc.is_file():
  97. yield home_rc.resolve()
  98. home_rc = user_home / ".config" / "pylintrc"
  99. if home_rc.is_file():
  100. yield home_rc.resolve()
  101. def find_default_config_files() -> Iterator[Path]:
  102. """Find all possible config files."""
  103. yield from _yield_default_files()
  104. try:
  105. yield from _find_project_config()
  106. except OSError:
  107. pass
  108. try:
  109. parent_pyproject = _find_pyproject()
  110. if parent_pyproject.is_file() and _toml_has_config(parent_pyproject):
  111. yield parent_pyproject.resolve()
  112. except OSError:
  113. pass
  114. try:
  115. yield from _find_config_in_home_or_environment()
  116. except OSError:
  117. pass
  118. try:
  119. if os.path.isfile("/etc/pylintrc"):
  120. yield Path("/etc/pylintrc").resolve()
  121. except OSError:
  122. pass