python-update-checker 0.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_update_checker-0.5/.gitignore +25 -0
- python_update_checker-0.5/.ruff.toml +47 -0
- python_update_checker-0.5/Makefile +188 -0
- python_update_checker-0.5/PKG-INFO +122 -0
- python_update_checker-0.5/README.md +104 -0
- python_update_checker-0.5/pyproject.toml +59 -0
- python_update_checker-0.5/scripts/install_dev.sh +113 -0
- python_update_checker-0.5/src/puc/__init__.py +3 -0
- python_update_checker-0.5/src/puc/cli.py +214 -0
- python_update_checker-0.5/src/puc/dependencies.py +187 -0
- python_update_checker-0.5/src/puc/logging.py +76 -0
- python_update_checker-0.5/src/puc/pyprojecttoml.py +179 -0
- python_update_checker-0.5/src/puc/requirementstxt.py +116 -0
- python_update_checker-0.5/src/puc/uvlock.py +133 -0
- python_update_checker-0.5/tests/__init__.py +63 -0
- python_update_checker-0.5/tests/data/other-requirements.txt +5 -0
- python_update_checker-0.5/tests/data/pyproject.toml +45 -0
- python_update_checker-0.5/tests/data/pyproject_compatible.toml +8 -0
- python_update_checker-0.5/tests/data/requirements.txt +31 -0
- python_update_checker-0.5/tests/data/uv.lock +52 -0
- python_update_checker-0.5/tests/test_latest.py +33 -0
- python_update_checker-0.5/tests/test_pyproject_toml.py +89 -0
- python_update_checker-0.5/tests/test_requirements_txt.py +91 -0
- python_update_checker-0.5/tests/test_uv_lock.py +66 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# generated python files
|
|
2
|
+
__pycache__
|
|
3
|
+
/.pytest_cache
|
|
4
|
+
/.ruff_cache
|
|
5
|
+
/uv.lock
|
|
6
|
+
/.venv
|
|
7
|
+
|
|
8
|
+
# build and dist dirs
|
|
9
|
+
/build
|
|
10
|
+
/dist
|
|
11
|
+
/.achievements
|
|
12
|
+
/MANIFEST
|
|
13
|
+
|
|
14
|
+
# configuration for direnv(1)
|
|
15
|
+
/.envrc
|
|
16
|
+
|
|
17
|
+
# local development files
|
|
18
|
+
/bin
|
|
19
|
+
/.python-version
|
|
20
|
+
|
|
21
|
+
# todo.txt files
|
|
22
|
+
/todo.txt
|
|
23
|
+
/todo.txt.bak
|
|
24
|
+
/done.txt
|
|
25
|
+
/report.txt
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
target-version = "py311"
|
|
2
|
+
|
|
3
|
+
[lint]
|
|
4
|
+
select = [
|
|
5
|
+
"E", # pycodestyle
|
|
6
|
+
"F", # pyflakes
|
|
7
|
+
"B", # flake8-bugbear
|
|
8
|
+
"I", # isort
|
|
9
|
+
"D", # pydocstyle
|
|
10
|
+
"FIX", # fixme
|
|
11
|
+
"FA", # future-annotation
|
|
12
|
+
"LOG", # logging
|
|
13
|
+
"INP", # PEP420
|
|
14
|
+
"PIE", # flake8-pie
|
|
15
|
+
"RSE", # flake8-raise
|
|
16
|
+
"RET", # flake8-return
|
|
17
|
+
"PLC", # pylint convention
|
|
18
|
+
"PLE", # pylint error
|
|
19
|
+
"PLW", # pylint warning
|
|
20
|
+
"RUF", # ruff
|
|
21
|
+
"UP", # pyupgrade
|
|
22
|
+
"YTT", # flake8-2020
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
# Ignored rules
|
|
26
|
+
ignore = [
|
|
27
|
+
"E501", # line-too-long
|
|
28
|
+
"I001", # unsorted-imports
|
|
29
|
+
"D205", # blank-line-after-summary
|
|
30
|
+
"D402", # no-signature
|
|
31
|
+
"D415", # ends-in-punctuation
|
|
32
|
+
"D417", # undocumented-param
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
[lint.per-file-ignores]
|
|
37
|
+
|
|
38
|
+
[lint.isort]
|
|
39
|
+
known-first-party = []
|
|
40
|
+
section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]
|
|
41
|
+
force-sort-within-sections = false
|
|
42
|
+
|
|
43
|
+
[lint.pydocstyle]
|
|
44
|
+
convention = "google"
|
|
45
|
+
|
|
46
|
+
[format]
|
|
47
|
+
quote-style = "preserve"
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# This Makefile is only used by developers.
|
|
2
|
+
|
|
3
|
+
############# Settings ############
|
|
4
|
+
# use Bash as shell, not sh
|
|
5
|
+
SHELL := bash
|
|
6
|
+
# execute makefile in a single bash process instead of one per target
|
|
7
|
+
.ONESHELL:
|
|
8
|
+
# set Bash flags
|
|
9
|
+
.SHELLFLAGS := -eu -o pipefail -c
|
|
10
|
+
# remove target files if a rule fails, forces reruns of aborted rules
|
|
11
|
+
.DELETE_ON_ERROR:
|
|
12
|
+
# warn for undefined variables
|
|
13
|
+
MAKEFLAGS += --warn-undefined-variables
|
|
14
|
+
# disable builtin default rules
|
|
15
|
+
MAKEFLAGS += --no-builtin-rules
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
############ Configuration ############
|
|
19
|
+
PROJECT:=$(shell egrep "name[[:space:]]*=" pyproject.toml | cut -d'"' -f2)
|
|
20
|
+
VERSION:=$(shell egrep "version[[:space:]]*=" pyproject.toml | cut -d'"' -f2)
|
|
21
|
+
|
|
22
|
+
# exclude packages that are newer than this
|
|
23
|
+
EXCLUDE_NEWER:="7 days"
|
|
24
|
+
# Pytest options:
|
|
25
|
+
# --full-trace: print full stacktrace on errors
|
|
26
|
+
PYTESTOPTS?=--full-trace
|
|
27
|
+
# which test modules to run
|
|
28
|
+
TESTS ?= tests/
|
|
29
|
+
# set test options
|
|
30
|
+
TESTOPTS=
|
|
31
|
+
# python files and directories
|
|
32
|
+
PY_FILES_DIRS:=\
|
|
33
|
+
src \
|
|
34
|
+
tests
|
|
35
|
+
|
|
36
|
+
# Release configuration
|
|
37
|
+
RELEASE_NAME:=$(subst -,_,$(PROJECT))-$(VERSION)
|
|
38
|
+
RELEASE_SOURCE:=$(RELEASE_NAME).tar.gz
|
|
39
|
+
RELEASE_WHEEL:=$(RELEASE_NAME)-py3-none-any.whl
|
|
40
|
+
RELEASE_BRANCH:=main
|
|
41
|
+
RELEASE_TAG:=$(VERSION)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
############ Default target ############
|
|
45
|
+
|
|
46
|
+
# `make help` displays all targets documented with `##`in the target line
|
|
47
|
+
.PHONY: help
|
|
48
|
+
help: ## display this help section
|
|
49
|
+
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {printf "\033[36m%-38s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
|
50
|
+
.DEFAULT_GOAL := help
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
############ Installation and provisioning ############
|
|
54
|
+
|
|
55
|
+
.PHONY: init
|
|
56
|
+
init: ## install python virtual env and required development packages
|
|
57
|
+
uv sync
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
############ Linting and syntax checks ############
|
|
61
|
+
|
|
62
|
+
.PHONY: lint
|
|
63
|
+
lint: lint-py lint-shell
|
|
64
|
+
|
|
65
|
+
.PHONY: lint-py
|
|
66
|
+
lint-py: ## lint python code
|
|
67
|
+
ruff check $(PY_FILES_DIRS)
|
|
68
|
+
|
|
69
|
+
.PHONY: lint-shell
|
|
70
|
+
lint-shell:
|
|
71
|
+
shellcheck -x scripts/*.sh
|
|
72
|
+
|
|
73
|
+
.PHONY: audit
|
|
74
|
+
audit: ## run audit checks
|
|
75
|
+
uv audit
|
|
76
|
+
|
|
77
|
+
.PHONY: reformat
|
|
78
|
+
reformat: ## format the python code
|
|
79
|
+
ruff check --fix $(PY_FILES_DIRS)
|
|
80
|
+
ruff format $(PY_FILES_DIRS)
|
|
81
|
+
|
|
82
|
+
.PHONY: checkoutdated checkoutdated-py checkoutdated-gh
|
|
83
|
+
checkoutdated: checkoutdated-py checkoutdated-gh
|
|
84
|
+
|
|
85
|
+
checkoutdated-py: ## Check for outdated package requirements
|
|
86
|
+
uv run puc --exclude-newer=$(EXCLUDE_NEWER) check pyproject.toml
|
|
87
|
+
|
|
88
|
+
checkoutdated-gh: ## check for outdated github projects
|
|
89
|
+
# github-check-outdated is a local tool which compares a given version with the latest available github release version
|
|
90
|
+
# see https://gist.github.com/wummel/ef14989766009effa4e262b01096fc8c for an example implementation
|
|
91
|
+
@echo "Check for outdated Github tools"
|
|
92
|
+
github-check-outdated astral-sh uv "$(shell uv --version | cut -f2 -d" ")"
|
|
93
|
+
github-check-outdated python cpython v$(shell python --version | cut -f2 -d" ") '^v3\.14\.[0-9]+$$'
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
.PHONY: upgradeoutdated
|
|
97
|
+
upgradeoutdated: upgradeoutdated-gh upgradeoutdated-py
|
|
98
|
+
|
|
99
|
+
.PHONY: upgradeoutdated-gh
|
|
100
|
+
upgradeoutdated-gh:
|
|
101
|
+
sed -i -e 's/uv_version_dev = ".*"/uv_version_dev = "$(shell github-check-outdated astral-sh uv 0 | cut -f4 -d" ")"/' pyproject.toml
|
|
102
|
+
sed -i -e 's/python_version_dev = ".*"/python_version_dev = "$(shell github-check-outdated python cpython 0 '^v3\.14\.[0-9]+$$' | cut -f4 -d" " | cut -b2-)"/' pyproject.toml
|
|
103
|
+
scripts/install_dev.sh
|
|
104
|
+
|
|
105
|
+
.PHONY: upgradeoutdated-py
|
|
106
|
+
upgradeoutdated-py: ## upgrade dependencies in pyproject.toml and uv.lock
|
|
107
|
+
# upgrade pyproject.toml dependencies
|
|
108
|
+
uv run puc --exclude-newer=$(EXCLUDE_NEWER) update pyproject.toml
|
|
109
|
+
# upgrade depencencies in uv lock file
|
|
110
|
+
uv lock --exclude-newer=$(EXCLUDE_NEWER) --upgrade
|
|
111
|
+
# install upgraded package versions in virtual environment
|
|
112
|
+
$(MAKE) init
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
############ Testing ############
|
|
116
|
+
|
|
117
|
+
.PHONY: test
|
|
118
|
+
test: ## run tests
|
|
119
|
+
uv run --isolated -- pytest $(PYTESTOPTS) $(TESTOPTS) $(TESTS)
|
|
120
|
+
|
|
121
|
+
.PHONY: typecheck
|
|
122
|
+
typecheck: ## run the ty type checker
|
|
123
|
+
ty check $(PY_FILES_DIRS)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
############ Release ############
|
|
127
|
+
|
|
128
|
+
.PHONY: distclean
|
|
129
|
+
distclean:
|
|
130
|
+
rm -rf dist $(shell find src tests -type d -name __pycache__)
|
|
131
|
+
|
|
132
|
+
.PHONY: dist
|
|
133
|
+
dist:
|
|
134
|
+
uv build
|
|
135
|
+
|
|
136
|
+
# Releases are tagged with the version, ie. "0.5"
|
|
137
|
+
.PHONY: release
|
|
138
|
+
release: distclean checkrelease ## create release
|
|
139
|
+
$(MAKE) dist release-gh release-pypi
|
|
140
|
+
|
|
141
|
+
# export GITHUB_TOKEN for the gh command
|
|
142
|
+
# Generate a fine grained access token with:
|
|
143
|
+
# - Restricted to this repository
|
|
144
|
+
# - Repository permission: Metadata -> Read (displayed as "Read access to metadata" in token view)
|
|
145
|
+
# - Repository permission: Contents -> Read and write (displayed as "Read and Write access to code" in token view)
|
|
146
|
+
# - Expiration in 90 days
|
|
147
|
+
# After that run "export GITHUB_TOKEN=<token-content>"
|
|
148
|
+
.PHONY: release-gh
|
|
149
|
+
release-gh: ## upload a new release to github
|
|
150
|
+
gh release create \
|
|
151
|
+
--title "Release $(RELEASE_TAG)" \
|
|
152
|
+
--notes-from-tag \
|
|
153
|
+
--latest \
|
|
154
|
+
--draft=false \
|
|
155
|
+
--prerelease=false \
|
|
156
|
+
--verify-tag \
|
|
157
|
+
"$(RELEASE_TAG)" \
|
|
158
|
+
dist/$(RELEASE_SOURCE)
|
|
159
|
+
|
|
160
|
+
.PHONY: release-pypi
|
|
161
|
+
release-pypi: ## upload a new release to pypi
|
|
162
|
+
uv publish dist/$(RELEASE_SOURCE) dist/$(RELEASE_WHEEL)
|
|
163
|
+
|
|
164
|
+
.PHONY: checkrelease
|
|
165
|
+
checkrelease: lint test typecheck checkgit ## check release conditions
|
|
166
|
+
|
|
167
|
+
.PHONY: checkgit
|
|
168
|
+
checkgit: ## various git release checks
|
|
169
|
+
# check that the current branch is the release branch
|
|
170
|
+
@if [ "$(shell git rev-parse --abbrev-ref HEAD)" != "$(RELEASE_BRANCH)" ]; then \
|
|
171
|
+
echo "ERROR: current branch is not '$(RELEASE_BRANCH)', but '$(shell git rev-parse --abbrev-ref HEAD;)'"; \
|
|
172
|
+
false; \
|
|
173
|
+
fi
|
|
174
|
+
# check for uncommitted versions
|
|
175
|
+
@if [ -n "$(shell git status --porcelain --untracked-files=all)" ]; then \
|
|
176
|
+
echo "ERROR: uncommitted changes"; \
|
|
177
|
+
git status --porcelain --untracked-files=all; \
|
|
178
|
+
false; \
|
|
179
|
+
fi
|
|
180
|
+
# check that release tag exists
|
|
181
|
+
@if [ -z "$(shell git tag -l -- $(RELEASE_TAG))" ]; then \
|
|
182
|
+
echo "ERROR: git tag \"$(RELEASE_TAG)\" does not exist, execute 'git tag -a $(RELEASE_TAG) -m \"$(RELEASE_TAG)\"'"; \
|
|
183
|
+
false; \
|
|
184
|
+
fi
|
|
185
|
+
# check that release tags is pushed to remote
|
|
186
|
+
@if ! git ls-remote --exit-code --tags origin $(RELEASE_TAG); then \
|
|
187
|
+
echo "ERROR: git tag \"$(RELEASE_TAG)\" does not exist on remote repo, execute 'git push --tags'"; \
|
|
188
|
+
fi
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-update-checker
|
|
3
|
+
Version: 0.5
|
|
4
|
+
License-Expression: GPL-3.0-only
|
|
5
|
+
Classifier: Environment :: Console
|
|
6
|
+
Classifier: Intended Audience :: Developers
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
15
|
+
Requires-Dist: packaging>=26.1
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
python-update-checker
|
|
20
|
+
======================
|
|
21
|
+
Python-update-checker (puc) checks or updates pinned dependencies
|
|
22
|
+
in `pyproject.toml`, `requirements.txt` or `uv.lock` files.
|
|
23
|
+
|
|
24
|
+
See https://github.com/astral-sh/uv/issues/6794 for a discussion
|
|
25
|
+
about different pinning strategies.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
Features
|
|
29
|
+
---------
|
|
30
|
+
|
|
31
|
+
* updates pinned dependencies, ignores unpinned dependencies
|
|
32
|
+
* supports pyproject.toml, uv.lock and requirements.txt formats
|
|
33
|
+
* supports `[project.dependencies]`, `[project.optional-dependencies]` and `[dependency-groups]` in pyproject.toml
|
|
34
|
+
* supports recursive references (-r) in requirements.txt formats
|
|
35
|
+
* can run in check only mode, ie. it checks if updates are available
|
|
36
|
+
* limit updates to specific packages
|
|
37
|
+
* limit updates with package version constraints (ie. "django<6")
|
|
38
|
+
* limit updates to versions that were uploaded prior to a given date
|
|
39
|
+
* (limited) support for environment markers, ie. `"pywin32==311; os_name=='nt'"`
|
|
40
|
+
* runs on Linux, MacOS and Windows platforms
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
Examples
|
|
44
|
+
---------
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
$ # check all pinned packages for updates in pyproject.toml
|
|
48
|
+
$ puc check pyproject.toml
|
|
49
|
+
puc INFO: check pyproject file pyproject.toml
|
|
50
|
+
puc WARNING: found update 'ty==0.0.29' --> 0.0.32
|
|
51
|
+
|
|
52
|
+
$ # update all pinned packages in pyproject.toml
|
|
53
|
+
$ # limit updates to versions that are at least 7 days old
|
|
54
|
+
$ puc --exclude-newer="7 days" update pyproject.toml
|
|
55
|
+
puc INFO: update pyproject file pyproject.toml
|
|
56
|
+
puc INFO: updating 'ty==0.0.29' --> 0.0.31
|
|
57
|
+
puc INFO: Wrote 1 updated package version(s) to pyproject.toml
|
|
58
|
+
|
|
59
|
+
$ # check all pinned packages for updates in requirements.txt
|
|
60
|
+
$ puc check requirements.txt
|
|
61
|
+
puc INFO: check requirements file requirements.txt
|
|
62
|
+
puc WARNING: found update 'argcomplete==3.6.1' --> 3.6.3
|
|
63
|
+
puc WARNING: found update 'Django==5.2.0' --> 6.0.4
|
|
64
|
+
|
|
65
|
+
$ # update only the django package version in requirements.txt
|
|
66
|
+
$ # limit updates to django versions less than 6
|
|
67
|
+
$ puc --package="Django" --constraints="Django<6" update requirements.txt
|
|
68
|
+
INFO: update requirements file requirements.txt
|
|
69
|
+
INFO: updating 'Django==5.2.0' --> 5.2.13
|
|
70
|
+
INFO: Wrote 1 updated package version(s) to requirements.txt
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Script behaviour
|
|
74
|
+
-----------------
|
|
75
|
+
|
|
76
|
+
The exit code of `puc check` is non-zero when updates are available.
|
|
77
|
+
|
|
78
|
+
Checking a `pyproject.toml` or `uv.lock`file with `puc` should be done
|
|
79
|
+
from the project of the `pyproject.toml` file,
|
|
80
|
+
especially if your project relies on a [project directory](https://docs.astral.sh/uv/concepts/projects/layout/) (for example to define additional packages index in pyproject.toml).
|
|
81
|
+
|
|
82
|
+
After updating versions in pyproject.toml, run `uv lock --upgrade` to update
|
|
83
|
+
the transitive dependencies in `uv.lock`.
|
|
84
|
+
|
|
85
|
+
Pinned dependencies are packages with `==` or `===` constraints and no wildcards in the version.
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
Installation
|
|
89
|
+
-------------
|
|
90
|
+
|
|
91
|
+
1) Install [python uv](https://docs.astral.sh/uv/getting-started/installation/)
|
|
92
|
+
2) Install puc with `uv pip install python-update-checker`.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
Architecture
|
|
97
|
+
-------------
|
|
98
|
+
|
|
99
|
+
Dependencies are
|
|
100
|
+
|
|
101
|
+
* [uv](https://docs.astral.sh/uv/):
|
|
102
|
+
The uv binary must be available for the script to call.
|
|
103
|
+
puc uses `echo "package" | uv pip compile -` to get latest package versions.
|
|
104
|
+
puc uses `uv add "package==<version>"` to update pyproject.toml dependencies.
|
|
105
|
+
|
|
106
|
+
* [packaging](https://packaging.pypa.io/):
|
|
107
|
+
Parses dependencies with the packaging.requirements.Requirement class.
|
|
108
|
+
|
|
109
|
+
puc needs Python >= 3.11 since it uses the tomllib Python module.
|
|
110
|
+
|
|
111
|
+
puc consists of a single python script. The script uses [inline script metadata](https://peps.python.org/pep-0723/) to be executed directly with `uv run --script`.
|
|
112
|
+
This enables simple packaging and installation.
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
Limitations
|
|
116
|
+
------------
|
|
117
|
+
|
|
118
|
+
* No support for custom dependency formats in pyproject.toml
|
|
119
|
+
(eg. `[tool.poetry.dependencies]`).
|
|
120
|
+
* puc has limited support for environment markers.
|
|
121
|
+
* Constraint references (`-c`) inside requirements.txt are not supported.
|
|
122
|
+
Use the `--constraints` option instead.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
python-update-checker
|
|
2
|
+
======================
|
|
3
|
+
Python-update-checker (puc) checks or updates pinned dependencies
|
|
4
|
+
in `pyproject.toml`, `requirements.txt` or `uv.lock` files.
|
|
5
|
+
|
|
6
|
+
See https://github.com/astral-sh/uv/issues/6794 for a discussion
|
|
7
|
+
about different pinning strategies.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Features
|
|
11
|
+
---------
|
|
12
|
+
|
|
13
|
+
* updates pinned dependencies, ignores unpinned dependencies
|
|
14
|
+
* supports pyproject.toml, uv.lock and requirements.txt formats
|
|
15
|
+
* supports `[project.dependencies]`, `[project.optional-dependencies]` and `[dependency-groups]` in pyproject.toml
|
|
16
|
+
* supports recursive references (-r) in requirements.txt formats
|
|
17
|
+
* can run in check only mode, ie. it checks if updates are available
|
|
18
|
+
* limit updates to specific packages
|
|
19
|
+
* limit updates with package version constraints (ie. "django<6")
|
|
20
|
+
* limit updates to versions that were uploaded prior to a given date
|
|
21
|
+
* (limited) support for environment markers, ie. `"pywin32==311; os_name=='nt'"`
|
|
22
|
+
* runs on Linux, MacOS and Windows platforms
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
Examples
|
|
26
|
+
---------
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
$ # check all pinned packages for updates in pyproject.toml
|
|
30
|
+
$ puc check pyproject.toml
|
|
31
|
+
puc INFO: check pyproject file pyproject.toml
|
|
32
|
+
puc WARNING: found update 'ty==0.0.29' --> 0.0.32
|
|
33
|
+
|
|
34
|
+
$ # update all pinned packages in pyproject.toml
|
|
35
|
+
$ # limit updates to versions that are at least 7 days old
|
|
36
|
+
$ puc --exclude-newer="7 days" update pyproject.toml
|
|
37
|
+
puc INFO: update pyproject file pyproject.toml
|
|
38
|
+
puc INFO: updating 'ty==0.0.29' --> 0.0.31
|
|
39
|
+
puc INFO: Wrote 1 updated package version(s) to pyproject.toml
|
|
40
|
+
|
|
41
|
+
$ # check all pinned packages for updates in requirements.txt
|
|
42
|
+
$ puc check requirements.txt
|
|
43
|
+
puc INFO: check requirements file requirements.txt
|
|
44
|
+
puc WARNING: found update 'argcomplete==3.6.1' --> 3.6.3
|
|
45
|
+
puc WARNING: found update 'Django==5.2.0' --> 6.0.4
|
|
46
|
+
|
|
47
|
+
$ # update only the django package version in requirements.txt
|
|
48
|
+
$ # limit updates to django versions less than 6
|
|
49
|
+
$ puc --package="Django" --constraints="Django<6" update requirements.txt
|
|
50
|
+
INFO: update requirements file requirements.txt
|
|
51
|
+
INFO: updating 'Django==5.2.0' --> 5.2.13
|
|
52
|
+
INFO: Wrote 1 updated package version(s) to requirements.txt
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Script behaviour
|
|
56
|
+
-----------------
|
|
57
|
+
|
|
58
|
+
The exit code of `puc check` is non-zero when updates are available.
|
|
59
|
+
|
|
60
|
+
Checking a `pyproject.toml` or `uv.lock`file with `puc` should be done
|
|
61
|
+
from the project of the `pyproject.toml` file,
|
|
62
|
+
especially if your project relies on a [project directory](https://docs.astral.sh/uv/concepts/projects/layout/) (for example to define additional packages index in pyproject.toml).
|
|
63
|
+
|
|
64
|
+
After updating versions in pyproject.toml, run `uv lock --upgrade` to update
|
|
65
|
+
the transitive dependencies in `uv.lock`.
|
|
66
|
+
|
|
67
|
+
Pinned dependencies are packages with `==` or `===` constraints and no wildcards in the version.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
Installation
|
|
71
|
+
-------------
|
|
72
|
+
|
|
73
|
+
1) Install [python uv](https://docs.astral.sh/uv/getting-started/installation/)
|
|
74
|
+
2) Install puc with `uv pip install python-update-checker`.
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
Architecture
|
|
79
|
+
-------------
|
|
80
|
+
|
|
81
|
+
Dependencies are
|
|
82
|
+
|
|
83
|
+
* [uv](https://docs.astral.sh/uv/):
|
|
84
|
+
The uv binary must be available for the script to call.
|
|
85
|
+
puc uses `echo "package" | uv pip compile -` to get latest package versions.
|
|
86
|
+
puc uses `uv add "package==<version>"` to update pyproject.toml dependencies.
|
|
87
|
+
|
|
88
|
+
* [packaging](https://packaging.pypa.io/):
|
|
89
|
+
Parses dependencies with the packaging.requirements.Requirement class.
|
|
90
|
+
|
|
91
|
+
puc needs Python >= 3.11 since it uses the tomllib Python module.
|
|
92
|
+
|
|
93
|
+
puc consists of a single python script. The script uses [inline script metadata](https://peps.python.org/pep-0723/) to be executed directly with `uv run --script`.
|
|
94
|
+
This enables simple packaging and installation.
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
Limitations
|
|
98
|
+
------------
|
|
99
|
+
|
|
100
|
+
* No support for custom dependency formats in pyproject.toml
|
|
101
|
+
(eg. `[tool.poetry.dependencies]`).
|
|
102
|
+
* puc has limited support for environment markers.
|
|
103
|
+
* Constraint references (`-c`) inside requirements.txt are not supported.
|
|
104
|
+
Use the `--constraints` option instead.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "python-update-checker"
|
|
3
|
+
version = "0.5"
|
|
4
|
+
requires-python = ">=3.11"
|
|
5
|
+
license = "GPL-3.0-only"
|
|
6
|
+
license_files = [
|
|
7
|
+
"COPYING",
|
|
8
|
+
]
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
# https://pypi.org/classifiers/
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Environment :: Console",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
"Topic :: Software Development :: Build Tools",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
# track compatibility for dependencies, do not pin
|
|
25
|
+
"packaging>=26.1",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
puc = "puc:cli.main"
|
|
30
|
+
|
|
31
|
+
[dependency-groups]
|
|
32
|
+
# these packages are only needed for development
|
|
33
|
+
dev = [
|
|
34
|
+
# for python code linting
|
|
35
|
+
"ruff==0.15.12",
|
|
36
|
+
# for python type checking
|
|
37
|
+
"ty==0.0.34",
|
|
38
|
+
# for testing
|
|
39
|
+
"pytest==9.0.3",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[build-system]
|
|
43
|
+
requires = ["uv_build>=0.11,<0.12"]
|
|
44
|
+
build-backend = "uv_build"
|
|
45
|
+
|
|
46
|
+
[tool.uv.build-backend]
|
|
47
|
+
module-name = "puc"
|
|
48
|
+
source-include = [
|
|
49
|
+
".gitignore",
|
|
50
|
+
".ruff.toml",
|
|
51
|
+
"Makefile",
|
|
52
|
+
"scripts/**",
|
|
53
|
+
"tests/**",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[tool.project]
|
|
57
|
+
# pin versions of Python and uv for development
|
|
58
|
+
python_version_dev = "3.14.4"
|
|
59
|
+
uv_version_dev = "0.11.11"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Description: set up a development environment ready for developing this software
|
|
4
|
+
# Package installation uses apt-get which requires sudo permissions
|
|
5
|
+
# Requirements: a Debian-based Linux system and sudo permissions for the current user
|
|
6
|
+
# Synopsis: scripts/install_dev.sh
|
|
7
|
+
|
|
8
|
+
#### shell settings
|
|
9
|
+
# abort when using unknown variables
|
|
10
|
+
set -o nounset
|
|
11
|
+
# abort on subprogram errors
|
|
12
|
+
set -o errexit
|
|
13
|
+
# abort on piping errors (ie. a|b, a fails)
|
|
14
|
+
set -o pipefail
|
|
15
|
+
# subshells should inherit error handlers/traps (set -E)
|
|
16
|
+
set -o errtrace
|
|
17
|
+
# for debugging: print every command with TRACE=1
|
|
18
|
+
if [[ "${TRACE-0}" == "1" ]]; then
|
|
19
|
+
set -o xtrace
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# command substitution inherits the errexit setting
|
|
23
|
+
shopt -s inherit_errexit
|
|
24
|
+
# do not print file globbing patters when no file exists
|
|
25
|
+
shopt -s nullglob
|
|
26
|
+
|
|
27
|
+
#### configuration variables
|
|
28
|
+
# the directory this script is stored
|
|
29
|
+
BASEDIR=$(realpath "$(dirname "${0}")")
|
|
30
|
+
# the project directory
|
|
31
|
+
PROJECTDIR=$(dirname "${BASEDIR}")
|
|
32
|
+
|
|
33
|
+
PY_VER=$(grep "python_version_dev =" "${PROJECTDIR}/pyproject.toml" | cut -f2 -d'"')
|
|
34
|
+
UV_VER=$(grep "uv_version_dev =" "${PROJECTDIR}/pyproject.toml" | cut -f2 -d'"')
|
|
35
|
+
DOWNLOAD_URL_UV=https://github.com/astral-sh/uv/releases/download/${UV_VER}/uv-x86_64-unknown-linux-gnu.tar.gz
|
|
36
|
+
CURL_OPTS=("--location" "--silent" "--show-error" "--retry" "2" "--fail" "--tlsv1.2" "--proto" "=https")
|
|
37
|
+
REINSTALL_PYTHON=0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
#### helper functions
|
|
41
|
+
|
|
42
|
+
# Install a debian package
|
|
43
|
+
install_package() {
|
|
44
|
+
local pkg=$1
|
|
45
|
+
if ! dpkg --status "${pkg}" >/dev/null 2>&1; then
|
|
46
|
+
echo "Installing ${pkg}"
|
|
47
|
+
sudo apt-get install "${pkg}"
|
|
48
|
+
fi
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#### main function
|
|
52
|
+
|
|
53
|
+
# execute make targets
|
|
54
|
+
install_package make
|
|
55
|
+
# download tool
|
|
56
|
+
install_package curl
|
|
57
|
+
# augment the local environment
|
|
58
|
+
install_package direnv
|
|
59
|
+
# lint shell
|
|
60
|
+
install_package shellcheck
|
|
61
|
+
|
|
62
|
+
# the rest of this scripts relies on being in the project directory
|
|
63
|
+
cd "${PROJECTDIR}"
|
|
64
|
+
|
|
65
|
+
# install uv
|
|
66
|
+
if [ ! -d bin ]; then
|
|
67
|
+
mkdir bin
|
|
68
|
+
fi
|
|
69
|
+
if [ ! -f bin/uv ]; then
|
|
70
|
+
echo "Install uv ${UV_VER} from ${DOWNLOAD_URL_UV}"
|
|
71
|
+
(cd bin; curl "${CURL_OPTS[@]}" "${DOWNLOAD_URL_UV}" | tar xzv --strip-components 1)
|
|
72
|
+
elif [ "$(bin/uv --version | cut -d" " -f2)" != "${UV_VER}" ]; then
|
|
73
|
+
echo "Updating $(bin/uv --version) to ${UV_VER} from ${DOWNLOAD_URL_UV}"
|
|
74
|
+
(cd bin; curl "${CURL_OPTS[@]}" "${DOWNLOAD_URL_UV}" | tar xzv --strip-components 1)
|
|
75
|
+
# new uv versions might use new versions from python-build-standalone
|
|
76
|
+
REINSTALL_PYTHON=1
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# add local development environment for direnv
|
|
80
|
+
if ! declare -F _direnv_hook >/dev/null; then
|
|
81
|
+
eval "$(direnv hook bash)"
|
|
82
|
+
fi
|
|
83
|
+
if [ ! -f .envrc ]; then
|
|
84
|
+
(
|
|
85
|
+
echo "# to find local tools"
|
|
86
|
+
echo "export PATH=${PROJECTDIR}/bin\${PATH:+:\$PATH}"
|
|
87
|
+
echo "export PATH=${PROJECTDIR}/.venv/bin\${PATH:+:\$PATH}"
|
|
88
|
+
) > .envrc
|
|
89
|
+
direnv allow .
|
|
90
|
+
source .envrc
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# create .python-version
|
|
94
|
+
if [ ! -f .python-version ]; then
|
|
95
|
+
echo "Generating $PROJECTDIR/.python-version"
|
|
96
|
+
echo "${PY_VER}" > .python-version
|
|
97
|
+
else
|
|
98
|
+
PROJ_PY_VER=$(<.python-version)
|
|
99
|
+
if [ ".$PROJ_PY_VER" != ".$PY_VER" ]; then
|
|
100
|
+
echo "Updating $PROJECTDIR/.python-version (${PROJ_PY_VER} -> ${PY_VER})"
|
|
101
|
+
echo "${PY_VER}" >| .python-version
|
|
102
|
+
rm -rf .venv
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# create virtual environment
|
|
107
|
+
if [ ! -d .venv ]; then
|
|
108
|
+
uv venv
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
if [ "${REINSTALL_PYTHON}" = "1" ]; then
|
|
112
|
+
uv python install --reinstall
|
|
113
|
+
fi
|