Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create additional namespaces for subdirectories with pylint configs #9395

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions .pyenchant_pylint_custom_dict.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ codecs
col's
conf
config
configs
const
Const
contextlib
Expand Down Expand Up @@ -310,11 +311,13 @@ str
stringified
subclasses
subcommands
subconfigs
subdicts
subgraphs
sublists
submodule
submodules
subpackage
subparsers
subparts
subprocess
Expand Down
9 changes: 9 additions & 0 deletions doc/user_guide/configuration/all-options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,13 @@ Standard Checkers
**Default:** ``False``


--use-local-configs
"""""""""""""""""""
*When some of the modules to be linted have a pylint config in their directory or any of their parent directories, all checkers use this local config to check those modules. If present, local config replaces entirely a config from current working directory (cwd). Modules that don't have local pylint config are still checked using config from cwd. When pylint starts, it always loads base config from the cwd first. Some options in base config can prevent local configs from loading (e.g. disable=all). Some options for Main checker will work only in base config: evaluation, exit_zero, fail_under, from_stdin, jobs, persistent, recursive, reports, score.*

**Default:** ``False``



.. raw:: html

Expand Down Expand Up @@ -285,6 +292,8 @@ Standard Checkers

unsafe-load-any-extension = false

use-local-configs = false



.. raw:: html
Expand Down
13 changes: 13 additions & 0 deletions doc/whatsnew/fragments/618.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Add new command line option: use-local-configs.

use-local-configs enables loading of local pylint configurations in addition to the base pylint config from $PWD. Local configurations are searched in the same directories where linted files are located and upwards until $PWD or root.
For example:
if there exists package/pylintrc, then
pylint --use-local-configs=y package/file.py
will use package/pylintrc instead of default config from $PWD.

if there exists package/pylintrc, and doesn't exist package/subpackage/pylintrc, then
pylint --use-local-configs=y package/subpackage/file.py
will use package/pylintrc instead of default config from $PWD.

Closes #618
5 changes: 5 additions & 0 deletions pylint/config/arguments_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ def __init__(
self._directory_namespaces: DirectoryNamespaceDict = {}
"""Mapping of directories and their respective namespace objects."""

self._cli_args: list[str] = []
"""Options that were passed as command line arguments and have highest priority."""

@property
def config(self) -> argparse.Namespace:
"""Namespace for all options."""
Expand Down Expand Up @@ -226,6 +229,8 @@ def _parse_command_line_configuration(
) -> list[str]:
"""Parse the arguments found on the command line into the namespace."""
arguments = sys.argv[1:] if arguments is None else arguments
if not self._cli_args:
self._cli_args = list(arguments)

self.config, parsed_args = self._arg_parser.parse_known_args(
arguments, self.config
Expand Down
2 changes: 1 addition & 1 deletion pylint/config/config_file_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def parse_config_file(
raise OSError(f"The config file {file_path} doesn't exist!")

if verbose:
print(f"Using config file {file_path}", file=sys.stderr)
print(f"Loading config file {file_path}", file=sys.stderr)

if file_path.suffix == ".toml":
return _RawConfParser.parse_toml_file(file_path)
Expand Down
7 changes: 6 additions & 1 deletion pylint/config/config_initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from pylint.lint import PyLinter


# pylint: disable = too-many-statements
def _config_initialization(
linter: PyLinter,
args_list: list[str],
Expand Down Expand Up @@ -82,6 +83,9 @@ def _config_initialization(
args_list = _order_all_first(args_list, joined=True)
parsed_args_list = linter._parse_command_line_configuration(args_list)

# save Runner.verbose to make this preprocessed option visible from other modules
linter.config.verbose = verbose_mode

# Remove the positional arguments separator from the list of arguments if it exists
try:
parsed_args_list.remove("--")
Expand Down Expand Up @@ -141,7 +145,8 @@ def _config_initialization(
linter._parse_error_mode()

# Link the base Namespace object on the current directory
linter._directory_namespaces[Path(".").resolve()] = (linter.config, {})
if Path(".").resolve() not in linter._directory_namespaces:
linter._directory_namespaces[Path(".").resolve()] = (linter.config, {})

# parsed_args_list should now only be a list of inputs to lint.
# All other options have been removed from the list.
Expand Down
17 changes: 12 additions & 5 deletions pylint/config/find_default_config_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,19 @@ def _cfg_has_config(path: Path | str) -> bool:
return any(section.startswith("pylint.") for section in parser.sections())


def _yield_default_files() -> Iterator[Path]:
def _yield_default_files(basedir: Path | str = ".") -> Iterator[Path]:
"""Iterate over the default config file names and see if they exist."""
basedir = Path(basedir)
for config_name in CONFIG_NAMES:
config_file = basedir / config_name
try:
if config_name.is_file():
if config_name.suffix == ".toml" and not _toml_has_config(config_name):
if config_file.is_file():
if config_file.suffix == ".toml" and not _toml_has_config(config_file):
continue
if config_name.suffix == ".cfg" and not _cfg_has_config(config_name):
if config_file.suffix == ".cfg" and not _cfg_has_config(config_file):
continue

yield config_name.resolve()
yield config_file.resolve()
except OSError:
pass

Expand Down Expand Up @@ -142,3 +144,8 @@ def find_default_config_files() -> Iterator[Path]:
yield Path("/etc/pylintrc").resolve()
except OSError:
pass


def find_subdirectory_config_files(basedir: Path | str) -> Iterator[Path]:
"""Find config file in arbitrary subdirectory."""
yield from _yield_default_files(basedir)
17 changes: 17 additions & 0 deletions pylint/lint/base_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,23 @@ def _make_linter_options(linter: PyLinter) -> Options:
"Useful if running pylint in a server-like mode.",
},
),
(
"use-local-configs",
{
"default": False,
"type": "yn",
"metavar": "<y or n>",
"help": "When some of the modules to be linted have a pylint config in their directory "
"or any of their parent directories, all checkers use this local config to check "
"those modules. "
"If present, local config replaces entirely a config from current working directory (cwd). "
"Modules that don't have local pylint config are still checked using config from cwd. "
"When pylint starts, it always loads base config from the cwd first. Some options in "
"base config can prevent local configs from loading (e.g. disable=all). "
"Some options for Main checker will work only in base config: "
"evaluation, exit_zero, fail_under, from_stdin, jobs, persistent, recursive, reports, score.",
},
),
)


Expand Down