prospector 1.12.0__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 +18 -51
  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.0.dist-info → prospector-1.13.0.dist-info}/METADATA +7 -8
  46. prospector-1.13.0.dist-info/RECORD +71 -0
  47. prospector-1.12.0.dist-info/RECORD +0 -69
  48. {prospector-1.12.0.dist-info → prospector-1.13.0.dist-info}/LICENSE +0 -0
  49. {prospector-1.12.0.dist-info → prospector-1.13.0.dist-info}/WHEEL +0 -0
  50. {prospector-1.12.0.dist-info → prospector-1.13.0.dist-info}/entry_points.txt +0 -0
prospector/autodetect.py CHANGED
@@ -2,6 +2,7 @@ import os
2
2
  import re
3
3
  import warnings
4
4
  from pathlib import Path
5
+ from typing import Union
5
6
 
6
7
  from requirements_detector import find_requirements
7
8
  from requirements_detector.detect import RequirementsNotFound
@@ -19,7 +20,7 @@ _IMPORT_REGEX = re.compile(r"^\s*import ([\._a-zA-Z0-9]+)$")
19
20
  _IMPORT_MULTIPLE_REGEX = re.compile(r"^\s*import ([\._a-zA-Z0-9]+(, ){1})+")
20
21
 
21
22
 
22
- def find_from_imports(file_contents):
23
+ def find_from_imports(file_contents: str) -> set[str]:
23
24
  names = set()
24
25
  for line in file_contents.split("\n"):
25
26
  match = _IMPORT_MULTIPLE_REGEX.match(line)
@@ -42,7 +43,7 @@ def find_from_imports(file_contents):
42
43
  return names
43
44
 
44
45
 
45
- def find_from_path(path: Path):
46
+ def find_from_path(path: Path) -> set[str]:
46
47
  names = set()
47
48
 
48
49
  try:
@@ -57,7 +58,7 @@ def find_from_path(path: Path):
57
58
  names |= find_from_imports(contents)
58
59
  except encoding.CouldNotHandleEncoding as err:
59
60
  # TODO: this output will break output formats such as JSON
60
- warnings.warn(f"{err.path}: {err.__cause__}", ImportWarning)
61
+ warnings.warn(f"{err.path}: {err.__cause__}", ImportWarning, stacklevel=0)
61
62
 
62
63
  if len(names) == len(POSSIBLE_LIBRARIES):
63
64
  # don't continue on recursing, there's no point!
@@ -68,22 +69,22 @@ def find_from_path(path: Path):
68
69
  return names
69
70
 
70
71
 
71
- def find_from_requirements(path):
72
+ def find_from_requirements(path: Union[str, Path]) -> set[str]:
72
73
  reqs = find_requirements(path)
73
- names = []
74
+ names: set[str] = set()
74
75
  for requirement in reqs:
75
76
  if requirement.name is not None and requirement.name.lower() in POSSIBLE_LIBRARIES:
76
- names.append(requirement.name.lower())
77
+ names.add(requirement.name.lower())
77
78
  return names
78
79
 
79
80
 
80
- def autodetect_libraries(path):
81
+ def autodetect_libraries(path: Union[str, Path]) -> set[str]:
81
82
  if os.path.isfile(path):
82
83
  path = os.path.dirname(path)
83
84
  if path == "":
84
85
  path = "."
85
86
 
86
- libraries = []
87
+ libraries: set[str] = set()
87
88
 
88
89
  try:
89
90
  libraries = find_from_requirements(path)
@@ -91,6 +92,6 @@ def autodetect_libraries(path):
91
92
  pass
92
93
 
93
94
  if len(libraries) < len(POSSIBLE_LIBRARIES):
94
- libraries = find_from_path(path)
95
+ libraries = find_from_path(Path(path))
95
96
 
96
97
  return libraries
prospector/blender.py CHANGED
@@ -6,16 +6,20 @@
6
6
  # remove duplicates.
7
7
  import pkgutil
8
8
  from collections import defaultdict
9
+ from pathlib import Path
10
+ from typing import Optional
9
11
 
10
12
  import yaml
11
13
 
14
+ from prospector.message import Message
15
+
12
16
  __all__ = (
13
17
  "blend",
14
18
  "BLEND_COMBOS",
15
19
  )
16
20
 
17
21
 
18
- def blend_line(messages, blend_combos=None):
22
+ def blend_line(messages: list[Message], blend_combos: Optional[list[list[tuple[str, str]]]] = None) -> list[Message]:
19
23
  """
20
24
  Given a list of messages on the same line, blend them together so that we
21
25
  end up with one message per actual problem. Note that we can still return
@@ -23,8 +27,8 @@ def blend_line(messages, blend_combos=None):
23
27
  the line.
24
28
  """
25
29
  blend_combos = blend_combos or BLEND_COMBOS
26
- blend_lists = [[] for _ in range(len(blend_combos))]
27
- blended = []
30
+ blend_lists: list[list[Message]] = [[] for _ in range(len(blend_combos))]
31
+ blended: list[Message] = []
28
32
 
29
33
  # first we split messages into each of the possible blendable categories
30
34
  # so that we have a list of lists of messages which can be blended together
@@ -72,18 +76,19 @@ def blend_line(messages, blend_combos=None):
72
76
  # it will appear in two blend_lists. Therefore we mark anything not taken from the blend list
73
77
  # as "consumed" and then filter later, to avoid such cases.
74
78
  for now_used in blend_list[1:]:
75
- now_used.used = True
79
+ now_used.used = True # type: ignore[attr-defined]
76
80
 
77
81
  return [m for m in blended if not getattr(m, "used", False)]
78
82
 
79
83
 
80
- def blend(messages, blend_combos=None):
84
+ def blend(messages: list[Message], blend_combos: Optional[list[list[tuple[str, str]]]] = None) -> list[Message]:
81
85
  blend_combos = blend_combos or BLEND_COMBOS
82
86
 
83
87
  # group messages by file and then line number
84
- msgs_grouped = defaultdict(lambda: defaultdict(list))
88
+ msgs_grouped: dict[Path, dict[int, list[Message]]] = defaultdict(lambda: defaultdict(list))
85
89
 
86
90
  for message in messages:
91
+ assert message.location.line is not None
87
92
  msgs_grouped[message.location.path][message.location.line].append(
88
93
  message,
89
94
  )
@@ -97,18 +102,20 @@ def blend(messages, blend_combos=None):
97
102
  return out
98
103
 
99
104
 
100
- def get_default_blend_combinations():
101
- combos = yaml.safe_load(pkgutil.get_data(__name__, "blender_combinations.yaml"))
105
+ def get_default_blend_combinations() -> list[list[tuple[str, str]]]:
106
+ blender_combinations = pkgutil.get_data(__name__, "blender_combinations.yaml")
107
+ assert blender_combinations is not None
108
+ combos = yaml.safe_load(blender_combinations)
102
109
  combos = combos.get("combinations", [])
103
110
 
104
- defaults = []
111
+ defaults: list[list[tuple[str, str]]] = []
105
112
  for combo in combos:
106
113
  toblend = []
107
114
  for msg in combo:
108
115
  toblend += msg.items()
109
- defaults.append(tuple(toblend))
116
+ defaults.append(toblend)
110
117
 
111
- return tuple(defaults)
118
+ return defaults
112
119
 
113
120
 
114
121
  BLEND_COMBOS = get_default_blend_combinations()
@@ -2,12 +2,18 @@ import os
2
2
  import re
3
3
  import sys
4
4
  from pathlib import Path
5
- from typing import Dict, List, Union
5
+ from typing import Any, Callable, Optional, Union
6
+
7
+ import setoptconf.config
8
+
9
+ from prospector.finder import FileFinder
6
10
 
7
11
  try: # Python >= 3.11
8
12
  import re._constants as sre_constants
9
13
  except ImportError:
10
- import sre_constants
14
+ import sre_constants # pylint: disable=deprecated-module
15
+
16
+ import contextlib
11
17
 
12
18
  from prospector import tools
13
19
  from prospector.autodetect import autodetect_libraries
@@ -26,7 +32,7 @@ class ProspectorConfig:
26
32
  # Also the 'too many instance attributes' warning is ignored, as this
27
33
  # is a config object and its sole purpose is to hold many properties!
28
34
 
29
- def __init__(self, workdir: Path = None):
35
+ def __init__(self, workdir: Optional[Path] = None):
30
36
  self.config, self.arguments = self._configure_prospector()
31
37
  self.paths = self._get_work_path(self.config, self.arguments)
32
38
  self.explicit_file_mode = all(p.is_file for p in self.paths)
@@ -36,17 +42,17 @@ class ProspectorConfig:
36
42
  self.libraries = self._find_used_libraries(self.config, self.profile)
37
43
  self.tools_to_run = self._determine_tool_runners(self.config, self.profile)
38
44
  self.ignores = self._determine_ignores(self.config, self.profile, self.libraries)
39
- self.configured_by: Dict[str, str] = {}
40
- self.messages: List[Message] = []
45
+ self.configured_by: dict[str, Optional[Union[str, Path]]] = {}
46
+ self.messages: list[Message] = []
41
47
 
42
- def make_exclusion_filter(self):
48
+ def make_exclusion_filter(self) -> Callable[[Path], bool]:
43
49
  # Only close over the attributes required by the filter, rather
44
50
  # than the entire self, because ProspectorConfig can't be pickled
45
51
  # because of the config attribute, which would break parallel
46
52
  # pylint.
47
53
  ignores, workdir = self.ignores, self.workdir
48
54
 
49
- def _filter(path: Path):
55
+ def _filter(path: Path) -> bool:
50
56
  for ignore in ignores:
51
57
  # first figure out where the path is, relative to the workdir
52
58
  # ignore-paths/patterns will usually be relative to a repository
@@ -60,26 +66,25 @@ class ProspectorConfig:
60
66
 
61
67
  return _filter
62
68
 
63
- def get_tools(self, found_files):
69
+ def get_tools(self, found_files: FileFinder) -> list[tools.ToolBase]:
64
70
  self.configured_by = {}
65
71
  runners = []
66
72
  for tool_name in self.tools_to_run:
67
73
  tool = tools.TOOLS[tool_name]()
68
74
  config_result = tool.configure(self, found_files)
69
- if config_result is None:
70
- configured_by = None
71
- messages = []
72
- else:
73
- configured_by, messages = config_result
74
- if messages is None:
75
- messages = []
75
+ messages: list[Message] = []
76
+ configured_by = None
77
+ if config_result is not None:
78
+ configured_by, config_messages = config_result
79
+ if config_messages is not None:
80
+ messages = list(config_messages)
76
81
 
77
82
  self.configured_by[tool_name] = configured_by
78
83
  self.messages += messages
79
84
  runners.append(tool)
80
85
  return runners
81
86
 
82
- def replace_deprecated_tool_names(self) -> List[str]:
87
+ def replace_deprecated_tool_names(self) -> list[str]:
83
88
  # pep8 was renamed pycodestyle ; pep257 was renamed pydocstyle
84
89
  # for backwards compatibility, these have been deprecated but will remain until prospector v2
85
90
  deprecated_found = []
@@ -93,12 +98,13 @@ class ProspectorConfig:
93
98
  self.tools_to_run = replaced
94
99
  return deprecated_found
95
100
 
96
- def get_output_report(self):
101
+ def get_output_report(self) -> list[tuple[str, list[str]]]:
97
102
  # Get the output formatter
103
+ output_report: list[tuple[str, list[str]]]
98
104
  if self.config.output_format is not None:
99
105
  output_report = self.config.output_format
100
106
  else:
101
- output_report = [(self.profile.output_format, self.profile.output_target)]
107
+ output_report = [(self.profile.output_format, self.profile.output_target)] # type: ignore[list-item]
102
108
 
103
109
  for index, report in enumerate(output_report):
104
110
  if not all(report):
@@ -106,13 +112,13 @@ class ProspectorConfig:
106
112
 
107
113
  return output_report
108
114
 
109
- def _configure_prospector(self):
115
+ def _configure_prospector(self) -> tuple[setoptconf.config.Configuration, dict[str, str]]:
110
116
  # first we will configure prospector as a whole
111
117
  mgr = cfg.build_manager()
112
118
  config = mgr.retrieve(*cfg.build_default_sources())
113
119
  return config, mgr.arguments
114
120
 
115
- def _get_work_path(self, config, arguments) -> List[Path]:
121
+ def _get_work_path(self, config: setoptconf.config.Configuration, arguments: dict[str, str]) -> list[Path]:
116
122
  # Figure out what paths we're prospecting
117
123
  if config["path"]:
118
124
  paths = [Path(self.config["path"])]
@@ -122,7 +128,9 @@ class ProspectorConfig:
122
128
  paths = [Path.cwd()]
123
129
  return [p.resolve() for p in paths]
124
130
 
125
- def _get_profile(self, workdir: Path, config):
131
+ def _get_profile(
132
+ self, workdir: Path, config: setoptconf.config.Configuration
133
+ ) -> tuple[ProspectorProfile, Optional[str]]:
126
134
  # Use the specified profiles
127
135
  profile_provided = False
128
136
  if len(config.profiles) > 0:
@@ -168,7 +176,7 @@ class ProspectorConfig:
168
176
 
169
177
  # Use the strictness profile only if no profile has been given
170
178
  if config.strictness is not None and config.strictness:
171
- cmdline_implicit.append("strictness_%s" % config.strictness)
179
+ cmdline_implicit.append(f"strictness_{config.strictness}")
172
180
  strictness = config.strictness
173
181
 
174
182
  # the profile path is
@@ -190,24 +198,30 @@ class ProspectorConfig:
190
198
  profile = ProspectorProfile.load(profile_name, profile_path, forced_inherits=forced_inherits)
191
199
  except CannotParseProfile as cpe:
192
200
  sys.stderr.write(
193
- "Failed to run:\nCould not parse profile %s as it is not valid YAML\n%s\n"
194
- % (cpe.filepath, cpe.get_parse_message())
201
+ "\n".join(
202
+ [
203
+ "Failed to run:",
204
+ f"Could not parse profile {cpe.filepath} as it is not valid YAML",
205
+ f"{cpe.get_parse_message()}",
206
+ "",
207
+ ]
208
+ )
195
209
  )
196
210
  sys.exit(1)
197
211
  except ProfileNotFound as nfe:
198
212
  search_path = ":".join(map(str, nfe.profile_path))
199
- profile = nfe.name.split(":")[0]
213
+ module_name = nfe.name.split(":")[0]
200
214
  sys.stderr.write(
201
215
  f"""Failed to run:
202
216
  Could not find profile {nfe.name}.
203
- Search path: {search_path}, or in module 'prospector_profile_{profile}'
217
+ Search path: {search_path}, or in module 'prospector_profile_{module_name}'
204
218
  """
205
219
  )
206
220
  sys.exit(1)
207
221
  else:
208
222
  return profile, strictness
209
223
 
210
- def _find_used_libraries(self, config, profile):
224
+ def _find_used_libraries(self, config: setoptconf.config.Configuration, profile: ProspectorProfile) -> list[str]:
211
225
  libraries = []
212
226
 
213
227
  # Bring in adaptors that we automatically detect are needed
@@ -222,7 +236,7 @@ Search path: {search_path}, or in module 'prospector_profile_{profile}'
222
236
 
223
237
  return libraries
224
238
 
225
- def _determine_tool_runners(self, config, profile):
239
+ def _determine_tool_runners(self, config: setoptconf.config.Configuration, profile: ProspectorProfile) -> list[str]:
226
240
  if config.tools is None:
227
241
  # we had no command line settings for an explicit list of
228
242
  # tools, so we use the defaults
@@ -255,7 +269,9 @@ Search path: {search_path}, or in module 'prospector_profile_{profile}'
255
269
 
256
270
  return sorted(list(to_run))
257
271
 
258
- def _determine_ignores(self, config, profile, libraries):
272
+ def _determine_ignores(
273
+ self, config: setoptconf.config.Configuration, profile: ProspectorProfile, libraries: list[str]
274
+ ) -> list[re.Pattern[str]]:
259
275
  # Grab ignore patterns from the options
260
276
  ignores = []
261
277
  for pattern in config.ignore_patterns + profile.ignore_patterns:
@@ -265,10 +281,8 @@ Search path: {search_path}, or in module 'prospector_profile_{profile}'
265
281
  # ignore-patterns:
266
282
  # uses: django
267
283
  continue
268
- try:
284
+ with contextlib.suppress(sre_constants.error):
269
285
  ignores.append(re.compile(pattern))
270
- except sre_constants.error:
271
- pass
272
286
 
273
287
  # Convert ignore paths into patterns
274
288
  boundary = r"(^|/|\\)%s(/|\\|$)"
@@ -284,7 +298,7 @@ Search path: {search_path}, or in module 'prospector_profile_{profile}'
284
298
 
285
299
  return ignores
286
300
 
287
- def get_summary_information(self):
301
+ def get_summary_information(self) -> dict[str, Any]:
288
302
  return {
289
303
  "libraries": self.libraries,
290
304
  "strictness": self.strictness,
@@ -292,64 +306,67 @@ Search path: {search_path}, or in module 'prospector_profile_{profile}'
292
306
  "tools": self.tools_to_run,
293
307
  }
294
308
 
295
- def exit_with_zero_on_success(self):
309
+ def exit_with_zero_on_success(self) -> bool:
296
310
  return self.config.zero_exit
297
311
 
298
- def get_disabled_messages(self, tool_name):
312
+ def get_disabled_messages(self, tool_name: str) -> list[str]:
299
313
  return self.profile.get_disabled_messages(tool_name)
300
314
 
301
- def use_external_config(self, _):
315
+ def get_enabled_messages(self, tool_name: str) -> list[str]:
316
+ return self.profile.get_enabled_messages(tool_name)
317
+
318
+ def use_external_config(self, _: Any) -> bool:
302
319
  # Currently there is only one single global setting for whether to use
303
320
  # global config, but this could be extended in the future
304
321
  return not self.config.no_external_config
305
322
 
306
- def tool_options(self, tool_name):
323
+ def tool_options(self, tool_name: str) -> dict[str, Any]:
307
324
  tool = getattr(self.profile, tool_name, None)
308
325
  if tool is None:
309
326
  return {}
310
327
  return tool.get("options", {})
311
328
 
312
- def external_config_location(self, tool_name):
313
- return getattr(self.config, "%s_config_file" % tool_name, None)
329
+ def external_config_location(self, tool_name: str) -> Optional[Path]:
330
+ return getattr(self.config, f"{tool_name}_config_file", None)
314
331
 
315
332
  @property
316
- def die_on_tool_error(self):
333
+ def die_on_tool_error(self) -> bool:
317
334
  return self.config.die_on_tool_error
318
335
 
319
336
  @property
320
- def summary_only(self):
337
+ def summary_only(self) -> bool:
321
338
  return self.config.summary_only
322
339
 
323
340
  @property
324
- def messages_only(self):
341
+ def messages_only(self) -> bool:
325
342
  return self.config.messages_only
326
343
 
327
344
  @property
328
- def quiet(self):
345
+ def quiet(self) -> bool:
329
346
  return self.config.quiet
330
347
 
331
348
  @property
332
- def blending(self):
349
+ def blending(self) -> bool:
333
350
  return self.config.blending
334
351
 
335
352
  @property
336
- def absolute_paths(self):
353
+ def absolute_paths(self) -> bool:
337
354
  return self.config.absolute_paths
338
355
 
339
356
  @property
340
- def max_line_length(self):
357
+ def max_line_length(self) -> int:
341
358
  return self.config.max_line_length
342
359
 
343
360
  @property
344
- def include_tool_stdout(self):
361
+ def include_tool_stdout(self) -> bool:
345
362
  return self.config.include_tool_stdout
346
363
 
347
364
  @property
348
- def direct_tool_stdout(self):
365
+ def direct_tool_stdout(self) -> bool:
349
366
  return self.config.direct_tool_stdout
350
367
 
351
368
  @property
352
- def show_profile(self):
369
+ def show_profile(self) -> bool:
353
370
  return self.config.show_profile
354
371
 
355
372
  @property
@@ -1,5 +1,5 @@
1
- # flake8: noqa
2
1
  import importlib.metadata
2
+ from typing import Optional
3
3
 
4
4
  import setoptconf as soc
5
5
 
@@ -12,7 +12,7 @@ __all__ = ("build_manager",)
12
12
  _VERSION = importlib.metadata.version("prospector")
13
13
 
14
14
 
15
- def build_manager():
15
+ def build_manager() -> soc.ConfigurationManager:
16
16
  manager = soc.ConfigurationManager("prospector")
17
17
 
18
18
  manager.add(soc.BooleanSetting("zero_exit", default=False))
@@ -77,7 +77,7 @@ def build_manager():
77
77
  return manager
78
78
 
79
79
 
80
- def build_default_sources():
80
+ def build_default_sources() -> list[soc.Source]:
81
81
  sources = [
82
82
  build_command_line_source(),
83
83
  soc.EnvironmentVariableSource(),
@@ -99,7 +99,9 @@ def build_default_sources():
99
99
  return sources
100
100
 
101
101
 
102
- def build_command_line_source(prog=None, description="Performs static analysis of Python code"):
102
+ def build_command_line_source(
103
+ prog: Optional[str] = None, description: Optional[str] = "Performs static analysis of Python code"
104
+ ) -> soc.CommandLineSource:
103
105
  parser_options = {}
104
106
  if prog is not None:
105
107
  parser_options["prog"] = prog
@@ -184,9 +186,10 @@ def build_command_line_source(prog=None, description="Performs static analysis o
184
186
  },
185
187
  "output_format": {
186
188
  "flags": ["-o", "--output-format"],
187
- "help": "The output format. Valid values are: %s. This will output to stdout by default, "
188
- "however a target file can be used instead by adding :path-to-output-file, eg, -o json:output.json"
189
- % (", ".join(sorted(FORMATTERS.keys())),),
189
+ "help": "The output format. Valid values are: {}. This will output to stdout by default, "
190
+ "however a target file can be used instead by adding :path-to-output-file, eg, -o json:output.json".format(
191
+ ", ".join(sorted(FORMATTERS.keys()))
192
+ ),
190
193
  },
191
194
  "absolute_paths": {
192
195
  "help": "Whether to output absolute paths when referencing files "
@@ -197,9 +200,8 @@ def build_command_line_source(prog=None, description="Performs static analysis o
197
200
  "flags": ["-t", "--tool"],
198
201
  "help": "A list of tools to run. This lets you set exactly which "
199
202
  "tools to run. To add extra tools to the defaults, see "
200
- "--with-tool. Possible values are: %s. By "
201
- "default, the following tools will be run: %s"
202
- % (
203
+ "--with-tool. Possible values are: {}. By "
204
+ "default, the following tools will be run: {}".format(
203
205
  ", ".join(sorted(TOOLS.keys())),
204
206
  ", ".join(sorted(DEFAULT_TOOLS)),
205
207
  ),
@@ -208,14 +210,14 @@ def build_command_line_source(prog=None, description="Performs static analysis o
208
210
  "flags": ["-w", "--with-tool"],
209
211
  "help": "A list of tools to run in addition to the default tools. "
210
212
  "To specify all tools explicitly, use the --tool argument. "
211
- "Possible values are %s." % (", ".join(sorted(TOOLS.keys()))),
213
+ "Possible values are {}.".format(", ".join(sorted(TOOLS.keys()))),
212
214
  },
213
215
  "without_tools": {
214
216
  "flags": ["-W", "--without-tool"],
215
217
  "help": "A list of tools that should not be run. Useful to turn off "
216
218
  "only a single tool from the defaults. "
217
219
  "To specify all tools explicitly, use the --tool argument. "
218
- "Possible values are %s." % (", ".join(sorted(TOOLS.keys()))),
220
+ "Possible values are {}.".format(", ".join(sorted(TOOLS.keys()))),
219
221
  },
220
222
  "profiles": {
221
223
  "flags": ["-P", "--profile"],
@@ -6,7 +6,7 @@ from setoptconf.datatype import Choice
6
6
 
7
7
 
8
8
  class OutputChoice(Choice):
9
- def sanitize(self, value):
9
+ def sanitize(self, value: str) -> tuple[str, list[str]]:
10
10
  parsed = re.split(r"[;:]", value)
11
11
  output_format, output_targets = parsed[0], parsed[1:]
12
12
  checked_targets = []
prospector/encoding.py CHANGED
@@ -7,8 +7,8 @@ from prospector.exceptions import CouldNotHandleEncoding, PermissionMissing
7
7
  # mypy complains with 'Incompatible return value type (got "str", expected "bytes")'
8
8
 
9
9
 
10
- def read_py_file(filepath: Path):
11
- # see https://docs.python.org/3/library/tokenize.html#tokenize.detect_encoding
10
+ def read_py_file(filepath: Path) -> str:
11
+ # See https://docs.python.org/3/library/tokenize.html#tokenize.detect_encoding
12
12
  # first just see if the file is properly encoded
13
13
  try:
14
14
  with open(filepath, "rb") as bfile_:
prospector/exceptions.py CHANGED
@@ -8,7 +8,6 @@ class FatalProspectorException(Exception):
8
8
  Problems in prospector itself should raise this to notify
9
9
  the user directly. Errors in dependent tools should be
10
10
  caught and the user notified elegantly.
11
-
12
11
  """
13
12
 
14
13
  # (see also the --die-on-tool-error flag)
@@ -27,10 +26,7 @@ class CouldNotHandleEncoding(Exception):
27
26
  class PermissionMissing(Exception):
28
27
  def __init__(self, path: Path):
29
28
  docs_url = "https://prospector.landscape.io/en/master/profiles.html#ignoring-paths-and-patterns"
30
- if os.path.isdir(path):
31
- what = f"directory {path}"
32
- else:
33
- what = f"the file {path}"
29
+ what = f"directory {path}" if os.path.isdir(path) else f"the file {path}"
34
30
  error_msg = (
35
31
  f"The current user {os.getlogin()} does not have permission to open "
36
32
  f"{what}. Either fix permissions or tell prospector to skip it "
prospector/finder.py CHANGED
@@ -1,5 +1,6 @@
1
+ from collections.abc import Iterable, Iterator
1
2
  from pathlib import Path
2
- from typing import Callable, Iterable, Iterator, List
3
+ from typing import Callable, Optional
3
4
 
4
5
  from prospector.exceptions import PermissionMissing
5
6
  from prospector.pathutils import is_python_module, is_python_package, is_virtualenv
@@ -17,7 +18,7 @@ class FileFinder:
17
18
  is basically to know which files to pass to which tools to be inspected.
18
19
  """
19
20
 
20
- def __init__(self, *provided_paths: Path, exclusion_filters: Iterable[Callable] = None):
21
+ def __init__(self, *provided_paths: Path, exclusion_filters: Optional[Iterable[Callable[[Path], bool]]] = None):
21
22
  """
22
23
  :param provided_paths:
23
24
  A list of Path objects to search for files and modules - can be either directories or files
@@ -44,7 +45,7 @@ class FileFinder:
44
45
  if path.is_dir():
45
46
  self._provided_dirs.append(path)
46
47
 
47
- def make_syspath(self) -> List[Path]:
48
+ def make_syspath(self) -> list[Path]:
48
49
  paths = set()
49
50
  for path in self._provided_dirs:
50
51
  paths.add(path)
@@ -55,7 +56,7 @@ class FileFinder:
55
56
  def is_excluded(self, path: Path) -> bool:
56
57
  return any(filt(path) for filt in self._exclusion_filters)
57
58
 
58
- def _filter(self, paths: Iterable[Path]) -> List[Path]:
59
+ def _filter(self, paths: Iterable[Path]) -> list[Path]:
59
60
  return [path for path in paths if not self.is_excluded(path)]
60
61
 
61
62
  def _walk(self, directory: Path) -> Iterator[Path]:
@@ -71,7 +72,7 @@ class FileFinder:
71
72
  yield path
72
73
 
73
74
  @property
74
- def files(self) -> List[Path]:
75
+ def files(self) -> list[Path]:
75
76
  """
76
77
  List every individual file found from the given configuration.
77
78
 
@@ -89,7 +90,7 @@ class FileFinder:
89
90
  return self._filter(files)
90
91
 
91
92
  @property
92
- def python_packages(self) -> List[Path]:
93
+ def python_packages(self) -> list[Path]:
93
94
  """
94
95
  Lists every directory found in the given configuration which is a python module (that is,
95
96
  contains an `__init__.py` file).
@@ -99,7 +100,7 @@ class FileFinder:
99
100
  return self._filter(d for d in self.directories if is_python_package(d))
100
101
 
101
102
  @property
102
- def python_modules(self) -> List[Path]:
103
+ def python_modules(self) -> list[Path]:
103
104
  """
104
105
  Lists every directory found in the given configuration which is a python module (that is,
105
106
  contains an `__init__.py` file).
@@ -109,7 +110,7 @@ class FileFinder:
109
110
  return self._filter(f for f in self.files if is_python_module(f))
110
111
 
111
112
  @property
112
- def directories(self) -> List[Path]:
113
+ def directories(self) -> list[Path]:
113
114
  """
114
115
  Lists every directory found from the given configuration, regardless of its contents.
115
116
 
@@ -4,7 +4,7 @@ from .base import Formatter
4
4
  __all__ = ("FORMATTERS", "Formatter")
5
5
 
6
6
 
7
- FORMATTERS = {
7
+ FORMATTERS: dict[str, type[Formatter]] = {
8
8
  "json": json.JsonFormatter,
9
9
  "text": text.TextFormatter,
10
10
  "grouped": grouped.GroupedFormatter,