diff --git a/doc/whatsnew/fragments/7169.bugfix b/doc/whatsnew/fragments/7169.bugfix new file mode 100644 index 0000000000..6ddf1a498e --- /dev/null +++ b/doc/whatsnew/fragments/7169.bugfix @@ -0,0 +1,3 @@ +Don't crash on ``OSError`` in config file discovery. + +Closes #7169 diff --git a/pylint/config/find_default_config_files.py b/pylint/config/find_default_config_files.py index 36917a380c..314e70e110 100644 --- a/pylint/config/find_default_config_files.py +++ b/pylint/config/find_default_config_files.py @@ -39,17 +39,26 @@ def _cfg_has_config(path: Path | str) -> bool: return any(section.startswith("pylint.") for section in parser.sections()) -def find_default_config_files() -> Iterator[Path]: - """Find all possible config files.""" +def _yield_default_files() -> Iterator[Path]: + """Iterate over the default config file names and see if they exist.""" for config_name in CONFIG_NAMES: - if config_name.is_file(): - if config_name.suffix == ".toml" and not _toml_has_config(config_name): - continue - if config_name.suffix == ".cfg" and not _cfg_has_config(config_name): - continue + try: + if config_name.is_file(): + if config_name.suffix == ".toml" and not _toml_has_config(config_name): + continue + if config_name.suffix == ".cfg" and not _cfg_has_config(config_name): + continue + + yield config_name.resolve() + except OSError: + pass + - yield config_name.resolve() +def _find_project_config() -> Iterator[Path]: + """Traverse up the directory tree to find a config file. + Stop if no '__init__' is found and thus we are no longer in a package. + """ if Path("__init__.py").is_file(): curdir = Path(os.getcwd()).resolve() while (curdir / "__init__.py").is_file(): @@ -59,6 +68,9 @@ def find_default_config_files() -> Iterator[Path]: if rc_path.is_file(): yield rc_path.resolve() + +def _find_config_in_home_or_environment() -> Iterator[Path]: + """Find a config file in the specified environment var or the home directory.""" if "PYLINTRC" in os.environ and Path(os.environ["PYLINTRC"]).exists(): if Path(os.environ["PYLINTRC"]).is_file(): yield Path(os.environ["PYLINTRC"]).resolve() @@ -68,16 +80,36 @@ def find_default_config_files() -> Iterator[Path]: except RuntimeError: # If the home directory does not exist a RuntimeError will be raised user_home = None + if user_home is not None and str(user_home) not in ("~", "/root"): home_rc = user_home / ".pylintrc" if home_rc.is_file(): yield home_rc.resolve() + home_rc = user_home / ".config" / "pylintrc" if home_rc.is_file(): yield home_rc.resolve() - if os.path.isfile("/etc/pylintrc"): - yield Path("/etc/pylintrc").resolve() + +def find_default_config_files() -> Iterator[Path]: + """Find all possible config files.""" + yield from _yield_default_files() + + try: + yield from _find_project_config() + except OSError: + pass + + try: + yield from _find_config_in_home_or_environment() + except OSError: + pass + + try: + if os.path.isfile("/etc/pylintrc"): + yield Path("/etc/pylintrc").resolve() + except OSError: + pass def find_pylintrc() -> str | None: diff --git a/tests/config/test_find_default_config_files.py b/tests/config/test_find_default_config_files.py index 9addb6db2e..10484be1d6 100644 --- a/tests/config/test_find_default_config_files.py +++ b/tests/config/test_find_default_config_files.py @@ -253,3 +253,12 @@ def test_non_existent_home() -> None: assert not list(config.find_default_config_files()) os.chdir(current_dir) + + +def test_permission_error() -> None: + """Test that we handle PermissionError correctly in find_default_config_files. + + Reported in https://github.com/PyCQA/pylint/issues/7169. + """ + with mock.patch("pathlib.Path.is_file", side_effect=PermissionError): + list(config.find_default_config_files())