Library / SDK
alanhamlett/pip-update-requirements avatar
alanhamlett/pip-update-requirements

pur: rewriting the pins in a requirements.txt file

Update the packages in a requirements.txt file.

646 stars25 forksPythonNOASSERTION

At a glance

What is it?
pur reads a requirements file, asks the package index what the latest versions are, and rewrites the file with new pins, leaving your environment untouched. The interesting parts are the flags that restrict what may move, especially the cooldown window and the minor-only and patch-only modes.
Who is it for?
pur fits a team that pins every dependency and wants the pins refreshed deliberately, with a dry run for review and a cooldown window to avoid pulling in a release that landed yesterday. It is the wrong tool if you want pip to install the upgrades, because pur only edits the file, and the wrong tool for a project that tracks dependencies loosely.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 180 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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

It edits a file, it does not touch your environment

The documentation states the scope in one sentence: pur never modifies your environment or installed packages, it only modifies your requirements.txt file. That single constraint is the design.

Installation is a normal pip install of the package, which is published on PyPI under the short name `pur`:

code
    pip install pur

The tool is invoked with the requirements file you want updated, and it prints what it changed:

code
    $ pur -r requirements.txt
    Updated flask: 0.9 -> 1.0.2
    Updated sqlalchemy: 0.9.10 -> 1.2.8
    Updated alembic: 0.8.4 -> 0.9.9
    All requirements up-to-date.

The trailing line is worth noting because it shows the tool reports on packages it did not change as well as those it did. The versions in the example are from the era the documentation was written, but the shape of the output, one line per package with the old and new version, is what you would script against.

The audience is anyone with a pinned requirements file, which in practice means an application rather than a library, since a library that pins its dependencies in a requirements file is doing something unusual.

The same function is importable from Python

pur is not only a console script. The documentation shows it being driven from a Python session, which matters if you want to call it from a script or a build step rather than from a shell:

code
    >>> from pur import update_requirements
    >>> print([x[0]['message'] for x in update_requirements(input_file='requirements.txt').values()])
    ['Updated flask: 0.9 -> 1.0.2', 'Updated sqlalchemy: 0.9.10 -> 1.2.8', 'Updated alembic: 0.8.4 -> 0.9.9']

The return value is a mapping whose values are lists, and each entry carries a `message` field with the human-readable line. The file on disk is then just the updated pins:

code
    flask==1.0.2
    sqlalchemy==1.2.8
    alembic==0.9.9

So the messages are structured data rather than printed text, which is what makes the tool usable from automation. The console script is registered as an entry point named `pur`, mapping to the function in the package, so the command line and the library route are the same code.

What the API does not do is decide anything. It reports and rewrites; deciding whether a major version bump is acceptable for your project is the job of whoever reads the diff afterwards.

The flags that decide how far versions may move

The option list is where the real behaviour lives, because a tool that rewrites pins is only as safe as its restrictions. The documented option set includes a set of flags that narrow what may change:

code
    --interactive            Interactively prompts before updating each package.
    -d, --dry-run            Output changes to STDOUT instead of overwriting the
                             requirements.txt file.
    --dry-run-changed        Enable dry run and only output packages with
                             updates, not packages that are already the latest.
    -n, --no-recursive       Prevents updating nested requirements files.
    --skip TEXT              Comma separated list of packages to skip updating.
    --skip-gt                Skip updating packages using > or >= spec, to allow
                             specifying minimum supported versions of packages.

The version-limiting flags are the pair worth understanding. `--minor` takes a comma separated list of packages to update to minor versions only, never major, and accepts `*` to limit every package to minor updates. `--patch` does the same one level tighter, updating to patch versions only, never major or minor, again accepting `*` for all packages. `--pre` allows pre-release versions for the named packages, with `*` meaning all of them, and by default packages are only updated to stable versions.

Two more are easy to miss. `--only` restricts the run to a named list of packages, the mirror image of `--skip`. And `--cooldown-days` sets a minimum number of days since a release before a new version is considered, with the documentation explaining the intent: versions released more recently than that threshold are ignored, helping avoid recently published versions that may contain critical bugs.

That last flag is the one that changes the character of the tool from a convenience into a safeguard.

Exit code 1 when something changed, for CI use

One flag turns the tool into something a pipeline can act on. `-z, --nonzero-exit-code` exits with status 1 when some packages were updated, 0 when no packages were updated, or a number greater than 1 when there was an error. By default the exit status is 0 unless there was an error, regardless of whether packages were updated or not.

That default matters. A pipeline that runs pur and checks the exit code sees nothing on a normal run, so a scheduled job that is meant to surface dependency drift needs `-z` to notice that anything moved. With it, the same run tells you both what changed and that it changed, and the error case stays distinct because it produces a value above one.

The connection options are present too, for environments where the default index is not reachable. `--index-url` sets the base URL of the Python Package Index and can be provided multiple times for extra index urls, `--cert` points at a PEM-encoded CA certificate bundle and overrides the default when given, and `--no-ssl-verify` disables verification of the server's TLS certificate. That last flag is a blunt instrument for a machine with a private index, and it is the kind of switch that belongs in a locked-down environment rather than a laptop.

The `--output` option writes the updated packages to a different file instead of overwriting the input, and `--force` updates packages that had no version specified in the input file, which is the case that most often needs a deliberate decision.

The project keeps its own dependencies fresh with itself

The most convincing endorsement of a tool is a project that uses it for its own housekeeping, and the Makefile in this repository does exactly that. The help text in the Makefile describes three targets, test to run the unit tests with the current default python, upgrade to run pip to check for dependency updates, and release to publish the current version to pypi.

The upgrade target is the interesting one:

code
upgrade:
	@pur --skip click -r dev-requirements.txt

So the project refreshes its own development requirements with pur, while deliberately skipping click, which is the one runtime dependency listed in requirements.txt. The Makefile also carries a test target running pytest with coverage over the pur package, and a release chain of clean, build and upload, where build runs `python ./setup.py sdist` and upload runs `twine upload ./dist/*`.

The contrast between the two update paths in that Makefile is the clearest statement of what pur is for. `pip` checks for dependency updates, which tells you what is out of date, and pur rewrites the file, which is the step that changes your pins. The Makefile has both, because they are different jobs, and using pur for the second one is what keeps the file consistent.

Custom licence wording, and a long list of supported versions

The repository is listed with a licence that GitHub cannot classify, described as a custom licence, and the LICENSE file at the root is the authoritative text. That is a point to check before the tool touches a file in a commercial project, and it is separate from the packaging metadata.

The packaging metadata says something different. The setup.py declares the package from its own `pur/__about__.py` file, takes its install requirements straight from the contents of requirements.txt, and registers the console script entry point that maps the command `pur` to `pur.__init__:pur`. Its classifiers list the licence as OSI Approved BSD License, and the supported interpreters run from Python 3.7 through 3.13, with the development status marked as Production/Stable. The single runtime dependency in requirements.txt is click at 8.0.0 or newer, and the build backend is plain setuptools with no extra build requirements.

Two independent licence signals, one generic and one specific, is a small inconsistency worth raising with the maintainer if it matters to you, and it is a reminder to read the LICENSE file rather than the classifier.

On maintenance, the repository is not archived and the last push was on 2026-04-26, which is also the date of release 7.4.0. The previous releases are 7.3.3 from 2024-12-12 and 7.3.2 from 2024-06-04, so the gap between 7.3.3 and 7.4.0 is long. There is a HISTORY.rst file at the root, which is where the changelog lives, alongside a NOTICE file and an AUTHORS file.

Editorial conclusion

pur fits a team that pins every dependency and wants the pins refreshed deliberately, with a dry run for review and a cooldown window to avoid pulling in a release that landed yesterday. It is the wrong tool if you want pip to install the upgrades, because pur only edits the file, and the wrong tool for a project that tracks dependencies loosely. Verify first that your index is reachable and that the packages you want held back are listed for --skip, then read the rewritten diff before you install anything.

Frequently asked questions

What does pur do to my requirements.txt file?

pur updates the packages in a requirements.txt file to their latest versions and rewrites the file. It is explicit in the documentation that pur never modifies your environment or installed packages, it only modifies your requirements.txt file.

How do I install and run pur?

Install it with pip install pur, then run pur -r requirements.txt. The command prints one line per package that changed, showing the old and new version, and finishes with a line stating that all requirements are up-to-date.

Can I use pur without overwriting my requirements file?

Yes. The -d, --dry-run flag outputs changes to STDOUT instead of overwriting the file, and --dry-run-changed enables dry run while only showing packages that have updates. The -o, --output option writes the result to a separate file.

How do I limit pur to minor or patch upgrades?

Use --minor with a comma separated list of packages to update to minor versions only and never major, or --patch to update to patch versions only. Both accept * as a value to limit every package in the file.

How can I use pur in a CI job?

The -z, --nonzero-exit-code flag exits with status 1 when some packages were updated, 0 when none were, and a number greater than 1 on error. Without it the exit status is 0 unless there was an error, so a scheduled job needs this flag to notice drift.

Official sources

  1. alanhamlett/pip-update-requirements on GitHub
  2. Issues
  3. Project website
  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/alanhamlett-pip-update-requirements.svg)](https://hysenlabs.com/projects/alanhamlett-pip-update-requirements)

Was this useful?