Dependencies: version specifiers and installed packages
A dependency line such as pandas>=2.2,<3 names a package and the versions the project accepts. Checking a version means comparing numbers part by part, and importlib.metadata reports what is really installed.
- Read a requirement: name, optional extras, version specifiers joined by commas, and an optional environment marker
- Compare versions part by part as numbers, never as text
- Ask
importlib.metadatafor an installed package's version and requirements, and handlePackageNotFoundError
Each string in dependencies is a requirement. pandas[parquet]>=2.2,<3; python_version >= "3.10" has four parts: the package name pandas; an extra in square brackets (an optional feature of that package); version specifiers, >=2.2,<3, which must *all* hold; and after ; a marker that says when the line applies at all.
The operators are ==, !=, <, <=, >, >=, and ~= ("compatible release": ~=2.2 means >=2.2,<3). On your computer, python -m pip install -r requirements.txt installs versions that satisfy every line, and python -m pip freeze prints exactly what is installed as name==version lines, which is how an environment is written down and rebuilt elsewhere.
To check a version yourself you have to compare it the way pip does. Text comparison gets it wrong:
Run each example to see what it prints. Running examples doesn't count toward progress.
print("2.10.0" > "2.9.1") print(sorted(["2.9.1", "2.10.0", "2.2.0"]))
Split the version at the dots and turn each part into an int. Tuples of numbers compare part by part, which is exactly the rule for release versions. (Real tools use the packaging library, which also understands pre-releases such as 2.0rc1; the release numbers here cover the common case.)
def version_tuple(text): return tuple([int(part) for part in text.split(".")]) print(version_tuple("2.10.0") > version_tuple("2.9.1")) installed = ["2.9.1", "2.10.0", "2.2.0"] print(sorted(installed, key=version_tuple)) print(version_tuple("2.0") == version_tuple("2.0.0"))
Which version is actually installed? When pip installs a package it writes a folder named like market_reports-1.4.2.dist-info into site-packages, holding a METADATA file. importlib.metadata reads those folders from every place on sys.path. This example writes such a folder itself, exactly as pip would, and puts it on the path.
import sys from importlib import metadata from pathlib import Path info = Path("site-packages/market_reports-1.4.2.dist-info") info.mkdir(parents=True) (info / "METADATA").write_text( "Metadata-Version: 2.1\nName: market-reports\nVersion: 1.4.2\n" "Requires-Dist: requests>=2.31\nRequires-Dist: pandas<3,>=2.2\n", encoding="utf-8", ) sys.path.insert(0, "site-packages") print(metadata.version("market-reports")) print(metadata.requires("market-reports")) try: metadata.version("runtime-charts") except metadata.PackageNotFoundError: print("runtime-charts is not installed")
When to use it: to report versions in a bug report or a --version flag, to check an optional feature's package before using it, or to audit an environment against the project's requirements. Mistakes to avoid: comparing version text with <; forgetting that every specifier in a comma list must hold; and treating Requests, requests and REQUESTS as different packages (names are compared case-insensitively, with -, _ and . treated alike).
- A requirement is a name, optional extras, specifiers that must all hold, and an optional marker.
- Compare versions as numbers, part by part, padding with zeros; never as text.
importlib.metadata.version(name)reads what pip installed and raisesPackageNotFoundErrorwhen it isn't there.- Next: storing data in a database with SQLite.