dependency-update-metrics 0.1.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.
- dependency_update_metrics-0.1.0/LICENSE +21 -0
- dependency_update_metrics-0.1.0/MANIFEST.in +6 -0
- dependency_update_metrics-0.1.0/PKG-INFO +320 -0
- dependency_update_metrics-0.1.0/README.md +283 -0
- dependency_update_metrics-0.1.0/dependency_metrics/__init__.py +12 -0
- dependency_update_metrics-0.1.0/dependency_metrics/analyzer.py +1687 -0
- dependency_update_metrics-0.1.0/dependency_metrics/cache_config.py +69 -0
- dependency_update_metrics-0.1.0/dependency_metrics/cli.py +2060 -0
- dependency_update_metrics-0.1.0/dependency_metrics/depsdev_client.py +147 -0
- dependency_update_metrics-0.1.0/dependency_metrics/depsdev_resolver.py +445 -0
- dependency_update_metrics-0.1.0/dependency_metrics/interfaces.py +57 -0
- dependency_update_metrics-0.1.0/dependency_metrics/models.py +50 -0
- dependency_update_metrics-0.1.0/dependency_metrics/osv_builder.py +295 -0
- dependency_update_metrics-0.1.0/dependency_metrics/osv_service.py +251 -0
- dependency_update_metrics-0.1.0/dependency_metrics/pypi_resolver.py +153 -0
- dependency_update_metrics-0.1.0/dependency_metrics/reporting.py +227 -0
- dependency_update_metrics-0.1.0/dependency_metrics/resolvers.py +1366 -0
- dependency_update_metrics-0.1.0/dependency_metrics/time_utils.py +39 -0
- dependency_update_metrics-0.1.0/dependency_update_metrics.egg-info/PKG-INFO +320 -0
- dependency_update_metrics-0.1.0/dependency_update_metrics.egg-info/SOURCES.txt +45 -0
- dependency_update_metrics-0.1.0/dependency_update_metrics.egg-info/dependency_links.txt +1 -0
- dependency_update_metrics-0.1.0/dependency_update_metrics.egg-info/entry_points.txt +2 -0
- dependency_update_metrics-0.1.0/dependency_update_metrics.egg-info/requires.txt +15 -0
- dependency_update_metrics-0.1.0/dependency_update_metrics.egg-info/top_level.txt +1 -0
- dependency_update_metrics-0.1.0/pyproject.toml +73 -0
- dependency_update_metrics-0.1.0/setup.cfg +4 -0
- dependency_update_metrics-0.1.0/tests/test_basic.py +122 -0
- dependency_update_metrics-0.1.0/tests/test_bulk_csv.py +96 -0
- dependency_update_metrics-0.1.0/tests/test_cache_config.py +52 -0
- dependency_update_metrics-0.1.0/tests/test_cli_progress.py +391 -0
- dependency_update_metrics-0.1.0/tests/test_cli_resume.py +262 -0
- dependency_update_metrics-0.1.0/tests/test_crates_resolver.py +162 -0
- dependency_update_metrics-0.1.0/tests/test_dependency_first_use.py +169 -0
- dependency_update_metrics-0.1.0/tests/test_depsdev_client.py +244 -0
- dependency_update_metrics-0.1.0/tests/test_depsdev_resolver.py +949 -0
- dependency_update_metrics-0.1.0/tests/test_metamorphic.py +156 -0
- dependency_update_metrics-0.1.0/tests/test_models.py +50 -0
- dependency_update_metrics-0.1.0/tests/test_npm_resolver.py +326 -0
- dependency_update_metrics-0.1.0/tests/test_npm_semver.py +29 -0
- dependency_update_metrics-0.1.0/tests/test_osv.py +184 -0
- dependency_update_metrics-0.1.0/tests/test_per_release.py +541 -0
- dependency_update_metrics-0.1.0/tests/test_pypi_resolver.py +260 -0
- dependency_update_metrics-0.1.0/tests/test_pypi_version_resolution.py +121 -0
- dependency_update_metrics-0.1.0/tests/test_reporting.py +116 -0
- dependency_update_metrics-0.1.0/tests/test_resolver_cache.py +411 -0
- dependency_update_metrics-0.1.0/tests/test_severity_breakdown.py +340 -0
- dependency_update_metrics-0.1.0/tests/test_time_utils.py +49 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Imranur Rahman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dependency-update-metrics
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A tool for analyzing time-to-update and time-to-remediate metrics for package dependencies
|
|
5
|
+
Author-email: Imranur Rahman <irahman3@ncsu.edu>, Mahzabina Tamanna <mtamman@ncsu.edu>, Laurie Williams <lawilli3@ncsu.edu>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://imranur-rahman.github.io/dependency-update-metrics/
|
|
8
|
+
Project-URL: Bug Reports, https://github.com/imranur-rahman/dependency-update-metrics/issues
|
|
9
|
+
Project-URL: Source, https://github.com/imranur-rahman/dependency-update-metrics
|
|
10
|
+
Keywords: dependencies,metrics,security,vulnerabilities,npm,pypi
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: pandas>=1.3.0
|
|
23
|
+
Requires-Dist: requests>=2.25.0
|
|
24
|
+
Requires-Dist: tqdm>=4.60.0
|
|
25
|
+
Requires-Dist: packaging>=21.0
|
|
26
|
+
Requires-Dist: openpyxl>=3.0.0
|
|
27
|
+
Requires-Dist: pyarrow>=6.0.0
|
|
28
|
+
Requires-Dist: psutil>=5.8.0
|
|
29
|
+
Requires-Dist: semantic-version>=2.10.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: black>=22.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: flake8>=4.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: mypy>=0.950; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# Dependency Update Metrics
|
|
39
|
+
|
|
40
|
+
A Python tool for analyzing time-to-update (TTU) and time-to-remediate (TTR) metrics for package dependencies across different ecosystems (npm, PyPI, Cargo/crates.io).
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- **Multi-ecosystem support**: Analyze packages from npm, PyPI, and Cargo/crates.io
|
|
45
|
+
- **Time-to-Update (TTU)**: Measure how long dependencies stay behind the latest version
|
|
46
|
+
- **Time-to-Remediate (TTR)**: Measure how long known vulnerabilities remain unpatched
|
|
47
|
+
- **Flexible weighting**: Support for linear, exponential, inverse, or no weighting
|
|
48
|
+
- **OSV integration**: Uses the Open Source Vulnerabilities database for security analysis
|
|
49
|
+
- **Export capabilities**: Export detailed worksheets and OSV data
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
### From PyPI (when published)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install dependency-update-metrics
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### From source
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/imranur-rahman/dependency-update-metrics.git
|
|
63
|
+
cd dependency-update-metrics
|
|
64
|
+
pip install -e .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
### As a command-line tool
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Basic usage
|
|
73
|
+
dependency-metrics --ecosystem npm --package express
|
|
74
|
+
|
|
75
|
+
# With date range
|
|
76
|
+
dependency-metrics --ecosystem npm --package express \
|
|
77
|
+
--start-date 2020-01-01 \
|
|
78
|
+
--end-date 2023-12-31
|
|
79
|
+
|
|
80
|
+
# With exponential weighting
|
|
81
|
+
dependency-metrics --ecosystem npm --package express \
|
|
82
|
+
--weighting-type exponential \
|
|
83
|
+
--half-life 180
|
|
84
|
+
|
|
85
|
+
# Build OSV database first (one-time operation)
|
|
86
|
+
dependency-metrics --ecosystem npm --package express --build-osv
|
|
87
|
+
|
|
88
|
+
# Export detailed worksheets
|
|
89
|
+
dependency-metrics --ecosystem npm --package express --get-worksheets
|
|
90
|
+
|
|
91
|
+
# Get OSV vulnerability data
|
|
92
|
+
dependency-metrics --ecosystem npm --package express --get-osv
|
|
93
|
+
|
|
94
|
+
# Test a PyPI package
|
|
95
|
+
dependency-metrics --ecosystem pypi --package requests --start-date 2020-01-01 --end-date 2021-01-01 --get-worksheets
|
|
96
|
+
|
|
97
|
+
# Test a Cargo/crates.io package
|
|
98
|
+
dependency-metrics --ecosystem cargo --package serde --start-date 2020-01-01 --end-date 2024-01-01
|
|
99
|
+
|
|
100
|
+
# Bulk CSV input (parallel by default)
|
|
101
|
+
dependency-metrics --input-csv ./input.csv --workers 8 --output-dir ./output
|
|
102
|
+
|
|
103
|
+
# Resume an interrupted bulk run
|
|
104
|
+
dependency-metrics --input-csv ./input.csv --output-dir ./output --resume
|
|
105
|
+
|
|
106
|
+
# Per-release analysis: MTTU/MTTR at every release of the parent package
|
|
107
|
+
dependency-metrics --ecosystem pypi --package requests \
|
|
108
|
+
--start-date 2020-01-01 --end-date 2021-01-01 \
|
|
109
|
+
--per-release --get-worksheets
|
|
110
|
+
|
|
111
|
+
# Per-release in bulk CSV mode
|
|
112
|
+
dependency-metrics --input-csv ./input.csv --per-release --workers 4
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Input CSV columns: `ecosystem`, `package_name`, optional `start_date`, optional `end_date`.
|
|
116
|
+
Accepted ecosystem values are `npm`, `pypi`, `cargo`, and `crates.io`; `crates.io` is normalized to `cargo` in outputs.
|
|
117
|
+
If `end_date` is omitted, the CLI `--end-date` value is used; when `--end-date` is also omitted, it defaults to today.
|
|
118
|
+
Extra columns are allowed; duplicates are removed by `ecosystem, package_name, end_date`.
|
|
119
|
+
Bulk mode computes metrics per package using the latest dependency set.
|
|
120
|
+
|
|
121
|
+
### As a Python module
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from datetime import datetime
|
|
125
|
+
from pathlib import Path
|
|
126
|
+
from dependency_metrics.analyzer import DependencyAnalyzer
|
|
127
|
+
|
|
128
|
+
# Create analyzer
|
|
129
|
+
analyzer = DependencyAnalyzer(
|
|
130
|
+
ecosystem="npm",
|
|
131
|
+
package="express",
|
|
132
|
+
start_date=datetime(2020, 1, 1),
|
|
133
|
+
end_date=datetime(2023, 12, 31),
|
|
134
|
+
weighting_type="exponential",
|
|
135
|
+
half_life=180,
|
|
136
|
+
output_dir=Path("./output")
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Run analysis
|
|
140
|
+
results = analyzer.analyze()
|
|
141
|
+
|
|
142
|
+
print(f"Average TTU: {results['ttu']:.2f} days")
|
|
143
|
+
print(f"Average TTR: {results['ttr']:.2f} days")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Command-line Arguments
|
|
147
|
+
|
|
148
|
+
- `--ecosystem`: Ecosystem to analyze (`npm`, `pypi`, `cargo`, or `crates.io`) [Required unless `--input-csv`]
|
|
149
|
+
- `--package`: Package name to analyze [Required unless `--input-csv`]
|
|
150
|
+
- `--input-csv`: CSV file with columns `ecosystem, package_name`, optional `start_date`, and optional `end_date`. If `end_date` is omitted, `--end-date` is used.
|
|
151
|
+
- `--start-date`: Start date for analysis (YYYY-MM-DD) [Default: 1900-01-01]
|
|
152
|
+
- `--end-date`: End date for analysis (YYYY-MM-DD) [Default: today]
|
|
153
|
+
- `--weighting-type`: Weighting method (`linear`, `exponential`, `inverse`, `disable`) [Default: disable]
|
|
154
|
+
- `--half-life`: Half-life in days (required for exponential weighting)
|
|
155
|
+
- `--build-osv`: Build/update the OSV vulnerability database
|
|
156
|
+
- `--get-osv`: Export OSV data for the package's dependencies
|
|
157
|
+
- `--get-worksheets`: Export detailed analysis worksheets to Excel
|
|
158
|
+
- `--output-dir`: Output directory for results [Default: ./output]
|
|
159
|
+
- `--workers`: Number of parallel workers for bulk CSV mode [Default: min(8, CPU count)]
|
|
160
|
+
- `--package-timeout`: Per-package analysis timeout in seconds (bulk CSV mode only). Packages that exceed this time receive an error row and are skipped [Default: disabled]
|
|
161
|
+
- `--resume`: Resume bulk CSV runs by skipping completed rows and retrying errors
|
|
162
|
+
- `--per-release`: Compute MTTU/MTTR at every release of the parent package within the window (one output row per release)
|
|
163
|
+
|
|
164
|
+
## Weighting Methods
|
|
165
|
+
|
|
166
|
+
### Disable (default)
|
|
167
|
+
No weighting applied. All time periods are weighted equally.
|
|
168
|
+
|
|
169
|
+
### Linear
|
|
170
|
+
Weight decreases linearly with age:
|
|
171
|
+
```
|
|
172
|
+
weight = 1 - (age / max_age)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Exponential
|
|
176
|
+
Weight decreases exponentially based on half-life:
|
|
177
|
+
```
|
|
178
|
+
weight = exp(-λ * age)
|
|
179
|
+
where λ = ln(2) / half_life
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Inverse
|
|
183
|
+
Weight is inversely proportional to age:
|
|
184
|
+
```
|
|
185
|
+
weight = 1 / (1 + age)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Output
|
|
189
|
+
|
|
190
|
+
The tool generates several outputs in the specified output directory:
|
|
191
|
+
|
|
192
|
+
1. **JSON results file**: Contains TTU, TTR, and metadata
|
|
193
|
+
2. **OSV data** (with `--get-osv`): CSV file with vulnerability information
|
|
194
|
+
3. **Excel worksheets** (with `--get-worksheets`): Detailed analysis for each dependency
|
|
195
|
+
4. **Bulk CSV results** (with `--input-csv`): Summary CSV, plus an optional dependency details CSV with `--write-dependency-details`
|
|
196
|
+
5. **Per-release results** (with `--per-release`): One row per package release with `package_version`, `package_release_date`, `window_start`, `window_end`, `mttu`, `mttr`
|
|
197
|
+
|
|
198
|
+
### Example JSON output
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"package": "express",
|
|
203
|
+
"ecosystem": "npm",
|
|
204
|
+
"version": "4.18.2",
|
|
205
|
+
"start_date": "2020-01-01T00:00:00",
|
|
206
|
+
"end_date": "2023-12-31T00:00:00",
|
|
207
|
+
"weighting_type": "exponential",
|
|
208
|
+
"half_life": 180,
|
|
209
|
+
"ttu": 45.32,
|
|
210
|
+
"ttr": 12.15,
|
|
211
|
+
"num_dependencies": 30
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## How It Works
|
|
216
|
+
|
|
217
|
+
1. **Package Analysis**: Fetches package metadata from the registry
|
|
218
|
+
2. **Dependency Extraction**: Extracts dependencies from the package version closest to the end date
|
|
219
|
+
3. **Timeline Construction**: Creates a timeline of version releases for each dependency
|
|
220
|
+
4. **Version Resolution**: Resolves dependency versions at each time interval via ecosystem resolvers
|
|
221
|
+
5. **Remediation Check**: Uses the OSV service to determine vulnerability remediation
|
|
222
|
+
6. **Metric Calculation**:
|
|
223
|
+
- **TTU**: Measures time when dependency is not at the highest available version
|
|
224
|
+
- **TTR**: Measures time when dependency has known vulnerabilities
|
|
225
|
+
7. **Weighting**: Applies optional time-based weighting to prioritize recent periods
|
|
226
|
+
8. **Aggregation**: Averages metrics across all dependencies
|
|
227
|
+
|
|
228
|
+
## Architecture Notes
|
|
229
|
+
|
|
230
|
+
- Ecosystem-specific resolution is encapsulated in `dependency_metrics/resolvers.py`.
|
|
231
|
+
- OSV remediation checks are handled by `dependency_metrics/osv_service.py`.
|
|
232
|
+
- Reporting/export helpers live in `dependency_metrics/reporting.py`.
|
|
233
|
+
- Bulk CSV mode groups rows by `(ecosystem, package_name)` and processes each group sequentially while running groups in parallel to maximize cache reuse.
|
|
234
|
+
- For bulk runs, a single package analysis computes metrics across multiple end dates using the latest dependency set.
|
|
235
|
+
- `--per-release` mode uses `analyze_at_release_points()` which fetches per-version dependency sets and builds a shared dep-cache covering the full window; it slices that cache for each release sub-window `[start_date, release_date]`.
|
|
236
|
+
- **Cache invalidation:** `ResolverCache` (`dependency_metrics/resolvers.py`) persists all HTTP responses to a SQLite file at `<output-dir>/cache/cache.db`, keyed only by `(namespace, key)` — there is no TTL. This is fine for per-version data (a specific version's dependencies/metadata never changes), but package-level version-list lookups (`DepsDevClient.get_package()`'s `depsdev_package` namespace, and each resolver's `fetch_package_metadata()`/`_get_npm_time_data()` under the `metadata`/`npm_time` namespaces) cache the *entire*, mutable version list. Those lookups compare the requested `end_date` against the newest publish date already in the cached listing and transparently re-fetch whenever the cache can't yet rule out a newer release — so re-running against the same `--output-dir` won't silently miss versions published since the last run. The tradeoff: analyzing "as of today" re-fetches each package's version list on every run (since "today" is always newer than the last known release), which is the correct behavior — only truly historical, fixed `end_date` values benefit from the cache staying warm across runs.
|
|
237
|
+
|
|
238
|
+
## Requirements
|
|
239
|
+
|
|
240
|
+
- Python 3.10+
|
|
241
|
+
- git (for auto-installing the patched pip on first PyPI analysis)
|
|
242
|
+
- npm CLI (for npm ecosystem analysis) `sudo apt install nodejs npm`
|
|
243
|
+
- Internet connection (for fetching package metadata and OSV data)
|
|
244
|
+
|
|
245
|
+
> **Note:** PyPI analysis uses a patched pip fork that adds date-bounded version
|
|
246
|
+
> resolution. The tool clones it automatically to `~/.dependency_metrics/vendor/pip`
|
|
247
|
+
> on first use — no manual setup required.
|
|
248
|
+
|
|
249
|
+
## Development
|
|
250
|
+
|
|
251
|
+
### Setup development environment
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
git clone https://github.com/imranur-rahman/dependency-update-metrics.git
|
|
255
|
+
cd dependency-update-metrics
|
|
256
|
+
pip install -e ".[dev]"
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Run tests
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
pytest
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Code formatting
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
black dependency_metrics/
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Publishing to PyPI
|
|
272
|
+
|
|
273
|
+
### Prepare for publishing
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# Install build tools
|
|
277
|
+
pip install build twine
|
|
278
|
+
|
|
279
|
+
# Build distribution
|
|
280
|
+
python -m build
|
|
281
|
+
|
|
282
|
+
# Check distribution
|
|
283
|
+
twine check dist/*
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Upload to PyPI
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
# Upload to Test PyPI first
|
|
290
|
+
twine upload --repository testpypi dist/*
|
|
291
|
+
|
|
292
|
+
# If all looks good, upload to PyPI
|
|
293
|
+
twine upload dist/*
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## License
|
|
297
|
+
|
|
298
|
+
MIT License - see LICENSE file for details
|
|
299
|
+
|
|
300
|
+
## Contributing
|
|
301
|
+
|
|
302
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
303
|
+
|
|
304
|
+
## Citation
|
|
305
|
+
|
|
306
|
+
If you use this tool in your research, please cite:
|
|
307
|
+
|
|
308
|
+
```bibtex
|
|
309
|
+
@software{dependency_metrics,
|
|
310
|
+
author = {Rahman, Imranur},
|
|
311
|
+
title = {Dependency Update Metrics},
|
|
312
|
+
year = {2025},
|
|
313
|
+
url = {https://github.com/imranur-rahman/dependency-update-metrics}
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Acknowledgments
|
|
318
|
+
|
|
319
|
+
- Uses data from [Open Source Vulnerabilities (OSV)](https://osv.dev/)
|
|
320
|
+
- Package metadata from [npm Registry](https://registry.npmjs.org/) and [PyPI](https://pypi.org/)
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# Dependency Update Metrics
|
|
2
|
+
|
|
3
|
+
A Python tool for analyzing time-to-update (TTU) and time-to-remediate (TTR) metrics for package dependencies across different ecosystems (npm, PyPI, Cargo/crates.io).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multi-ecosystem support**: Analyze packages from npm, PyPI, and Cargo/crates.io
|
|
8
|
+
- **Time-to-Update (TTU)**: Measure how long dependencies stay behind the latest version
|
|
9
|
+
- **Time-to-Remediate (TTR)**: Measure how long known vulnerabilities remain unpatched
|
|
10
|
+
- **Flexible weighting**: Support for linear, exponential, inverse, or no weighting
|
|
11
|
+
- **OSV integration**: Uses the Open Source Vulnerabilities database for security analysis
|
|
12
|
+
- **Export capabilities**: Export detailed worksheets and OSV data
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
### From PyPI (when published)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install dependency-update-metrics
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### From source
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/imranur-rahman/dependency-update-metrics.git
|
|
26
|
+
cd dependency-update-metrics
|
|
27
|
+
pip install -e .
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### As a command-line tool
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Basic usage
|
|
36
|
+
dependency-metrics --ecosystem npm --package express
|
|
37
|
+
|
|
38
|
+
# With date range
|
|
39
|
+
dependency-metrics --ecosystem npm --package express \
|
|
40
|
+
--start-date 2020-01-01 \
|
|
41
|
+
--end-date 2023-12-31
|
|
42
|
+
|
|
43
|
+
# With exponential weighting
|
|
44
|
+
dependency-metrics --ecosystem npm --package express \
|
|
45
|
+
--weighting-type exponential \
|
|
46
|
+
--half-life 180
|
|
47
|
+
|
|
48
|
+
# Build OSV database first (one-time operation)
|
|
49
|
+
dependency-metrics --ecosystem npm --package express --build-osv
|
|
50
|
+
|
|
51
|
+
# Export detailed worksheets
|
|
52
|
+
dependency-metrics --ecosystem npm --package express --get-worksheets
|
|
53
|
+
|
|
54
|
+
# Get OSV vulnerability data
|
|
55
|
+
dependency-metrics --ecosystem npm --package express --get-osv
|
|
56
|
+
|
|
57
|
+
# Test a PyPI package
|
|
58
|
+
dependency-metrics --ecosystem pypi --package requests --start-date 2020-01-01 --end-date 2021-01-01 --get-worksheets
|
|
59
|
+
|
|
60
|
+
# Test a Cargo/crates.io package
|
|
61
|
+
dependency-metrics --ecosystem cargo --package serde --start-date 2020-01-01 --end-date 2024-01-01
|
|
62
|
+
|
|
63
|
+
# Bulk CSV input (parallel by default)
|
|
64
|
+
dependency-metrics --input-csv ./input.csv --workers 8 --output-dir ./output
|
|
65
|
+
|
|
66
|
+
# Resume an interrupted bulk run
|
|
67
|
+
dependency-metrics --input-csv ./input.csv --output-dir ./output --resume
|
|
68
|
+
|
|
69
|
+
# Per-release analysis: MTTU/MTTR at every release of the parent package
|
|
70
|
+
dependency-metrics --ecosystem pypi --package requests \
|
|
71
|
+
--start-date 2020-01-01 --end-date 2021-01-01 \
|
|
72
|
+
--per-release --get-worksheets
|
|
73
|
+
|
|
74
|
+
# Per-release in bulk CSV mode
|
|
75
|
+
dependency-metrics --input-csv ./input.csv --per-release --workers 4
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Input CSV columns: `ecosystem`, `package_name`, optional `start_date`, optional `end_date`.
|
|
79
|
+
Accepted ecosystem values are `npm`, `pypi`, `cargo`, and `crates.io`; `crates.io` is normalized to `cargo` in outputs.
|
|
80
|
+
If `end_date` is omitted, the CLI `--end-date` value is used; when `--end-date` is also omitted, it defaults to today.
|
|
81
|
+
Extra columns are allowed; duplicates are removed by `ecosystem, package_name, end_date`.
|
|
82
|
+
Bulk mode computes metrics per package using the latest dependency set.
|
|
83
|
+
|
|
84
|
+
### As a Python module
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from datetime import datetime
|
|
88
|
+
from pathlib import Path
|
|
89
|
+
from dependency_metrics.analyzer import DependencyAnalyzer
|
|
90
|
+
|
|
91
|
+
# Create analyzer
|
|
92
|
+
analyzer = DependencyAnalyzer(
|
|
93
|
+
ecosystem="npm",
|
|
94
|
+
package="express",
|
|
95
|
+
start_date=datetime(2020, 1, 1),
|
|
96
|
+
end_date=datetime(2023, 12, 31),
|
|
97
|
+
weighting_type="exponential",
|
|
98
|
+
half_life=180,
|
|
99
|
+
output_dir=Path("./output")
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Run analysis
|
|
103
|
+
results = analyzer.analyze()
|
|
104
|
+
|
|
105
|
+
print(f"Average TTU: {results['ttu']:.2f} days")
|
|
106
|
+
print(f"Average TTR: {results['ttr']:.2f} days")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Command-line Arguments
|
|
110
|
+
|
|
111
|
+
- `--ecosystem`: Ecosystem to analyze (`npm`, `pypi`, `cargo`, or `crates.io`) [Required unless `--input-csv`]
|
|
112
|
+
- `--package`: Package name to analyze [Required unless `--input-csv`]
|
|
113
|
+
- `--input-csv`: CSV file with columns `ecosystem, package_name`, optional `start_date`, and optional `end_date`. If `end_date` is omitted, `--end-date` is used.
|
|
114
|
+
- `--start-date`: Start date for analysis (YYYY-MM-DD) [Default: 1900-01-01]
|
|
115
|
+
- `--end-date`: End date for analysis (YYYY-MM-DD) [Default: today]
|
|
116
|
+
- `--weighting-type`: Weighting method (`linear`, `exponential`, `inverse`, `disable`) [Default: disable]
|
|
117
|
+
- `--half-life`: Half-life in days (required for exponential weighting)
|
|
118
|
+
- `--build-osv`: Build/update the OSV vulnerability database
|
|
119
|
+
- `--get-osv`: Export OSV data for the package's dependencies
|
|
120
|
+
- `--get-worksheets`: Export detailed analysis worksheets to Excel
|
|
121
|
+
- `--output-dir`: Output directory for results [Default: ./output]
|
|
122
|
+
- `--workers`: Number of parallel workers for bulk CSV mode [Default: min(8, CPU count)]
|
|
123
|
+
- `--package-timeout`: Per-package analysis timeout in seconds (bulk CSV mode only). Packages that exceed this time receive an error row and are skipped [Default: disabled]
|
|
124
|
+
- `--resume`: Resume bulk CSV runs by skipping completed rows and retrying errors
|
|
125
|
+
- `--per-release`: Compute MTTU/MTTR at every release of the parent package within the window (one output row per release)
|
|
126
|
+
|
|
127
|
+
## Weighting Methods
|
|
128
|
+
|
|
129
|
+
### Disable (default)
|
|
130
|
+
No weighting applied. All time periods are weighted equally.
|
|
131
|
+
|
|
132
|
+
### Linear
|
|
133
|
+
Weight decreases linearly with age:
|
|
134
|
+
```
|
|
135
|
+
weight = 1 - (age / max_age)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Exponential
|
|
139
|
+
Weight decreases exponentially based on half-life:
|
|
140
|
+
```
|
|
141
|
+
weight = exp(-λ * age)
|
|
142
|
+
where λ = ln(2) / half_life
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Inverse
|
|
146
|
+
Weight is inversely proportional to age:
|
|
147
|
+
```
|
|
148
|
+
weight = 1 / (1 + age)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Output
|
|
152
|
+
|
|
153
|
+
The tool generates several outputs in the specified output directory:
|
|
154
|
+
|
|
155
|
+
1. **JSON results file**: Contains TTU, TTR, and metadata
|
|
156
|
+
2. **OSV data** (with `--get-osv`): CSV file with vulnerability information
|
|
157
|
+
3. **Excel worksheets** (with `--get-worksheets`): Detailed analysis for each dependency
|
|
158
|
+
4. **Bulk CSV results** (with `--input-csv`): Summary CSV, plus an optional dependency details CSV with `--write-dependency-details`
|
|
159
|
+
5. **Per-release results** (with `--per-release`): One row per package release with `package_version`, `package_release_date`, `window_start`, `window_end`, `mttu`, `mttr`
|
|
160
|
+
|
|
161
|
+
### Example JSON output
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"package": "express",
|
|
166
|
+
"ecosystem": "npm",
|
|
167
|
+
"version": "4.18.2",
|
|
168
|
+
"start_date": "2020-01-01T00:00:00",
|
|
169
|
+
"end_date": "2023-12-31T00:00:00",
|
|
170
|
+
"weighting_type": "exponential",
|
|
171
|
+
"half_life": 180,
|
|
172
|
+
"ttu": 45.32,
|
|
173
|
+
"ttr": 12.15,
|
|
174
|
+
"num_dependencies": 30
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## How It Works
|
|
179
|
+
|
|
180
|
+
1. **Package Analysis**: Fetches package metadata from the registry
|
|
181
|
+
2. **Dependency Extraction**: Extracts dependencies from the package version closest to the end date
|
|
182
|
+
3. **Timeline Construction**: Creates a timeline of version releases for each dependency
|
|
183
|
+
4. **Version Resolution**: Resolves dependency versions at each time interval via ecosystem resolvers
|
|
184
|
+
5. **Remediation Check**: Uses the OSV service to determine vulnerability remediation
|
|
185
|
+
6. **Metric Calculation**:
|
|
186
|
+
- **TTU**: Measures time when dependency is not at the highest available version
|
|
187
|
+
- **TTR**: Measures time when dependency has known vulnerabilities
|
|
188
|
+
7. **Weighting**: Applies optional time-based weighting to prioritize recent periods
|
|
189
|
+
8. **Aggregation**: Averages metrics across all dependencies
|
|
190
|
+
|
|
191
|
+
## Architecture Notes
|
|
192
|
+
|
|
193
|
+
- Ecosystem-specific resolution is encapsulated in `dependency_metrics/resolvers.py`.
|
|
194
|
+
- OSV remediation checks are handled by `dependency_metrics/osv_service.py`.
|
|
195
|
+
- Reporting/export helpers live in `dependency_metrics/reporting.py`.
|
|
196
|
+
- Bulk CSV mode groups rows by `(ecosystem, package_name)` and processes each group sequentially while running groups in parallel to maximize cache reuse.
|
|
197
|
+
- For bulk runs, a single package analysis computes metrics across multiple end dates using the latest dependency set.
|
|
198
|
+
- `--per-release` mode uses `analyze_at_release_points()` which fetches per-version dependency sets and builds a shared dep-cache covering the full window; it slices that cache for each release sub-window `[start_date, release_date]`.
|
|
199
|
+
- **Cache invalidation:** `ResolverCache` (`dependency_metrics/resolvers.py`) persists all HTTP responses to a SQLite file at `<output-dir>/cache/cache.db`, keyed only by `(namespace, key)` — there is no TTL. This is fine for per-version data (a specific version's dependencies/metadata never changes), but package-level version-list lookups (`DepsDevClient.get_package()`'s `depsdev_package` namespace, and each resolver's `fetch_package_metadata()`/`_get_npm_time_data()` under the `metadata`/`npm_time` namespaces) cache the *entire*, mutable version list. Those lookups compare the requested `end_date` against the newest publish date already in the cached listing and transparently re-fetch whenever the cache can't yet rule out a newer release — so re-running against the same `--output-dir` won't silently miss versions published since the last run. The tradeoff: analyzing "as of today" re-fetches each package's version list on every run (since "today" is always newer than the last known release), which is the correct behavior — only truly historical, fixed `end_date` values benefit from the cache staying warm across runs.
|
|
200
|
+
|
|
201
|
+
## Requirements
|
|
202
|
+
|
|
203
|
+
- Python 3.10+
|
|
204
|
+
- git (for auto-installing the patched pip on first PyPI analysis)
|
|
205
|
+
- npm CLI (for npm ecosystem analysis) `sudo apt install nodejs npm`
|
|
206
|
+
- Internet connection (for fetching package metadata and OSV data)
|
|
207
|
+
|
|
208
|
+
> **Note:** PyPI analysis uses a patched pip fork that adds date-bounded version
|
|
209
|
+
> resolution. The tool clones it automatically to `~/.dependency_metrics/vendor/pip`
|
|
210
|
+
> on first use — no manual setup required.
|
|
211
|
+
|
|
212
|
+
## Development
|
|
213
|
+
|
|
214
|
+
### Setup development environment
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
git clone https://github.com/imranur-rahman/dependency-update-metrics.git
|
|
218
|
+
cd dependency-update-metrics
|
|
219
|
+
pip install -e ".[dev]"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Run tests
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
pytest
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Code formatting
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
black dependency_metrics/
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Publishing to PyPI
|
|
235
|
+
|
|
236
|
+
### Prepare for publishing
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
# Install build tools
|
|
240
|
+
pip install build twine
|
|
241
|
+
|
|
242
|
+
# Build distribution
|
|
243
|
+
python -m build
|
|
244
|
+
|
|
245
|
+
# Check distribution
|
|
246
|
+
twine check dist/*
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Upload to PyPI
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
# Upload to Test PyPI first
|
|
253
|
+
twine upload --repository testpypi dist/*
|
|
254
|
+
|
|
255
|
+
# If all looks good, upload to PyPI
|
|
256
|
+
twine upload dist/*
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
MIT License - see LICENSE file for details
|
|
262
|
+
|
|
263
|
+
## Contributing
|
|
264
|
+
|
|
265
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
266
|
+
|
|
267
|
+
## Citation
|
|
268
|
+
|
|
269
|
+
If you use this tool in your research, please cite:
|
|
270
|
+
|
|
271
|
+
```bibtex
|
|
272
|
+
@software{dependency_metrics,
|
|
273
|
+
author = {Rahman, Imranur},
|
|
274
|
+
title = {Dependency Update Metrics},
|
|
275
|
+
year = {2025},
|
|
276
|
+
url = {https://github.com/imranur-rahman/dependency-update-metrics}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Acknowledgments
|
|
281
|
+
|
|
282
|
+
- Uses data from [Open Source Vulnerabilities (OSV)](https://osv.dev/)
|
|
283
|
+
- Package metadata from [npm Registry](https://registry.npmjs.org/) and [PyPI](https://pypi.org/)
|