Open-source project
netromdk/vermin avatar
netromdk/vermin

vermin: working out the minimum Python version your code needs

Concurrently detect the minimum Python versions needed to run code

538 stars35 forksPythonMIT

At a glance

What is it?
vermin parses Python source into an abstract syntax tree and matches what it finds against thousands of rules to report the lowest interpreter version that can run the code, on both the 2.x and 3.x lines. It is a small, dependency-free tool with no runtime requirements at all, and it doubles as a linter and a CI gate on version drift.
Who is it for?
Adopt vermin when you support a range of Python versions and need to know what your code actually requires, or when you want CI to fail when a pull request silently raises the minimum interpreter version.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What vermin actually computes, and the rule table behind it

vermin concurrently detects the minimum Python versions needed to run code, and the mechanism is an abstract syntax tree traversal matched against internal dictionaries of rules.

The scale of that rule table is the point, because a hand-written version check would never keep up. The README counts 4312 rules covering v2.0 to 2.7 and v3.0 to 3.15, divided into 194 modules, 2995 classes, functions or constants that are members of modules, 991 keyword arguments of functions, 4 strftime directives, 3 bytes format directives, 6 array typecodes, 3 codecs error handler names, 19 codecs encodings, 78 builtin generic annotation types, 9 builtin dict union types using the pipe operator, 8 builtin dict union merge types, and 2 user function decorators.

The feature detection list gives you a sense of what is in there. It covers the print function versus the print statement, long, f-strings, self-documenting f-strings, PEP 701 f-strings for 3.12 and later, await inside f-strings, coroutines, asynchronous generators, asynchronous comprehensions, await in comprehensions, asynchronous for loops, boolean constants, named expressions, keyword-only and positional-only parameters, nonlocal, yield from, exception chaining with raise from, except star, set literals and comprehensions, dict comprehensions, unpacking inside comprehension value expressions, the infix matrix multiplication operator, string format calls, the three import forms, function calls with names and keyword arguments, strftime and strptime directives, and function and variable annotations including Final and Literal.

The counts are the interesting engineering fact, because they show this is a maintained database rather than a heuristic. Every Python release that adds a language feature means a new rule, and a rule that is wrong in either direction produces a wrong answer: too low and the code fails on the interpreter you told people to use, too high and you drop support you did not need to drop.

The project is fairly well-tested with 4605 unit and integration tests executed on Linux, macOS and Windows, and the Makefile has a target that runs vermin against its own source, which is a neat check that a version detector can pass its own minimum version requirement.

Installing it, and the advice to use a recent interpreter

The usage section is straightforward, and running from a checkout is the first option:

code
  % ./vermin.py /path/to/your/project        # (1) executing via `/usr/bin/env python`
  % python3 vermin.py /path/to/your/project  # (2) specifically `python3`

Installed from PyPI, the same command works as a console script:

code
  % pip install vermin
  % vermin /path/to/your/project

There are package manager routes as well. Homebrew installs it with brew install vermin. Spack is supported through a clone and setup of the spack environment followed by spack install py-vermin and spack load py-vermin. And for Arch Linux there is an AUR package installed with yay -S python-vermin.

One piece of advice in the README is worth following. It is recommended to use the most recent Python version to run Vermin on projects, since Python's own language parser is used to detect language features, like f-strings since Python 3.6.

That is a direct consequence of the AST approach. Vermin does not have its own parser, it asks the interpreter running it to parse the source, and the set of constructs the parser accepts is what determines the detection. A Python 3.5 interpreter cannot parse f-strings at all, so it cannot report that the code needs 3.6.

There is a second constraint from the same design. Since the code is vanilla Python with no external dependencies, it can be run with v3 and later, but it still includes detection of v2.x functionality, because the point of the tool is to report what your project needs, and your project might be Python 2.

Backports of the standard library, like typing, can be enabled for better results, and the full list of backports is available through the help output. This is the escape hatch for the false positive case, where a project uses a feature that only exists in a newer version but has backported it.

Using it in CI and as a pre-commit hook

The two integrations are the parts of vermin that change how a team works, and both are about catching version drift before it reaches users.

For CI, the README gives a Travis CI example that checks the minimum required versions did not change:

code
  install:
  - ./setup_virtual_env.sh
  - pip install vermin
  script:
  - vermin -t=2.7 -t=3 project_package otherfile.py

The point is that the check runs against a declared target rather than against nothing. Passing -t=2.7 and -t=3 tells vermin which versions you intend to support, and any feature in the code that would break one of them is a violation rather than a curiosity.

As a pre-commit hook, the configuration is a normal pre-commit repo entry:

yaml
  repos:
    - repo: https://github.com/netromdk/vermin
      rev: GIT_SHA_OR_TAG  # ex: 'e88bda9' or 'v1.3.4'
      hooks:
        - id: vermin
          # specify your target version here, OR in a Vermin config file as usual:
          args: ['-t=3.8-', '--violations']
          # (if your target is specified in a Vermin config, you may omit the 'args' entry entirely)

There is one requirement that catches people out. When using the hook, a target version must be specified via a Vermin config file in the package, or via the args option in the .pre-commit-config.yaml file. If you are passing the target through args, it is recommended to also include --violations, which is shown in the example.

And then the distinction between the two hooks. If you are using the vermin-all hook you can specify any target as you usually would, but if you are using the vermin hook your target must be in the form of x.y- as opposed to x.y, otherwise you will run into issues when your staged changes meet a minimum version that is lower than your target.

That trailing dash is a small syntactic detail with a real meaning behind it. The form x.y- means at least that version, while x.y means exactly that version, and for a staged change that only needs to work on a newer interpreter, the exact form turns a perfectly fine change into a failure.

The feature list, and what the analysis reports alongside versions

Beyond the version numbers, vermin does two more things that matter in practice.

The first is syntax error reporting. As it parses each file, it reports Python syntax errors and warnings with full context, making it easy to fix them in addition to the minimum versions detected. That is a natural by-product of an AST-based tool, and a useful one, since a file that does not parse cannot be version-analysed either, and the error output tells you where the parse stopped.

The second is the reporting surface, and the table of contents shows the options. There is a Configuration File section and an Examples section, and then a group of output modes that are clearly aimed at automation rather than at a human reading a terminal: a linting mode that shows only target version violations, an experimental API, analysis exclusions, parsable output and GitHub output.

The linting mode is the CI-friendly one, since it turns vermin into something that reports only the things that violate your declared target, which is what a pre-commit hook or a pipeline step actually wants. The parsable output is for scripts. The GitHub output is for the annotation format, where findings appear inline on the diff, which is the difference between a pipeline step a developer reads and one they click through to a file and line.

Analysis exclusions are the other feature worth naming, because a codebase always has directories that are not your code, vendored trees, generated files or fixtures with deliberately old syntax. The section exists to let you exclude them, and a version detector that walks a whole repository without an exclusion mechanism is not usable on a real project.

The API is marked experimental, which is the honest label for a programmatic interface that has not settled. The module structure of the package reflects that the same functionality is available from Python, with the console script entry point pointing at a main function in the vermin module.

The packaging, and what the project uses to keep itself honest

The pyproject.toml is a good example of a small tool with strict packaging.

The build backend is setuptools with wheel, the project name is vermin at version 1.9.2, and the requires-python is 3.0 or later. The dependencies list is empty, and the optional-dependencies list is empty too, which matches the README statement that the code is vanilla Python with no external dependencies. The package ships a single console script named vermin pointing at a main function.

That zero-dependency stance is the design decision that makes the tool usable in the places you need it. A version detector that you add to a pre-commit hook, a CI container or a minimal build image cannot pull in a tree of packages, and a tool whose whole job is to tell you the minimum version of the interpreter you need would be ironic if it required recent versions of other libraries.

The authors list names Morten Kristensen, and the classifiers mark the project as a production stable utility for developers, with Python 3.0 as the only version classifier. The repository links are the GitHub homepage, source and bug reports.

The Makefile is where the self-checking discipline shows. There is a test-self target that runs vermin against its own source files with a target of 3, and a test-tests target that runs it against the test suite with a target of 3.2-, and the test target runs both plus the runtests.py script. The two different target forms in one Makefile are the same distinction the pre-commit section describes, applied to the project itself.

There is a count target that prints the number of tests and the source line counts using sloccount, which is how the rule and test counts in the README are kept honest. There are setup targets for a virtualenv and for the coverage, analysis and docs requirements, with separate files under misc/ for each, and the requirements files are regenerated from a pip freeze so the tooling is pinned reproducibly.

The repository root also carries a .pre-commit-hooks.yaml, which is the file pre-commit reads to know what the hook is called and what it does, and a .semgrepignore, which is a hint that static analysis with semgrep is part of the project's own quality process, since the Makefile has a semgrep ci invocation with metrics off and a timeout.

Editorial conclusion

Adopt vermin when you support a range of Python versions and need to know what your code actually requires, or when you want CI to fail when a pull request silently raises the minimum interpreter version. Do not adopt it to check whether code runs on a given interpreter, since it works from the syntax tree and not from execution, and do not trust it on code that hides its version requirements behind backports unless you enable the backport options, which is why the tool has them at all. Before you wire it into a pre-commit hook, read the note about the target format, because the single-file hook needs a target in the form of x.y- while the all-files hook does not.

Frequently asked questions

How does vermin determine the minimum Python version?

It parses the code into an abstract syntax tree, traverses it and matches against internal rule dictionaries. The rules cover v2.0 to 2.7 and v3.0 to 3.15, spread across 194 modules and thousands of individual members, keyword arguments and directives.

How do I install vermin?

With pip install vermin, brew install vermin, the Arch AUR package via yay -S python-vermin, or Spack with spack install py-vermin. You can also run it straight from a checkout with ./vermin.py against a project path. The package has no runtime dependencies.

Can vermin be used as a pre-commit hook?

Yes. Point the hook at the repository and set the target, either in a Vermin config file or through the args option, for example args of -t=3.8- and --violations. The single-file vermin hook requires the target in the form x.y- rather than x.y, while the vermin-all hook accepts any target.

Why should I run vermin with a recent Python version?

Because Vermin uses Python's own language parser to detect language features, so an older interpreter cannot parse newer syntax and therefore cannot report it. The README recommends the most recent Python version for exactly that reason.

What can vermin do besides version detection?

It reports Python syntax errors and warnings with full context as it parses each file. It also offers a linting mode that shows only target version violations, a parsable output mode, a GitHub output mode, analysis exclusions and an experimental API.

Official sources

  1. Issues
  2. License: MIT
  3. netromdk/vermin on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/netromdk-vermin.svg)](https://hysenlabs.com/projects/netromdk-vermin)

Was this useful?