Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ Release date: TBA

Ref #5392

* ``MapReduceMixin`` has been deprecated. ``BaseChecker`` now implements ``get_map_data`` and
``reduce_map_data``. If a checker actually needs to reduce data it should define ``get_map_data``
as returning something different than ``None`` and let its ``reduce_map_data`` handle a list
of the types returned by ``get_map_data``.
An example can be seen by looking at ``pylint/checkers/similar.py``.

* ``UnsupportedAction`` has been deprecated.

Ref #5392
Expand Down
11 changes: 11 additions & 0 deletions doc/how_tos/custom_checkers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,17 @@ Now we can debug our checker!
environment variable or by adding the ``my_plugin.py``
file to the ``pylint/checkers`` directory if running from source.

Parallelize a Checker
---------------------

``BaseChecker`` has two methods ``get_map_data`` and ``reduce_map_data`` that
permit to parallelize the checks when used with the ``-j`` option. If a checker
actually needs to reduce data it should define ``get_map_data`` as returning
something different than ``None`` and let its ``reduce_map_data`` handle a list
of the types returned by ``get_map_data``.

An example can be seen by looking at ``pylint/checkers/similar.py``.

Testing a Checker
-----------------
Pylint is very well suited to test driven development.
Expand Down
6 changes: 6 additions & 0 deletions doc/whatsnew/2.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ New checkers

Closes #5895

* ``MapReduceMixin`` has been deprecated. ``BaseChecker`` now implements ``get_map_data`` and
``reduce_map_data``. If a checker actually needs to reduce data it should define ``get_map_data``
as returning something different than ``None`` and let its ``reduce_map_data`` handle a list
of the types returned by ``get_map_data``.
An example can be seen by looking at ``pylint/checkers/similar.py``.

* Add new check ``unnecessary-dunder-call`` for unnecessary dunder method calls.

Closes #5936
Expand Down
8 changes: 8 additions & 0 deletions pylint/checkers/base_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,14 @@ def open(self):
def close(self):
"""Called after visiting project (i.e set of modules)."""

# pylint: disable-next=no-self-use
def get_map_data(self) -> Any:
return None

# pylint: disable-next=no-self-use, unused-argument
def reduce_map_data(self, linter: PyLinter, data: list[Any]) -> None:
return None


class BaseTokenChecker(BaseChecker):
"""Base class for checkers that want to have access to the token stream."""
Expand Down
18 changes: 16 additions & 2 deletions pylint/checkers/mapreduce_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,30 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt

from __future__ import annotations

import abc
import warnings
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from pylint.lint import PyLinter


class MapReduceMixin(metaclass=abc.ABCMeta):
"""A mixin design to allow multiprocess/threaded runs of a Checker."""

def __init__(self) -> None:
warnings.warn(
"MapReduceMixin has been deprecated and will be removed in pylint 3.0. "
"To make a checker reduce map data simply implement get_map_data and reduce_map_data.",
DeprecationWarning,
)

@abc.abstractmethod
def get_map_data(self):
def get_map_data(self) -> Any:
"""Returns mergeable/reducible data that will be examined."""

@abc.abstractmethod
def reduce_map_data(self, linter, data):
def reduce_map_data(self, linter: PyLinter, data: list[Any]) -> None:
"""For a given Checker, receives data for all mapped runs."""
4 changes: 2 additions & 2 deletions pylint/checkers/similar.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
import astroid
from astroid import nodes

from pylint.checkers import BaseChecker, MapReduceMixin, table_lines_from_stats
from pylint.checkers import BaseChecker, table_lines_from_stats
from pylint.interfaces import IRawChecker
from pylint.reporters.ureports.nodes import Table
from pylint.typing import Options
Expand Down Expand Up @@ -727,7 +727,7 @@ def report_similarities(


# wrapper to get a pylint checker from the similar class
class SimilarChecker(BaseChecker, Similar, MapReduceMixin):
class SimilarChecker(BaseChecker, Similar):
"""Checks for similarities and duplicated code.

This computation may be memory / CPU intensive, so you
Expand Down
15 changes: 6 additions & 9 deletions pylint/lint/parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,9 @@ def _worker_check_single_file(
_worker_linter.check_single_file_item(file_item)
mapreduce_data = defaultdict(list)
for checker in _worker_linter.get_checkers():
try:
data = checker.get_map_data() # type: ignore[attr-defined]
except AttributeError:
continue
mapreduce_data[checker.name].append(data)
data = checker.get_map_data()
if data is not None:
mapreduce_data[checker.name].append(data)
msgs = _worker_linter.reporter.messages
assert isinstance(_worker_linter.reporter, reporters.CollectingReporter)
_worker_linter.reporter.reset()
Expand Down Expand Up @@ -108,7 +106,7 @@ def _merge_mapreduce_data(
# validation. The intent here is to collect all the mapreduce data for all checker-
# runs across processes - that will then be passed to a static method on the
# checkers to be reduced and further processed.
collated_map_reduce_data = defaultdict(list)
collated_map_reduce_data: defaultdict[str, list[Any]] = defaultdict(list)
for linter_data in all_mapreduce_data.values():
for run_data in linter_data:
for checker_name, data in run_data.items():
Expand All @@ -120,7 +118,7 @@ def _merge_mapreduce_data(
if checker.name in collated_map_reduce_data:
# Assume that if the check has returned map/reduce data that it has the
# reducer function
checker.reduce_map_data(linter, collated_map_reduce_data[checker.name]) # type: ignore[attr-defined]
checker.reduce_map_data(linter, collated_map_reduce_data[checker.name])


def check_parallel(
Expand All @@ -132,8 +130,7 @@ def check_parallel(
"""Use the given linter to lint the files with given amount of workers (jobs).

This splits the work filestream-by-filestream. If you need to do work across
multiple files, as in the similarity-checker, then inherit from MapReduceMixin and
implement the map/reduce mixin functionality.
multiple files, as in the similarity-checker, then implement the map/reduce mixin functionality.
"""
# The linter is inherited by all the pool's workers, i.e. the linter
# is identical to the linter object here. This is required so that
Expand Down
2 changes: 1 addition & 1 deletion tests/checkers/unittest_similar.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ def test_no_args() -> None:


def test_get_map_data() -> None:
"""Tests that a SimilarChecker respects the MapReduceMixin interface."""
"""Tests that a SimilarChecker can return and reduce mapped data."""
linter = PyLinter(reporter=Reporter())
# Add a parallel checker to ensure it can map and reduce
linter.register_checker(similar.SimilarChecker(linter))
Expand Down
3 changes: 1 addition & 2 deletions tests/test_check_parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import pylint.interfaces
import pylint.lint.parallel
from pylint.checkers.base_checker import BaseChecker
from pylint.checkers.mapreduce_checker import MapReduceMixin
from pylint.lint import PyLinter
from pylint.lint.parallel import _worker_check_single_file as worker_check_single_file
from pylint.lint.parallel import _worker_initialize as worker_initialize
Expand Down Expand Up @@ -73,7 +72,7 @@ def process_module(self, _node: nodes.Module) -> None:
self.data.append(record)


class ParallelTestChecker(BaseChecker, MapReduceMixin):
class ParallelTestChecker(BaseChecker):
"""A checker that does need to consolidate data.

To simulate the need to consolidate data, this checker only
Expand Down
28 changes: 28 additions & 0 deletions tests/test_deprecation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt

"""Check deprecation across the codebase."""

from __future__ import annotations

from typing import Any

import pytest

from pylint.checkers.mapreduce_checker import MapReduceMixin
from pylint.lint import PyLinter


def test_mapreducemixin() -> None:
"""Test that MapReduceMixin has been deprecated correctly."""

class MyChecker(MapReduceMixin):
def get_map_data(self) -> Any:
...

def reduce_map_data(self, linter: PyLinter, data: list[Any]) -> None:
...

with pytest.warns(DeprecationWarning):
MyChecker()