prospector 1.15.2__py3-none-any.whl → 1.15.4a1__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.
prospector/autodetect.py CHANGED
@@ -11,7 +11,7 @@ from prospector import encoding
11
11
  from prospector.exceptions import PermissionMissing
12
12
  from prospector.pathutils import is_virtualenv
13
13
 
14
- POSSIBLE_LIBRARIES = ("django", "celery", "flask")
14
+ POSSIBLE_LIBRARIES = ("django", "celery")
15
15
 
16
16
 
17
17
  # see http://docs.python.org/2/reference/lexical_analysis.html#identifiers
@@ -4,17 +4,22 @@
4
4
  #
5
5
  # Note that since not all tools will necessarily be run, the first message for
6
6
  # a line as sorted by the code list will be the one remaining after blending.
7
+ #
8
+ # We put at first the PyLint, mypy and Ruff codes because we can
9
+ # ignore a specific code on a specific line.
7
10
 
8
11
  combinations:
9
12
  - # Unused Import
10
13
  - pylint: unused-import
14
+ - pylint: W0611
15
+ - ruff: F401
11
16
  - pyflakes: F401
12
17
  - frosted: E101
13
- - ruff: F401
14
18
 
15
19
  - # Syntax Error
16
- - dodgy: diff
17
20
  - pylint: syntax-error
21
+ - pylint: E0001
22
+ - dodgy: diff
18
23
  - pyflakes: F999
19
24
  - pep8: E901
20
25
  # expected an indented block
@@ -26,362 +31,416 @@ combinations:
26
31
 
27
32
  - # Undefined local variable
28
33
  - pylint: undefined-variable
34
+ - pylint: E0602
35
+ - mypy: name-defined
36
+ - ruff: F821
29
37
  - pyflakes: F821
30
38
  - frosted: E303
31
- - ruff: F821
32
- - mypy: name-defined
33
39
 
34
40
  - # Unused variable
35
41
  - pylint: unused-variable
42
+ - pylint: W0612
43
+ - ruff: F841
36
44
  - vulture: unused-variable
37
45
  - pyflakes: F841
38
46
  - frosted: E307
39
- - ruff: F841
40
47
 
41
48
  - # Mixed tabs and spaces
49
+ - pylint: indentation-mixture
50
+ - ruff: D206
42
51
  - pep257: D206
43
52
  - pydocstyle: D206
44
53
  - pep8: E101
45
54
  - pycodestyle: E101
46
- - pylint: indentation-mixture
47
- - ruff: D206
48
55
 
49
56
  - # Import from __future__ not first import
50
57
  - pylint: misplaced-future
58
+ - pylint: W0410
59
+ - ruff: F404
51
60
  - pyflakes: F404
52
61
  - frosted: E207
53
- - ruff: F404
54
62
 
55
63
  - # Line too long
64
+ - pylint: line-too-long
65
+ - pylint: C0301
56
66
  - pep8: E501
57
67
  - pycodestyle: E501
58
- - pylint: line-too-long
59
68
 
60
69
  - # Trailing whitespace
70
+ - pylint: trailing-whitespace
71
+ - pylint: C0303
61
72
  - pep8: W291
62
73
  - pycodestyle: W291
63
- - pylint: trailing-whitespace
64
74
 
65
75
  - # Blank line contains whitespace
76
+ - pylint: trailing-whitespace
77
+ - pylint: C0303
66
78
  - pep8: W293
67
79
  - pycodestyle: W293
68
- - pylint: trailing-whitespace
69
80
 
70
81
  - # No newline at end of file
82
+ - pylint: missing-final-newline
83
+ - pylint: C0304
71
84
  - pep8: W292
72
85
  - pycodestyle: W292
73
- - pylint: missing-final-newline
74
86
 
75
87
  - # line ends with semi-colon
88
+ - pylint: unnecessary-semicolon
89
+ - pylint: W0301
76
90
  - pep8: E703
77
91
  - pycodestyle: E703
78
- - pylint: unnecessary-semicolon
79
92
 
80
93
  - # multiple statements on one line (colon)
94
+ - pylint: multiple-statements
95
+ - pylint: C0321
81
96
  - pep8: E701
82
97
  - pycodestyle: E701
83
- - pylint: multiple-statements
84
98
 
85
99
  - # multiple statements on one line (semicolon)
100
+ - pylint: multiple-statements
101
+ - pylint: C0321
86
102
  - pep8: E702
87
103
  - pycodestyle: E702
88
- - pylint: multiple-statements
89
104
 
90
105
  - # incorrect indentation
106
+ - pylint: bad-indentation
107
+ - pylint: W0311
108
+ - ruff: D207
91
109
  - pep257: D207
92
110
  - pydocstyle: D207
93
111
  - pep8: E111
94
112
  - pycodestyle: E111
95
- - pylint: bad-indentation
96
- - ruff: D207
97
113
 
98
114
  - # incorrect indentation
115
+ - pylint: bad-indentation
116
+ - pylint: W0311
117
+ - ruff: D208
99
118
  - pep257: D208
100
119
  - pydocstyle: D208
101
120
  - pep8: E111
102
121
  - pycodestyle: E111
103
- - pylint: bad-indentation
104
- - ruff: D208
105
122
 
106
123
  - # comma not followed by a space
107
- - pep8: E231
108
- - pycodestyle: E231
109
124
  - pylint: C0324
110
125
  - pylint: bad-whitespace
126
+ - pep8: E231
127
+ - pycodestyle: E231
111
128
 
112
129
  - # missing whitespace around operator
113
- - pep8: E225
114
- - pycodestyle: E225
115
130
  - pylint: C0322
116
131
  - pylint: bad-whitespace
117
-
118
- - # missing whitespace around operator
119
132
  - pep8: E225
120
133
  - pycodestyle: E225
134
+
135
+ - # missing whitespace around operator
121
136
  - pylint: C0323
122
137
  - pylint: bad-whitespace
138
+ - pep8: E225
139
+ - pycodestyle: E225
123
140
 
124
141
  - # undefined name in __all__
125
142
  - pylint: undefined-all-variable
143
+ - pylint: E0603
144
+ - ruff: F822
126
145
  - pyflakes: F822
127
146
  - frosted: E304
128
- - ruff: F822
129
147
 
130
148
  - # duplicate argument in function definition
131
149
  - pylint: duplicate-argument-name
150
+ - pylint: E0108
151
+ - ruff: F831
132
152
  - pyflakes: F831
133
153
  - frosted: E206
134
- - ruff: F831
135
154
 
136
155
  - # redefinition of unused function
137
- - pyflakes: F811
138
156
  - pylint: function-redefined
157
+ - pylint: E0102
139
158
  - ruff: F811
159
+ - pyflakes: F811
140
160
 
141
161
  - # f-string is missing placeholders
142
162
  - pylint: f-string-without-interpolation
143
- - pyflakes: F541
163
+ - pylint: W1309
144
164
  - ruff: F541
165
+ - pyflakes: F541
145
166
 
146
167
  - # Duplicate key in dictionary
147
168
  - pylint: duplicate-key
148
- - pyflakes: F601
169
+ - pylint: W0109
149
170
  - ruff: F601
171
+ - pyflakes: F601
150
172
 
151
173
  - # More than one starred expression in assignment
152
174
  - pylint: too-many-star-expressions
153
- - pyflakes: F622
175
+ - pylint: E0112
154
176
  - ruff: F622
177
+ - pyflakes: F622
155
178
 
156
179
  - # Assert called on a tuple
157
180
  - pylint: assert-on-tuple
158
- - pyflakes: F631
181
+ - pylint: W0199
159
182
  - ruff: F631
183
+ - pyflakes: F631
160
184
 
161
185
  - # 'break' outside loop
162
186
  - pylint: not-in-loop
163
- - pyflakes: F701
187
+ - pylint: E0103
164
188
  - ruff: F701
189
+ - pyflakes: F701
165
190
 
166
191
  - # 'continue' not properly in loop
167
192
  - pylint: not-in-loop
168
- - pyflakes: F702
193
+ - pylint: E0103
169
194
  - ruff: F702
195
+ - pyflakes: F702
170
196
 
171
197
  - # 'continue' not supported inside 'finally' clause
172
198
  - pylint: continue-in-finally
173
199
  - pylint: E0116
174
- - pyflakes: F703
175
200
  - ruff: F703
176
201
  - ruff: PLE0116
202
+ - pyflakes: F703
177
203
 
178
204
  - # Yield outside function
179
205
  - pylint: yield-outside-function
180
- - pyflakes: F704
206
+ - pylint: E0105
181
207
  - ruff: F704
208
+ - pyflakes: F704
182
209
 
183
210
  - # Return outside function
184
211
  - pylint: return-outside-function
185
- - pyflakes: F706
212
+ - pylint: E0104
186
213
  - ruff: F706
214
+ - pyflakes: F706
187
215
 
188
216
  - # default 'except:' must be last
189
217
  - pylint: bad-except-order
190
- - pyflakes: F707
218
+ - pylint: E0701
191
219
  - ruff: F707
220
+ - pyflakes: F707
192
221
 
193
222
  - # NotImplemented raised - should raise NotImplementedError
194
223
  - pylint: notimplemented-raised
195
- - pyflakes: F901
224
+ - pylint: E0711
196
225
  - ruff: F901
226
+ - pyflakes: F901
197
227
 
198
228
  - # first argument of a classmethod should be named 'cls'
199
- - pep8: N804
200
- - pycodestyle: N804
201
229
  - pylint: bad-classmethod-argument
230
+ - pylint: C0202
202
231
  - ruff: N804
232
+ - pep8: N804
233
+ - pycodestyle: N804
203
234
 
204
235
  - # '<>' is deprecated, use '!='
236
+ - pylint: W0331
205
237
  - pep8: W603
206
238
  - pycodestyle: W603
207
- - pylint: W0331
208
239
 
209
240
  - # backticks are deprecated, use 'repr()'
241
+ - pylint: W0333
210
242
  - pep8: W604
211
243
  - pycodestyle: W604
212
- - pylint: W0333
213
244
 
214
245
  - # Redefining name from outer scope
215
246
  - pylint: redefined-outer-name
247
+ - pylint: W0621
248
+ - ruff: F810
216
249
  - pyflakes: F810
217
250
  - frosted: E306
218
- - ruff: F810
219
251
 
220
252
  - # Redefinition of unused variable
221
253
  - pylint: redefined-outer-name
254
+ - pylint: W0621
222
255
  - pyflakes: F811
223
256
 
224
257
  - # Wildcard import
225
258
  - pylint: wildcard-import
259
+ - pylint: W0614
260
+ - ruff: F403
226
261
  - pyflakes: F403
227
262
  - frosted: E103
228
- - ruff: F403
229
263
 
230
264
  - # Return with argument inside generator
231
265
  - pylint: return-arg-in-generator
266
+ - pylint: E0110
232
267
  - frosted: E208
233
268
 
234
269
  - # Too many positional arguments for function call
235
270
  - pylint: too-many-function-args
271
+ - pylint: E1121
236
272
  - frosted: E203
237
273
 
238
274
  - # Passing unexpected keyword argument
239
275
  - pylint: unexpected-keyword-arg
276
+ - pylint: E1123
240
277
  - frosted: E204
241
278
 
242
279
  - # Missing mandatory keyword argument
243
280
  - pylint: missing-kwoa
281
+ - pylint: E1125
244
282
  - frosted: E205
245
283
 
246
284
  - # No exception type(s) specified
247
285
  - pylint: bare-except
248
- - frosted: W101
249
- - pep8: E722
250
- - pycodestyle: E722
286
+ - pylint: W0702
251
287
  - ruff: E722
252
288
  # try_except_pass
253
289
  - ruff: S110
290
+ - frosted: W101
291
+ - pep8: E722
292
+ - pycodestyle: E722
254
293
  - bandit: B110
255
294
 
256
295
  - # No exception type(s) specified
257
296
  - pylint: bare-except
258
- - frosted: W101
259
- - pep8: E722
260
- - pycodestyle: E722
297
+ - pylint: W0702
261
298
  - ruff: E722
262
299
  # try_except_continue
263
300
  - ruff: S112
301
+ - frosted: W101
302
+ - pep8: E722
303
+ - pycodestyle: E722
264
304
  - bandit: B112
265
305
 
266
306
  - # Do not catch blind exception: `Exception`
267
307
  - pylint: broad-exception-caught
308
+ - pylint: W0718
268
309
  - ruff: BLE001
269
310
 
311
+ - # Do not catch blind exception: `BaseException`
312
+ - pylint: broad-except
313
+ - pylint: W0703
314
+ - ruff: BLE002
315
+
270
316
  - # Create your own exception
271
317
  - pylint: broad-exception-raised
318
+ - pylint: W0719
272
319
  - ruff: TRY002
273
320
 
274
321
  - # Spaces around keyword/parameter equals
322
+ - pylint: bad-whitespace
275
323
  - pep8: E251
276
324
  - pycodestyle: E251
277
- - pylint: bad-whitespace
278
325
 
279
326
  - # Missing space after a comma
327
+ - pylint: bad-whitespace
280
328
  - pep8: E231
281
329
  - pycodestyle: E231
282
- - pylint: bad-whitespace
283
330
 
284
331
  - # redefinition of unused %r from line %r
332
+ - ruff: F811
285
333
  - pyflakes: F811
286
334
  - frosted: E301
287
- - ruff: F811
288
335
 
289
336
  - # list comprehension redefines %r from line %r
337
+ - ruff: F812
290
338
  - pyflakes: F812
291
339
  - frosted: E302
292
- - ruff: F812
293
340
 
294
341
  - # import %r from line %r shadowed by loop variable
342
+ - ruff: F402
295
343
  - pyflakes: F402
296
344
  - frosted: E102
297
- - ruff: F402
298
345
 
299
346
  - # syntax error in doctest
347
+ - ruff: FL0007
300
348
  - pyflakes: FL0007
301
349
  - frosted: E401
302
- - ruff: FL0007
303
350
 
304
351
  - # local variable %r referenced before assignment
352
+ - ruff: F823
305
353
  - pyflakes: F823
306
354
  - frosted: E305
307
- - ruff: F823
308
355
 
309
356
  - # pep8-naming incorrectly suggests that the first argument of a metaclass __new__ method should be 'cls'
310
357
  - pylint: bad-mcs-classmethod-argument
358
+ - pylint: C0204
311
359
  - pep8: N804
312
360
  - pycodestyle: N804
313
361
 
314
362
  - # class names should be camelcase
363
+ - pylint: invalid-name
364
+ - pylint: C0103
315
365
  - pep8: N801
316
366
  - pycodestyle: N801
317
- - pylint: invalid-name
318
367
 
319
- # function name should be lowercase
320
- - - pylint: invalid-name
368
+ - # function name should be lowercase
369
+ - pylint: invalid-name
370
+ - pylint: C0103
371
+ - ruff: N802
321
372
  - pep8: N802
322
373
  - pycodestyle: N802
323
- - ruff: N802
324
374
 
325
- # argument name should be lowercase
326
- - - pylint: invalid-name
375
+ - # argument name should be lowercase
376
+ - pylint: invalid-name
377
+ - pylint: C0103
378
+ - ruff: N803
327
379
  - pep8: N803
328
380
  - pycodestyle: N803
329
- - ruff: N803
330
381
 
331
- # Variable in function should be lowercase
332
- - - pylint: invalid-name
382
+ - # Variable in function should be lowercase
383
+ - pylint: invalid-name
384
+ - pylint: C0103
385
+ - ruff: N806
333
386
  - pep8: N806
334
387
  - pycodestyle: N806
335
- - ruff: N806
336
388
 
337
389
  # too complex
338
390
  - # Too many branches ({branches} > {max_branches})
339
391
  - pylint: too-many-branches
340
392
  - pylint: R0912
341
- - mccabe: MC0001
342
393
  - ruff: PLR0912
394
+ - mccabe: MC0001
395
+
343
396
  - # Too many statements ({statements} > {max_statements})
344
397
  - pylint: too-many-statements
345
398
  - pylint: R0915
346
- - mccabe: MC0001
347
- - pylint: too-many-statements
348
399
  - ruff: PLR0915
400
+ - mccabe: MC0001
349
401
 
350
402
  - # pep257 takes preference over pylint documentation warnings
351
- - pep257: D100
352
- - pydocstyle: D100
353
403
  - pylint: missing-docstring
404
+ - pylint: C0111
354
405
  - ruff: D100
406
+ - pep257: D100
407
+ - pydocstyle: D100
355
408
 
356
409
  - # Missing docstring in public class
357
- - pep257: D101
358
- - pydocstyle: D101
359
410
  - pylint: missing-docstring
411
+ - pylint: C0111
360
412
  - ruff: D101
413
+ - pep257: D101
414
+ - pydocstyle: D101
361
415
 
362
416
  - # Missing docstring in public method
363
- - pep257: D102
364
- - pydocstyle: D102
365
417
  - pylint: missing-docstring
418
+ - pylint: C0111
366
419
  - ruff: D102
420
+ - pep257: D102
421
+ - pydocstyle: D102
367
422
 
368
423
  - # Missing docstring in public function
369
- - pep257: D103
370
- - pydocstyle: D103
371
424
  - pylint: missing-docstring
425
+ - pylint: C0111
372
426
  - ruff: D103
427
+ - pep257: D103
428
+ - pydocstyle: D103
373
429
 
374
430
  - - pylint: singleton-comparison
431
+ - pylint: C0221
375
432
  - pep8: E711
376
433
  - pycodestyle: E711
377
434
 
378
435
  - - pylint: subprocess-run-check
379
- - bandit: B603
436
+ - pylint: W1510
380
437
  - ruff: S603
381
438
  - ruff: PLW1510
439
+ - bandit: B603
382
440
 
383
441
  - # Private member accessed:
384
442
  - pylint: protected-access
443
+ - pylint: W0212
385
444
  - ruff: SLF001
386
445
 
387
446
  # assert_used
@@ -418,6 +477,7 @@ combinations:
418
477
 
419
478
  # request_without_timeout
420
479
  - - pylint: missing-timeout
480
+ - pylint: W3101
421
481
  - ruff: S113
422
482
  - bandit: B113
423
483
 
@@ -528,13 +588,15 @@ combinations:
528
588
  # Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
529
589
  # See: https://docs.astral.sh/ruff/rules/raise-without-from-inside-except
530
590
  - - pylint: raise-missing-from
591
+ - pylint: W0707
531
592
  - ruff: B904
532
593
 
533
594
  # Use of possibly insecure function - consider using safer ast.literal_eval
534
595
  # See: https://docs.astral.sh/ruff/rules/suspicious-eval-usage
535
596
  - - pylint: eval-used
536
- - bandit: B307
597
+ - pylint: W0123
537
598
  - ruff: S307
599
+ - bandit: B307
538
600
 
539
601
  - # {kind} name "{param_name}" does not reflect its {variance}; consider renaming it to "{replacement_name}"
540
602
  - pylint: type-name-incorrect-variance
@@ -1081,6 +1143,11 @@ combinations:
1081
1143
  - pylint: W3301
1082
1144
  - ruff: PLW3301
1083
1145
 
1146
+ - # Consider using 'with' for resource-allocating operations
1147
+ - pylint: consider-using-with
1148
+ - pylint: R1732
1149
+ - ruff: SIM115
1150
+
1084
1151
  - # Missing docstring in public package
1085
1152
  - ruff: D104
1086
1153
  - pycodestyle: D104
@@ -127,7 +127,7 @@ def build_command_line_source(
127
127
  "uses": {
128
128
  "flags": ["-u", "--uses"],
129
129
  "help": "A list of one or more libraries or frameworks that the"
130
- " project uses. Possible values are: django, celery, flask. This will be"
130
+ " project uses. Possible values are: django, celery. This will be"
131
131
  " autodetected by default, but if autodetection doesn't"
132
132
  " work, manually specify them using this flag.",
133
133
  },
@@ -1,4 +1,9 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
1
5
  from prospector.formatters.base import Formatter
6
+ from prospector.message import Location, Message
2
7
 
3
8
 
4
9
  class SummaryFormatter(Formatter):
@@ -41,3 +46,45 @@ class SummaryFormatter(Formatter):
41
46
  output = ["Profile", "=======", "", self.profile.as_yaml().strip()]
42
47
 
43
48
  return "\n".join(output)
49
+
50
+ def get_ci_annotation(self, message: Message) -> Optional[str]:
51
+ intro = (
52
+ f"({message.source})"
53
+ if message.code is None
54
+ else f"{message.code}({message.source})"
55
+ if message.location.function is None
56
+ else f"[{message.code}({message.source}), {message.location.function}]"
57
+ )
58
+ ci_prefix = self._get_ci_prefix(message.location, intro)
59
+ if ci_prefix:
60
+ github_message = f"{ci_prefix}{intro}%0A{message.message.strip()}"
61
+ if message.doc_url:
62
+ github_message += f"%0ASee: {message.doc_url}"
63
+ return github_message
64
+ return None
65
+
66
+ def _get_ci_prefix(self, location: Location, title: str) -> Optional[str]:
67
+ if location.path is None:
68
+ return None
69
+ if os.environ.get("GITHUB_ACTIONS") == "true":
70
+ path = location.path
71
+ if "PROSPECTOR_FILE_PREFIX" in os.environ:
72
+ path = Path(os.environ["PROSPECTOR_FILE_PREFIX"]) / path
73
+ # pylint: disable-next=line-too-long
74
+ # See: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-an-error-message
75
+ parameters = {
76
+ "file": path,
77
+ "line": location.line,
78
+ "title": title,
79
+ }
80
+ if location.line_end:
81
+ parameters["endLine"] = location.line_end
82
+ if location.character:
83
+ parameters["col"] = location.character
84
+ if location.character_end:
85
+ parameters["endColumn"] = location.character_end
86
+
87
+ parameters_str = ",".join(f"{key}={value}" for key, value in parameters.items())
88
+ return f"::error {parameters_str}::"
89
+
90
+ return None
@@ -52,18 +52,23 @@ class PylintFormatter(SummaryFormatter):
52
52
  message_str = message.message.strip()
53
53
  if message.doc_url:
54
54
  message_str += f" (See: {message.doc_url})"
55
- output.append(
56
- template
57
- % {
58
- "path": self._make_path(message.location),
59
- "line": message.location.line,
60
- "character": message.location.character,
61
- "source": message.source,
62
- "code": message.code,
63
- "function": message.location.function,
64
- "message": message.message.strip(),
65
- }
66
- )
55
+ template_args = {
56
+ "path": self._make_path(message.location),
57
+ "line": message.location.line,
58
+ "character": message.location.character,
59
+ "source": message.source,
60
+ "code": message.code,
61
+ "function": message.location.function,
62
+ "message": message.message.strip(),
63
+ }
64
+ output.append(template % template_args)
65
+ if message.doc_url:
66
+ template_args["message"] = ""
67
+ indent = len(template % template_args)
68
+ output.append(f"{' ' * indent}See: {message.doc_url}")
69
+ ci_annotation = self.get_ci_annotation(message)
70
+ if ci_annotation:
71
+ output.append(ci_annotation)
67
72
  return output
68
73
 
69
74
  def render(self, summary: bool = True, messages: bool = True, profile: bool = False) -> str:
@@ -24,6 +24,9 @@ class TextFormatter(SummaryFormatter):
24
24
  )
25
25
 
26
26
  output.append(f" {message.message}")
27
+ ci_annotation = self.get_ci_annotation(message)
28
+ if ci_annotation:
29
+ output.append(ci_annotation)
27
30
 
28
31
  return "\n".join(output)
29
32
 
prospector/identify.py ADDED
@@ -0,0 +1,90 @@
1
+ # Largely inspired by https://github.com/pre-commit/identify/blob/main/identify/identify.py#L178
2
+
3
+ import errno
4
+ import os
5
+ import shlex
6
+ import string
7
+ from pathlib import Path
8
+ from typing import IO
9
+
10
+ printable = frozenset(string.printable)
11
+
12
+
13
+ def _shebang_split(line: str) -> tuple[str, ...]:
14
+ try:
15
+ # shebangs aren't supposed to be quoted, though some tools such as
16
+ # setuptools will write them with quotes so we'll best-guess parse
17
+ # with shlex first
18
+ return tuple(shlex.split(line))
19
+ except ValueError:
20
+ # failing that, we'll do a more "traditional" shebang parsing which
21
+ # just involves splitting by whitespace
22
+ return tuple(line.split())
23
+
24
+
25
+ def _parse_nix_shebang(
26
+ bytes_io: IO[bytes],
27
+ cmd: tuple[str, ...],
28
+ ) -> tuple[str, ...]:
29
+ while bytes_io.read(2) == b"#!":
30
+ next_line_b = bytes_io.readline()
31
+ try:
32
+ next_line = next_line_b.decode("UTF-8")
33
+ except UnicodeDecodeError:
34
+ return cmd
35
+
36
+ for c in next_line:
37
+ if c not in printable:
38
+ return cmd
39
+
40
+ line_tokens = _shebang_split(next_line.strip())
41
+ for i, token in enumerate(line_tokens[:-1]):
42
+ if token != "-i": # noqa: S105
43
+ continue
44
+ # The argument to -i flag
45
+ cmd = (line_tokens[i + 1],)
46
+ return cmd
47
+
48
+
49
+ def _parse_shebang(bytes_io: IO[bytes]) -> tuple[str, ...]:
50
+ """Parse the shebang from a file opened for reading binary."""
51
+ if bytes_io.read(2) != b"#!":
52
+ return ()
53
+ first_line_b = bytes_io.readline()
54
+ try:
55
+ first_line = first_line_b.decode("UTF-8")
56
+ except UnicodeDecodeError:
57
+ return ()
58
+
59
+ # Require only printable ascii
60
+ for c in first_line:
61
+ if c not in printable:
62
+ return ()
63
+
64
+ cmd = _shebang_split(first_line.strip())
65
+ if cmd[:2] == ("/usr/bin/env", "-S"):
66
+ cmd = cmd[2:]
67
+ elif cmd[:1] == ("/usr/bin/env",):
68
+ cmd = cmd[1:]
69
+
70
+ if cmd == ("nix-shell",):
71
+ return _parse_nix_shebang(bytes_io, cmd)
72
+
73
+ return cmd
74
+
75
+
76
+ def parse_shebang_from_file(path: Path) -> tuple[str, ...]:
77
+ """Parse the shebang given a file path."""
78
+ if not path.exists():
79
+ return ()
80
+ if not os.access(path, os.X_OK):
81
+ return ()
82
+
83
+ try:
84
+ with path.open("rb") as f:
85
+ return _parse_shebang(f)
86
+ except OSError as e:
87
+ if e.errno == errno.EINVAL:
88
+ return ()
89
+ else:
90
+ raise
prospector/pathutils.py CHANGED
@@ -1,14 +1,28 @@
1
+ import mimetypes
1
2
  import os
3
+ import re
2
4
  from pathlib import Path
3
5
 
6
+ from prospector import identify
7
+
8
+ _PYTHON_COMMAND_RE = re.compile(r"^python[0-9]?$")
9
+
4
10
 
5
11
  def is_python_package(path: Path) -> bool:
6
12
  return path.is_dir() and (path / "__init__.py").exists()
7
13
 
8
14
 
9
15
  def is_python_module(path: Path) -> bool:
10
- # TODO: is this too simple?
11
- return path.suffix == ".py"
16
+ mimetype, encoding = mimetypes.guess_type(path)
17
+ del encoding
18
+ if mimetype == "text/x-python":
19
+ return True
20
+
21
+ executor = identify.parse_shebang_from_file(path)
22
+ if executor is not None and len(executor) > 0:
23
+ return _PYTHON_COMMAND_RE.match(Path(executor[0]).name) is not None
24
+
25
+ return False
12
26
 
13
27
 
14
28
  def is_virtualenv(path: Path) -> bool:
@@ -26,7 +26,7 @@ class ProspectorProfile:
26
26
  self.output_target = profile_dict.get("output-target")
27
27
  self.autodetect = profile_dict.get("autodetect", True)
28
28
  self.uses = [
29
- uses for uses in _ensure_list(profile_dict.get("uses", [])) if uses in ("django", "celery", "flask")
29
+ uses for uses in _ensure_list(profile_dict.get("uses", [])) if uses in ("django", "celery")
30
30
  ]
31
31
  self.max_line_length = profile_dict.get("max-line-length")
32
32
 
@@ -133,7 +133,10 @@ def _load_content_package(name: str) -> Optional[dict[str, Any]]:
133
133
  used_name = None
134
134
  for file_name in file_names:
135
135
  used_name = f"{module_name}:{file_name}"
136
- data = pkgutil.get_data(module_name, file_name)
136
+ try:
137
+ data = pkgutil.get_data(module_name, file_name)
138
+ except (ModuleNotFoundError, FileNotFoundError):
139
+ continue
137
140
  if data is not None:
138
141
  break
139
142
 
prospector/suppression.py CHANGED
@@ -174,8 +174,8 @@ def get_suppressions(
174
174
  for line_number, line_content in enumerate(file_contents):
175
175
  for tool_name, tool in tools.items():
176
176
  tool_ignores = tool.get_ignored_codes(line_content)
177
- for tool_ignore in tool_ignores:
178
- tools_ignore[filepath][line_number + 1].add(Ignore(tool_name, tool_ignore))
177
+ for tool_ignore, offset in tool_ignores:
178
+ tools_ignore[filepath][line_number + 1 + offset].add(Ignore(tool_name, tool_ignore))
179
179
 
180
180
  # Ignore the blending messages
181
181
  if blending:
prospector/tools/base.py CHANGED
@@ -44,9 +44,9 @@ class ToolBase(ABC):
44
44
  """
45
45
  raise NotImplementedError
46
46
 
47
- def get_ignored_codes(self, line: str) -> list[str]:
47
+ def get_ignored_codes(self, line: str) -> list[tuple[str, int]]:
48
48
  """
49
- Return a list of error codes that the tool will ignore from a line of code.
49
+ Return a list of error codes and line offset that the tool will ignore from a line of code.
50
50
  """
51
51
  del line # unused
52
52
  return []
@@ -154,8 +154,8 @@ class MypyTool(ToolBase):
154
154
 
155
155
  return messages
156
156
 
157
- def get_ignored_codes(self, line: str) -> list[str]:
157
+ def get_ignored_codes(self, line: str) -> list[tuple[str, int]]:
158
158
  match = _IGNORE_RE.search(line)
159
159
  if match:
160
- return [e.strip() for e in match.group(1).split(",")]
160
+ return [(e.strip(), 0) for e in match.group(1).split(",")]
161
161
  return []
@@ -123,7 +123,7 @@ class ProfileValidationTool(ToolBase):
123
123
  )
124
124
 
125
125
  if "uses" in parsed:
126
- possible_libs = ("django", "celery", "flask")
126
+ possible_libs = ("django", "celery")
127
127
  parsed_list = parsed["uses"] if isinstance(parsed["uses"], list) else [parsed["uses"]]
128
128
  for uses in parsed_list:
129
129
  if uses not in possible_libs:
@@ -22,6 +22,7 @@ if TYPE_CHECKING:
22
22
  _UNUSED_WILDCARD_IMPORT_RE = re.compile(r"^Unused import(\(s\))? (.*) from wildcard import")
23
23
 
24
24
  _IGNORE_RE = re.compile(r"#\s*pylint:\s*disable=([^#]*[^#\s])(\s*#.*)?$", re.IGNORECASE)
25
+ _IGNORE_NEXT_RE = re.compile(r"#\s*pylint:\s*disable-next=([^#]*[^#\s])(\s*#.*)?$", re.IGNORECASE)
25
26
 
26
27
 
27
28
  def _is_in_dir(subpath: Path, path: Path) -> bool:
@@ -46,8 +47,6 @@ class PylintTool(ToolBase):
46
47
  linter.load_plugin_modules(["pylint_django"])
47
48
  if "celery" in prospector_config.libraries:
48
49
  linter.load_plugin_modules(["pylint_celery"])
49
- if "flask" in prospector_config.libraries:
50
- linter.load_plugin_modules(["pylint_flask"])
51
50
 
52
51
  profile_path = os.path.join(prospector_config.workdir, prospector_config.profile.name)
53
52
  for plugin in prospector_config.profile.pylint.get("load-plugins", []): # type: ignore[attr-defined]
@@ -269,8 +268,11 @@ class PylintTool(ToolBase):
269
268
  messages = self._collector.get_messages()
270
269
  return self.combine(messages)
271
270
 
272
- def get_ignored_codes(self, line: str) -> list[str]:
271
+ def get_ignored_codes(self, line: str) -> list[tuple[str, int]]:
273
272
  match = _IGNORE_RE.search(line)
274
273
  if match:
275
- return [e.strip() for e in match.group(1).split(",")]
274
+ return [(e.strip(), 0) for e in match.group(1).split(",")]
275
+ match = _IGNORE_NEXT_RE.search(line)
276
+ if match:
277
+ return [(e.strip(), 1) for e in match.group(1).split(",")]
276
278
  return []
@@ -88,8 +88,8 @@ class RuffTool(ToolBase):
88
88
  )
89
89
  return messages
90
90
 
91
- def get_ignored_codes(self, line: str) -> list[str]:
91
+ def get_ignored_codes(self, line: str) -> list[tuple[str, int]]:
92
92
  match = PEP8_IGNORE_LINE_CODE.search(line)
93
93
  if match:
94
- return [e.strip() for e in match.group(1).split(",")]
94
+ return [(e.strip(), 0) for e in match.group(1).split(",")]
95
95
  return []
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: prospector
3
- Version: 1.15.2
3
+ Version: 1.15.4a1
4
4
  Summary: Prospector is a tool to analyse Python code by aggregating the result of other tools.
5
- Home-page: http://prospector.readthedocs.io
6
5
  License: GPLv2+
7
6
  Keywords: pylint,prospector,static code analysis
8
7
  Author: Carl Crowder
@@ -19,25 +18,23 @@ Classifier: Operating System :: Unix
19
18
  Classifier: Programming Language :: Python :: 3
20
19
  Classifier: Programming Language :: Python :: 3.9
21
20
  Classifier: Programming Language :: Python :: 3.10
22
- Classifier: Programming Language :: Python :: 3.10
23
21
  Classifier: Programming Language :: Python :: 3.11
24
22
  Classifier: Programming Language :: Python :: 3.12
25
23
  Classifier: Programming Language :: Python :: 3.13
26
- Classifier: Programming Language :: Python :: 3.9
27
24
  Classifier: Topic :: Software Development :: Quality Assurance
28
- Provides-Extra: with_bandit
29
- Provides-Extra: with_everything
30
- Provides-Extra: with_mypy
31
- Provides-Extra: with_pyright
32
- Provides-Extra: with_pyroma
33
- Provides-Extra: with_ruff
34
- Provides-Extra: with_vulture
25
+ Provides-Extra: with-bandit
26
+ Provides-Extra: with-everything
27
+ Provides-Extra: with-mypy
28
+ Provides-Extra: with-pyright
29
+ Provides-Extra: with-pyroma
30
+ Provides-Extra: with-ruff
31
+ Provides-Extra: with-vulture
35
32
  Requires-Dist: GitPython (>=3.1.27,<4.0.0)
36
33
  Requires-Dist: PyYAML
37
- Requires-Dist: bandit (>=1.5.1); extra == "with_bandit" or extra == "with_everything"
34
+ Requires-Dist: bandit (>=1.5.1) ; extra == "with-bandit" or extra == "with-everything"
38
35
  Requires-Dist: dodgy (>=0.2.1,<0.3.0)
39
36
  Requires-Dist: mccabe (>=0.7.0,<0.8.0)
40
- Requires-Dist: mypy (>=0.600); extra == "with_mypy" or extra == "with_everything"
37
+ Requires-Dist: mypy (>=0.600) ; extra == "with-mypy" or extra == "with-everything"
41
38
  Requires-Dist: packaging
42
39
  Requires-Dist: pep8-naming (>=0.3.3,<=0.10.0)
43
40
  Requires-Dist: pycodestyle (>=2.9.0)
@@ -46,14 +43,14 @@ Requires-Dist: pyflakes (>=2.2.0)
46
43
  Requires-Dist: pylint (>=3.0)
47
44
  Requires-Dist: pylint-celery (==0.3)
48
45
  Requires-Dist: pylint-django (>=2.6.1)
49
- Requires-Dist: pylint-flask (==0.6)
50
- Requires-Dist: pyright (>=1.1.3); extra == "with_pyright" or extra == "with_everything"
51
- Requires-Dist: pyroma (>=2.4); extra == "with_pyroma" or extra == "with_everything"
46
+ Requires-Dist: pyright (>=1.1.3) ; extra == "with-pyright" or extra == "with-everything"
47
+ Requires-Dist: pyroma (>=2.4) ; extra == "with-pyroma" or extra == "with-everything"
52
48
  Requires-Dist: requirements-detector (>=1.3.2)
53
- Requires-Dist: ruff; extra == "with_ruff" or extra == "with_everything"
49
+ Requires-Dist: ruff ; extra == "with-ruff" or extra == "with-everything"
54
50
  Requires-Dist: setoptconf-tmp (>=0.3.1,<0.4.0)
55
51
  Requires-Dist: toml (>=0.10.2,<0.11.0)
56
- Requires-Dist: vulture (>=1.5); extra == "with_vulture" or extra == "with_everything"
52
+ Requires-Dist: vulture (>=1.5) ; extra == "with-vulture" or extra == "with-everything"
53
+ Project-URL: Homepage, http://prospector.readthedocs.io
57
54
  Project-URL: Repository, https://github.com/PyCQA/prospector
58
55
  Description-Content-Type: text/x-rst
59
56
 
@@ -1,33 +1,34 @@
1
1
  prospector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  prospector/__main__.py,sha256=-gdHYZxwq_P8er7HuZEBImY0pwaFq8uIa78dQdJsTTQ,71
3
- prospector/autodetect.py,sha256=Ok8S6jpDiGyhQlnRCMWpsLpSAIXWxA-NQphQuPaOm6o,3112
3
+ prospector/autodetect.py,sha256=sUcayzZjLGESxoPDN_t4_amOAnh9bwKX-9ANBlprP8k,3103
4
4
  prospector/blender.py,sha256=ldQSkfoEKv6pd72B9YCYdapeGUzgfhGzieAu7To3l6Y,4926
5
- prospector/blender_combinations.yaml,sha256=Cr0YAwsGKTy095GO8ktMXqCLUWwq3i3Za1Bu34JR-sI,30949
5
+ prospector/blender_combinations.yaml,sha256=BD2mp4wjb2IRl08kAFBGUmk416WcUSeu-KcBwBII3_Y,32370
6
6
  prospector/compat.py,sha256=p_2BOebzUcKbUAd7mW8rn6tIc10R96gJuZS71QI0XY4,360
7
7
  prospector/config/__init__.py,sha256=4nYshBncKUvZrwNKmp2bQ2mQ8uRS7GU20xPbiC-nJ9g,14793
8
- prospector/config/configuration.py,sha256=sNYR5Jc3P7tfIDXQoDMrOX4i2Yde0o9bc21ZXZNn6rw,14046
8
+ prospector/config/configuration.py,sha256=fPSmyBPCOb-JvkOh2QMe0PfTdIHsy6V5plagI0d_vaU,14039
9
9
  prospector/config/datatype.py,sha256=u2i3YxtFKXkeGKzrYnc8koI-Xq-Owl74e_XveNhFY8Y,658
10
10
  prospector/encoding.py,sha256=67sbqzcUoQqi3PRm_P3GNGwcL1N56RZ3T_YHmSrICEE,1549
11
11
  prospector/exceptions.py,sha256=3P58RNF7j1n4CUIZ8VM5BVhB4Q6UtVs-dQRG8TRCZ7o,1248
12
12
  prospector/finder.py,sha256=pmGuxf9Jq5SX1ANHHdnFKLDfFwTk4fKakxnFHuwzpvs,4775
13
13
  prospector/formatters/__init__.py,sha256=Ko96rZA7C0vjWdsU80DHdHoCk8PNjfDEQI-AACs3_CI,552
14
14
  prospector/formatters/base.py,sha256=f3ku_wbFDk_woCPS15FvLDFQBSw8Bxf9Wytuz8vdplc,1883
15
- prospector/formatters/base_summary.py,sha256=C2O6XAU4l-rOHL1q4rA56jO9sU7Sf0sHRvqgiY6PQgw,1410
15
+ prospector/formatters/base_summary.py,sha256=mLV2Tw68vFAcljeYT9xH87EXoUXiQgOW5mrWRIeKLOU,3353
16
16
  prospector/formatters/emacs.py,sha256=lDOxDJSYYLV1gzevO_z7NlM4AUH6JC9EwoEZu6knnqs,724
17
17
  prospector/formatters/grouped.py,sha256=-vD8I3unoO0FBISoUPHZPCa0p-btWMvt1D_UxzmcGDA,1357
18
18
  prospector/formatters/json.py,sha256=_DbZ_Eb0fmZf6qZMXCU__QRXv_awkBisZ-jvGfOr4aw,1000
19
- prospector/formatters/pylint.py,sha256=uotSwdXb0eSey4cltbvGVfOVi6bZcb4c7XebfkcexLU,3027
19
+ prospector/formatters/pylint.py,sha256=Tq_x_9YoIUR7XDat2eknL7CQIMqTU6rViFoQ4GeEIQg,3330
20
20
  prospector/formatters/pylint_parseable.py,sha256=o80NQZjPqKIHVfywJTci4ky_FzP4XlPa6ZSXoVPPxnY,2724
21
- prospector/formatters/text.py,sha256=5czha8YfdJ9SD6vWLHE85LgB3-d0lHOsxpYhtokRmTU,1611
21
+ prospector/formatters/text.py,sha256=poq8mQF7GIULnS6phqSGS5WW5w796zSCmWAeqp9FHwM,1734
22
22
  prospector/formatters/vscode.py,sha256=ffP-JmrgZhhdirTj1ldV5fG10hdDiHjRfekSqQpQmx0,1643
23
23
  prospector/formatters/xunit.py,sha256=e5ObAWSLm-ekvWs8xsi-OaIL2yoYedlxuJdUhLZ8gkk,2464
24
24
  prospector/formatters/yaml.py,sha256=0RAL5BaaL9A0DfWZ-bdpK1mwgr8zJ_ULVwlnSXVPlDU,684
25
+ prospector/identify.py,sha256=EKDD5gGIArN6Gf0FPAgO7bOqUe1wvGH3xNjDcymPoyA,2474
25
26
  prospector/message.py,sha256=HKXdO8bOezRKSnTtjqu0k04Cb7JgL0oPUWakuSBE9s4,3710
26
- prospector/pathutils.py,sha256=CyZIj4WXGill8OfnqRvcVqZYX0lzL3QcIxpkxCz-dkE,1219
27
+ prospector/pathutils.py,sha256=D43-p3eBS8oM_XIdlrAfUvaFIewEf7zkaM84cjY5-mE,1592
27
28
  prospector/postfilter.py,sha256=BpmR1g1ZlgQB92_Uk6hRyYoIBUcivvcU8RGLUHXDdcU,2695
28
29
  prospector/profiles/__init__.py,sha256=q9zPLVEwo7qoouYFrmENsmByFrKKkr27Dd_Wo9btTJI,683
29
30
  prospector/profiles/exceptions.py,sha256=MDky4KXVwfOlW1yCbyp8Y07D8Kfz76jL3z-8T3WQIFI,1062
30
- prospector/profiles/profile.py,sha256=U8vDdyfka0_Ht9cYT2i_c-xbMcktSpS1h53cU7tGerk,17828
31
+ prospector/profiles/profile.py,sha256=iX9krQ-Pp0Zr2b0udEAvIo8MH0LrEMmq1GgrIAufIaY,17914
31
32
  prospector/profiles/profiles/default.yaml,sha256=tMy-G49ZdV7gkfBoN2ngtIWha3n7JVr_rEeNkLzpKsk,100
32
33
  prospector/profiles/profiles/doc_warnings.yaml,sha256=K_cBhUeKnSvOCwgwXE2tMZ-Fr5cJovC1XSholWglzN4,48
33
34
  prospector/profiles/profiles/flake8.yaml,sha256=wC-TJYuVobo9zPm4Yc_Ocd4Wwfemx0IufmAnfiuKkHk,156
@@ -45,28 +46,28 @@ prospector/profiles/profiles/strictness_veryhigh.yaml,sha256=whQFEUtkySSEIriEwYO
45
46
  prospector/profiles/profiles/strictness_verylow.yaml,sha256=YxZowcBtA3tAaHJGz2htTdAJ-AXmlHB-o4zEYKPRfJg,833
46
47
  prospector/profiles/profiles/test_warnings.yaml,sha256=arUcV9MnqiZJEHURH9bVRSYDhYUegNc-ltFYe_yQW44,23
47
48
  prospector/run.py,sha256=Sjh28jI-Bergzr3rxZp7QzRHP9Ue0SPdfcB0TEkMSDQ,8631
48
- prospector/suppression.py,sha256=qT3TIe8a87mnXPOcRr6m58UodUREExBZxttTlWFTJbY,7654
49
+ prospector/suppression.py,sha256=TbJCl2i5CyEszkrOMMflSSKJUtZkysy0AKv8MqPuy1g,7671
49
50
  prospector/tools/__init__.py,sha256=9tDmxL_kn5jmAACeSi1jtSvT-9tI468Ccn1Up2wUFi0,2956
50
51
  prospector/tools/bandit/__init__.py,sha256=iU39U28_YP5QUm8sgU21Ps96czIIl6tSV7qtCxbFvms,3035
51
- prospector/tools/base.py,sha256=xL_uUbhZUyPrDU-2ntgmzhs-DW1e5sfDKb_OA7VtxKQ,2146
52
+ prospector/tools/base.py,sha256=s9reA-prAqNGMf1FaP-_73sptpgt6NXGIwrmKHJu3-I,2174
52
53
  prospector/tools/dodgy/__init__.py,sha256=howLCjFEheW_6ZoyQ15gLbiNNNUr0Pm2qNpLg3kleFY,1648
53
54
  prospector/tools/exceptions.py,sha256=Q-u4n6YzZuoMu17XkeKac1o1gBY36JK4MnvWaYrVYL0,170
54
55
  prospector/tools/mccabe/__init__.py,sha256=80-aYPqKCREeBqxiIUgsDvxc_GqYxHb5R0JduKHPRaE,3277
55
- prospector/tools/mypy/__init__.py,sha256=MLFBEnICTsLNO2piwRbcrWIv12ddgIqnQSuq43CRGTc,5350
56
- prospector/tools/profile_validator/__init__.py,sha256=bAkVG-fKtm3WaEv-We36wqzvtqWv4s06Z7YnRVG7UQ4,8326
56
+ prospector/tools/mypy/__init__.py,sha256=JxczQoHYdUNFLunhvtMSs7MkW7sL92pOoqSY_WI2wVs,5367
57
+ prospector/tools/profile_validator/__init__.py,sha256=CrQeWfbnKav3xlcY2A14Ld_v-9KE5Yz5HJNtTmrST8c,8317
57
58
  prospector/tools/pycodestyle/__init__.py,sha256=uMpUxqsPsryEsfyfGxpLzwoWUjIvfxIQke4Xvkfbi9A,5970
58
59
  prospector/tools/pydocstyle/__init__.py,sha256=WB-AT-c1FeUUUWATUzJbBLeREtu-lxT03bChh4nablo,2776
59
60
  prospector/tools/pyflakes/__init__.py,sha256=53NQFODU416KO991NxW14gChjagbSAhhfErx1ll7VUQ,5631
60
- prospector/tools/pylint/__init__.py,sha256=Nixin4odPsTj7q1C2Tud2mU5An1lCc7aKQdGAQLyf8g,11398
61
+ prospector/tools/pylint/__init__.py,sha256=MwvKY8ESvlD3FXa6GgZ5JB0ALzWRV60LVbX2RxsrzLM,11540
61
62
  prospector/tools/pylint/collector.py,sha256=7FHgO6OOLGD15_U4DERiXyEbSFnMjGUBeCwdmUlhO8I,1559
62
63
  prospector/tools/pylint/linter.py,sha256=YQ9SOna4WjbbauqSgUio6Ss8zN08PCr3aKdK9rMi7Ag,2143
63
64
  prospector/tools/pyright/__init__.py,sha256=USqauZofh-8ZSKGwXRXoaM2ItzfSFo2nGwPtLGEWICU,3346
64
65
  prospector/tools/pyroma/__init__.py,sha256=GPQRJZfbs_SI0RBTyySz-4SIuM__YoLfXAm7uYVXAS8,3151
65
- prospector/tools/ruff/__init__.py,sha256=r55pEbmjLFWmY1jiQ7aZ2OYBxkEAH5An3-CpL04rdc0,3915
66
+ prospector/tools/ruff/__init__.py,sha256=vQLBI7PvrnKIUVbi3XKSES6mH5UUz99X2QNBk3QAIAI,3932
66
67
  prospector/tools/utils.py,sha256=cRCogsMCH0lPBhdujPsIY0ovNAL6TAxBMohZRES02-4,1770
67
68
  prospector/tools/vulture/__init__.py,sha256=eaTh4X5onNlBMuz1x0rmcRn7x5XDVDgqftjIEd47eWI,3583
68
- prospector-1.15.2.dist-info/entry_points.txt,sha256=SxvCGt8MJTEZefHAvwnUc6jDetgCaaYY1Zpifuk8tqU,50
69
- prospector-1.15.2.dist-info/LICENSE,sha256=WoTRadDy8VbcIKoVzl5Q1QipuD_cexAf3ul4MaVLttc,18044
70
- prospector-1.15.2.dist-info/WHEEL,sha256=gSF7fibx4crkLz_A-IKR6kcuq0jJ64KNCkG8_bcaEao,88
71
- prospector-1.15.2.dist-info/METADATA,sha256=nqGJNXbYx4ygQlRYWmuYYgOui8Zk9PhsA90WKIejqyk,9962
72
- prospector-1.15.2.dist-info/RECORD,,
69
+ prospector-1.15.4a1.dist-info/LICENSE,sha256=WoTRadDy8VbcIKoVzl5Q1QipuD_cexAf3ul4MaVLttc,18044
70
+ prospector-1.15.4a1.dist-info/METADATA,sha256=aGqdN09IQoh1yHMiRcA3oeXzpc-TVyY2XMKwF0Eqf-o,9845
71
+ prospector-1.15.4a1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
72
+ prospector-1.15.4a1.dist-info/entry_points.txt,sha256=SxvCGt8MJTEZefHAvwnUc6jDetgCaaYY1Zpifuk8tqU,50
73
+ prospector-1.15.4a1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.2.0
2
+ Generator: poetry-core 2.1.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any