Skip to content

Commit 673cd59

Browse files
kaiixCopilot
andcommitted
fix(lint): avoid mutating proc models in top_procs
Convert proc models to plain dicts before adding Prometheus runtime metrics, so lint no longer calls dict-style update() on pydantic deployments. Add a regression test covering top_procs with pydantic-backed workloads and Prometheus metrics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 10c42ab commit 673cd59

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

lain_cli/utils.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2680,14 +2680,19 @@ def top_procs(appname):
26802680
cpu_top, accurate = prometheus.cpu_p95(appname, proc_name)
26812681
if not accurate:
26822682
continue
2683-
proc.update(
2683+
proc_data = (
2684+
proc.model_dump(mode="python", exclude_none=True)
2685+
if hasattr(proc, "model_dump")
2686+
else deepcopy(proc)
2687+
)
2688+
proc_data.update(
26842689
{
26852690
"memory_top": memory_top,
26862691
"memory_top_str": memory_top_str,
26872692
"cpu_top": cpu_top,
26882693
}
26892694
)
2690-
result[proc_name] = proc
2695+
result[proc_name] = proc_data
26912696

26922697
return result
26932698

tests/test_utils.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from types import SimpleNamespace
2+
3+
import click
14
import json
25
import shutil
36
import subprocess
@@ -35,6 +38,7 @@
3538
tell_ingress_urls,
3639
tell_job_names,
3740
tell_release_name,
41+
top_procs,
3842
user_challenge,
3943
update_canary_annotations,
4044
yadu,
@@ -329,6 +333,41 @@ def test_cluster_values_override():
329333
assert getattr(cc, "registry") == fake_registry
330334

331335

336+
@pytest.mark.usefixtures("dummy_helm_chart")
337+
def test_top_procs_does_not_mutate_pydantic_proc_models(monkeypatch):
338+
_, values = run_under_click_context(load_helm_values)
339+
340+
class FakePrometheus:
341+
def memory_quantile(self, appname, proc_name):
342+
assert appname == DUMMY_APPNAME
343+
return 128 * 1024 * 1024
344+
345+
def cpu_p95(self, appname, proc_name):
346+
assert appname == DUMMY_APPNAME
347+
return 250, True
348+
349+
monkeypatch.setattr("lain_cli.prometheus.Prometheus", FakePrometheus)
350+
monkeypatch.setattr(
351+
"lain_cli.utils.tell_cluster_config",
352+
lambda: SimpleNamespace(prometheus="http://prometheus.example"),
353+
)
354+
355+
web_proc = values.procs["web"]
356+
with click.Context(click.Command("top-procs"), obj={"values": values}):
357+
tops = top_procs(DUMMY_APPNAME)
358+
359+
assert isinstance(tops["web"], dict)
360+
assert (
361+
tops["web"]["resources"]["requests"]["cpu"] == web_proc.resources.requests.cpu
362+
)
363+
assert tops["web"]["memory_top"] == 128 * 1024 * 1024
364+
assert tops["web"]["memory_top_str"] == "128Mi"
365+
assert tops["web"]["cpu_top"] == 250
366+
assert "memory_top" not in (web_proc.model_extra or {})
367+
assert "memory_top_str" not in (web_proc.model_extra or {})
368+
assert "cpu_top" not in (web_proc.model_extra or {})
369+
370+
332371
def test_cluster_config_schema_current_cluster_resolves_secrets_env(mocker):
333372
mocker.patch.dict(
334373
"lain_cli.utils.ENV",

0 commit comments

Comments
 (0)