depkeeper 0.1.0.dev3__tar.gz → 1.0.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 (70) hide show
  1. depkeeper-1.0.0/PKG-INFO +428 -0
  2. depkeeper-1.0.0/README.md +378 -0
  3. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper/__init__.py +2 -3
  4. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper/__main__.py +6 -4
  5. depkeeper-1.0.0/depkeeper/__version__.py +11 -0
  6. depkeeper-1.0.0/depkeeper/cli.py +210 -0
  7. depkeeper-1.0.0/depkeeper/commands/__init__.py +7 -0
  8. depkeeper-1.0.0/depkeeper/commands/_report.py +741 -0
  9. depkeeper-1.0.0/depkeeper/commands/check.py +845 -0
  10. depkeeper-1.0.0/depkeeper/commands/update.py +1398 -0
  11. depkeeper-1.0.0/depkeeper/config.py +538 -0
  12. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper/constants.py +46 -25
  13. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper/context.py +11 -7
  14. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper/core/__init__.py +5 -6
  15. depkeeper-1.0.0/depkeeper/core/checker.py +892 -0
  16. depkeeper-1.0.0/depkeeper/core/data_store.py +891 -0
  17. depkeeper-1.0.0/depkeeper/core/dependency_analyzer.py +1321 -0
  18. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper/core/parser.py +332 -313
  19. depkeeper-1.0.0/depkeeper/core/sources/__init__.py +24 -0
  20. depkeeper-1.0.0/depkeeper/core/sources/base.py +98 -0
  21. depkeeper-1.0.0/depkeeper/core/sources/conda_yaml.py +395 -0
  22. depkeeper-1.0.0/depkeeper/core/sources/pyproject_toml.py +588 -0
  23. depkeeper-1.0.0/depkeeper/core/sources/registry.py +101 -0
  24. depkeeper-1.0.0/depkeeper/core/sources/requirements_txt.py +161 -0
  25. depkeeper-1.0.0/depkeeper/core/verify.py +520 -0
  26. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper/exceptions.py +56 -16
  27. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper/models/__init__.py +5 -6
  28. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper/models/conflict.py +29 -16
  29. depkeeper-1.0.0/depkeeper/models/package.py +407 -0
  30. depkeeper-1.0.0/depkeeper/models/requirement.py +313 -0
  31. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper/utils/__init__.py +22 -32
  32. depkeeper-1.0.0/depkeeper/utils/console.py +349 -0
  33. depkeeper-1.0.0/depkeeper/utils/filesystem.py +517 -0
  34. depkeeper-1.0.0/depkeeper/utils/http.py +664 -0
  35. depkeeper-1.0.0/depkeeper/utils/logger.py +193 -0
  36. depkeeper-1.0.0/depkeeper/utils/naming.py +55 -0
  37. depkeeper-1.0.0/depkeeper/utils/redact.py +44 -0
  38. depkeeper-1.0.0/depkeeper/utils/version_utils.py +546 -0
  39. depkeeper-1.0.0/depkeeper.egg-info/PKG-INFO +428 -0
  40. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper.egg-info/SOURCES.txt +17 -1
  41. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper.egg-info/requires.txt +6 -11
  42. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/pyproject.toml +20 -15
  43. depkeeper-1.0.0/tests/test_config.py +444 -0
  44. depkeeper-1.0.0/tests/test_config_allow_major.py +30 -0
  45. depkeeper-1.0.0/tests/test_config_hardening.py +63 -0
  46. depkeeper-1.0.0/tests/test_context.py +124 -0
  47. depkeeper-1.0.0/tests/test_main.py +132 -0
  48. depkeeper-0.1.0.dev3/PKG-INFO +0 -220
  49. depkeeper-0.1.0.dev3/README.md +0 -167
  50. depkeeper-0.1.0.dev3/depkeeper/__version__.py +0 -12
  51. depkeeper-0.1.0.dev3/depkeeper/cli.py +0 -157
  52. depkeeper-0.1.0.dev3/depkeeper/commands/__init__.py +0 -0
  53. depkeeper-0.1.0.dev3/depkeeper/commands/check.py +0 -608
  54. depkeeper-0.1.0.dev3/depkeeper/commands/update.py +0 -684
  55. depkeeper-0.1.0.dev3/depkeeper/core/checker.py +0 -458
  56. depkeeper-0.1.0.dev3/depkeeper/core/data_store.py +0 -645
  57. depkeeper-0.1.0.dev3/depkeeper/core/dependency_analyzer.py +0 -1011
  58. depkeeper-0.1.0.dev3/depkeeper/models/package.py +0 -371
  59. depkeeper-0.1.0.dev3/depkeeper/models/requirement.py +0 -148
  60. depkeeper-0.1.0.dev3/depkeeper/utils/console.py +0 -238
  61. depkeeper-0.1.0.dev3/depkeeper/utils/filesystem.py +0 -324
  62. depkeeper-0.1.0.dev3/depkeeper/utils/http.py +0 -267
  63. depkeeper-0.1.0.dev3/depkeeper/utils/logger.py +0 -146
  64. depkeeper-0.1.0.dev3/depkeeper/utils/version_utils.py +0 -92
  65. depkeeper-0.1.0.dev3/depkeeper.egg-info/PKG-INFO +0 -220
  66. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/LICENSE +0 -0
  67. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper.egg-info/dependency_links.txt +0 -0
  68. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper.egg-info/entry_points.txt +0 -0
  69. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/depkeeper.egg-info/top_level.txt +0 -0
  70. {depkeeper-0.1.0.dev3 → depkeeper-1.0.0}/setup.cfg +0 -0
@@ -0,0 +1,428 @@
1
+ Metadata-Version: 2.4
2
+ Name: depkeeper
3
+ Version: 1.0.0
4
+ Summary: Modern Python dependency management for requirements.txt files
5
+ Author: Rahul Kaushal
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/rahulkaushal04/depkeeper
8
+ Project-URL: Documentation, https://github.com/rahulkaushal04/depkeeper/blob/main/docs/index.md
9
+ Project-URL: Repository, https://github.com/rahulkaushal04/depkeeper
10
+ Project-URL: Bug Tracker, https://github.com/rahulkaushal04/depkeeper/issues
11
+ Project-URL: Changelog, https://github.com/rahulkaushal04/depkeeper/blob/main/CHANGELOG.md
12
+ Keywords: dependencies,requirements,pip,package-management,security,updates
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: System :: Software Distribution
25
+ Requires-Python: >=3.8
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: click>=8.1.8
29
+ Requires-Dist: packaging>=26.2
30
+ Requires-Dist: httpx[http2]>=0.24.1
31
+ Requires-Dist: rich>=14.3.4
32
+ Requires-Dist: tomli>=2.5.0
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=7.4.4; extra == "dev"
35
+ Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
36
+ Requires-Dist: pytest-asyncio>=0.23.8; extra == "dev"
37
+ Requires-Dist: pytest-mock>=3.14.1; extra == "dev"
38
+ Requires-Dist: pytest-httpx>=0.22.0; extra == "dev"
39
+ Requires-Dist: mypy>=1.14.1; extra == "dev"
40
+ Requires-Dist: ruff>=0.17.4; extra == "dev"
41
+ Requires-Dist: types-setuptools; extra == "dev"
42
+ Requires-Dist: pre-commit>=3.5.0; extra == "dev"
43
+ Provides-Extra: test
44
+ Requires-Dist: pytest>=7.4.4; extra == "test"
45
+ Requires-Dist: pytest-cov>=5.0.0; extra == "test"
46
+ Requires-Dist: pytest-asyncio>=0.23.8; extra == "test"
47
+ Requires-Dist: pytest-mock>=3.14.1; extra == "test"
48
+ Requires-Dist: pytest-httpx>=0.22.0; extra == "test"
49
+ Dynamic: license-file
50
+
51
+ <p align="center"><img src="assets/logo.svg" width="120" alt="depkeeper logo"></p>
52
+
53
+ <h1 align="center">depkeeper</h1>
54
+
55
+ <p align="center"><strong>Safe dependency updates for requirements.txt, pyproject.toml and conda environment.yml.</strong></p>
56
+
57
+ <p align="center">
58
+ <a href="https://github.com/rahulkaushal04/depkeeper/actions/workflows/tests.yml"><img src="https://github.com/rahulkaushal04/depkeeper/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
59
+ <a href="https://codecov.io/gh/rahulkaushal04/depkeeper"><img src="https://codecov.io/gh/rahulkaushal04/depkeeper/branch/main/graph/badge.svg" alt="Coverage"></a>
60
+ <a href="https://pypi.org/project/depkeeper/"><img src="https://img.shields.io/pypi/v/depkeeper.svg" alt="PyPI"></a>
61
+ <a href="https://pypi.org/project/depkeeper/"><img src="https://img.shields.io/pypi/pyversions/depkeeper.svg" alt="Python versions"></a>
62
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg" alt="License: Apache-2.0"></a>
63
+ <a href="https://github.com/rahulkaushal04/depkeeper/blob/main/docs/index.md"><img src="https://img.shields.io/badge/docs-in_repo-blue" alt="Docs"></a>
64
+ </p>
65
+
66
+ <p align="center">
67
+ <a href="#install">Install</a> ·
68
+ <a href="#quick-start">Quick start</a> ·
69
+ <a href="#commands">Commands</a> ·
70
+ <a href="#configuration">Configuration</a> ·
71
+ <a href="#use-it-in-ci">CI</a> ·
72
+ <a href="#limitations">Limitations</a> ·
73
+ <a href="https://github.com/rahulkaushal04/depkeeper/blob/main/docs/index.md">Docs</a>
74
+ </p>
75
+
76
+ ---
77
+
78
+ depkeeper is a command-line tool. It reads your dependency file, asks PyPI what is newer, and picks the
79
+ newest version that is **safe** for each package. Then it edits the file in place.
80
+
81
+ It keeps the ranges you wrote. It never jumps a major version. It does not install anything.
82
+
83
+ ## Why
84
+
85
+ `pip list --outdated` shows the latest release. That is often a new major version that breaks your build.
86
+
87
+ depkeeper answers one question:
88
+
89
+ > What is the newest version of each dependency I can take without crossing a major version, without
90
+ > breaking Python compatibility, without violating the constraints I wrote, and without conflicting with
91
+ > the other packages in my file?
92
+
93
+ | | `pip list --outdated` | pip-tools | depkeeper |
94
+ |---|---|---|---|
95
+ | Shows newer versions | ✓ (latest only) | – | ✓ (latest and a safe target) |
96
+ | Respects major-version boundaries | – | – | ✓ |
97
+ | Edits your existing file, keeps your ranges | – | regenerates from `.in` | ✓ |
98
+ | Needs a new file format or lock file | no | `.in` files | no |
99
+ | Resolves the full transitive graph | at install time | ✓ | – (see [Limitations](#limitations)) |
100
+
101
+ depkeeper does not replace a resolver. Run `pip install` and your tests after every update.
102
+
103
+ ## Features
104
+
105
+ **Safe recommendations**
106
+ - Stays inside the current major version (`1.x` is never moved to `2.x`).
107
+ - Skips releases whose `requires_python` excludes your target Python.
108
+ - Keeps upper bounds, exclusions and `~=` ranges. Only the lower bound moves.
109
+ - Never recommends yanked releases. Pre-releases are excluded unless a package sets `allow_prerelease = true` in config.
110
+ - Calendar-versioned packages (such as `certifi`) are detected and labelled `year`, `month` or `patch`.
111
+ - Cross-checks the recommended versions against each other and backs off on conflicts.
112
+
113
+ **File formats**
114
+ - `requirements*.txt` (including `-r` and `-c` files)
115
+ - `pyproject.toml` (PEP 621, PEP 735 dependency groups, `build-system.requires`, Poetry), edited without touching comments or formatting
116
+ - conda `environment.yml` (`dependencies:` and `pip:` lists)
117
+
118
+ **Policy and cooldown**
119
+ - `--min-age 7d` skips versions released too recently.
120
+ - `hold`, `ignore`, `max_update`, `allow_major`, `allow_prerelease` and `versioning` per package, in config.
121
+ - `--ignore PKG` and `--max-update {patch,minor,major}` on the command line.
122
+ - `--allow-major PKG` opts a package into a major upgrade (default: never crosses a major; newer majors are reported).
123
+
124
+ **CI gating**
125
+ - `check --fail-on {none,updates,minor,major}` exits `3` when updates at that level exist.
126
+ - Exit `4` when a package could not be looked up. A failed lookup is never reported as "up to date".
127
+
128
+ **Safe writes**
129
+ - Atomic file replacement, with rollback for multi-file updates.
130
+ - `--backup` saves a timestamped copy of every file it changes.
131
+ - Refuses to drop `--hash` lines unless you say so. `--refresh-hashes` regenerates them from PyPI digests.
132
+ - Aborts if a file changed on disk since it was read.
133
+
134
+ **Verify before writing**
135
+ - `update --verify` asks pip's resolver whether the new set resolves for your target Python (wheels only, nothing is built). On a conflict, nothing is written, and success states what was checked.
136
+
137
+ **Output formats** (`check --format`)
138
+
139
+ | Format | Use it for |
140
+ |---|---|
141
+ | `table` | Reading in a terminal (default) |
142
+ | `simple` | Plain lines, easy to grep |
143
+ | `json` | Bare array of packages |
144
+ | `json-v2` | Versioned envelope with file, line, age and links |
145
+ | `markdown` | A report for a PR body or CI summary |
146
+
147
+ For `simple`, `json`, `json-v2` and `markdown`, only the payload goes to stdout. Logs and warnings go to
148
+ stderr, so `depkeeper -v check -f json | jq` works.
149
+
150
+ ## Install
151
+
152
+ ```bash
153
+ python -m pip install depkeeper # into your project's environment
154
+ pipx install depkeeper # or as an isolated tool
155
+ ```
156
+
157
+ Requires Python 3.8 or newer.
158
+
159
+ > [!IMPORTANT]
160
+ > depkeeper only proposes versions that support your **target Python**. It picks that version in this
161
+ > order: `--python-version X.Y`, the `python_version` config key, the file's own `requires-python` (or
162
+ > conda `python` pin), then the Python that runs depkeeper. If your project targets 3.12 and none of the
163
+ > first three is set, run depkeeper on 3.12 or pass `--python-version 3.12`.
164
+
165
+ ```bash
166
+ depkeeper --version
167
+ ```
168
+
169
+ ## Quick start
170
+
171
+ Start with a small `requirements.txt`:
172
+
173
+ ```text
174
+ requests==2.28.0
175
+ flask>=2.0,<2.3
176
+ click~=8.0
177
+ urllib3==1.26.0
178
+ ```
179
+
180
+ **1. Look (read-only).** The output below is real, captured on the date of writing. Versions and ages will
181
+ differ when you run it.
182
+
183
+ ```console
184
+ $ depkeeper check --format simple
185
+ [OUTDATED] requests 2.28.0 → 2.34.2
186
+ released 175 days ago
187
+ Python: installed: >=3.7, <4, latest: >=3.10, recommended: >=3.10
188
+ [OUTDATED] flask 2.0 → 3.1.3 (recommended: 2.2.5)
189
+ released 1283 days ago
190
+ Python: latest: >=3.9, recommended: >=3.7
191
+ [OUTDATED] click 8.0 → 8.5.0
192
+ released 72 days ago
193
+ Python: latest: >=3.10, recommended: >=3.10
194
+ [OUTDATED] urllib3 1.26.0 → 2.8.0 (recommended: 1.26.20)
195
+ released 798 days ago
196
+ Python: installed: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4, latest: >=3.10, recommended: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7
197
+ ```
198
+
199
+ Read `recommended` as "what depkeeper will write". `flask` stays on 2.2.5 because of your `<2.3` cap.
200
+ `urllib3` stays on 1.x because 2.x is a major jump.
201
+
202
+ **2. Preview the write.**
203
+
204
+ ```console
205
+ $ depkeeper update --dry-run
206
+ [WARNING] major available: urllib3 2.8.0 (use --allow-major urllib3)
207
+ Update Plan (Dry Run)
208
+ ┏━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
209
+ ┃ Package ┃ Current ┃ New Version ┃ Change ┃ Python Requires ┃
210
+ ┡━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
211
+ │ requests │ 2.28.0 │ 2.34.2 │ minor │ >=3.10 │
212
+ │ flask │ 2.0 │ 2.2.5 │ minor │ >=3.7 │
213
+ │ click │ 8.0 │ 8.5.0 │ minor │ >=3.10 │
214
+ │ urllib3 │ 1.26.0 │ 1.26.20 │ patch │ !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2. │
215
+ └──────────┴─────────┴─────────────┴────────┴──────────────────────────────────────────────────────┘
216
+ Dry run mode - no changes applied
217
+ ```
218
+
219
+ (The real table also prints a `175d old`-style age under each new version. It is trimmed here.)
220
+
221
+ **3. Apply.**
222
+
223
+ ```console
224
+ $ depkeeper update --backup --yes
225
+ [OK] Backup saved: requirements.txt.20261106_182111_458701_7f45f023.backup
226
+ [OK] Successfully updated 4 package(s)
227
+ ```
228
+
229
+ ```text
230
+ requests==2.34.2
231
+ flask>=2.2.5,<2.3
232
+ click~=8.5
233
+ urllib3==1.26.20
234
+ ```
235
+
236
+ Only the lower bounds moved. `<2.3` stayed. `~=` stayed. Add `--pin` if you want exact `==` pins instead.
237
+
238
+ **4. Test.**
239
+
240
+ ```bash
241
+ python -m pip install -r requirements.txt && pytest
242
+ ```
243
+
244
+ ## Supported files
245
+
246
+ | File | Read | Written | Notes |
247
+ |---|---|---|---|
248
+ | `requirements*.txt` | ✓ | ✓ | `-r` includes are followed and rewritten. `-c` constraint files are read, never rewritten. |
249
+ | `pyproject.toml` | ✓ | ✓ | PEP 621, PEP 735 groups, `build-system.requires`, Poetry. Comments and layout are kept. |
250
+ | conda `environment.yml` | ✓ | ✓ | Conda packages are looked up on PyPI by name. This is approximate. |
251
+ | `Pipfile`, `poetry.lock`, `setup.py`, `setup.cfg` | – | – | Not supported. |
252
+
253
+ Editable installs, VCS URLs, direct URLs and local paths are reported but never changed.
254
+
255
+ ## Commands
256
+
257
+ ```text
258
+ depkeeper [-c FILE] [-v|-vv] [--color|--no-color] COMMAND [FILE]
259
+ ```
260
+
261
+ Global options go before the command: `depkeeper -v check`. `FILE` defaults to `requirements.txt`.
262
+
263
+ | Command | What it does |
264
+ |---|---|
265
+ | `depkeeper check` | Report available updates. Never writes. |
266
+ | `depkeeper check --outdated-only` | Hide packages that are up to date. |
267
+ | `depkeeper check -f json` | Machine-readable output (`simple`, `json`, `json-v2`, `markdown`, `table`). |
268
+ | `depkeeper check --fail-on minor` | Exit `3` if a minor or major update exists. |
269
+ | `depkeeper check --min-age 7d` | Ignore versions released in the last 7 days. |
270
+ | `depkeeper check --python-version 3.12` | Pick versions that support Python 3.12. |
271
+ | `depkeeper check --ignore PKG` | Skip a package (repeatable). |
272
+ | `depkeeper update --allow-major django` | Let `django` cross its major, within your declared bounds. |
273
+ | `depkeeper update --dry-run` | Show the plan. Write nothing. |
274
+ | `depkeeper update -y` | Apply without the confirmation prompt. |
275
+ | `depkeeper update --backup` | Save a timestamped backup of every file first. |
276
+ | `depkeeper update --verify` | Check with pip's resolver before writing. |
277
+ | `depkeeper update --pin` | Write exact `==` pins. |
278
+ | `depkeeper update -p NAME` | Update only these packages (repeatable). |
279
+ | `depkeeper update --ignore NAME` | Never update these packages (repeatable). |
280
+ | `depkeeper update --max-update minor` | Skip updates larger than `minor`. |
281
+ | `depkeeper update --refresh-hashes` | Regenerate `--hash` lines for the new version. |
282
+ | `depkeeper update --allow-hash-removal` | Update hashed requirements and drop their hashes. |
283
+ | `depkeeper update -f markdown` | Print a PR description of the changes (needs `--yes` or `--dry-run`). |
284
+
285
+ Both commands also accept `--strict-version-matching` and `--check-conflicts / --no-check-conflicts`.
286
+ `--refresh-hashes` and `--allow-hash-removal` cannot be used together.
287
+
288
+ Full flag reference: [CLI commands](https://github.com/rahulkaushal04/depkeeper/blob/main/docs/reference/cli-commands.md).
289
+
290
+ ### Exit codes
291
+
292
+ | Code | Meaning |
293
+ |---|---|
294
+ | `0` | Success. Updates being available is **not** an error. |
295
+ | `1` | Error (bad file, bad config, write failed, `--verify` found a conflict, hash removal refused). `update` also exits `1` if you decline the prompt or `--packages` matches nothing. |
296
+ | `2` | Usage error. |
297
+ | `3` | `check --fail-on`: updates at or above the chosen level exist. |
298
+ | `4` | A package could not be looked up on PyPI. |
299
+ | `130` | Interrupted. |
300
+
301
+ ## Configuration
302
+
303
+ Put settings in `depkeeper.toml` (table `[depkeeper]`) or in `pyproject.toml` (table `[tool.depkeeper]`).
304
+ depkeeper looks in the current directory, then its parents up to the repository root. A command-line flag
305
+ always beats the config file.
306
+
307
+ ```toml
308
+ # depkeeper.toml
309
+ [depkeeper]
310
+ min_age = "7d" # cooldown: skip releases newer than 7 days
311
+ fail_on = "minor" # `check` exits 3 on minor or major updates
312
+ python_version = "3.12" # target Python for compatibility filtering
313
+ ignore = ["boto3"] # never touch these
314
+ check_conflicts = true
315
+
316
+ [depkeeper.packages.django]
317
+ max_update = "patch" # django: patch updates only
318
+
319
+ [depkeeper.packages.flask]
320
+ hold = true # flask: report, but do not update
321
+ ```
322
+
323
+ Keys: `check_conflicts`, `strict_version_matching`, `fail_on`, `ignore`, `python_version`, `min_age`,
324
+ `versioning`, `packages`. Unknown keys are an error. Details:
325
+ [Configuration options](https://github.com/rahulkaushal04/depkeeper/blob/main/docs/reference/configuration-options.md).
326
+
327
+ ## Use it in CI
328
+
329
+ `check` exits `0` even when updates exist. Use `--fail-on` to make a pipeline fail on drift. A scheduled
330
+ job that fails when a minor or major update is waiting:
331
+
332
+ ```yaml
333
+ # .github/workflows/dependency-drift.yml
334
+ name: Dependency drift
335
+ on:
336
+ schedule:
337
+ - cron: "0 6 * * 1" # Mondays, 06:00 UTC
338
+ workflow_dispatch:
339
+
340
+ permissions:
341
+ contents: read
342
+
343
+ jobs:
344
+ check:
345
+ runs-on: ubuntu-latest
346
+ steps:
347
+ - uses: actions/checkout@v4
348
+ - uses: actions/setup-python@v5
349
+ with:
350
+ python-version: "3.12"
351
+ - run: python -m pip install "depkeeper==X.Y.Z" # pin to a release you have tested: recommendations are behaviour
352
+ - run: depkeeper check --fail-on minor --min-age 7d
353
+ ```
354
+
355
+ The job fails on exit `3` (updates found) and on exit `4` (a lookup failed). To publish a report instead of
356
+ failing, drop `--fail-on` and add `--format markdown >> "$GITHUB_STEP_SUMMARY"`.
357
+
358
+ <details>
359
+ <summary>Open an update PR instead</summary>
360
+
361
+ `update --format markdown` prints a PR body. Pair it with any pull-request action.
362
+
363
+ ```bash
364
+ depkeeper update --yes --verify --format markdown > pr-body.md
365
+ python -m pip install -r requirements.txt && pytest
366
+ ```
367
+
368
+ `--verify` aborts with exit `1` and writes nothing if pip's resolver rejects the new set.
369
+ More patterns: [CI/CD integration](https://github.com/rahulkaushal04/depkeeper/blob/main/docs/guides/ci-cd-integration.md).
370
+
371
+ </details>
372
+
373
+ ## How it works
374
+
375
+ <p align="center"><img src="assets/diagrams/how-it-works.svg" alt="depkeeper parses your dependency file, fetches PyPI metadata, filters each package down to one safe version (staying within its major unless --allow-major), cross-checks the picks, then reports or writes." width="900"></p>
376
+
377
+ <p align="center"><em>Candidate versions pass through six named rules to one pick; only the write branch touches your files.</em></p>
378
+
379
+ depkeeper is a synchronous CLI around an async core. It keeps no cache and no state between runs.
380
+ Design notes: [ARCHITECTURE.md](ARCHITECTURE.md).
381
+
382
+ ## Limitations
383
+
384
+ - **Not a full resolver.** depkeeper cross-checks the packages you declared using each version's direct
385
+ dependencies. It does not expand the transitive graph. `update --verify` and `pip install` are the
386
+ real test.
387
+ - **PyPI only.** `--index-url`, `--extra-index-url` and `--find-links` are parsed and ignored.
388
+ Packages that exist only on a private index show up as errors.
389
+ - **Major upgrades are opt-in.** A floor of `django>=3.2,<5.0` stays on the newest `3.x` and reports the
390
+ newer major. Use `--allow-major django` to move to the newest `4.x` (your `<5.0` is still respected).
391
+ - **conda is approximate.** Conda-channel packages are matched to PyPI by name. A missing match is a
392
+ warning, not a failure.
393
+ - **Pre-releases are off by default.** There is no `--pre` flag; opt in per package with `allow_prerelease = true`.
394
+ - **No security scanning.** Use `pip-audit` or similar next to depkeeper.
395
+ - **No cache.** Every run fetches metadata again.
396
+ - **Hash removal leaves a partly hashed file.** `pip install --require-hashes` rejects that. Prefer
397
+ `--refresh-hashes`.
398
+ - **Direct references are never updated.** VCS, URL, local path and editable requirements are reported only.
399
+
400
+ Full list: [Known limitations](https://github.com/rahulkaushal04/depkeeper/blob/main/docs/reference/limitations.md).
401
+
402
+ ## Documentation
403
+
404
+ Full docs: **[docs/](https://github.com/rahulkaushal04/depkeeper/blob/main/docs/index.md)**
405
+
406
+ - [Getting started](https://github.com/rahulkaushal04/depkeeper/blob/main/docs/getting-started/index.md)
407
+ - [Concepts](https://github.com/rahulkaushal04/depkeeper/blob/main/docs/concepts/index.md): how versions are recommended, how conflicts are resolved, write safety
408
+ - [Guides](https://github.com/rahulkaushal04/depkeeper/blob/main/docs/guides/index.md): checking, updating, configuration, CI/CD, troubleshooting
409
+ - [Reference](https://github.com/rahulkaushal04/depkeeper/blob/main/docs/reference/index.md): CLI, config, exit codes, file formats, JSON output
410
+ - [CHANGELOG.md](CHANGELOG.md)
411
+
412
+ ## Contributing
413
+
414
+ Issues and pull requests are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md).
415
+
416
+ ```bash
417
+ git clone https://github.com/rahulkaushal04/depkeeper.git
418
+ cd depkeeper
419
+ pip install -e ".[dev]"
420
+ python -m pytest tests -q --no-cov
421
+ ```
422
+
423
+ Report security problems privately. See [SECURITY.md](SECURITY.md). Please follow the
424
+ [Code of Conduct](CODE_OF_CONDUCT.md).
425
+
426
+ ## License
427
+
428
+ [Apache License 2.0](LICENSE). Copyright 2025-2026 Rahul Kaushal.