Skip to content

Commit 860e8d2

Browse files
authored
feat: add test and docs for get_loaded_kernels. (#490)
* feat: add test and docs for get_loaded_kernels. * ruff * up * fix * feat: add test and docs for get_loaded_kernels. * namedtuple -> dataclasses. * reminder. * upgrade to kernels-community/relu.
1 parent c1b041f commit 860e8d2

4 files changed

Lines changed: 158 additions & 1 deletion

File tree

docs/source/api/kernels.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
[[autodoc]] kernels.has_kernel
1616

17+
### get_loaded_kernels
18+
19+
[[autodoc]] kernels.get_loaded_kernels
20+
1721
## Loading locked kernels
1822

1923
### load_kernel

docs/source/basic-usage.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,23 @@ from kernels import has_kernel
4343
is_available = has_kernel("kernels-community/activation", version=1)
4444
print(f"Kernel available: {is_available}")
4545
```
46+
47+
## Inspecting Loaded Kernels
48+
49+
`get_loaded_kernels()` returns a snapshot of every kernel that has been loaded
50+
into the current process. Each entry is a `LoadedKernel` namedtuple with the
51+
imported `module`, the `package_name`, and `repo_infos` (repo id, resolved
52+
revision, and the backend argument that was passed).
53+
54+
```python
55+
from kernels import get_kernel, get_loaded_kernels
56+
57+
get_kernel("kernels-community/activation", version=1)
58+
59+
for loaded in get_loaded_kernels():
60+
print(loaded.package_name, loaded.repo_infos)
61+
```
62+
63+
`repo_infos` is populated only for kernels loaded with `get_kernel`. Kernels
64+
loaded from a local path (`get_local_kernel`) or via a lockfile
65+
(`get_locked_kernel`, `load_kernel`) have `repo_infos=None`.

kernels/src/kernels/utils.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,42 @@ class LoadedKernel:
5252

5353

5454
def get_loaded_kernels() -> list[LoadedKernel]:
55-
"""Returns a copy of the loaded kernels registry (see `kernels.utils.LoadedKernel` NamedTuple)."""
55+
"""
56+
Return a snapshot of every kernel that has been loaded into the current process.
57+
58+
Each entry is a `kernels.utils.LoadedKernel` dataclass with fields:
59+
60+
- `kernel_id` (`str`): unique identifier used as the `sys.modules` key
61+
for this variant (either `metadata.id` or a hash-suffixed module name).
62+
- `module` (`ModuleType`): the imported kernel module.
63+
- `module_name` (`str`): the kernel's module name.
64+
- `repo_infos` (`kernels.utils.RepoInfos | None`): populated only for
65+
kernels loaded via `get_kernel`. Loaders that work from a local path
66+
(`get_local_kernel`) or a lockfile (`get_locked_kernel`, `load_kernel`)
67+
leave this as `None`.
68+
69+
`RepoInfos` has `repo_id`, `revision`, and `backend` fields. `backend`
70+
reflects the value passed by the caller — it is `None` when the caller
71+
relied on backend auto-detection.
72+
73+
The returned list is a new list; mutating it does not affect the registry.
74+
75+
> [!NOTE]
76+
> These arguments might be renamed / changed a bit.
77+
78+
Returns:
79+
`list[LoadedKernel]`: one entry per distinct kernel variant path
80+
loaded in this process.
81+
82+
Example:
83+
```python
84+
from kernels import get_kernel, get_loaded_kernels
85+
86+
get_kernel("kernels-community/activation", version=1)
87+
for loaded in get_loaded_kernels():
88+
print(loaded.module_name, loaded.repo_infos)
89+
```
90+
"""
5691
return list(_loaded_kernels.values())
5792

5893

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
from dataclasses import fields
2+
3+
import pytest
4+
5+
from kernels import get_kernel, get_loaded_kernels, get_local_kernel, install_kernel
6+
from kernels.utils import LoadedKernel, RepoInfos, _loaded_kernels
7+
8+
_REPO_ID = "kernels-community/relu"
9+
_PACKAGE_NAME = "relu"
10+
_VERSION = 1
11+
12+
13+
@pytest.fixture
14+
def fresh_registry():
15+
"""Snapshot the process-wide registry, run the test with a clean one, restore on teardown."""
16+
saved = _loaded_kernels.copy()
17+
_loaded_kernels.clear()
18+
yield
19+
_loaded_kernels.clear()
20+
_loaded_kernels.update(saved)
21+
22+
23+
def test_dataclass_shape():
24+
assert tuple(f.name for f in fields(LoadedKernel)) == (
25+
"kernel_id",
26+
"module",
27+
"module_name",
28+
"repo_infos",
29+
)
30+
assert tuple(f.name for f in fields(RepoInfos)) == ("repo_id", "revision", "backend")
31+
32+
33+
def test_get_loaded_kernels_returns_copy(fresh_registry):
34+
kernel = get_kernel(_REPO_ID, version=_VERSION, backend="cpu")
35+
36+
snapshot = get_loaded_kernels()
37+
assert len(snapshot) == 1
38+
39+
snapshot.clear()
40+
snapshot.append("garbage") # type: ignore[arg-type]
41+
42+
again = get_loaded_kernels()
43+
assert len(again) == 1
44+
assert again[0].module is kernel
45+
46+
47+
def test_get_kernel_registers_loaded_kernel(fresh_registry):
48+
kernel = get_kernel(_REPO_ID, version=_VERSION, backend="cpu")
49+
50+
loaded = get_loaded_kernels()
51+
assert len(loaded) == 1
52+
53+
entry = loaded[0]
54+
assert entry.module is kernel
55+
assert entry.module_name == _PACKAGE_NAME
56+
assert entry.repo_infos is not None
57+
assert entry.repo_infos.repo_id == _REPO_ID
58+
assert isinstance(entry.repo_infos.revision, str) and entry.repo_infos.revision
59+
assert entry.repo_infos.backend == "cpu"
60+
61+
62+
def test_repeated_get_kernel_is_cached(fresh_registry):
63+
first = get_kernel(_REPO_ID, version=_VERSION, backend="cpu")
64+
second = get_kernel(_REPO_ID, version=_VERSION, backend="cpu")
65+
66+
assert first is second
67+
assert len(get_loaded_kernels()) == 1
68+
69+
70+
def test_get_local_kernel_registers_with_null_repo_infos(fresh_registry):
71+
# Populate the HF cache via get_kernel, grab the variant path it registered,
72+
# then clear the registry and exercise get_local_kernel against that path.
73+
get_kernel(_REPO_ID, version=_VERSION, backend="cpu")
74+
(variant_path,) = list(_loaded_kernels.keys())
75+
76+
_loaded_kernels.clear()
77+
78+
kernel = get_local_kernel(variant_path, _PACKAGE_NAME, backend="cpu")
79+
80+
loaded = get_loaded_kernels()
81+
assert len(loaded) == 1
82+
83+
entry = loaded[0]
84+
assert entry.module is kernel
85+
assert entry.module_name == _PACKAGE_NAME
86+
assert entry.repo_infos is None
87+
88+
89+
def test_install_kernel_plus_import_does_not_set_repo_infos(fresh_registry):
90+
# install_kernel alone does not import; it returns a path. Any loader
91+
# that does not go through get_kernel must leave repo_infos as None.
92+
package_name, variant_path = install_kernel(_REPO_ID, revision="main", backend="cpu")
93+
assert package_name == _PACKAGE_NAME
94+
assert get_loaded_kernels() == []
95+
96+
get_local_kernel(variant_path, package_name, backend="cpu")
97+
(entry,) = get_loaded_kernels()
98+
assert entry.repo_infos is None

0 commit comments

Comments
 (0)