runtime
148 / 250
lesson 148 Packaging & Databases · advanced

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.

You'll be able to
  • 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.metadata for an installed package's version and requirements, and handle PackageNotFoundError
The idea

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.

example-1.py · Versions compared as text
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.)

example-2.py · Versions compared as numbers
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.

example-3.py · What is installed?
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).

Review
  • 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 raises PackageNotFoundError when it isn't there.
  • Next: storing data in a database with SQLite.
Practise this

Loading your progress…

To master this lesson, solve both challenges without hints or the answer, and pass the concept check.

Dependencies: version specifiers and installed packages — Packaging & Databases · Runtime