PyPI Commands

Overview

The netapi pypi commands provide CLI access to the Python Package Index (PyPI) JSON API. All commands support -f json for jq piping.

Prerequisites

No authentication required. The JSON API is public, free, and has no documented rate limits.

# No tokens or keys needed
netapi pypi info requests

Commands (8 total)

Command Description

info

Package metadata (name, version, summary, license, author)

versions

List all released versions

deps

List dependencies (requires_dist)

urls

Download URLs for latest release

releases

Release history with dates

search

Search packages (via PyPI search or warehouse)

compare

Compare two packages side-by-side

api

Raw API access

JSON Output

All commands support -f json for machine-readable output:

netapi pypi info requests -f json
netapi pypi versions django -f json
netapi pypi deps flask -f json

jq Patterns

Exploring Structure

# Top-level keys
netapi pypi info requests -f json | jq '.info | keys'

# Full info object
netapi pypi info requests -f json | jq '.info'

Package Information

# Summary card
netapi pypi info requests -f json | jq '{name: .info.name, version: .info.version, summary: .info.summary, license: .info.license}'

# Author and project URLs
netapi pypi info flask -f json | jq '{author: .info.author, home_page: .info.home_page, project_urls: .info.project_urls}'

# Python version requirement
netapi pypi info django -f json | jq -r '.info.requires_python'

# Package classifiers (filtered)
netapi pypi info requests -f json | jq -r '.info.classifiers[] | select(startswith("Programming Language"))'

Version Listing

# All versions
netapi pypi versions requests -f json | jq '.'

# Latest 10 versions
netapi pypi versions django -f json | jq '.[-10:]'

# Major versions only (X.Y format)
netapi pypi versions django -f json | jq '[.[] | select(test("^[0-9]+\\.[0-9]+$"))] | sort | .[-10:]'

Dependencies

# All dependencies
netapi pypi deps flask -f json | jq '.[]'

# Required deps only (no extras)
netapi pypi deps flask -f json | jq -r '.[] | select(contains("extra") | not)'

# Deps for a specific extra
netapi pypi deps flask -f json | jq -r '.[] | select(contains("async"))'

Package Comparison

# Compare requests vs httpx
netapi pypi compare requests httpx -f json | jq '.[] | {name, version, summary}'

Raw API Access

# Any package
netapi pypi api requests | jq '.'

# Specific version
netapi pypi api requests/2.28.0 | jq '.info.version'

Environment Variables

No environment variables required. All endpoints are public.

Useful jq Recipes

Dependency Tree (One Level)

netapi pypi deps flask -f json | jq -r '.[] | select(contains("extra") | not) | split(" ")[0]' \
  | xargs -I{} netapi pypi info "{}" -f json | jq '{name: .info.name, version: .info.version}'

Export to CSV

for pkg in requests flask django fastapi; do
  netapi pypi info "$pkg" -f json | jq -r '[.info.name, .info.version, .info.license] | @csv'
done

Check for Updates

# Compare installed vs latest
pip list --format=json | jq -r '.[].name' | head -10 | while read pkg; do
  latest=$(netapi pypi info "$pkg" -f json 2>/dev/null | jq -r '.info.version // "unknown"')
  installed=$(pip show "$pkg" 2>/dev/null | awk '/^Version:/ {print $2}')
  [[ "$installed" != "$latest" ]] && echo "$pkg: $installed → $latest"
done

curl Equivalents (Works Now)

These raw curl + jq patterns work today without the netapi CLI. No authentication required.

Package Metadata

# Full package info
curl -s https://pypi.org/pypi/requests/json \
  | jq '{name: .info.name, version: .info.version, summary: .info.summary, license: .info.license, home_page: .info.home_page}'

# Author and URLs
curl -s https://pypi.org/pypi/flask/json \
  | jq '{author: .info.author, urls: .info.project_urls}'

# Python version requirement
curl -s https://pypi.org/pypi/django/json | jq -r '.info.requires_python'

# Package description (first 300 chars)
curl -s https://pypi.org/pypi/fastapi/json | jq -r '.info.summary'

Version Listing

# All versions (keys of releases object)
curl -s https://pypi.org/pypi/requests/json | jq '[.releases | keys[]]'

# Latest 10 versions
curl -s https://pypi.org/pypi/django/json | jq '[.releases | keys[]] | sort | .[-10:]'

# Major versions only
curl -s https://pypi.org/pypi/django/json \
  | jq '[.releases | keys[] | select(test("^[0-9]+\\.[0-9]+$"))] | sort | .[-10:]'

# Specific version metadata
curl -s https://pypi.org/pypi/requests/2.28.0/json \
  | jq '{version: .info.version, requires_python: .info.requires_python}'

Dependencies

# All dependencies
curl -s https://pypi.org/pypi/flask/json | jq '.info.requires_dist'

# Required only (no extras)
curl -s https://pypi.org/pypi/flask/json \
  | jq -r '.info.requires_dist[] | select(contains("extra") | not)'

# Just package names (strip version specifiers)
curl -s https://pypi.org/pypi/flask/json \
  | jq -r '.info.requires_dist[] | select(contains("extra") | not) | split(" ")[0]'

# Deps for a specific extra
curl -s https://pypi.org/pypi/flask/json \
  | jq -r '.info.requires_dist[] | select(contains("async"))'

Package Comparison

# Compare two HTTP libraries
for pkg in requests httpx; do
  curl -s "https://pypi.org/pypi/${pkg}/json" \
    | jq --arg p "$pkg" '{name: $p, version: .info.version, summary: .info.summary}'
done | jq -s '.'

# Compare web frameworks
for pkg in flask django fastapi; do
  curl -s "https://pypi.org/pypi/${pkg}/json" \
    | jq --arg p "$pkg" '{name: $p, version: .info.version, python: .info.requires_python, license: .info.license}'
done | jq -s '.'

Download URLs

# Latest release download URLs
curl -s https://pypi.org/pypi/requests/json \
  | jq '.urls[] | {filename, packagetype, size: .size, url: .url}'

# Wheel only
curl -s https://pypi.org/pypi/requests/json \
  | jq '.urls[] | select(.packagetype == "bdist_wheel") | {filename, size, python_requires: .requires_python}'

# Source distribution only
curl -s https://pypi.org/pypi/requests/json \
  | jq '.urls[] | select(.packagetype == "sdist") | .url'

Release History

# Release dates (latest 10 versions)
curl -s https://pypi.org/pypi/requests/json \
  | jq '[.releases | to_entries | .[] | select(.value | length > 0) | {version: .key, date: .value[0].upload_time[0:10]}] | sort_by(.date) | .[-10:]'

Dependency Tree (One Level Deep)

# Get Flask's deps, then look up each one
curl -s https://pypi.org/pypi/flask/json \
  | jq -r '.info.requires_dist[] | select(contains("extra") | not) | split(" ")[0]' \
  | xargs -I{} sh -c 'curl -s "https://pypi.org/pypi/{}/json" | jq "{name: .info.name, version: .info.version}"'

Security: Check Package Hashes

# SHA256 digests for verification
curl -s https://pypi.org/pypi/requests/json \
  | jq '.urls[] | {filename, sha256: .digests.sha256}'

Bulk Package Info

# Audit your requirements.txt
cat requirements.txt | grep -v '^#' | awk -F'[=<>]' '{print $1}' | while read pkg; do
  curl -s "https://pypi.org/pypi/${pkg}/json" 2>/dev/null \
    | jq -r --arg p "$pkg" '"\($p): \(.info.version // "not found")"'
done

# Export package inventory to CSV
for pkg in requests flask django celery redis pydantic; do
  curl -s "https://pypi.org/pypi/${pkg}/json" \
    | jq -r '[.info.name, .info.version, .info.license, .info.requires_python // "any"] | @csv'
done