pydoctor-cli 2.0.1__tar.gz → 2.2.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.
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/PKG-INFO +33 -3
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/README.md +30 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/__init__.py +1 -1
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/analysis/health_score.py +48 -5
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/cache/cache_manager.py +8 -1
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/cli/main.py +260 -60
- pydoctor_cli-2.2.0/pydoctor/core/__init__.py +17 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/core/analyzer.py +67 -1
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/core/project.py +105 -78
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/reports/json_formatter.py +44 -15
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/reports/table_formatter.py +7 -2
- pydoctor_cli-2.2.0/pydoctor/scanners/ci_scanner.py +319 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/scanners/dependency_scanner.py +54 -9
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/scanners/env_scanner.py +30 -12
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/scanners/outdated_package_scanner.py +40 -14
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/scanners/unused_package_scanner.py +160 -8
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/scanners/vulnerability_scanner.py +10 -2
- pydoctor_cli-2.2.0/pydoctor/security/cvss.py +77 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/security/osv_client.py +140 -61
- pydoctor_cli-2.2.0/pydoctor/security/secrets.py +296 -0
- pydoctor_cli-2.2.0/pydoctor/utils/env_probe.py +145 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/utils/file_utils.py +4 -2
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/utils/parser_utils.py +41 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/utils/pip_utils.py +40 -29
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor_cli.egg-info/PKG-INFO +33 -3
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor_cli.egg-info/SOURCES.txt +7 -3
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pyproject.toml +3 -3
- pydoctor_cli-2.2.0/tests/test_ci_scanner.py +196 -0
- pydoctor_cli-2.2.0/tests/test_cli_ux.py +72 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/tests/test_env_scanner.py +27 -14
- pydoctor_cli-2.2.0/tests/test_environment.py +197 -0
- pydoctor_cli-2.2.0/tests/test_fix_command.py +141 -0
- pydoctor_cli-2.2.0/tests/test_osv_parsing.py +181 -0
- pydoctor_cli-2.2.0/tests/test_report_output.py +72 -0
- pydoctor_cli-2.2.0/tests/test_unused_scanner.py +192 -0
- pydoctor_cli-2.0.1/pydoctor/core/__init__.py +0 -7
- pydoctor_cli-2.0.1/pydoctor/performance/__init__.py +0 -5
- pydoctor_cli-2.0.1/pydoctor/performance/executor.py +0 -132
- pydoctor_cli-2.0.1/pydoctor/performance/parallel.py +0 -40
- pydoctor_cli-2.0.1/pydoctor/scanners/ci_scanner.py +0 -141
- pydoctor_cli-2.0.1/tests/test_ci_scanner.py +0 -39
- pydoctor_cli-2.0.1/tests/test_cli_ux.py +0 -38
- pydoctor_cli-2.0.1/tests/test_unused_scanner.py +0 -106
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/LICENSE +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/analysis/__init__.py +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/cache/__init__.py +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/cli/__init__.py +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/config/__init__.py +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/config/settings.py +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/core/report.py +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/reports/__init__.py +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/reports/terminal_colors.py +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/scanners/__init__.py +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/security/__init__.py +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/utils/__init__.py +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor/utils/subprocess_utils.py +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor_cli.egg-info/dependency_links.txt +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor_cli.egg-info/entry_points.txt +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor_cli.egg-info/requires.txt +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/pydoctor_cli.egg-info/top_level.txt +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/setup.cfg +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/tests/conftest.py +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/tests/test_dependency_scanner.py +0 -0
- {pydoctor_cli-2.0.1 → pydoctor_cli-2.2.0}/tests/test_vulnerability_scanner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydoctor-cli
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Premium Python environment diagnostic CLI — your project's personal physician.
|
|
5
5
|
Author-email: Favour Bamgboye <demiladebamgboye@gmail.com>
|
|
6
6
|
Maintainer-email: Favour Bamgboye <demiladebamgboye@gmail.com>
|
|
@@ -206,9 +206,9 @@ License: Apache License
|
|
|
206
206
|
See the License for the specific language governing permissions and
|
|
207
207
|
limitations under the License.
|
|
208
208
|
|
|
209
|
-
Project-URL: Homepage, https://pydoctor.
|
|
209
|
+
Project-URL: Homepage, https://pydoctor-docs.thally.app
|
|
210
210
|
Project-URL: Repository, https://github.com/iamAgbaCoder/pydoctor-cli
|
|
211
|
-
Project-URL: Documentation, https://pydoctor.
|
|
211
|
+
Project-URL: Documentation, https://pydoctor-docs.thally.app
|
|
212
212
|
Project-URL: Bug Tracker, https://github.com/iamAgbaCoder/pydoctor-cli/issues
|
|
213
213
|
Project-URL: Changelog, https://github.com/iamAgbaCoder/pydoctor-cli/releases
|
|
214
214
|
Keywords: pydoctor,diagnosis,dependency-checker,vulnerability-scanner,python-health,cli-tool,security-audit,static-analysis
|
|
@@ -253,6 +253,7 @@ Dynamic: license-file
|
|
|
253
253
|
|
|
254
254
|
**PyDoctor** is a standalone, developer-friendly diagnostic assistant for Python environments. It acts like a personal physician for your code — scanning your project and environment to prescribe fixes for misconfigurations, dependencies, and security vulnerabilities.
|
|
255
255
|
|
|
256
|
+
[](https://pepy.tech/projects/pydoctor-cli)
|
|
256
257
|
[](https://github.com/iamAgbaCoder/pydoctor-cli/actions)
|
|
257
258
|
[](https://pypi.org/project/pydoctor-cli/)
|
|
258
259
|
[](https://pypi.org/project/pydoctor-cli/)
|
|
@@ -299,6 +300,16 @@ pipx install pydoctor-cli
|
|
|
299
300
|
pip install pydoctor-cli
|
|
300
301
|
```
|
|
301
302
|
|
|
303
|
+
> Install `pydoctor-cli`, not `pydoctor`: that is an unrelated project (Twisted's API doc generator) that installs a command with the same name.
|
|
304
|
+
|
|
305
|
+
PyDoctor scans your **project's** Python, wherever PyDoctor itself is installed. It uses the active virtualenv, or a `.venv`/`venv` in the project, and tells you which interpreter it chose. To choose one yourself:
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
pydoctor diagnose --python .venv/bin/python # or set PYDOCTOR_PYTHON
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
📖 Full documentation: **https://pydoctor-docs.thally.app**
|
|
312
|
+
|
|
302
313
|
---
|
|
303
314
|
|
|
304
315
|
## ⚡ Usage
|
|
@@ -323,6 +334,25 @@ pydoctor fix
|
|
|
323
334
|
|
|
324
335
|
## Release History
|
|
325
336
|
|
|
337
|
+
### v2.2.0
|
|
338
|
+
Results you can trust: every fix in this release removes a case where PyDoctor reported a problem as fine, or got a severity wrong.
|
|
339
|
+
|
|
340
|
+
- **Scans the right environment:** facts come from your project's interpreter, not PyDoctor's. Under pipx or `uv tool`, and in uv environments (which have no pip), 2.1.0 saw 0 packages and reported everything as OK. Works without pip (package metadata, `uv pip list --outdated`). New `--python` / `PYDOCTOR_PYTHON`; the report shows which interpreter was scanned and why.
|
|
341
|
+
- **Fails loudly:** `ENV_PACKAGES_UNAVAILABLE` (error) when the environment can't be read, `ENV_DEPS_NOT_INSTALLED` when the scanned Python has none of your dependencies, and outdated checks that can't run say so instead of "all up to date".
|
|
342
|
+
- **Virtualenv detection works:** it was always "active". The `diagnose` no-venv prompt now appears, and never blocks CI or piped runs.
|
|
343
|
+
- **Real CVSS scores:** severity is computed from the CVSS v3 vector. Previously a 3.1 (Low) and a 9.8 (Critical) vulnerability were both shown as critical. GitHub's `MODERATE` is now medium.
|
|
344
|
+
- **Right fix version:** for advisories with one fix per release line, `fix` targets your line (Django 5.1.2 → 5.1.4, not 4.2.17).
|
|
345
|
+
- **CI guard finds real leaks:** scans every committed text file, adds Stripe, OpenAI, Anthropic, Slack, Google, private-key, database-URL and `SECRET_KEY` rules, flags committed `.env` files, and checks workflows for `pull_request_target` "pwn requests", script injection and `write-all` permissions. Findings have line numbers; add `# pydoctor: ignore` to silence one. Zero findings on Flask, Django, requests, httpx, FastAPI, DRF and pip.
|
|
346
|
+
- **A score that moves:** a package's advisories count once, categories have diminishing returns, and any critical issue keeps the score below "Good". Expect different (usually higher) scores than 2.1.0.
|
|
347
|
+
- **JSON:** includes `health`; `scan-security --json` reports advisory IDs, aliases, fixed version and CVSS score.
|
|
348
|
+
- Smaller: database drivers, hashers and cache clients picked by Django settings count as used; faster advisory fetching; `--no-cache` no longer wipes the cache; circular import fixed; `github <repo>` and `docker` describe what they actually do.
|
|
349
|
+
|
|
350
|
+
### v2.1.0
|
|
351
|
+
- **`pydoctor fix` reports what really happened:** upgrades are verified against the installed version. If a pin in `pyproject.toml` blocks an upgrade, pydoctor says so and shows the constraint instead of printing "fixed".
|
|
352
|
+
- **One prompt per package:** vulnerable packages are offered once, with the advisory count, and upgraded to the lowest version that fixes every advisory.
|
|
353
|
+
- **No accidental removals:** `pydoctor fix <package>` no longer offers to uninstall it. Removal only runs with `--remove`.
|
|
354
|
+
- **Fewer false "unused" reports:** packages imported under a different name (`decouple`), loaded from strings (Django `INSTALLED_APPS`, `MIDDLEWARE`, backends), or run from a Procfile/Dockerfile (`gunicorn`) are recognised as used. Remaining findings are labelled "Possibly unused".
|
|
355
|
+
|
|
326
356
|
### v2.0.1
|
|
327
357
|
- **Security scans fail closed:** if the OSV database can't be reached, pydoctor now reports `SEC_SCAN_INCOMPLETE` (error, exit 1) instead of a clean result.
|
|
328
358
|
- **No more double-counted advisories:** advisories listed under several IDs (e.g. GHSA and PYSEC) are reported and scored once.
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**PyDoctor** is a standalone, developer-friendly diagnostic assistant for Python environments. It acts like a personal physician for your code — scanning your project and environment to prescribe fixes for misconfigurations, dependencies, and security vulnerabilities.
|
|
6
6
|
|
|
7
|
+
[](https://pepy.tech/projects/pydoctor-cli)
|
|
7
8
|
[](https://github.com/iamAgbaCoder/pydoctor-cli/actions)
|
|
8
9
|
[](https://pypi.org/project/pydoctor-cli/)
|
|
9
10
|
[](https://pypi.org/project/pydoctor-cli/)
|
|
@@ -50,6 +51,16 @@ pipx install pydoctor-cli
|
|
|
50
51
|
pip install pydoctor-cli
|
|
51
52
|
```
|
|
52
53
|
|
|
54
|
+
> Install `pydoctor-cli`, not `pydoctor`: that is an unrelated project (Twisted's API doc generator) that installs a command with the same name.
|
|
55
|
+
|
|
56
|
+
PyDoctor scans your **project's** Python, wherever PyDoctor itself is installed. It uses the active virtualenv, or a `.venv`/`venv` in the project, and tells you which interpreter it chose. To choose one yourself:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pydoctor diagnose --python .venv/bin/python # or set PYDOCTOR_PYTHON
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
📖 Full documentation: **https://pydoctor-docs.thally.app**
|
|
63
|
+
|
|
53
64
|
---
|
|
54
65
|
|
|
55
66
|
## ⚡ Usage
|
|
@@ -74,6 +85,25 @@ pydoctor fix
|
|
|
74
85
|
|
|
75
86
|
## Release History
|
|
76
87
|
|
|
88
|
+
### v2.2.0
|
|
89
|
+
Results you can trust: every fix in this release removes a case where PyDoctor reported a problem as fine, or got a severity wrong.
|
|
90
|
+
|
|
91
|
+
- **Scans the right environment:** facts come from your project's interpreter, not PyDoctor's. Under pipx or `uv tool`, and in uv environments (which have no pip), 2.1.0 saw 0 packages and reported everything as OK. Works without pip (package metadata, `uv pip list --outdated`). New `--python` / `PYDOCTOR_PYTHON`; the report shows which interpreter was scanned and why.
|
|
92
|
+
- **Fails loudly:** `ENV_PACKAGES_UNAVAILABLE` (error) when the environment can't be read, `ENV_DEPS_NOT_INSTALLED` when the scanned Python has none of your dependencies, and outdated checks that can't run say so instead of "all up to date".
|
|
93
|
+
- **Virtualenv detection works:** it was always "active". The `diagnose` no-venv prompt now appears, and never blocks CI or piped runs.
|
|
94
|
+
- **Real CVSS scores:** severity is computed from the CVSS v3 vector. Previously a 3.1 (Low) and a 9.8 (Critical) vulnerability were both shown as critical. GitHub's `MODERATE` is now medium.
|
|
95
|
+
- **Right fix version:** for advisories with one fix per release line, `fix` targets your line (Django 5.1.2 → 5.1.4, not 4.2.17).
|
|
96
|
+
- **CI guard finds real leaks:** scans every committed text file, adds Stripe, OpenAI, Anthropic, Slack, Google, private-key, database-URL and `SECRET_KEY` rules, flags committed `.env` files, and checks workflows for `pull_request_target` "pwn requests", script injection and `write-all` permissions. Findings have line numbers; add `# pydoctor: ignore` to silence one. Zero findings on Flask, Django, requests, httpx, FastAPI, DRF and pip.
|
|
97
|
+
- **A score that moves:** a package's advisories count once, categories have diminishing returns, and any critical issue keeps the score below "Good". Expect different (usually higher) scores than 2.1.0.
|
|
98
|
+
- **JSON:** includes `health`; `scan-security --json` reports advisory IDs, aliases, fixed version and CVSS score.
|
|
99
|
+
- Smaller: database drivers, hashers and cache clients picked by Django settings count as used; faster advisory fetching; `--no-cache` no longer wipes the cache; circular import fixed; `github <repo>` and `docker` describe what they actually do.
|
|
100
|
+
|
|
101
|
+
### v2.1.0
|
|
102
|
+
- **`pydoctor fix` reports what really happened:** upgrades are verified against the installed version. If a pin in `pyproject.toml` blocks an upgrade, pydoctor says so and shows the constraint instead of printing "fixed".
|
|
103
|
+
- **One prompt per package:** vulnerable packages are offered once, with the advisory count, and upgraded to the lowest version that fixes every advisory.
|
|
104
|
+
- **No accidental removals:** `pydoctor fix <package>` no longer offers to uninstall it. Removal only runs with `--remove`.
|
|
105
|
+
- **Fewer false "unused" reports:** packages imported under a different name (`decouple`), loaded from strings (Django `INSTALLED_APPS`, `MIDDLEWARE`, backends), or run from a Procfile/Dockerfile (`gunicorn`) are recognised as used. Remaining findings are labelled "Possibly unused".
|
|
106
|
+
|
|
77
107
|
### v2.0.1
|
|
78
108
|
- **Security scans fail closed:** if the OSV database can't be reached, pydoctor now reports `SEC_SCAN_INCOMPLETE` (error, exit 1) instead of a clean result.
|
|
79
109
|
- **No more double-counted advisories:** advisories listed under several IDs (e.g. GHSA and PYSEC) are reported and scored once.
|
|
@@ -6,6 +6,7 @@ Calculates the numerical project health score and verdict.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import math
|
|
9
10
|
from dataclasses import dataclass
|
|
10
11
|
|
|
11
12
|
from pydoctor.core.report import DiagnosisReport, Issue
|
|
@@ -18,20 +19,62 @@ class HealthMetrics:
|
|
|
18
19
|
message: str
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
# Each category can cost at most this many points. Within a category every
|
|
23
|
+
# extra issue still lowers the score, but by less (diminishing returns), so
|
|
24
|
+
# fixing issues in a project with dozens of them visibly moves the score.
|
|
25
|
+
CATEGORY_CAPS: dict[str, float] = {
|
|
26
|
+
"security": 60,
|
|
27
|
+
"ci": 50,
|
|
28
|
+
"dependencies": 40,
|
|
29
|
+
"environment": 30,
|
|
30
|
+
"outdated": 15,
|
|
31
|
+
"unused": 10,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# A project with any critical issue can't be rated Good or Excellent.
|
|
35
|
+
CRITICAL_CEILING = 69
|
|
36
|
+
|
|
37
|
+
# Issues that mean part of the scan didn't run.
|
|
38
|
+
INCOMPLETE_CODES = {"ENV_PACKAGES_UNAVAILABLE", "SEC_SCAN_INCOMPLETE"}
|
|
39
|
+
|
|
40
|
+
|
|
21
41
|
def calculate_health(report: DiagnosisReport) -> HealthMetrics:
|
|
22
42
|
"""
|
|
23
43
|
Calculate project health score (0-100) based on identified issues.
|
|
24
44
|
"""
|
|
25
45
|
score = _compute_score(report)
|
|
26
|
-
categories = list({i.category for i in report.issues})
|
|
27
|
-
|
|
46
|
+
categories = list({i.category for i in report.issues if i.code not in INCOMPLETE_CODES})
|
|
47
|
+
metrics = _get_verdict(score, categories)
|
|
48
|
+
if any(i.code in INCOMPLETE_CODES for i in report.issues):
|
|
49
|
+
metrics.verdict = "Incomplete"
|
|
50
|
+
metrics.message = (
|
|
51
|
+
"Some checks couldn't run, so this score only covers what was checked. "
|
|
52
|
+
"See the errors above."
|
|
53
|
+
)
|
|
54
|
+
return metrics
|
|
28
55
|
|
|
29
56
|
|
|
30
57
|
def _compute_score(report: DiagnosisReport) -> int:
|
|
31
|
-
|
|
58
|
+
raw: dict[str, float] = {}
|
|
59
|
+
# One upgrade usually fixes every advisory for a package, so a package
|
|
60
|
+
# costs the penalty of its worst advisory, not one penalty per advisory.
|
|
61
|
+
per_package: dict[str, int] = {}
|
|
32
62
|
for issue in report.issues:
|
|
33
|
-
|
|
34
|
-
|
|
63
|
+
penalty = _calculate_issue_penalty(issue)
|
|
64
|
+
if issue.code == "SEC_VULNERABILITY" and issue.package:
|
|
65
|
+
per_package[issue.package] = max(per_package.get(issue.package, 0), penalty)
|
|
66
|
+
continue
|
|
67
|
+
raw[issue.category] = raw.get(issue.category, 0) + penalty
|
|
68
|
+
if per_package:
|
|
69
|
+
raw["security"] = raw.get("security", 0) + sum(per_package.values())
|
|
70
|
+
lost = 0.0
|
|
71
|
+
for category, points in raw.items():
|
|
72
|
+
cap = CATEGORY_CAPS.get(category)
|
|
73
|
+
lost += cap * (1 - math.exp(-points / cap)) if cap else points
|
|
74
|
+
score = max(0, round(100 - lost))
|
|
75
|
+
if any(i.severity.lower() == "critical" for i in report.issues):
|
|
76
|
+
score = min(score, CRITICAL_CEILING)
|
|
77
|
+
return score
|
|
35
78
|
|
|
36
79
|
|
|
37
80
|
def _calculate_issue_penalty(issue: Issue) -> int:
|
|
@@ -79,7 +79,7 @@ class CacheManager:
|
|
|
79
79
|
|
|
80
80
|
return entry.get("value")
|
|
81
81
|
|
|
82
|
-
def set(self, key: str, value: Any) -> None:
|
|
82
|
+
def set(self, key: str, value: Any, save: bool = True) -> None:
|
|
83
83
|
"""
|
|
84
84
|
Store a value in the cache with the current timestamp.
|
|
85
85
|
|
|
@@ -87,8 +87,15 @@ class CacheManager:
|
|
|
87
87
|
----------
|
|
88
88
|
key: Cache key.
|
|
89
89
|
value: Any JSON-serialisable value.
|
|
90
|
+
save: Write to disk now. Pass False when setting many keys, then call
|
|
91
|
+
``save()`` once (writing the whole file per key is quadratic).
|
|
90
92
|
"""
|
|
91
93
|
self._data[key] = {"_ts": time.time(), "value": value}
|
|
94
|
+
if save:
|
|
95
|
+
self._save()
|
|
96
|
+
|
|
97
|
+
def save(self) -> None:
|
|
98
|
+
"""Persist the cache to disk."""
|
|
92
99
|
self._save()
|
|
93
100
|
|
|
94
101
|
def invalidate(self, key: str) -> None:
|