driverclient 0.2.6__tar.gz → 0.2.7__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 (23) hide show
  1. {driverclient-0.2.6 → driverclient-0.2.7}/PKG-INFO +1 -1
  2. {driverclient-0.2.6 → driverclient-0.2.7}/pyproject.toml +1 -1
  3. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/__init__.py +1 -1
  4. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/main.py +2 -0
  5. driverclient-0.2.7/src/driverclient/ops/diagnose.py +158 -0
  6. {driverclient-0.2.6 → driverclient-0.2.7}/.gitignore +0 -0
  7. {driverclient-0.2.6 → driverclient-0.2.7}/README.md +0 -0
  8. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/__main__.py +0 -0
  9. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/config.json +0 -0
  10. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/config.py +0 -0
  11. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/core/__init__.py +0 -0
  12. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/core/hardware.py +0 -0
  13. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/core/http.py +0 -0
  14. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/core/hwid.py +0 -0
  15. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/core/proc.py +0 -0
  16. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/events.py +0 -0
  17. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/ops/__init__.py +0 -0
  18. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/ops/automate.py +0 -0
  19. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/ops/capture.py +0 -0
  20. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/ops/install.py +0 -0
  21. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/ops/resolve.py +0 -0
  22. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/ops/scan.py +0 -0
  23. {driverclient-0.2.6 → driverclient-0.2.7}/src/driverclient/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: driverclient
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: Driver Server client with an embeddable connector for config injection at runtime.
5
5
  Project-URL: Homepage, https://example.com/driverclient
6
6
  Author-email: Raja Sanaullah <sanaullah@99technologies.com>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "driverclient"
7
- version = "0.2.6"
7
+ version = "0.2.7"
8
8
  description = "Driver Server client with an embeddable connector for config injection at runtime."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -25,7 +25,7 @@ from driverclient.main import run as _run
25
25
 
26
26
  __all__ = ["DriverClient", "ClientEvent"]
27
27
 
28
- __version__ = "0.2.6"
28
+ __version__ = "0.2.7"
29
29
 
30
30
 
31
31
  class DriverClient:
@@ -26,6 +26,7 @@ from driverclient.ops.capture import (
26
26
  wu_full,
27
27
  wu_update,
28
28
  )
29
+ from driverclient.ops.diagnose import run_diagnostics
29
30
  from driverclient.ops.install import resolve_and_install
30
31
  from driverclient.ops.resolve import resolve
31
32
  from driverclient.ops.scan import scan
@@ -40,6 +41,7 @@ _OPERATIONS: dict[str, callable] = {
40
41
  "wu-update": wu_update,
41
42
  "wu-full": wu_full,
42
43
  "automate": run_automation,
44
+ "test": run_diagnostics,
43
45
  }
44
46
 
45
47
 
@@ -0,0 +1,158 @@
1
+ """
2
+ client/ops/diagnose.py — the `test` command. READ-ONLY.
3
+
4
+ Shows, side by side and per device:
5
+ • what the machine detects (scan: devices, HWIDs, driver_name, versions)
6
+ • what the repo serves back (resolve: to_install / already_current /
7
+ not_found / stale)
8
+ • what capture-delta WOULD harvest, and how that compares to capture-all.
9
+
10
+ No install, no upload, no Windows Update — nothing changes. Use it to verify
11
+ the identity/matching end to end and to see exactly why capture-delta uploads
12
+ fewer packages than capture-all.
13
+
14
+ Public API:
15
+ run_diagnostics() -> dict
16
+ """
17
+ from driverclient import events
18
+ from driverclient.config import get
19
+ from driverclient.core import hwid as hwid_mod
20
+ from driverclient.ops.capture import _enumerate_all_packages, _find_installed_packages_for_hwids
21
+ from driverclient.ops.resolve import resolve
22
+ from driverclient.ops.scan import scan
23
+
24
+
25
+ def _short(name: str, n: int = 44) -> str:
26
+ name = name or ""
27
+ return name if len(name) <= n else name[: n - 1] + "…"
28
+
29
+
30
+ def _verdict_for(dev, matched: dict, nf_set: set, stale_set: set):
31
+ """Classify one device against the repo response."""
32
+ mids = hwid_mod.matchable_ids(dev.hwids)
33
+ for mid in mids:
34
+ if mid in matched:
35
+ return "INSTALL", matched[mid], mid
36
+ for mid in mids:
37
+ if mid in stale_set:
38
+ return "STALE", "", mid
39
+ for mid in mids:
40
+ if mid in nf_set:
41
+ return "NOT_IN_REPO", "", mid
42
+ if dev.has_driver:
43
+ return "CURRENT/COVERED", "", (mids[0] if mids else "")
44
+ return "NO_DRIVER/SW", "", (mids[0] if mids else "")
45
+
46
+
47
+ def run_diagnostics() -> dict:
48
+ cfg = get()
49
+ events.start("test", "=" * 64)
50
+ events.start("test", " TEST — scan vs repo (READ-ONLY: no install / upload / WU)")
51
+ events.start("test", "=" * 64)
52
+
53
+ scan_result = scan(force=True)
54
+ resolve_result = resolve(force=False) # consumes the fresh scan just done
55
+
56
+ drivers = resolve_result.drivers
57
+ not_found = list(resolve_result.not_found)
58
+ stale = list(getattr(resolve_result, "stale", []) or [])
59
+
60
+ matched: dict[str, str] = {}
61
+ for d in drivers:
62
+ h = d.get("hwid")
63
+ if h:
64
+ matched[hwid_mod.canonicalize(h)] = d.get("package_id", "?")
65
+ nf_set = {hwid_mod.canonicalize(h) for h in not_found}
66
+ stale_set = {hwid_mod.canonicalize(h) for h in stale}
67
+
68
+ # ── SCAN side ────────────────────────────────────────────────────────
69
+ events.progress("test", "")
70
+ events.progress(
71
+ "test",
72
+ f"── SCAN (system detected) ── {scan_result.vendor} {scan_result.model} | "
73
+ f"OS {scan_result.os_build} | machine {scan_result.machine_id}",
74
+ )
75
+ events.progress(
76
+ "test",
77
+ f" {len(scan_result.devices)} devices | {len(scan_result.has_driver)} with driver | "
78
+ f"{len(scan_result.missing_driver)} without | "
79
+ f"{len(scan_result.installed_versions)} installed-version entries",
80
+ )
81
+
82
+ # ── RESOLVE side ─────────────────────────────────────────────────────
83
+ events.progress("test", "")
84
+ events.progress("test", f"── RESOLVE (repo {cfg.get('local_repo_url')}) ──")
85
+ events.progress(
86
+ "test",
87
+ f" to_install={len(drivers)} already_current={resolve_result.already_current} "
88
+ f"not_found={len(not_found)} stale={len(stale)}",
89
+ )
90
+
91
+ # ── Per-device cross reference ───────────────────────────────────────
92
+ order = ["INSTALL", "STALE", "NOT_IN_REPO", "CURRENT/COVERED", "NO_DRIVER/SW"]
93
+ buckets: dict[str, list[str]] = {k: [] for k in order}
94
+ for dev in scan_result.devices:
95
+ v, pkg, mid = _verdict_for(dev, matched, nf_set, stale_set)
96
+ drv = "inbox" if dev.is_inbox else (dev.driver_name or "no-drv")
97
+ ver = f" v{dev.installed_version}" if dev.installed_version else ""
98
+ line = f"{_short(dev.device_name)} [{dev.class_name or '-'}] {drv}{ver}"
99
+ if v == "INSTALL":
100
+ line += f" → repo pkg {pkg}"
101
+ elif mid:
102
+ line += f" ({mid})"
103
+ buckets[v].append(line)
104
+
105
+ events.progress("test", "")
106
+ events.progress("test", "── PER-DEVICE (scan ⟷ repo) ──")
107
+ for v in order:
108
+ events.progress("test", f" [{v}] {len(buckets[v])}")
109
+ for line in buckets[v]:
110
+ events.progress("test", f" {line}")
111
+
112
+ # ── Harvest preview vs capture-all ───────────────────────────────────
113
+ delta = set(not_found) | set(stale)
114
+ to_harvest = _find_installed_packages_for_hwids(delta, scan_result, cfg)
115
+ all_pkgs = _enumerate_all_packages(cfg)
116
+
117
+ events.progress("test", "")
118
+ events.progress(
119
+ "test",
120
+ f"── HARVEST PREVIEW — capture-delta WOULD upload {len(to_harvest)} OEM package(s) ──",
121
+ )
122
+ for p in to_harvest:
123
+ events.progress(
124
+ "test",
125
+ f" {p.get('published_name')} "
126
+ f"({p.get('provider_name', '?')} {p.get('class_name', '')})",
127
+ )
128
+ events.progress("test", "")
129
+ events.progress(
130
+ "test",
131
+ f"── capture-all WOULD dump {len(all_pkgs)} DriverStore package(s) "
132
+ f"(everything, incl. inbox / co-installers) ──",
133
+ )
134
+ events.progress(
135
+ "test",
136
+ f" Why the gap: capture-delta uploads ONLY OEM drivers backing the repo "
137
+ f"delta (not_found ∪ stale) = {len(to_harvest)}; capture-all dumps all "
138
+ f"{len(all_pkgs)} regardless. Devices marked CURRENT/COVERED or inbox are "
139
+ f"intentionally NOT harvested by delta.",
140
+ )
141
+ events.done("test", "=" * 64)
142
+
143
+ return {
144
+ "scan": {
145
+ "devices": len(scan_result.devices),
146
+ "with_driver": len(scan_result.has_driver),
147
+ "installed_versions": len(scan_result.installed_versions),
148
+ },
149
+ "resolve": {
150
+ "to_install": len(drivers),
151
+ "already_current": resolve_result.already_current,
152
+ "not_found": len(not_found),
153
+ "stale": len(stale),
154
+ },
155
+ "verdicts": {k: len(v) for k, v in buckets.items()},
156
+ "harvest_delta_packages": len(to_harvest),
157
+ "capture_all_packages": len(all_pkgs),
158
+ }
File without changes
File without changes