Skip to content

Commit 61031dc

Browse files
authored
Merge pull request #307 from netromdk/dev
Python 3.13 support and various fixes and features
2 parents c75aca7 + c9299b5 commit 61031dc

34 files changed

+1396
-263
lines changed

.github/workflows/analyze.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
runs-on: ubuntu-latest
1919
strategy:
2020
matrix:
21-
python-version: ['3.12']
21+
python-version: ['3.13']
2222

2323
steps:
2424
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8

.github/workflows/test.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ jobs:
1717
runs-on: ${{ matrix.os }}
1818
strategy:
1919
matrix:
20-
# Start Linux jobs last since they are fastest to start and complete, and start 3.12 first,
21-
# since it pairs wiht macOS+Windows jobs. 3.4 is not supported on GitHub anymore and 3.5,
22-
# 3.6, and 3.7 for x64 isn't produced for Ubuntu 22.04 and macOS.
23-
python-version: ['3.12', '3.11', '3.10', 3.9, 3.8]
20+
# Start Linux jobs last since they are fastest to start and complete, and start 3.13 first,
21+
# since it pairs wiht macOS+Windows jobs, and 3.5 and 3.6 last since they only run tests and
22+
# don't use venv. 3.4 is not supported on GitHub anymore and 3.5 and 3.6 for x64 isn't
23+
# produced for ubuntu 22.04.
24+
python-version: ['3.13', '3.12', '3.11', '3.10', 3.9, 3.8, 3.7, 3.6, 3.5]
2425
os: [windows-latest, macos-latest, ubuntu-latest]
2526

2627
# Choose test script depending on OS.
@@ -33,7 +34,7 @@ jobs:
3334
test_script_name: ./misc/actions/test.ps1
3435

3536
exclude:
36-
# Only test on macOS and Windows with Python 3.12.
37+
# Only test on macOS and Windows with Python 3.13.
3738
- os: macos-latest
3839
python-version: 3.5
3940
- os: macos-latest
@@ -48,6 +49,8 @@ jobs:
4849
python-version: '3.10'
4950
- os: macos-latest
5051
python-version: '3.11'
52+
- os: macos-latest
53+
python-version: '3.12'
5154

5255
- os: windows-latest
5356
python-version: 3.5
@@ -63,6 +66,8 @@ jobs:
6366
python-version: '3.10'
6467
- os: windows-latest
6568
python-version: '3.11'
69+
- os: windows-latest
70+
python-version: '3.12'
6671

6772
# Ignore 3.5, 3.6, and 3.7 on Linux because it isn't compiled for x64 on Ubuntu 22.04.
6873
- os: ubuntu-latest

README.rst

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Table of Contents
6363
* `API (experimental) <#api-experimental>`__
6464
* `Analysis Exclusions <#analysis-exclusions>`__
6565
* `Parsable Output <#parsable-output>`__
66+
* `GitHub Output <#github-output>`__
6667
* `Contributing <#contributing>`__
6768

6869
Usage
@@ -147,9 +148,10 @@ bytearray, ``with`` statement, asynchronous ``with`` statement, multiple context
147148
unpacking assignment, generalized unpacking, ellipsis literal (``...``) out of slices, dictionary
148149
union (``{..} | {..}``), dictionary union merge (``a = {..}; a |= {..}``), builtin generic type
149150
annotations (``list[str]``), function decorators, class decorators, relaxed decorators,
150-
``metaclass`` class keyword, pattern matching with ``match``, union types written as ``X | Y``, and
151-
type alias statements (``type X = SomeType``). It tries to detect and ignore user-defined functions,
152-
classes, arguments, and variables with names that clash with library-defined symbols.
151+
``metaclass`` class keyword, pattern matching with ``match``, union types written as ``X | Y``, type
152+
alias statements (``type X = SomeType``), and type alias statements with lambdas/comprehensions in
153+
class scopes. It tries to detect and ignore user-defined functions, classes, arguments, and
154+
variables with names that clash with library-defined symbols.
153155

154156
Caveats
155157
=======
@@ -209,18 +211,18 @@ Examples
209211
210212
% ./vermin.py vermin
211213
Minimum required versions: 3.0
212-
Incompatible versions: 2
214+
Incompatible versions: 2.x
213215
214216
% ./vermin.py -t=3.3 vermin
215217
Minimum required versions: 3.0
216-
Incompatible versions: 2
218+
Incompatible versions: 2.x
217219
Target versions not met: 3.3
218220
% echo $?
219221
1
220222
221223
% ./vermin.py --versions vermin
222224
Minimum required versions: 3.0
223-
Incompatible versions: 2
225+
Incompatible versions: 2.x
224226
Version range: 2.0, 2.6, 2.7, 3.0
225227
226228
% ./vermin.py -v examples
@@ -233,7 +235,7 @@ Examples
233235
!2, 3.4 /path/to/examples/abc.py
234236
/path/to/examples/unknown.py
235237
Minimum required versions: 3.4
236-
Incompatible versions: 2
238+
Incompatible versions: 2.x
237239
238240
% ./vermin.py -vv /path/to/examples/abc.py
239241
Detecting python files..
@@ -243,7 +245,7 @@ Examples
243245
'abc.ABC' requires !2, 3.4
244246
245247
Minimum required versions: 3.4
246-
Incompatible versions: 2
248+
Incompatible versions: 2.x
247249
248250
% ./vermin.py -vvv /path/to/examples/abc.py
249251
Detecting python files..
@@ -253,7 +255,7 @@ Examples
253255
L2: 'abc.ABC' requires !2, 3.4
254256
255257
Minimum required versions: 3.4
256-
Incompatible versions: 2
258+
Incompatible versions: 2.x
257259
258260
% ./vermin.py -f parsable /path/to/examples/abc.py
259261
/path/to/examples/abc.py:1:7:2.6:3.0:'abc' module
@@ -432,6 +434,19 @@ That means that the final result is ``!2`` and ``3.4``, which is shown by the la
432434
433435
:::!2:3.4:
434436
437+
GitHub Output
438+
=============
439+
440+
The GitHub output format has the same output as `parsable <#parsable-output>`__, but the lines are
441+
formatted as GitHub Actions annotations. This let's you see minimum version violations as annotated
442+
errors directly from a GitHub pipeline.
443+
444+
For annotations to appear in a pull request:
445+
446+
- Vermin must be called from a GitHub Actions workflow triggered by a PR
447+
- Vermin must be called with current working directory as the root of the repository
448+
- Only violations found in files changed in the PR will show up
449+
435450
Contributing
436451
============
437452

misc/.analysis-requirements.txt

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
annotated-types==0.7.0
12
astroid==2.15.5
23
attrs==23.1.0
34
bandit==1.7.5
@@ -6,57 +7,77 @@ bracex==2.3.post1
67
build==0.10.0
78
certifi==2025.4.26
89
charset-normalizer==3.1.0
9-
click==8.1.4
10+
click==8.1.8
1011
click-option-group==0.5.6
1112
colorama==0.4.6
1213
defusedxml==0.7.1
14+
Deprecated==1.2.18
1315
dill==0.3.6
16+
distlib==0.4.0
1417
docutils==0.20.1
18+
exceptiongroup==1.2.2
1519
face==22.0.0
20+
filelock==3.19.1
1621
flake8==6.0.0
17-
gitdb==4.0.10
22+
gitdb==4.0.12
1823
GitPython==3.1.44
1924
glom==22.1.0
25+
googleapis-common-protos==1.70.0
2026
idna==3.10
27+
importlib_metadata==7.1.0
2128
isort==5.12.0
2229
jsonschema==4.18.0
2330
jsonschema-specifications==2023.6.1
2431
lazy-object-proxy==1.9.0
2532
markdown-it-py==3.0.0
2633
mccabe==0.7.0
2734
mdurl==0.1.2
35+
opentelemetry-api==1.25.0
36+
opentelemetry-exporter-otlp-proto-common==1.25.0
37+
opentelemetry-exporter-otlp-proto-http==1.25.0
38+
opentelemetry-instrumentation==0.46b0
39+
opentelemetry-instrumentation-requests==0.46b0
40+
opentelemetry-proto==1.25.0
41+
opentelemetry-sdk==1.25.0
42+
opentelemetry-semantic-conventions==0.46b0
43+
opentelemetry-util-http==0.46b0
2844
packaging==23.1
2945
pbr==5.11.1
3046
peewee==3.16.2
31-
pep517==0.13.0
32-
platformdirs==3.5.3
47+
pep517==0.13.1
48+
platformdirs==3.9.1
49+
protobuf==4.25.8
3350
pycodestyle==2.10.0
51+
pydantic==2.8.2
52+
pydantic_core==2.20.1
3453
pyflakes==3.0.1
3554
Pygments==2.15.1
3655
pylint==2.17.4
3756
pyparsing==3.0.9
3857
pyproject_hooks==1.0.0
3958
pyroma==4.2
4059
python-lsp-jsonrpc==1.0.0
41-
PyYAML==6.0.1
60+
PyYAML==6.0.2
4261
referencing==0.29.1
43-
requests==2.32.3
62+
requests==2.32.5
4463
rich==13.4.2
4564
rpds-py==0.8.10
46-
ruamel.yaml==0.17.35
47-
ruamel.yaml.clib==0.2.8
48-
semgrep==1.31.1
65+
ruamel.yaml==0.17.40
66+
ruamel.yaml.clib==0.2.14
67+
semgrep==1.86.0
4968
setuptools==80.9.0
5069
six==1.16.0
51-
smmap==5.0.0
70+
smmap==5.0.2
5271
stevedore==5.1.0
5372
toml==0.10.2
5473
tomli==2.0.1
5574
tomlkit==0.11.8
5675
trove-classifiers==2023.5.24
57-
typing_extensions==4.7.1
58-
ujson==5.8.0
59-
urllib3==1.26.20
76+
typing_extensions==4.12.2
77+
ujson==5.11.0
78+
urllib3==2.5.0
79+
virtualenv==20.34.0
6080
vulture==2.7
6181
wcmatch==8.4.1
6282
wrapt==1.15.0
83+
zipp==3.23.0

misc/actions/test.ps1

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,13 @@ else {
3333
coveralls debug --service=github-actions
3434

3535
# Report coverage. Note that it requires COVERALLS_REPO_TOKEN to be set!
36-
coveralls --service=github-actions
36+
for ($i = 0; $i -lt 5; $i++) {
37+
$result = coveralls --service=github-actions
38+
$exitCode = $LASTEXITCODE
39+
Write-Host "Coveralls exit code: $exitCode"
40+
if ($exitCode -eq 0) {
41+
break
42+
}
43+
Start-Sleep -Seconds 10
44+
}
3745
}

misc/actions/test.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,11 @@ else
2828
coveralls debug --service=github-actions
2929

3030
# Note that it requires COVERALLS_REPO_TOKEN to be set!
31-
coveralls --service=github-actions
31+
for i in {1..5}; do
32+
coveralls --service=github-actions
33+
CODE=$?
34+
echo "Coveralls exit code: $CODE"
35+
if [ $CODE -eq 0 ]; then break; fi
36+
sleep 10
37+
done
3238
fi

runtests.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
#!/usr/bin/env python
2+
import os
23
import sys
34
import unittest
45
from time import time
56

7+
ROOT_MODULE = "tests."
8+
9+
# Isolate tests to be run "suite[.Class.test_method]". Class and method are optional.
10+
ISOLATE = os.getenv("VERMIN_TEST_ISOLATE")
11+
if ISOLATE is not None:
12+
ISOLATE = ROOT_MODULE + ISOLATE.strip()
13+
614
SUITES = (
715
"general",
816
"config",
@@ -33,10 +41,19 @@
3341
)
3442

3543
def runsuite(suite):
36-
suite = "tests.{}".format(suite)
44+
# skip suite if doesn't match isolation filter
45+
suite = ROOT_MODULE + suite
46+
if ISOLATE:
47+
if ISOLATE != suite and not ISOLATE.startswith(suite + "."):
48+
print("Skipping test suite: {}".format(suite))
49+
return 0
50+
print("Isolated test(s): {}".format(ISOLATE))
3751
print("Running test suite: {}".format(suite))
3852
# Buffer output such that only on test errors will stdout/stderr be shown.
39-
res = unittest.main(suite, exit=False, buffer=True).result
53+
if ISOLATE:
54+
res = unittest.main(None, argv=[sys.argv[0], ISOLATE], exit=False, buffer=True).result
55+
else:
56+
res = unittest.main(suite, exit=False, buffer=True).result
4057
if len(res.failures) > 0 or len(res.errors) > 0:
4158
sys.exit(-1)
4259
return res.testsRun

tests/arguments.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -337,23 +337,24 @@ def test_format(self):
337337
self.assertContainsDict({"code": 0}, self.parse_args(["--format", fmt]))
338338
self.assertEqual(fmt, self.config.format().name())
339339

340-
# Parsable verbose level 3, no tips, ignore incompatible versions, no `--versions`.
341-
for args in (["--format", "parsable"], ["--format", "parsable", "--verbose"],
342-
["--format", "parsable", "--versions"]):
340+
for fmt in ("parsable", "github"):
341+
# Parsable verbose level 3, no tips, ignore incompatible versions, no `--versions`.
342+
for args in (["--format", fmt], ["--format", fmt, "--verbose"],
343+
["--format", fmt, "--versions"]):
344+
self.config.reset()
345+
self.assertContainsDict({"code": 0, "versions": False}, self.parse_args(args))
346+
self.assertEqual(3, self.config.verbose())
347+
self.assertTrue(self.config.ignore_incomp())
348+
self.assertFalse(self.config.show_tips())
349+
350+
# Verbosity can be higher for parsable
343351
self.config.reset()
344-
self.assertContainsDict({"code": 0, "versions": False}, self.parse_args(args))
345-
self.assertEqual(3, self.config.verbose())
352+
self.assertContainsDict({"code": 0, "versions": False},
353+
self.parse_args(["--format", fmt, "-vvvv"]))
354+
self.assertEqual(4, self.config.verbose())
346355
self.assertTrue(self.config.ignore_incomp())
347356
self.assertFalse(self.config.show_tips())
348357

349-
# Verbosity can be higher for parsable.
350-
self.config.reset()
351-
self.assertContainsDict({"code": 0, "versions": False},
352-
self.parse_args(["--format", "parsable", "-vvvv"]))
353-
self.assertEqual(4, self.config.verbose())
354-
self.assertTrue(self.config.ignore_incomp())
355-
self.assertFalse(self.config.show_tips())
356-
357358
def test_pessimistic(self):
358359
self.assertFalse(self.config.pessimistic())
359360
self.assertContainsDict({"code": 0}, self.parse_args(["--pessimistic"]))

tests/array_typecodes.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,12 @@ def test_typecode_q(self, source, min_versions):
1818
])
1919
def test_typecode_Q(self, source, min_versions):
2020
self.assertDetectMinVersions(source, min_versions)
21+
22+
@VerminTest.parameterized_args([
23+
("from array import array\narray('w')", (3, 13)),
24+
("import array\narray.array('w')", (3, 13)),
25+
("from array import array\narray('w', [1, 2])", (3, 13)),
26+
("from array import array as a\na('w', [1, 2])", (3, 13)),
27+
])
28+
def test_typecode_w(self, source, min_versions):
29+
self.assertDetectMinVersions(source, min_versions)

tests/builtin_exceptions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ def test_PermissionError(self):
107107
def test_ProcessLookupError(self):
108108
self.assertOnlyIn((3, 3), self.detect("ProcessLookupError()"))
109109

110+
def test_PythonFinalizationError(self):
111+
self.assertOnlyIn((3, 13), self.detect("PythonFinalizationError()"))
112+
110113
def test_RecursionError(self):
111114
self.assertOnlyIn((3, 5), self.detect("RecursionError()"))
112115

0 commit comments

Comments
 (0)