prospector 1.12.1__py3-none-any.whl → 1.13.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. prospector/autodetect.py +10 -9
  2. prospector/blender.py +18 -11
  3. prospector/config/__init__.py +66 -49
  4. prospector/config/configuration.py +14 -12
  5. prospector/config/datatype.py +1 -1
  6. prospector/encoding.py +2 -2
  7. prospector/exceptions.py +1 -5
  8. prospector/finder.py +9 -8
  9. prospector/formatters/__init__.py +1 -1
  10. prospector/formatters/base.py +16 -11
  11. prospector/formatters/base_summary.py +43 -0
  12. prospector/formatters/emacs.py +5 -5
  13. prospector/formatters/grouped.py +9 -7
  14. prospector/formatters/json.py +3 -2
  15. prospector/formatters/pylint.py +31 -8
  16. prospector/formatters/text.py +10 -58
  17. prospector/formatters/vscode.py +17 -6
  18. prospector/formatters/xunit.py +6 -6
  19. prospector/formatters/yaml.py +4 -2
  20. prospector/message.py +18 -13
  21. prospector/pathutils.py +3 -10
  22. prospector/postfilter.py +8 -7
  23. prospector/profiles/exceptions.py +14 -11
  24. prospector/profiles/profile.py +69 -58
  25. prospector/run.py +20 -18
  26. prospector/suppression.py +12 -10
  27. prospector/tools/__init__.py +19 -13
  28. prospector/tools/bandit/__init__.py +27 -15
  29. prospector/tools/base.py +11 -3
  30. prospector/tools/dodgy/__init__.py +7 -3
  31. prospector/tools/mccabe/__init__.py +13 -6
  32. prospector/tools/mypy/__init__.py +44 -81
  33. prospector/tools/profile_validator/__init__.py +24 -15
  34. prospector/tools/pycodestyle/__init__.py +22 -15
  35. prospector/tools/pydocstyle/__init__.py +12 -6
  36. prospector/tools/pyflakes/__init__.py +35 -19
  37. prospector/tools/pylint/__init__.py +47 -28
  38. prospector/tools/pylint/collector.py +3 -5
  39. prospector/tools/pylint/linter.py +11 -9
  40. prospector/tools/pyright/__init__.py +18 -7
  41. prospector/tools/pyroma/__init__.py +10 -6
  42. prospector/tools/ruff/__init__.py +75 -0
  43. prospector/tools/utils.py +25 -17
  44. prospector/tools/vulture/__init__.py +25 -15
  45. {prospector-1.12.1.dist-info → prospector-1.13.0.dist-info}/METADATA +4 -3
  46. prospector-1.13.0.dist-info/RECORD +71 -0
  47. prospector-1.12.1.dist-info/RECORD +0 -69
  48. {prospector-1.12.1.dist-info → prospector-1.13.0.dist-info}/LICENSE +0 -0
  49. {prospector-1.12.1.dist-info → prospector-1.13.0.dist-info}/WHEEL +0 -0
  50. {prospector-1.12.1.dist-info → prospector-1.13.0.dist-info}/entry_points.txt +0 -0
@@ -2,8 +2,9 @@ import os
2
2
  import re
3
3
  import sys
4
4
  from collections import defaultdict
5
+ from collections.abc import Iterable
5
6
  from pathlib import Path
6
- from typing import List
7
+ from typing import TYPE_CHECKING, Any, Optional, Union
7
8
 
8
9
  from pylint.config import find_default_config_files
9
10
  from pylint.exceptions import UnknownMessageError
@@ -15,6 +16,9 @@ from prospector.tools.base import ToolBase
15
16
  from prospector.tools.pylint.collector import Collector
16
17
  from prospector.tools.pylint.linter import ProspectorLinter
17
18
 
19
+ if TYPE_CHECKING:
20
+ from prospector.config import ProspectorConfig
21
+
18
22
  _UNUSED_WILDCARD_IMPORT_RE = re.compile(r"^Unused import(\(s\))? (.*) from wildcard import")
19
23
 
20
24
 
@@ -27,12 +31,13 @@ class PylintTool(ToolBase):
27
31
  # be functions (they don't use the 'self' argument) but that would
28
32
  # make this module/class a bit ugly.
29
33
 
30
- def __init__(self):
31
- self._args = None
32
- self._collector = self._linter = None
33
- self._orig_sys_path = []
34
+ def __init__(self) -> None:
35
+ self._args: Any = None
36
+ self._collector: Optional[Collector] = None
37
+ self._linter: Optional[ProspectorLinter] = None
38
+ self._orig_sys_path: list[str] = []
34
39
 
35
- def _prospector_configure(self, prospector_config, linter: ProspectorLinter):
40
+ def _prospector_configure(self, prospector_config: "ProspectorConfig", linter: ProspectorLinter) -> list[Message]:
36
41
  errors = []
37
42
 
38
43
  if "django" in prospector_config.libraries:
@@ -43,14 +48,14 @@ class PylintTool(ToolBase):
43
48
  linter.load_plugin_modules(["pylint_flask"])
44
49
 
45
50
  profile_path = os.path.join(prospector_config.workdir, prospector_config.profile.name)
46
- for plugin in prospector_config.profile.pylint.get("load-plugins", []):
51
+ for plugin in prospector_config.profile.pylint.get("load-plugins", []): # type: ignore[attr-defined]
47
52
  try:
48
53
  linter.load_plugin_modules([plugin])
49
54
  except ImportError:
50
55
  errors.append(self._error_message(profile_path, f"Could not load plugin {plugin}"))
51
56
 
52
57
  for msg_id in prospector_config.get_disabled_messages("pylint"):
53
- try:
58
+ try: # noqa: SIM105
54
59
  linter.disable(msg_id)
55
60
  except UnknownMessageError:
56
61
  # If the msg_id doesn't exist in PyLint any more,
@@ -64,7 +69,9 @@ class PylintTool(ToolBase):
64
69
  continue
65
70
  for option in checker.options:
66
71
  if option[0] in options:
67
- checker._arguments_manager.set_option(option[0], options[option[0]])
72
+ checker._arguments_manager.set_option( # pylint: disable=protected-access
73
+ option[0], options[option[0]]
74
+ )
68
75
 
69
76
  # The warnings about disabling warnings are useful for figuring out
70
77
  # with other tools to suppress messages from. For example, an unused
@@ -82,16 +89,17 @@ class PylintTool(ToolBase):
82
89
  if not hasattr(checker, "options"):
83
90
  continue
84
91
  for option in checker.options:
85
- if max_line_length is not None:
86
- if option[0] == "max-line-length":
87
- checker.set_option("max-line-length", max_line_length)
92
+ if max_line_length is not None and option[0] == "max-line-length":
93
+ checker._arguments_manager.set_option( # pylint: disable=protected-access
94
+ "max-line-length", max_line_length
95
+ )
88
96
  return errors
89
97
 
90
- def _error_message(self, filepath, message):
98
+ def _error_message(self, filepath: Union[str, Path], message: str) -> Message:
91
99
  location = Location(filepath, None, None, 0, 0)
92
100
  return Message("prospector", "config-problem", location, message)
93
101
 
94
- def _pylintrc_configure(self, pylintrc, linter):
102
+ def _pylintrc_configure(self, pylintrc: Union[str, Path], linter: ProspectorLinter) -> list[Message]:
95
103
  errors = []
96
104
  are_plugins_loaded = linter.config_from_file(pylintrc)
97
105
  if not are_plugins_loaded and hasattr(linter.config, "load_plugins"):
@@ -102,7 +110,9 @@ class PylintTool(ToolBase):
102
110
  errors.append(self._error_message(pylintrc, f"Could not load plugin {plugin}"))
103
111
  return errors
104
112
 
105
- def configure(self, prospector_config, found_files: FileFinder):
113
+ def configure(
114
+ self, prospector_config: "ProspectorConfig", found_files: FileFinder
115
+ ) -> Optional[tuple[Optional[Union[str, Path]], Optional[Iterable[Message]]]]:
106
116
  extra_sys_path = found_files.make_syspath()
107
117
  check_paths = self._get_pylint_check_paths(found_files)
108
118
 
@@ -127,13 +137,13 @@ class PylintTool(ToolBase):
127
137
  self._linter = linter
128
138
  return configured_by, config_messages
129
139
 
130
- def _set_path_finder(self, extra_sys_path: List[Path], pylint_options):
140
+ def _set_path_finder(self, extra_sys_path: list[Path], pylint_options: dict[str, Any]) -> None:
131
141
  # insert the target path into the system path to get correct behaviour
132
142
  self._orig_sys_path = sys.path
133
143
  if not pylint_options.get("use_pylint_default_path_finder"):
134
144
  sys.path = sys.path + [str(path.absolute()) for path in extra_sys_path]
135
145
 
136
- def _get_pylint_check_paths(self, found_files: FileFinder) -> List[Path]:
146
+ def _get_pylint_check_paths(self, found_files: FileFinder) -> list[Path]:
137
147
  # create a list of packages, but don't include packages which are
138
148
  # subpackages of others as checks will be duplicated
139
149
  check_paths = set()
@@ -165,17 +175,21 @@ class PylintTool(ToolBase):
165
175
  return sorted(check_paths)
166
176
 
167
177
  def _get_pylint_configuration(
168
- self, check_paths: List[Path], linter: ProspectorLinter, prospector_config, pylint_options
169
- ):
178
+ self,
179
+ check_paths: list[Path],
180
+ linter: ProspectorLinter,
181
+ prospector_config: "ProspectorConfig",
182
+ pylint_options: dict[str, Any],
183
+ ) -> tuple[list[Message], Optional[Union[Path, str]]]:
170
184
  self._args = check_paths
171
185
  linter.load_default_plugins()
172
186
 
173
- config_messages = self._prospector_configure(prospector_config, linter)
174
- configured_by = None
187
+ config_messages: list[Message] = self._prospector_configure(prospector_config, linter)
188
+ configured_by: Optional[Union[str, Path]] = None
175
189
 
176
190
  if prospector_config.use_external_config("pylint"):
177
- # try to find a .pylintrc
178
- pylintrc = pylint_options.get("config_file")
191
+ # Try to find a .pylintrc
192
+ pylintrc: Optional[Union[str, Path]] = pylint_options.get("config_file")
179
193
  external_config = prospector_config.external_config_location("pylint")
180
194
 
181
195
  pylintrc = pylintrc or external_config
@@ -201,7 +215,7 @@ class PylintTool(ToolBase):
201
215
 
202
216
  return config_messages, configured_by
203
217
 
204
- def _combine_w0614(self, messages):
218
+ def _combine_w0614(self, messages: list[Message]) -> list[Message]:
205
219
  """
206
220
  For the "unused import from wildcard import" messages,
207
221
  we want to combine all warnings about the same line into
@@ -219,15 +233,17 @@ class PylintTool(ToolBase):
219
233
  for location, message_list in by_loc.items():
220
234
  names = []
221
235
  for msg in message_list:
222
- names.append(_UNUSED_WILDCARD_IMPORT_RE.match(msg.message).group(1))
236
+ match_ = _UNUSED_WILDCARD_IMPORT_RE.match(msg.message)
237
+ assert match_ is not None
238
+ names.append(match_.group(1))
223
239
 
224
- msgtxt = "Unused imports from wildcard import: %s" % ", ".join(names)
240
+ msgtxt = "Unused imports from wildcard import: {}".format(", ".join(names))
225
241
  combined_message = Message("pylint", "unused-wildcard-import", location, msgtxt)
226
242
  out.append(combined_message)
227
243
 
228
244
  return out
229
245
 
230
- def combine(self, messages):
246
+ def combine(self, messages: list[Message]) -> list[Message]:
231
247
  """
232
248
  Combine repeated messages.
233
249
 
@@ -241,7 +257,10 @@ class PylintTool(ToolBase):
241
257
  combined = self._combine_w0614(messages)
242
258
  return sorted(combined)
243
259
 
244
- def run(self, found_files) -> List[Message]:
260
+ def run(self, found_files: FileFinder) -> list[Message]:
261
+ assert self._collector is not None
262
+ assert self._linter is not None
263
+
245
264
  self._linter.check(self._args)
246
265
  sys.path = self._orig_sys_path
247
266
 
@@ -3,6 +3,7 @@ from typing import List
3
3
 
4
4
  from pylint.exceptions import UnknownMessageError
5
5
  from pylint.message import Message as PylintMessage
6
+ from pylint.message import MessageDefinitionStore
6
7
  from pylint.reporters import BaseReporter
7
8
 
8
9
  from prospector.message import Location, Message
@@ -11,10 +12,10 @@ from prospector.message import Location, Message
11
12
  class Collector(BaseReporter):
12
13
  name = "collector"
13
14
 
14
- def __init__(self, message_store):
15
+ def __init__(self, message_store: MessageDefinitionStore) -> None:
15
16
  BaseReporter.__init__(self, output=StringIO())
16
17
  self._message_store = message_store
17
- self._messages = []
18
+ self._messages: list[Message] = []
18
19
 
19
20
  def handle_message(self, msg: PylintMessage) -> None:
20
21
  loc = Location(msg.abspath, msg.module, msg.obj, msg.line, msg.column)
@@ -34,8 +35,5 @@ class Collector(BaseReporter):
34
35
  message = Message("pylint", msg_symbol, loc, msg.msg)
35
36
  self._messages.append(message)
36
37
 
37
- def _display(self, layout) -> None:
38
- pass
39
-
40
38
  def get_messages(self) -> List[Message]:
41
39
  return self._messages
@@ -1,37 +1,39 @@
1
+ from collections.abc import Iterable
1
2
  from pathlib import Path
3
+ from typing import Any, Optional, Union
2
4
 
3
5
  from packaging import version as packaging_version
4
6
  from pylint import version as pylint_version
5
7
  from pylint.config.config_initialization import _config_initialization
6
8
  from pylint.lint import PyLinter
7
9
 
10
+ from prospector.finder import FileFinder
11
+
8
12
 
9
13
  class UnrecognizedOptions(Exception):
10
14
  """Raised when an unrecognized option is found in the Pylint configuration."""
11
15
 
12
- pass
13
-
14
16
 
15
17
  class ProspectorLinter(PyLinter):
16
- def __init__(self, found_files, *args, **kwargs):
18
+ def __init__(self, found_files: FileFinder, *args: Any, **kwargs: Any) -> None:
17
19
  self._files = found_files
18
20
  # set up the standard PyLint linter
19
21
  PyLinter.__init__(self, *args, **kwargs)
20
22
 
21
23
  # Largely inspired by https://github.com/pylint-dev/pylint/blob/main/pylint/config/config_initialization.py#L26
22
- def config_from_file(self, config_file=None):
24
+ def config_from_file(self, config_file: Optional[Union[str, Path]] = None) -> bool:
23
25
  """Initialize the configuration from a file."""
24
26
  _config_initialization(self, [], config_file=config_file)
25
27
  return True
26
28
 
27
- def _expand_files(self, modules):
28
- expanded = super()._expand_files(modules)
29
- filtered = {}
29
+ def _expand_files(self, files_or_modules: list[str]) -> Union[Iterable[Any], dict[str, Any]]:
30
+ expanded = super()._expand_files(files_or_modules)
31
+ filtered: dict[str, Any] = {}
30
32
  # PyLinter._expand_files returns dict since 2.15.7.
31
33
  if packaging_version.parse(pylint_version) > packaging_version.parse("2.15.6"):
32
- for module in expanded:
34
+ for module, expanded_module in expanded.items():
33
35
  if not self._files.is_excluded(Path(module)):
34
- filtered[module] = expanded[module]
36
+ filtered[module] = expanded_module
35
37
  return filtered
36
38
  else:
37
39
  for module in expanded:
@@ -1,14 +1,21 @@
1
1
  import json
2
- import subprocess
2
+ import subprocess # nosec
3
+ from collections.abc import Iterable
4
+ from typing import TYPE_CHECKING, Any, Optional
3
5
 
4
6
  import pyright
5
7
 
8
+ from prospector.finder import FileFinder
6
9
  from prospector.message import Location, Message
7
10
  from prospector.tools import ToolBase
11
+ from prospector.tools.exceptions import BadToolConfig
12
+
13
+ if TYPE_CHECKING:
14
+ from prospector.config import ProspectorConfig
15
+
8
16
 
9
17
  __all__ = ("PyrightTool",)
10
18
 
11
- from prospector.tools.exceptions import BadToolConfig
12
19
 
13
20
  VALID_OPTIONS = [
14
21
  "level",
@@ -21,7 +28,7 @@ VALID_OPTIONS = [
21
28
  ]
22
29
 
23
30
 
24
- def format_messages(json_encoded):
31
+ def format_messages(json_encoded: str) -> list[Message]:
25
32
  json_decoded = json.loads(json_encoded)
26
33
  diagnostics = json_decoded.get("generalDiagnostics", [])
27
34
  messages = []
@@ -42,15 +49,17 @@ def format_messages(json_encoded):
42
49
 
43
50
 
44
51
  class PyrightTool(ToolBase):
45
- def __init__(self, *args, **kwargs):
52
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
46
53
  super().__init__(*args, **kwargs)
47
54
  self.checker = pyright
48
55
  self.options = ["--outputjson"]
49
56
 
50
- def configure(self, prospector_config, _):
57
+ def configure( # pylint: disable=useless-return
58
+ self, prospector_config: "ProspectorConfig", _: Any
59
+ ) -> Optional[tuple[str, Optional[Iterable[Message]]]]:
51
60
  options = prospector_config.tool_options("pyright")
52
61
 
53
- for option_key in options.keys():
62
+ for option_key in options:
54
63
  if option_key not in VALID_OPTIONS:
55
64
  url = "https://github.com/PyCQA/prospector/blob/master/prospector/tools/pyright/__init__.py"
56
65
  raise BadToolConfig(
@@ -80,7 +89,9 @@ class PyrightTool(ToolBase):
80
89
  if venv_path:
81
90
  self.options.extend(["--venv-path", venv_path])
82
91
 
83
- def run(self, found_files):
92
+ return None
93
+
94
+ def run(self, found_files: FileFinder) -> list[Message]:
84
95
  paths = [str(path) for path in found_files.python_modules]
85
96
  paths.extend(self.options)
86
97
  result = self.checker.run(*paths, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
@@ -1,5 +1,6 @@
1
1
  import logging
2
- from typing import TYPE_CHECKING, List
2
+ from collections.abc import Iterable
3
+ from typing import TYPE_CHECKING, Any, Optional
3
4
 
4
5
  from prospector.finder import FileFinder
5
6
  from prospector.message import Location, Message
@@ -48,7 +49,7 @@ PYROMA_ALL_CODES = {
48
49
  PYROMA_CODES = {}
49
50
 
50
51
 
51
- def _copy_codes():
52
+ def _copy_codes() -> None:
52
53
  for name, code in PYROMA_ALL_CODES.items():
53
54
  if hasattr(ratings, name):
54
55
  PYROMA_CODES[getattr(ratings, name)] = code
@@ -60,14 +61,17 @@ PYROMA_TEST_CLASSES = [t.__class__ for t in ratings.ALL_TESTS]
60
61
 
61
62
 
62
63
  class PyromaTool(ToolBase):
63
- def __init__(self, *args, **kwargs):
64
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
64
65
  super().__init__(*args, **kwargs)
65
- self.ignore_codes = ()
66
+ self.ignore_codes: list[str] = []
66
67
 
67
- def configure(self, prospector_config: "ProspectorConfig", found_files: FileFinder):
68
+ def configure( # pylint: disable=useless-return
69
+ self, prospector_config: "ProspectorConfig", found_files: FileFinder
70
+ ) -> Optional[tuple[str, Optional[Iterable[Message]]]]:
68
71
  self.ignore_codes = prospector_config.get_disabled_messages("pyroma")
72
+ return None
69
73
 
70
- def run(self, found_files: FileFinder) -> List[Message]:
74
+ def run(self, found_files: FileFinder) -> list[Message]:
71
75
  messages = []
72
76
  for directory in found_files.directories:
73
77
  # just list directories which are not ignored, but find any `setup.py` ourselves
@@ -0,0 +1,75 @@
1
+ import json
2
+ import subprocess # nosec
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from ruff.__main__ import find_ruff_bin
6
+
7
+ from prospector.finder import FileFinder
8
+ from prospector.message import Location, Message
9
+ from prospector.tools.base import ToolBase
10
+
11
+ if TYPE_CHECKING:
12
+ from prospector.config import ProspectorConfig
13
+
14
+
15
+ class RuffTool(ToolBase):
16
+ def configure(self, prospector_config: "ProspectorConfig", _: Any) -> None:
17
+ self.ruff_bin = find_ruff_bin()
18
+ self.ruff_args = ["check", "--output-format=json"]
19
+
20
+ enabled = prospector_config.get_enabled_messages("ruff")
21
+ if enabled:
22
+ enabled_arg_value = ",".join(enabled)
23
+ self.ruff_args.append(f"--select={enabled_arg_value}")
24
+ disabled = prospector_config.get_disabled_messages("ruff")
25
+ if disabled:
26
+ disabled_arg_value = ",".join(disabled)
27
+ self.ruff_args.append(f"--ignore={disabled_arg_value}")
28
+
29
+ options = prospector_config.tool_options("ruff")
30
+ for key, value in options.items():
31
+ if value is True:
32
+ self.ruff_args.append(f"--{key}")
33
+ elif value is False:
34
+ pass
35
+ elif isinstance(value, list):
36
+ arg_value = ",".join(value)
37
+ self.ruff_args.append(f"--{key}={arg_value}")
38
+ # dict is like array but with a dict with true/false value to be able to merge profiles
39
+ elif isinstance(value, dict):
40
+ arg_value = ",".join(k for k, v in value.items() if v)
41
+ self.ruff_args.append(f"--{key}={arg_value}")
42
+ else:
43
+ self.ruff_args.append(f"--{key}={value}")
44
+
45
+ def run(self, found_files: FileFinder) -> list[Message]:
46
+ print([self.ruff_bin, *self.ruff_args])
47
+ messages = []
48
+ completed_process = subprocess.run( # noqa: S603
49
+ [self.ruff_bin, *self.ruff_args, *found_files.python_modules], capture_output=True
50
+ )
51
+ for message in json.loads(completed_process.stdout):
52
+ sub_message = {}
53
+ if message.get("url"):
54
+ sub_message["See"] = message["url"]
55
+ if message.get("fix") and message["fix"].get("applicability"):
56
+ sub_message["Fix applicability"] = message["fix"]["applicability"]
57
+ message_str = message.get("message", "")
58
+ if sub_message:
59
+ message_str += f" [{', '.join(f'{k}: {v}' for k, v in sub_message.items())}]"
60
+
61
+ messages.append(
62
+ Message(
63
+ "ruff",
64
+ message.get("code") or "unknown",
65
+ Location(
66
+ message.get("filename") or "unknown",
67
+ None,
68
+ None,
69
+ line=message.get("location", {}).get("row"),
70
+ character=message.get("location", {}).get("column"),
71
+ ),
72
+ message_str,
73
+ )
74
+ )
75
+ return messages
prospector/tools/utils.py CHANGED
@@ -1,47 +1,55 @@
1
1
  import sys
2
+ from io import TextIOWrapper
3
+ from typing import Optional
2
4
 
3
5
 
4
- class CaptureStream:
5
- def __init__(self):
6
+ class CaptureStream(TextIOWrapper):
7
+ def __init__(self) -> None:
6
8
  self.contents = ""
7
9
 
8
- def write(self, text):
10
+ def write(self, text: str, /) -> int:
9
11
  self.contents += text
12
+ return len(text)
10
13
 
11
- def close(self):
14
+ def close(self) -> None:
12
15
  pass
13
16
 
14
- def flush(self):
17
+ def flush(self) -> None:
15
18
  pass
16
19
 
17
20
 
18
21
  class CaptureOutput:
19
- def __init__(self, hide):
22
+ _prev_streams = None
23
+ stdout: Optional[TextIOWrapper] = None
24
+ stderr: Optional[TextIOWrapper] = None
25
+
26
+ def __init__(self, hide: bool) -> None:
20
27
  self.hide = hide
21
- self._prev_streams = None
22
- self.stdout, self.stderr = None, None
23
28
 
24
- def __enter__(self):
29
+ def __enter__(self) -> "CaptureOutput":
25
30
  if self.hide:
26
- self._prev_streams = [
31
+ self._prev_streams = (
27
32
  sys.stdout,
28
33
  sys.stderr,
29
34
  sys.__stdout__,
30
35
  sys.__stderr__,
31
- ]
36
+ )
32
37
  self.stdout = CaptureStream()
33
38
  self.stderr = CaptureStream()
34
- sys.stdout, sys.__stdout__ = self.stdout, self.stdout
35
- sys.stderr, sys.__stderr__ = self.stderr, self.stderr
39
+ sys.stdout, sys.__stdout__ = self.stdout, self.stdout # type: ignore[misc]
40
+ sys.stderr, sys.__stderr__ = self.stderr, self.stderr # type: ignore[misc]
36
41
  return self
37
42
 
38
- def get_hidden_stdout(self):
43
+ def get_hidden_stdout(self) -> str:
44
+ assert isinstance(self.stdout, CaptureStream)
39
45
  return self.stdout.contents
40
46
 
41
- def get_hidden_stderr(self):
47
+ def get_hidden_stderr(self) -> str:
48
+ assert isinstance(self.stderr, CaptureStream)
42
49
  return self.stderr.contents
43
50
 
44
- def __exit__(self, exc_type, exc_val, exc_tb):
51
+ def __exit__(self, exc_type: type, exc_val: Exception, exc_tb: type) -> None:
45
52
  if self.hide:
46
- sys.stdout, sys.stderr, sys.__stdout__, sys.__stderr__ = self._prev_streams
53
+ assert self._prev_streams is not None
54
+ sys.stdout, sys.stderr, sys.__stdout__, sys.__stderr__ = self._prev_streams # type: ignore[misc]
47
55
  del self._prev_streams
@@ -1,19 +1,27 @@
1
+ from collections.abc import Iterable
2
+ from pathlib import Path
3
+ from typing import TYPE_CHECKING, Any, Optional
4
+
1
5
  from vulture import Vulture
2
6
 
3
7
  from prospector.encoding import CouldNotHandleEncoding, read_py_file
8
+ from prospector.finder import FileFinder
4
9
  from prospector.message import Location, Message, make_tool_error_message
5
10
  from prospector.tools.base import ToolBase
6
11
 
12
+ if TYPE_CHECKING:
13
+ from prospector.config import ProspectorConfig
14
+
7
15
 
8
16
  class ProspectorVulture(Vulture):
9
- def __init__(self, found_files):
17
+ def __init__(self, found_files: FileFinder) -> None:
10
18
  Vulture.__init__(self, verbose=False)
11
19
  self._files = found_files
12
- self._internal_messages = []
13
- self.file = None
14
- self.filename = None
20
+ self._internal_messages: list[Message] = []
21
+ self.file: Optional[Path] = None
22
+ self.filename: Optional[Path] = None
15
23
 
16
- def scavenge(self, _=None, __=None):
24
+ def scavenge(self, _: Any = None, __: Any = None) -> None:
17
25
  # The argument is a list of paths, but we don't care
18
26
  # about that as we use the found_files object. The
19
27
  # argument is here to explicitly acknowledge that we
@@ -27,7 +35,9 @@ class ProspectorVulture(Vulture):
27
35
  module,
28
36
  "vulture",
29
37
  "V000",
30
- message=f"Could not handle the encoding of this file: {err.encoding}",
38
+ message=(
39
+ f"Could not handle the encoding of this file: {err.encoding}" # type: ignore[attr-defined]
40
+ ),
31
41
  )
32
42
  )
33
43
  continue
@@ -38,7 +48,7 @@ class ProspectorVulture(Vulture):
38
48
  except TypeError:
39
49
  self.scan(module_string)
40
50
 
41
- def get_messages(self):
51
+ def get_messages(self) -> list[Message]:
42
52
  all_items = (
43
53
  ("unused-function", "Unused function %s", self.unused_funcs),
44
54
  ("unused-property", "Unused property %s", self.unused_props),
@@ -53,10 +63,7 @@ class ProspectorVulture(Vulture):
53
63
  filename = item.file
54
64
  except AttributeError:
55
65
  filename = item.filename
56
- if hasattr(item, "lineno"):
57
- lineno = item.lineno # for older versions of vulture
58
- else:
59
- lineno = item.first_lineno
66
+ lineno = item.lineno if hasattr(item, "lineno") else item.first_lineno
60
67
  loc = Location(filename, None, None, lineno, -1)
61
68
  message_text = template % item
62
69
  message = Message("vulture", code, loc, message_text)
@@ -66,15 +73,18 @@ class ProspectorVulture(Vulture):
66
73
 
67
74
 
68
75
  class VultureTool(ToolBase):
69
- def __init__(self):
76
+ def __init__(self) -> None:
70
77
  ToolBase.__init__(self)
71
78
  self._vulture = None
72
- self.ignore_codes = ()
79
+ self.ignore_codes: list[str] = []
73
80
 
74
- def configure(self, prospector_config, found_files):
81
+ def configure( # pylint: disable=useless-return
82
+ self, prospector_config: "ProspectorConfig", found_files: FileFinder
83
+ ) -> Optional[tuple[Optional[str], Optional[Iterable[Message]]]]:
75
84
  self.ignore_codes = prospector_config.get_disabled_messages("vulture")
85
+ return None
76
86
 
77
- def run(self, found_files):
87
+ def run(self, found_files: FileFinder) -> list[Message]:
78
88
  vulture = ProspectorVulture(found_files)
79
89
  vulture.scavenge()
80
90
  return [message for message in vulture.get_messages() if message.code not in self.ignore_codes]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prospector
3
- Version: 1.12.1
3
+ Version: 1.13.0
4
4
  Summary: Prospector is a tool to analyse Python code by aggregating the result of other tools.
5
5
  Home-page: http://prospector.readthedocs.io
6
6
  License: GPLv2+
@@ -30,12 +30,12 @@ Provides-Extra: with_everything
30
30
  Provides-Extra: with_mypy
31
31
  Provides-Extra: with_pyright
32
32
  Provides-Extra: with_pyroma
33
+ Provides-Extra: with_ruff
33
34
  Provides-Extra: with_vulture
34
35
  Requires-Dist: GitPython (>=3.1.27,<4.0.0)
35
36
  Requires-Dist: PyYAML
36
37
  Requires-Dist: bandit (>=1.5.1); extra == "with_bandit" or extra == "with_everything"
37
38
  Requires-Dist: dodgy (>=0.2.1,<0.3.0)
38
- Requires-Dist: flake8
39
39
  Requires-Dist: mccabe (>=0.7.0,<0.8.0)
40
40
  Requires-Dist: mypy (>=0.600); extra == "with_mypy" or extra == "with_everything"
41
41
  Requires-Dist: packaging
@@ -49,7 +49,8 @@ Requires-Dist: pylint-django (>=2.6.1)
49
49
  Requires-Dist: pylint-flask (==0.6)
50
50
  Requires-Dist: pyright (>=1.1.3); extra == "with_pyright" or extra == "with_everything"
51
51
  Requires-Dist: pyroma (>=2.4); extra == "with_pyroma" or extra == "with_everything"
52
- Requires-Dist: requirements-detector (>=1.3.1)
52
+ Requires-Dist: requirements-detector (>=1.3.2)
53
+ Requires-Dist: ruff; extra == "with_ruff" or extra == "with_everything"
53
54
  Requires-Dist: setoptconf-tmp (>=0.3.1,<0.4.0)
54
55
  Requires-Dist: toml (>=0.10.2,<0.11.0)
55
56
  Requires-Dist: vulture (>=1.5); extra == "with_vulture" or extra == "with_everything"