python-update-checker 0.6__tar.gz → 0.8.0__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.
Files changed (25) hide show
  1. {python_update_checker-0.6 → python_update_checker-0.8.0}/Makefile +30 -13
  2. {python_update_checker-0.6 → python_update_checker-0.8.0}/PKG-INFO +41 -31
  3. {python_update_checker-0.6 → python_update_checker-0.8.0}/README.md +39 -29
  4. {python_update_checker-0.6 → python_update_checker-0.8.0}/pyproject.toml +22 -29
  5. python_update_checker-0.8.0/pyproject.toml.orig +67 -0
  6. {python_update_checker-0.6 → python_update_checker-0.8.0}/scripts/install_dev.sh +15 -8
  7. {python_update_checker-0.6 → python_update_checker-0.8.0}/src/puc/dependencies.py +11 -7
  8. {python_update_checker-0.6 → python_update_checker-0.8.0}/src/puc/pyprojecttoml.py +22 -12
  9. {python_update_checker-0.6 → python_update_checker-0.8.0}/src/puc/requirementstxt.py +9 -4
  10. {python_update_checker-0.6 → python_update_checker-0.8.0}/src/puc/uvlock.py +20 -16
  11. {python_update_checker-0.6 → python_update_checker-0.8.0}/tests/data/uv.lock +5 -0
  12. {python_update_checker-0.6 → python_update_checker-0.8.0}/tests/test_uv_lock.py +1 -0
  13. {python_update_checker-0.6 → python_update_checker-0.8.0}/.gitignore +0 -0
  14. {python_update_checker-0.6 → python_update_checker-0.8.0}/.ruff.toml +0 -0
  15. {python_update_checker-0.6 → python_update_checker-0.8.0}/src/puc/__init__.py +0 -0
  16. {python_update_checker-0.6 → python_update_checker-0.8.0}/src/puc/cli.py +0 -0
  17. {python_update_checker-0.6 → python_update_checker-0.8.0}/src/puc/logging.py +0 -0
  18. {python_update_checker-0.6 → python_update_checker-0.8.0}/tests/__init__.py +0 -0
  19. {python_update_checker-0.6 → python_update_checker-0.8.0}/tests/data/other-requirements.txt +0 -0
  20. {python_update_checker-0.6 → python_update_checker-0.8.0}/tests/data/pyproject.toml +0 -0
  21. {python_update_checker-0.6 → python_update_checker-0.8.0}/tests/data/pyproject_compatible.toml +0 -0
  22. {python_update_checker-0.6 → python_update_checker-0.8.0}/tests/data/requirements.txt +0 -0
  23. {python_update_checker-0.6 → python_update_checker-0.8.0}/tests/test_latest.py +0 -0
  24. {python_update_checker-0.6 → python_update_checker-0.8.0}/tests/test_pyproject_toml.py +0 -0
  25. {python_update_checker-0.6 → python_update_checker-0.8.0}/tests/test_requirements_txt.py +0 -0
@@ -19,8 +19,6 @@ MAKEFLAGS += --no-builtin-rules
19
19
  PROJECT:=$(shell egrep "^name[[:space:]]*=" pyproject.toml | cut -d'"' -f2)
20
20
  VERSION:=$(shell egrep "^version[[:space:]]*=" pyproject.toml | cut -d'"' -f2)
21
21
 
22
- # exclude packages that are newer than this
23
- EXCLUDE_NEWER:="7 days"
24
22
  # Pytest options:
25
23
  # --full-trace: print full stacktrace on errors
26
24
  PYTESTOPTS?=--full-trace
@@ -57,10 +55,19 @@ init: ## install python virtual env and required development packages
57
55
  uv sync
58
56
 
59
57
 
58
+ ############ Versioning ############
59
+
60
+ bumpversion-major: _bumpversion-major ## bump major version
61
+ bumpversion-minor: _bumpversion-minor ## bump minor version
62
+
63
+ _bumpversion-%: ## shortcut target for bumpversion
64
+ bumpversion $*
65
+
66
+
60
67
  ############ Linting and syntax checks ############
61
68
 
62
69
  .PHONY: lint
63
- lint: lint-py lint-shell
70
+ lint: lint-py lint-shell lint-md
64
71
 
65
72
  .PHONY: lint-py
66
73
  lint-py: ## lint python code
@@ -70,51 +77,61 @@ lint-py: ## lint python code
70
77
  lint-shell:
71
78
  shellcheck -x scripts/*.sh
72
79
 
80
+ .PHONY: lint-md
81
+ lint-md: ## lint markdown files
82
+ rumdl check *.md
83
+
73
84
  .PHONY: audit
74
85
  audit: ## run audit checks
86
+ zizmor --pedantic .github/workflows/python-package.yml
75
87
  uv audit
76
88
 
77
89
  .PHONY: reformat
78
90
  reformat: ## format the python code
79
91
  ruff check --fix $(PY_FILES_DIRS)
80
92
  ruff format $(PY_FILES_DIRS)
93
+ shfmt -w scripts/
94
+ tombi format pyproject.toml
81
95
 
82
96
  .PHONY: checkoutdated checkoutdated-py checkoutdated-gh
83
97
  checkoutdated: checkoutdated-py checkoutdated-gh
84
98
 
85
99
  checkoutdated-py: ## Check for outdated package requirements
86
100
  uv run puc \
87
- --exclude-newer $(EXCLUDE_NEWER) \
88
- --exclude-newer-package "python-update-checker=1 minute" \
89
101
  check pyproject.toml uv.lock
90
102
 
91
- checkoutdated-gh: ## check for outdated github projects
103
+ checkoutdated-gh: checkratelimit-gh ## check for outdated github projects
92
104
  # github-check-outdated is a local tool which compares a given version with the latest available github release version
93
105
  # see https://gist.github.com/wummel/ef14989766009effa4e262b01096fc8c for an example implementation
94
106
  @echo "Check for outdated Github tools"
95
107
  github-check-outdated astral-sh uv "$(shell uv --version | cut -f2 -d" ")"
96
108
  github-check-outdated python cpython v$(shell python --version | cut -f2 -d" ") '^v3\.14\.[0-9]+$$'
97
109
 
110
+ .PHONY: checkratelimit-gh
111
+ checkratelimit-gh: ## test for rate limiting
112
+ (
113
+ set +e
114
+ github-check-outdated astral-sh uv 0 > /dev/null
115
+ if [ "$$?" == 2 ]; then exit 1; fi
116
+ )
117
+
98
118
 
99
119
  .PHONY: upgradeoutdated
100
120
  upgradeoutdated: upgradeoutdated-gh upgradeoutdated-py
101
121
 
102
122
  .PHONY: upgradeoutdated-gh
103
- upgradeoutdated-gh:
123
+ upgradeoutdated-gh: checkratelimit-gh
104
124
  sed -i -e 's/uv_version_dev = ".*"/uv_version_dev = "$(shell github-check-outdated astral-sh uv 0 | cut -f4 -d" ")"/' pyproject.toml
125
+ sed -i -e 's/ version: ".*"/ version: "$(shell github-check-outdated astral-sh uv 0 | cut -f4 -d" ")"/' .github/workflows/python-package.yml
105
126
  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
106
127
  scripts/install_dev.sh
107
128
 
108
129
  .PHONY: upgradeoutdated-py
109
130
  upgradeoutdated-py: ## upgrade dependencies in pyproject.toml and uv.lock
110
131
  # upgrade pyproject.toml dependencies
111
- uv run puc \
112
- --exclude-newer $(EXCLUDE_NEWER) \
113
- update pyproject.toml
132
+ uv run puc update pyproject.toml
114
133
  # upgrade depencencies in uv lock file
115
- uv lock \
116
- --exclude-newer $(EXCLUDE_NEWER) \
117
- --upgrade
134
+ uv lock --upgrade
118
135
  # install upgraded package versions in virtual environment
119
136
  $(MAKE) init
120
137
 
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-update-checker
3
- Version: 0.6
3
+ Version: 0.8.0
4
4
  License-Expression: GPL-3.0-only
5
+ Classifier: Development Status :: 4 - Beta
5
6
  Classifier: Environment :: Console
6
7
  Classifier: Intended Audience :: Developers
7
- Classifier: Development Status :: 4 - Beta
8
8
  Classifier: Operating System :: OS Independent
9
9
  Classifier: Programming Language :: Python :: 3 :: Only
10
10
  Classifier: Programming Language :: Python :: 3.11
@@ -16,21 +16,25 @@ Requires-Dist: packaging>=26.1
16
16
  Requires-Python: >=3.11
17
17
  Description-Content-Type: text/markdown
18
18
 
19
- python-update-checker
20
- ======================
19
+ # python-update-checker
20
+
21
21
  Python-update-checker (puc) checks or updates pinned dependencies
22
22
  in `pyproject.toml`, `requirements.txt` or `uv.lock` files.
23
23
 
24
- See https://github.com/astral-sh/uv/issues/6794 for a discussion
25
- about different pinning strategies.
24
+ If you are developing an application you should pin all dependencies.
25
+ When developing a library, only development dependencies should be pinned.
26
+ Since puc updates pinned dependencies, it is mostly suitable
27
+ when developing applications.
26
28
 
29
+ See [https://github.com/astral-sh/uv/issues/6794] for a discussion
30
+ about different pinning strategies.
27
31
 
28
- Features
29
- ---------
32
+ ## Features
30
33
 
31
34
  * updates pinned dependencies, ignores unpinned dependencies
32
35
  * supports pyproject.toml, uv.lock and requirements.txt formats
33
- * supports `[project.dependencies]`, `[project.optional-dependencies]` and `[dependency-groups]` in pyproject.toml
36
+ * supports `[project.dependencies]`, `[project.optional-dependencies]`
37
+ and `[dependency-groups]` in pyproject.toml
34
38
  * supports recursive references (-r) in requirements.txt formats
35
39
  * can run in check only mode, ie. it checks if updates are available
36
40
  * limit updates to specific packages
@@ -39,11 +43,18 @@ Features
39
43
  * (limited) support for environment markers, ie. `"pywin32==311; os_name=='nt'"`
40
44
  * runs on Linux, MacOS and Windows platforms
41
45
 
46
+ ## Quickstart
47
+
48
+ ```bash
49
+
50
+ uvx --from python-update-checker puc --exclude-newer "1 day" update pyproject.toml
51
+ uv sync
52
+ ```
42
53
 
43
- Examples
44
- ---------
54
+ ## Examples
45
55
 
46
56
  ```bash
57
+
47
58
  $ # check all pinned packages for updates in pyproject.toml
48
59
  $ puc check pyproject.toml
49
60
  puc INFO: check pyproject file pyproject.toml
@@ -55,6 +66,8 @@ $ puc --exclude-newer="7 days" update pyproject.toml
55
66
  puc INFO: update pyproject file pyproject.toml
56
67
  puc INFO: updating 'ty==0.0.29' --> 0.0.31
57
68
  puc INFO: Wrote 1 updated package version(s) to pyproject.toml
69
+ $ # update the project environment after upgrading pyproject.toml
70
+ $ uv sync
58
71
 
59
72
  $ # check all pinned packages for updates in requirements.txt
60
73
  $ puc check requirements.txt
@@ -65,52 +78,49 @@ puc WARNING: found update 'Django==5.2.0' --> 6.0.4
65
78
  $ # update only the django package version in requirements.txt
66
79
  $ # limit updates to django versions less than 6
67
80
  $ 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
81
+ puc INFO: update requirements file requirements.txt
82
+ puc INFO: updating 'Django==5.2.0' --> 5.2.13
83
+ puc INFO: Wrote 1 updated package version(s) to requirements.txt
71
84
  ```
72
85
 
73
- Script behaviour
74
- -----------------
86
+ ## Script behaviour
75
87
 
76
88
  The exit code of `puc check` is non-zero when updates are available.
77
89
 
78
90
  Checking a `pyproject.toml` or `uv.lock`file with `puc` should be done
79
91
  from the directory 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 package indexes in pyproject.toml).
92
+ especially if your project relies on a [project directory](https://docs.astral.sh/uv/concepts/projects/layout/)
93
+ (for example to define additional package indexes in pyproject.toml).
81
94
 
82
95
  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.
96
+ the transitive dependencies in `uv.lock` and `uv sync` to update your
97
+ virtual environment.
86
98
 
99
+ Pinned dependencies are packages with `==` or `===` constraints
100
+ and no wildcards in the version.
87
101
 
88
- Installation
89
- -------------
102
+ ## Installation
90
103
 
91
104
  1) Install [python uv](https://docs.astral.sh/uv/getting-started/installation/)
92
- 2) Install puc with `uv pip install python-update-checker`.
105
+ 2) Install puc with `uv pip install python-update-checker`
106
+ or run with `uvx --from python-update-checker puc ...`
93
107
 
94
-
95
-
96
- Architecture
97
- -------------
108
+ ## Architecture
98
109
 
99
110
  Dependencies are
100
111
 
101
112
  * [uv](https://docs.astral.sh/uv/):
102
113
  The uv binary must be available for the script to call.
103
114
  puc uses `echo "package" | uv pip compile -` to get latest package versions.
104
- puc uses `uv add "package==<version>"` to update pyproject.toml dependencies.
115
+ puc uses `uv add "package==<version>"` to update pyproject.toml
116
+ dependencies and `uv lock --upgrade-package` to update uv.lock dependencies.
105
117
 
106
118
  * [packaging](https://packaging.pypa.io/):
107
119
  Parses dependencies with the packaging.requirements.Requirement class.
108
120
 
109
121
  puc needs Python >= 3.11 since it uses the tomllib Python module.
110
122
 
111
-
112
- Limitations
113
- ------------
123
+ ## Limitations
114
124
 
115
125
  * No support for custom dependency formats in pyproject.toml
116
126
  (eg. `[tool.poetry.dependencies]`).
@@ -1,18 +1,22 @@
1
- python-update-checker
2
- ======================
1
+ # python-update-checker
2
+
3
3
  Python-update-checker (puc) checks or updates pinned dependencies
4
4
  in `pyproject.toml`, `requirements.txt` or `uv.lock` files.
5
5
 
6
- See https://github.com/astral-sh/uv/issues/6794 for a discussion
7
- about different pinning strategies.
6
+ If you are developing an application you should pin all dependencies.
7
+ When developing a library, only development dependencies should be pinned.
8
+ Since puc updates pinned dependencies, it is mostly suitable
9
+ when developing applications.
8
10
 
11
+ See [https://github.com/astral-sh/uv/issues/6794] for a discussion
12
+ about different pinning strategies.
9
13
 
10
- Features
11
- ---------
14
+ ## Features
12
15
 
13
16
  * updates pinned dependencies, ignores unpinned dependencies
14
17
  * supports pyproject.toml, uv.lock and requirements.txt formats
15
- * supports `[project.dependencies]`, `[project.optional-dependencies]` and `[dependency-groups]` in pyproject.toml
18
+ * supports `[project.dependencies]`, `[project.optional-dependencies]`
19
+ and `[dependency-groups]` in pyproject.toml
16
20
  * supports recursive references (-r) in requirements.txt formats
17
21
  * can run in check only mode, ie. it checks if updates are available
18
22
  * limit updates to specific packages
@@ -21,11 +25,18 @@ Features
21
25
  * (limited) support for environment markers, ie. `"pywin32==311; os_name=='nt'"`
22
26
  * runs on Linux, MacOS and Windows platforms
23
27
 
28
+ ## Quickstart
29
+
30
+ ```bash
31
+
32
+ uvx --from python-update-checker puc --exclude-newer "1 day" update pyproject.toml
33
+ uv sync
34
+ ```
24
35
 
25
- Examples
26
- ---------
36
+ ## Examples
27
37
 
28
38
  ```bash
39
+
29
40
  $ # check all pinned packages for updates in pyproject.toml
30
41
  $ puc check pyproject.toml
31
42
  puc INFO: check pyproject file pyproject.toml
@@ -37,6 +48,8 @@ $ puc --exclude-newer="7 days" update pyproject.toml
37
48
  puc INFO: update pyproject file pyproject.toml
38
49
  puc INFO: updating 'ty==0.0.29' --> 0.0.31
39
50
  puc INFO: Wrote 1 updated package version(s) to pyproject.toml
51
+ $ # update the project environment after upgrading pyproject.toml
52
+ $ uv sync
40
53
 
41
54
  $ # check all pinned packages for updates in requirements.txt
42
55
  $ puc check requirements.txt
@@ -47,52 +60,49 @@ puc WARNING: found update 'Django==5.2.0' --> 6.0.4
47
60
  $ # update only the django package version in requirements.txt
48
61
  $ # limit updates to django versions less than 6
49
62
  $ 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
63
+ puc INFO: update requirements file requirements.txt
64
+ puc INFO: updating 'Django==5.2.0' --> 5.2.13
65
+ puc INFO: Wrote 1 updated package version(s) to requirements.txt
53
66
  ```
54
67
 
55
- Script behaviour
56
- -----------------
68
+ ## Script behaviour
57
69
 
58
70
  The exit code of `puc check` is non-zero when updates are available.
59
71
 
60
72
  Checking a `pyproject.toml` or `uv.lock`file with `puc` should be done
61
73
  from the directory 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 package indexes in pyproject.toml).
74
+ especially if your project relies on a [project directory](https://docs.astral.sh/uv/concepts/projects/layout/)
75
+ (for example to define additional package indexes in pyproject.toml).
63
76
 
64
77
  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.
78
+ the transitive dependencies in `uv.lock` and `uv sync` to update your
79
+ virtual environment.
68
80
 
81
+ Pinned dependencies are packages with `==` or `===` constraints
82
+ and no wildcards in the version.
69
83
 
70
- Installation
71
- -------------
84
+ ## Installation
72
85
 
73
86
  1) Install [python uv](https://docs.astral.sh/uv/getting-started/installation/)
74
- 2) Install puc with `uv pip install python-update-checker`.
87
+ 2) Install puc with `uv pip install python-update-checker`
88
+ or run with `uvx --from python-update-checker puc ...`
75
89
 
76
-
77
-
78
- Architecture
79
- -------------
90
+ ## Architecture
80
91
 
81
92
  Dependencies are
82
93
 
83
94
  * [uv](https://docs.astral.sh/uv/):
84
95
  The uv binary must be available for the script to call.
85
96
  puc uses `echo "package" | uv pip compile -` to get latest package versions.
86
- puc uses `uv add "package==<version>"` to update pyproject.toml dependencies.
97
+ puc uses `uv add "package==<version>"` to update pyproject.toml
98
+ dependencies and `uv lock --upgrade-package` to update uv.lock dependencies.
87
99
 
88
100
  * [packaging](https://packaging.pypa.io/):
89
101
  Parses dependencies with the packaging.requirements.Requirement class.
90
102
 
91
103
  puc needs Python >= 3.11 since it uses the tomllib Python module.
92
104
 
93
-
94
- Limitations
95
- ------------
105
+ ## Limitations
96
106
 
97
107
  * No support for custom dependency formats in pyproject.toml
98
108
  (eg. `[tool.poetry.dependencies]`).
@@ -1,17 +1,13 @@
1
1
  [project]
2
2
  name = "python-update-checker"
3
- version = "0.6"
3
+ version = "0.8.0"
4
+ readme = "README.md"
4
5
  requires-python = ">=3.11"
5
6
  license = "GPL-3.0-only"
6
- license_files = [
7
- "COPYING",
8
- ]
9
- readme = "README.md"
10
- # https://pypi.org/classifiers/
11
7
  classifiers = [
8
+ "Development Status :: 4 - Beta",
12
9
  "Environment :: Console",
13
10
  "Intended Audience :: Developers",
14
- "Development Status :: 4 - Beta",
15
11
  "Operating System :: OS Independent",
16
12
  "Programming Language :: Python :: 3 :: Only",
17
13
  "Programming Language :: Python :: 3.11",
@@ -20,40 +16,37 @@ classifiers = [
20
16
  "Programming Language :: Python :: 3.14",
21
17
  "Topic :: Software Development :: Build Tools",
22
18
  ]
23
- dependencies = [
24
- # track compatibility for dependencies, do not pin
25
- "packaging>=26.1",
26
- ]
19
+ dependencies = ["packaging>=26.1"]
20
+ license_files = ["COPYING"]
27
21
 
28
22
  [project.scripts]
29
23
  puc = "puc:cli.main"
30
24
 
31
25
  [dependency-groups]
32
- # these packages are only needed for development
33
26
  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",
27
+ "bump2version==1.0.1",
28
+ "pytest==9.1.1",
29
+ "ruff==0.16.4",
30
+ "rumdl==0.2.61",
31
+ "tombi==1.4.1",
32
+ "ty==0.0.75",
33
+ "zizmor==1.29.0",
40
34
  ]
41
35
 
42
36
  [build-system]
43
- requires = ["uv_build>=0.11,<0.12"]
37
+ requires = ["uv_build>=0.11.32,<0.13"]
44
38
  build-backend = "uv_build"
45
39
 
40
+ [tool.project]
41
+ python_version_dev = "3.14.7"
42
+ uv_version_dev = "0.12.9"
43
+
46
44
  [tool.uv.build-backend]
47
45
  module-name = "puc"
48
46
  source-include = [
49
- ".gitignore",
50
- ".ruff.toml",
51
- "Makefile",
52
- "scripts/**",
53
- "tests/**",
47
+ ".gitignore",
48
+ ".ruff.toml",
49
+ "Makefile",
50
+ "scripts/**",
51
+ "tests/**",
54
52
  ]
55
-
56
- [tool.project]
57
- # pin versions of Python and uv for development
58
- python_version_dev = "3.14.5"
59
- uv_version_dev = "0.11.14"
@@ -0,0 +1,67 @@
1
+ [project]
2
+ name = "python-update-checker"
3
+ version = "0.8.0"
4
+ readme = "README.md"
5
+ requires-python = ">=3.11"
6
+ license = "GPL-3.0-only"
7
+ # https://pypi.org/classifiers/
8
+ classifiers = [
9
+ "Development Status :: 4 - Beta",
10
+ "Environment :: Console",
11
+ "Intended Audience :: Developers",
12
+ "Operating System :: OS Independent",
13
+ "Programming Language :: Python :: 3 :: Only",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ "Programming Language :: Python :: 3.14",
18
+ "Topic :: Software Development :: Build Tools",
19
+ ]
20
+ dependencies = [
21
+ # track compatibility for dependencies, do not pin
22
+ "packaging>=26.1",
23
+ ]
24
+ license_files = [
25
+ "COPYING",
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 upgrading the version number
35
+ "bump2version==1.0.1",
36
+ # for testing
37
+ "pytest==9.1.1",
38
+ # for python code linting
39
+ "ruff==0.16.4",
40
+ # Markdown style checking and formatting
41
+ "rumdl==0.2.61",
42
+ # reformat toml files
43
+ "tombi==1.4.1",
44
+ # for python type checking
45
+ "ty==0.0.75",
46
+ # for auditing github actions
47
+ "zizmor==1.29.0",
48
+ ]
49
+
50
+ [build-system]
51
+ requires = ["uv_build>=0.11.32,<0.13"]
52
+ build-backend = "uv_build"
53
+
54
+ [tool.project]
55
+ # pin versions of Python and uv for development
56
+ python_version_dev = "3.14.7"
57
+ uv_version_dev = "0.12.9"
58
+
59
+ [tool.uv.build-backend]
60
+ module-name = "puc"
61
+ source-include = [
62
+ ".gitignore",
63
+ ".ruff.toml",
64
+ "Makefile",
65
+ "scripts/**",
66
+ "tests/**",
67
+ ]
@@ -36,15 +36,14 @@ DOWNLOAD_URL_UV=https://github.com/astral-sh/uv/releases/download/${UV_VER}/uv-x
36
36
  CURL_OPTS=("--location" "--silent" "--show-error" "--retry" "2" "--fail" "--tlsv1.2" "--proto" "=https")
37
37
  REINSTALL_PYTHON=0
38
38
 
39
-
40
39
  #### helper functions
41
40
 
42
41
  # Install a debian package
43
42
  install_package() {
44
43
  local pkg=$1
45
- if ! dpkg --status "${pkg}" >/dev/null 2>&1; then
44
+ if ! dpkg --status "${pkg}" > /dev/null 2>&1; then
46
45
  echo "Installing ${pkg}"
47
- sudo apt-get install "${pkg}"
46
+ sudo apt-get install --assume-yes "${pkg}"
48
47
  fi
49
48
  }
50
49
 
@@ -58,6 +57,8 @@ install_package curl
58
57
  install_package direnv
59
58
  # lint shell
60
59
  install_package shellcheck
60
+ # for formatting shell scripts (used by make reformat)
61
+ install_package shfmt
61
62
 
62
63
  # the rest of this scripts relies on being in the project directory
63
64
  cd "${PROJECTDIR}"
@@ -68,16 +69,22 @@ if [ ! -d bin ]; then
68
69
  fi
69
70
  if [ ! -f bin/uv ]; then
70
71
  echo "Install uv ${UV_VER} from ${DOWNLOAD_URL_UV}"
71
- (cd bin; curl "${CURL_OPTS[@]}" "${DOWNLOAD_URL_UV}" | tar xzv --strip-components 1)
72
+ (
73
+ cd bin
74
+ curl "${CURL_OPTS[@]}" "${DOWNLOAD_URL_UV}" | tar xzv --strip-components 1
75
+ )
72
76
  elif [ "$(bin/uv --version | cut -d" " -f2)" != "${UV_VER}" ]; then
73
77
  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)
78
+ (
79
+ cd bin
80
+ curl "${CURL_OPTS[@]}" "${DOWNLOAD_URL_UV}" | tar xzv --strip-components 1
81
+ )
75
82
  # new uv versions might use new versions from python-build-standalone
76
83
  REINSTALL_PYTHON=1
77
84
  fi
78
85
 
79
86
  # add local development environment for direnv
80
- if ! declare -F _direnv_hook >/dev/null; then
87
+ if ! declare -F _direnv_hook > /dev/null; then
81
88
  eval "$(direnv hook bash)"
82
89
  fi
83
90
  if [ ! -f .envrc ]; then
@@ -85,7 +92,7 @@ if [ ! -f .envrc ]; then
85
92
  echo "# to find local tools"
86
93
  echo "export PATH=${PROJECTDIR}/bin\${PATH:+:\$PATH}"
87
94
  echo "export PATH=${PROJECTDIR}/.venv/bin\${PATH:+:\$PATH}"
88
- ) > .envrc
95
+ ) > .envrc
89
96
  direnv allow .
90
97
  source .envrc
91
98
  fi
@@ -95,7 +102,7 @@ if [ ! -f .python-version ]; then
95
102
  echo "Generating $PROJECTDIR/.python-version"
96
103
  echo "${PY_VER}" > .python-version
97
104
  else
98
- PROJ_PY_VER=$(<.python-version)
105
+ PROJ_PY_VER=$(< .python-version)
99
106
  if [ ".$PROJ_PY_VER" != ".$PY_VER" ]; then
100
107
  echo "Updating $PROJECTDIR/.python-version (${PROJ_PY_VER} -> ${PY_VER})"
101
108
  echo "${PY_VER}" >| .python-version
@@ -4,6 +4,7 @@
4
4
 
5
5
  import subprocess
6
6
  import re
7
+ import logging
7
8
  from packaging.requirements import Requirement
8
9
  from packaging.markers import Variable, MarkerList
9
10
  from packaging.version import parse as parse_version
@@ -12,9 +13,9 @@ from .logging import logger
12
13
 
13
14
  def get_latest_version(
14
15
  package: str,
15
- exclude_newer: None | str = None,
16
- exclude_newer_package: None | str = None,
17
- constraint_file: None | str = None,
16
+ exclude_newer: str | None = None,
17
+ exclude_newer_package: str | None = None,
18
+ constraint_file: str | None = None,
18
19
  python_platform: str | None = None,
19
20
  python_version: str | None = None,
20
21
  ) -> str:
@@ -28,12 +29,15 @@ def get_latest_version(
28
29
  "compile",
29
30
  "-",
30
31
  "--color=never",
31
- "--quiet",
32
32
  "--no-deps",
33
33
  "--no-header",
34
34
  "--no-annotate",
35
35
  "--no-progress",
36
36
  ]
37
+ if logger.getEffectiveLevel() <= logging.DEBUG:
38
+ cmd.append("--verbose")
39
+ else:
40
+ cmd.append("--quiet")
37
41
  if exclude_newer:
38
42
  cmd.extend(("--exclude-newer", exclude_newer))
39
43
  if exclude_newer_package:
@@ -122,9 +126,9 @@ def get_min_python_version_from_req(pkg_req: Requirement) -> str | None:
122
126
 
123
127
  def parse_requirement(
124
128
  line: str,
125
- exclude_newer: None | str = None,
126
- constraint_file: None | str = None,
127
- ) -> None | str | Requirement:
129
+ exclude_newer: str | None = None,
130
+ constraint_file: str | None = None,
131
+ ) -> str | Requirement | None:
128
132
  """Parse one line of a requirements.txt file."""
129
133
  line = line.strip()
130
134
  if not line or line.startswith("#"):
@@ -5,6 +5,7 @@
5
5
  import subprocess
6
6
  import os
7
7
  import tomllib
8
+ import logging
8
9
  from packaging.utils import canonicalize_name
9
10
  from packaging.requirements import Requirement
10
11
 
@@ -14,16 +15,17 @@ from .dependencies import (
14
15
  get_python_platform_from_req,
15
16
  get_min_python_version_from_req,
16
17
  check_requirement,
18
+ is_newer_version,
17
19
  )
18
20
 
19
21
 
20
22
  def handle_pyproject_toml(
21
23
  pyproject_path: str,
22
- command: None | str = None,
24
+ command: str | None = None,
23
25
  packages=None,
24
- exclude_newer: None | str = None,
25
- exclude_newer_package: None | str = None,
26
- constraint_file: None | str = None,
26
+ exclude_newer: str | None = None,
27
+ exclude_newer_package: str | None = None,
28
+ constraint_file: str | None = None,
27
29
  color: bool = True,
28
30
  ) -> int:
29
31
  """Check or update pinned dependencies of a pyproject.toml file.
@@ -96,13 +98,13 @@ def update_pyproject_dependencies(
96
98
  dependencies: list[str | dict],
97
99
  project_dir: str,
98
100
  projectname: str,
99
- group: None | str = None,
101
+ group: str | None = None,
100
102
  optional=False,
101
- command: None | str = None,
103
+ command: str | None = None,
102
104
  packages=None,
103
- exclude_newer: None | str = None,
104
- exclude_newer_package: None | str = None,
105
- constraint_file: None | str = None,
105
+ exclude_newer: str | None = None,
106
+ exclude_newer_package: str | None = None,
107
+ constraint_file: str | None = None,
106
108
  color: bool = True,
107
109
  ) -> int:
108
110
  """Update given dependency list of a pyproject.toml file."""
@@ -140,6 +142,10 @@ def update_pyproject_dependencies(
140
142
  spec = next(s for s in pkg_req.specifier)
141
143
  if latest_version is not None and latest_version != spec.version:
142
144
  updatable += 1
145
+ if not is_newer_version(spec.version, latest_version):
146
+ logger.warning(
147
+ f"{pkg_req.name} latest version {latest_version} is older than specified version {spec.version}"
148
+ )
143
149
  version = (
144
150
  colorize_updated_version(spec.version, latest_version)
145
151
  if color
@@ -160,7 +166,7 @@ def update_pyproject_pkg(
160
166
  dependency: str,
161
167
  package: str,
162
168
  projectdir: str,
163
- group: None | str = None,
169
+ group: str | None = None,
164
170
  optional: bool = False,
165
171
  ) -> None:
166
172
  """Update one package in pyproject.toml."""
@@ -169,11 +175,15 @@ def update_pyproject_pkg(
169
175
  "add",
170
176
  "--project",
171
177
  projectdir,
172
- "--quiet",
173
178
  "--frozen",
174
179
  "--color=never",
175
- f"--upgrade-package={package}",
180
+ "--upgrade-package",
181
+ package,
176
182
  ]
183
+ if logger.getEffectiveLevel() <= logging.DEBUG:
184
+ command.append("--verbose")
185
+ else:
186
+ command.append("--quiet")
177
187
  if optional and group:
178
188
  command.append("--optional")
179
189
  command.append(group)
@@ -14,6 +14,7 @@ from .dependencies import (
14
14
  get_python_platform_from_req,
15
15
  get_min_python_version_from_req,
16
16
  parse_requirement,
17
+ is_newer_version,
17
18
  )
18
19
 
19
20
 
@@ -23,11 +24,11 @@ max_rec_level = 5
23
24
 
24
25
  def handle_requirements_txt(
25
26
  requirements_txt_path: str,
26
- command: None | str = None,
27
+ command: str | None = None,
27
28
  packages=None,
28
- exclude_newer: None | str = None,
29
- exclude_newer_package: None | str = None,
30
- constraint_file: None | str = None,
29
+ exclude_newer: str | None = None,
30
+ exclude_newer_package: str | None = None,
31
+ constraint_file: str | None = None,
31
32
  color: bool = True,
32
33
  rec_level: int = 0,
33
34
  handled_files: list[str] | None = None,
@@ -91,6 +92,10 @@ def handle_requirements_txt(
91
92
  output.write(line)
92
93
  elif latest_version is not None and latest_version != spec.version:
93
94
  updatable += 1
95
+ if not is_newer_version(spec.version, latest_version):
96
+ logger.warning(
97
+ f"{pkg_req.name} latest version {latest_version} is older than specified version {spec.version}"
98
+ )
94
99
  version = (
95
100
  colorize_updated_version(spec.version, latest_version)
96
101
  if color
@@ -16,11 +16,11 @@ from .dependencies import (
16
16
 
17
17
  def handle_uv_lock(
18
18
  uvlock_path: str,
19
- command: None | str = None,
19
+ command: str | None = None,
20
20
  packages=None,
21
- exclude_newer: None | str = None,
22
- exclude_newer_package: None | str = None,
23
- constraint_file: None | str = None,
21
+ exclude_newer: str | None = None,
22
+ exclude_newer_package: str | None = None,
23
+ constraint_file: str | None = None,
24
24
  color: bool = True,
25
25
  ) -> int:
26
26
  """Check or update pinned dependencies of a uv.lock file."""
@@ -49,12 +49,20 @@ def handle_uv_lock(
49
49
  if version is None:
50
50
  logger.warning(f"missing version in package {uvpackage}")
51
51
  continue
52
+ # respect package filter
53
+ if packages and canonicalize_name(name) not in packages:
54
+ logger.info(f"skip package {name} due to package filter")
55
+ continue
56
+ # skip virtual packages (ie. the project itself)
57
+ source = uvpackage.get("source", {})
58
+ if "virtual" in source:
59
+ logger.info(f"skip virtual package {name}")
60
+ continue
52
61
  updatable += update_uvlock_dependency(
53
62
  name,
54
63
  version,
55
64
  project_dir,
56
65
  command=command,
57
- packages=packages,
58
66
  exclude_newer=exclude_newer,
59
67
  exclude_newer_package=exclude_newer_package,
60
68
  constraint_file=constraint_file,
@@ -69,18 +77,13 @@ def update_uvlock_dependency(
69
77
  package,
70
78
  version,
71
79
  project_dir: str,
72
- command: None | str = None,
73
- packages=None,
74
- exclude_newer: None | str = None,
75
- exclude_newer_package: None | str = None,
76
- constraint_file: None | str = None,
80
+ command: str | None = None,
81
+ exclude_newer: str | None = None,
82
+ exclude_newer_package: str | None = None,
83
+ constraint_file: str | None = None,
77
84
  color: bool = True,
78
85
  ) -> int:
79
86
  """Update uv lock dependency."""
80
- # respect optional package filter
81
- if packages and canonicalize_name(package) not in packages:
82
- return 0
83
-
84
87
  try:
85
88
  latest_version = get_latest_version(
86
89
  package,
@@ -114,7 +117,7 @@ def update_uvlock_dependency(
114
117
  def update_uvlock_pkg(
115
118
  package: str,
116
119
  projectdir: str,
117
- exclude_newer: None | str = None,
120
+ exclude_newer: str | None = None,
118
121
  ) -> None:
119
122
  """Update one package in pyproject.toml."""
120
123
  command = [
@@ -124,7 +127,8 @@ def update_uvlock_pkg(
124
127
  projectdir,
125
128
  "--quiet",
126
129
  "--color=never",
127
- f"--upgrade-package={package}",
130
+ "--upgrade-package",
131
+ package,
128
132
  ]
129
133
  if exclude_newer:
130
134
  command.extend(
@@ -50,3 +50,8 @@ wheels = [
50
50
  { url = "https://files.pythonhosted.org/packages/d2/ef/3ef01c17785ff9a69378465c7d0faccd48a07b163554db0995e5d65a5a23/ty-0.0.29-py3-none-win_amd64.whl", hash = "sha256:fc1294200226b91615acbf34e0a9ad81caf98c081e9c6a912a31b0a7b603bc3f", size = 11023644, upload-time = "2026-04-05T15:01:04.432Z" },
51
51
  { url = "https://files.pythonhosted.org/packages/2c/55/87280a994d6a2d2647c65e12abbc997ed49835794366153c04c4d9304d76/ty-0.0.29-py3-none-win_arm64.whl", hash = "sha256:f9794bbd1bb3ce13f78c191d0c89ae4c63f52c12b6daa0c6fe220b90d019d12c", size = 10428165, upload-time = "2026-04-05T15:01:34.665Z" },
52
52
  ]
53
+
54
+ [[package]]
55
+ name = "imadoofus"
56
+ version = "1.1.1"
57
+ source = { virtual = "." }
@@ -34,6 +34,7 @@ class UvLockTest(unittest.TestCase):
34
34
  self.assertTrue(result.returncode > 0)
35
35
  self.assertIn("found update 'ty==", output)
36
36
  self.assertIn("found update 'ruff==", output)
37
+ self.assertIn("skip virtual package imadoofus", output)
37
38
 
38
39
  @needs_program('uv')
39
40
  def test_uvlock_check_package(self):