{"id":15564375,"url":"https://github.com/gaborbernat/pypi-changes","last_synced_at":"2026-03-12T05:31:47.248Z","repository":{"id":37956064,"uuid":"424194185","full_name":"gaborbernat/pypi-changes","owner":"gaborbernat","description":"Check what PyPI dependencies changed and when.","archived":false,"fork":false,"pushed_at":"2026-02-26T11:58:05.000Z","size":438,"stargazers_count":30,"open_issues_count":4,"forks_count":5,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-26T17:49:17.237Z","etag":null,"topics":["changes","pypi"],"latest_commit_sha":null,"homepage":"https://pypi-changes.readthedocs.io/en/latest/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gaborbernat.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yaml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"tidelift":"pypi/pypi-changes"}},"created_at":"2021-11-03T11:19:51.000Z","updated_at":"2026-02-26T11:58:08.000Z","dependencies_parsed_at":"2024-06-03T20:04:47.039Z","dependency_job_id":"b415e290-1e51-4e22-b791-7ce30550ac2a","html_url":"https://github.com/gaborbernat/pypi-changes","commit_stats":{"total_commits":139,"total_committers":6,"mean_commits":"23.166666666666668","dds":0.5035971223021583,"last_synced_commit":"a49791d55e8fc11ba6139628ef68530fb1acf59c"},"previous_names":["gaborbernat/pypi_changes"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/gaborbernat/pypi-changes","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gaborbernat%2Fpypi-changes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gaborbernat%2Fpypi-changes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gaborbernat%2Fpypi-changes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gaborbernat%2Fpypi-changes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gaborbernat","download_url":"https://codeload.github.com/gaborbernat/pypi-changes/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gaborbernat%2Fpypi-changes/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30416310,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-12T04:41:02.746Z","status":"ssl_error","status_checked_at":"2026-03-12T04:40:12.571Z","response_time":114,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["changes","pypi"],"created_at":"2024-10-02T16:40:41.916Z","updated_at":"2026-03-12T05:31:47.244Z","avatar_url":"https://github.com/gaborbernat.png","language":"Python","funding_links":["https://tidelift.com/funding/github/pypi/pypi-changes"],"categories":[],"sub_categories":[],"readme":"# pypi-changes\n\n[![PyPI](https://img.shields.io/pypi/v/pypi-changes?style=flat-square)](https://pypi.org/project/pypi-changes/)\n[![Supported Python versions](https://img.shields.io/pypi/pyversions/pypi-changes.svg)](https://pypi.org/project/pypi-changes/)\n[![check](https://github.com/gaborbernat/pypi_changes/actions/workflows/check.yaml/badge.svg)](https://github.com/gaborbernat/pypi_changes/actions/workflows/check.yaml)\n[![Downloads](https://static.pepy.tech/badge/pypi-changes/month)](https://pepy.tech/project/pypi-changes)\n\nA CLI tool that inspects a Python interpreter's installed packages and compares them against the latest releases on\nPyPI. It shows which packages are outdated, how long ago each release was published, and highlights major version bumps\nso you can make informed upgrade decisions.\n\n[![asciicast](https://asciinema.org/a/446966.svg)](https://asciinema.org/a/446966)\n\n## Getting started\n\nInstall from PyPI:\n\n```bash\npip install pypi-changes\n```\n\nRun against the current Python interpreter:\n\n```bash\npypi-changes\n```\n\nOr point it at a specific interpreter (useful for checking virtual environments):\n\n```bash\npypi-changes /path/to/venv/bin/python\n```\n\n## How-to guides\n\n### Check for outdated packages\n\nRun `pypi-changes` without arguments to see a tree of all installed packages with their version status. Outdated\npackages appear in red, with major version bumps in bold red:\n\n```console\n$ pypi-changes\n🐍 Distributions within /usr/bin/python\n├── virtualenv 20.38.0 10 days remote 21.1.0 2 days\n├── certifi 2026.1.4 2 months remote 2026.2.25 4 days\n├── rich 14.3.3 9 days\n├── packaging 26.0 a month\n├── urllib3 1.26.20 1 year, 6 months remote 2.6.3 2 months\n├── requests 2.32.5 6 months\n├── pluggy 1.6.0 10 months\n└── covdefaults 2.3.0 2 years\n```\n\n### Generate a requirements file for upgrades\n\nUse the `requirements` output format to produce a `requirements.txt`-compatible list of outdated packages pinned to\ntheir latest versions:\n\n```console\n$ pypi-changes --output requirements\nvirtualenv==21.1.0\ncertifi==2026.2.25\nurllib3==2.6.3\nwheel-filename==2.1.0\n```\n\nPipe directly into `pip install` to upgrade:\n\n```bash\npypi-changes --output requirements \u003e upgrades.txt\npip install -r upgrades.txt\n```\n\n### Get machine-readable output\n\nUse JSON output for scripting or integration with other tools:\n\n```console\n$ pypi-changes --output json\n[\n  {\n    \"name\": \"virtualenv\",\n    \"version\": \"20.38.0\",\n    \"up_to_date\": false,\n    \"current\": {\n      \"version\": \"20.38.0\",\n      \"date\": \"2026-02-19T07:47:59.778389+00:00\",\n      \"since\": \"10 days\"\n    },\n    \"latest\": {\n      \"version\": \"21.1.0\",\n      \"date\": \"2026-02-27T08:49:27.516392+00:00\",\n      \"since\": \"2 days\"\n    }\n  },\n  {\n    \"name\": \"rich\",\n    \"version\": \"14.3.3\",\n    \"up_to_date\": true,\n    \"current\": {\n      \"version\": \"14.3.3\",\n      \"date\": \"2026-02-19T17:23:13.732467+00:00\",\n      \"since\": \"9 days\"\n    },\n    \"latest\": {\n      \"version\": \"14.3.3\",\n      \"date\": \"2026-02-19T17:23:13.732467+00:00\",\n      \"since\": \"9 days\"\n    }\n  }\n]\n```\n\nEach entry includes `name`, `version`, `up_to_date`, and detailed `current`/`latest` release information with dates and\nhuman-readable time deltas.\n\n### Sort packages alphabetically\n\nBy default, packages are sorted by release date (most recently updated first). To sort alphabetically:\n\n```bash\npypi-changes --sort alphabetic\n```\n\n### Speed up repeated runs with caching\n\nRequests to PyPI are cached in a local SQLite database for 1 hour by default. To adjust the cache duration:\n\n```bash\npypi-changes --cache-duration 7200  # cache for 2 hours\npypi-changes --cache-duration 0     # bypass cache entirely\npypi-changes --cache-duration -1    # cache forever\n```\n\nTo change the cache file location:\n\n```bash\npypi-changes --cache-path /tmp/pypi-cache.sqlite\n```\n\n### Use with a private package index\n\nSet the `PIP_INDEX_URL` environment variable to merge releases from a private index server (e.g. Artifactory) with PyPI\ndata:\n\n```bash\nPIP_INDEX_URL=https://my-artifactory.example.com/simple pypi-changes\n```\n\n### Control request parallelism\n\nPyPI release information is fetched in parallel. Adjust the number of concurrent requests with `--jobs`:\n\n```bash\npypi-changes --jobs 20  # increase parallelism\npypi-changes --jobs 1   # sequential requests\n```\n\n## Reference\n\n### Usage\n\n```\npypi-changes [-h] [--jobs COUNT] [--cache-path PATH] [--cache-duration SEC]\n             [--sort [{a,alphabetic,u,updated}]] [--output {tree,json,requirements}]\n             [PYTHON_EXE]\n```\n\n### Positional arguments\n\n| Argument     | Description                                                           |\n| ------------ | --------------------------------------------------------------------- |\n| `PYTHON_EXE` | Python interpreter to inspect. Defaults to `python` found on `$PATH`. |\n\n### Options\n\n| Flag                     | Default       | Description                                                                          |\n| ------------------------ | ------------- | ------------------------------------------------------------------------------------ |\n| `--jobs`, `-j`           | `10`          | Maximum number of parallel requests when loading distribution information from PyPI. |\n| `--cache-path`, `-c`     | platform path | Path to the SQLite file used for caching HTTP requests.                              |\n| `--cache-duration`, `-d` | `3600`        | Seconds to cache requests. `0` bypasses the cache, `-1` caches forever.              |\n| `--sort`, `-s`           | `updated`     | Sort order: `a`/`alphabetic` or `u`/`updated` (most recent first).                   |\n| `--output`, `-o`         | `tree`        | Output format: `tree`, `json`, or `requirements`.                                    |\n\n### Output formats\n\n**`tree`** (default) -- a Rich-rendered tree showing each package with its installed version, time since release, and\nremote version if outdated. Major version bumps appear in bold red; minor/patch bumps in red.\n\n**`json`** -- a JSON array where each element contains:\n\n- `name` -- package name\n- `version` -- installed version\n- `up_to_date` -- boolean\n- `current` -- object with `version`, `date` (ISO 8601), and `since` (human-readable delta)\n- `latest` -- object with the same fields for the newest stable release\n\n**`requirements`** -- prints only outdated packages in `name==version` format, suitable for piping into\n`pip install -r`.\n\n### Environment variables\n\n| Variable        | Description                                                                                |\n| --------------- | ------------------------------------------------------------------------------------------ |\n| `PIP_INDEX_URL` | When set to a non-PyPI URL, merges releases from that index server with PyPI release data. |\n\n## How it works\n\n`pypi-changes` inspects the target Python interpreter's `sys.path` to discover all installed distributions (packages\nwith `.dist-info` or `.egg-info` directories). It then fetches each package's release history from the\n[PyPI JSON API](https://warehouse.pypa.io/api-reference/json/) in parallel, using a thread pool controlled by `--jobs`.\n\nReleases are sorted by semantic version. The latest stable release (excluding dev and pre-releases) is selected for\ncomparison against the installed version. When a release has no uploaded artifacts (common for some yanked or\nplaceholder versions), a synthesized timestamp is generated internally for sorting purposes but is not displayed to the\nuser.\n\nHTTP responses are cached in a local SQLite database via\n[requests-cache](https://requests-cache.readthedocs.io/en/stable/) to avoid redundant network calls on repeated runs.\nExpired entries are cleaned up automatically on each invocation.\n\nWhen `PIP_INDEX_URL` is set to a non-PyPI URL, `pypi-changes` also queries that index via the\n[Simple Repository API](https://packaging.python.org/en/latest/specifications/simple-repository-api/) and merges any\nversions not found on PyPI into the release list. This is useful for organizations hosting internal packages on private\nregistries.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgaborbernat%2Fpypi-changes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgaborbernat%2Fpypi-changes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgaborbernat%2Fpypi-changes/lists"}