diff --git a/src/flake8/api/legacy.py b/src/flake8/api/legacy.py index 4d5c91d..db26a7b 100644 --- a/src/flake8/api/legacy.py +++ b/src/flake8/api/legacy.py @@ -204,13 +204,15 @@ def get_style_guide(**kwargs: Any) -> StyleGuide: # options set instead before we make our formatter, notifier, internal # style guide and file checker manager. options = application.options + option_overrides = {} for key, value in kwargs.items(): try: getattr(options, key) setattr(options, key, value) + option_overrides[key] = value except AttributeError: LOG.error('Could not update option "%s"', key) application.make_formatter() application.make_guide() - application.make_file_checker_manager([]) + application.make_file_checker_manager([], option_overrides) return StyleGuide(application) diff --git a/src/flake8/checker.py b/src/flake8/checker.py index c6a24eb..299ce5b 100644 --- a/src/flake8/checker.py +++ b/src/flake8/checker.py @@ -61,7 +61,10 @@ def _mp_prefork( _mp = None -def _mp_init(argv: Sequence[str]) -> None: +def _mp_init( + argv: Sequence[str], + option_overrides: dict[str, Any] | None = None, +) -> None: global _mp # Ensure correct signaling of ^C using multiprocessing.Pool. @@ -70,6 +73,9 @@ def _mp_init(argv: Sequence[str]) -> None: # for `fork` this'll already be set if _mp is None: plugins, options = parse_args(argv) + if option_overrides: + for key, value in option_overrides.items(): + setattr(options, key, value) _mp = plugins.checkers, options @@ -105,6 +111,7 @@ class Manager: style_guide: StyleGuideManager, plugins: Checkers, argv: Sequence[str], + option_overrides: dict[str, Any] | None = None, ) -> None: """Initialize our Manager instance.""" self.style_guide = style_guide @@ -119,6 +126,7 @@ class Manager: } self.exclude = (*self.options.exclude, *self.options.extend_exclude) self.argv = argv + self.option_overrides = option_overrides self.results: list[tuple[str, Results, dict[str, int]]] = [] def _process_statistics(self) -> None: @@ -192,7 +200,9 @@ class Manager: def run_parallel(self) -> None: """Run the checkers in parallel.""" with _mp_prefork(self.plugins, self.options): - pool = _try_initialize_processpool(self.jobs, self.argv) + pool = _try_initialize_processpool( + self.jobs, self.argv, self.option_overrides, + ) if pool is None: self.run_serial() @@ -547,10 +557,13 @@ class FileChecker: def _try_initialize_processpool( job_count: int, argv: Sequence[str], + option_overrides: dict[str, Any] | None = None, ) -> multiprocessing.pool.Pool | None: """Return a new process pool instance if we are able to create one.""" try: - return multiprocessing.Pool(job_count, _mp_init, initargs=(argv,)) + return multiprocessing.Pool( + job_count, _mp_init, initargs=(argv, option_overrides), + ) except OSError as err: if err.errno not in SERIAL_RETRY_ERRNOS: raise diff --git a/src/flake8/main/application.py b/src/flake8/main/application.py index 165a6ef..cb77934 100644 --- a/src/flake8/main/application.py +++ b/src/flake8/main/application.py @@ -6,6 +6,7 @@ import json import logging import time from collections.abc import Sequence +from typing import Any import flake8 from flake8 import checker @@ -79,7 +80,11 @@ class Application: self.options, self.formatter, ) - def make_file_checker_manager(self, argv: Sequence[str]) -> None: + def make_file_checker_manager( + self, + argv: Sequence[str], + option_overrides: dict[str, Any] | None = None, + ) -> None: """Initialize our FileChecker Manager.""" assert self.guide is not None assert self.plugins is not None @@ -87,6 +92,7 @@ class Application: style_guide=self.guide, plugins=self.plugins.checkers, argv=argv, + option_overrides=option_overrides, ) def run_checks(self) -> None: diff --git a/tests/integration/test_api_legacy.py b/tests/integration/test_api_legacy.py index b386bd5..0e8a5a9 100644 --- a/tests/integration/test_api_legacy.py +++ b/tests/integration/test_api_legacy.py @@ -13,3 +13,17 @@ def test_legacy_api(tmpdir): style_guide = legacy.get_style_guide() report = style_guide.check_files([t_py.strpath]) assert report.total_errors == 1 + + +def test_legacy_api_parallel_checks_use_option_overrides(tmpdir): + long_line = f"x = \"{'a' * 80}\"\n" + assert len(long_line.rstrip()) == 86 + + file1 = tmpdir.join("file1.py") + file1.write(long_line) + file2 = tmpdir.join("file2.py") + file2.write(long_line) + + style_guide = legacy.get_style_guide(max_line_length=88) + report = style_guide.check_files([file1.strpath, file2.strpath]) + assert report.total_errors == 0 diff --git a/tests/integration/test_checker.py b/tests/integration/test_checker.py index f7f07af..ea917d0 100644 --- a/tests/integration/test_checker.py +++ b/tests/integration/test_checker.py @@ -291,7 +291,7 @@ def test_acquire_when_multiprocessing_pool_can_initialize(): with mock.patch("multiprocessing.Pool") as pool: result = checker._try_initialize_processpool(2, []) - pool.assert_called_once_with(2, checker._mp_init, initargs=([],)) + pool.assert_called_once_with(2, checker._mp_init, initargs=([], None)) assert result is pool.return_value @@ -310,7 +310,7 @@ def test_acquire_when_multiprocessing_pool_can_not_initialize(): with mock.patch("multiprocessing.Pool", side_effect=ImportError) as pool: result = checker._try_initialize_processpool(2, []) - pool.assert_called_once_with(2, checker._mp_init, initargs=([],)) + pool.assert_called_once_with(2, checker._mp_init, initargs=([], None)) assert result is None diff --git a/tests/unit/test_checker_manager.py b/tests/unit/test_checker_manager.py index eecba3b..0289fde 100644 --- a/tests/unit/test_checker_manager.py +++ b/tests/unit/test_checker_manager.py @@ -63,6 +63,17 @@ def test_multiprocessing_cpu_count_not_implemented(): assert manager.jobs == 0 +def test_mp_init_applies_option_overrides(): + checker._mp = None + try: + checker._mp_init([], {"max_line_length": 88}) + assert checker._mp is not None + _, options = checker._mp + assert options.max_line_length == 88 + finally: + checker._mp = None + + def test_jobs_count_limited_to_file_count(): style_guide = style_guide_mock() style_guide.options.jobs = JobsArgument("4")