Skip to content

Commit aed353e

Browse files
leeclemnetclaude
andcommitted
fix(model-evals): rename SDK attr project_idproject (URL slug)
Pairs with roboflow#11636 dropping `projectId` from the public response. The SDK previously read `info["projectId"]` (the Firestore doc id) into `ModelEval.project_id`. That field was a doc-id leak — the API now only returns `project` (the URL slug) on the principle that public APIs should not expose storage-layer ids. Rename: `ModelEval.project_id` → `ModelEval.project`. Accept legacy `projectId` from cached older-server responses for forward-compat. CLI list/get handlers also pull from `project` first. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3618634 commit aed353e

4 files changed

Lines changed: 34 additions & 16 deletions

File tree

roboflow/cli/handlers/eval.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,9 @@ def _list_evals(args): # noqa: ANN001
225225
# Prefer DNA's `evalId`; tolerate legacy `id` from older server versions.
226226
"id": e.get("evalId", e.get("id", "")),
227227
"status": e.get("status", ""),
228-
"project": e.get("projectId", ""),
228+
# `project` is the URL slug; the public API does not expose the doc id.
229+
# Tolerate legacy `projectId` for forward-compat against older deploys.
230+
"project": e.get("project") or e.get("projectId", ""),
229231
"version": e.get("versionId", ""),
230232
"model": e.get("modelId", "") or "",
231233
"created": e.get("createdAt", ""),
@@ -259,7 +261,8 @@ def _get_eval(args): # noqa: ANN001
259261
# Prefer DNA's `evalId`; tolerate legacy `id`.
260262
f"Eval: {info.get('evalId', info.get('id', args.eval_id))}",
261263
f" Status: {info.get('status', '')}",
262-
f" Project: {info.get('projectId', '')}",
264+
# `project` is the URL slug; tolerate legacy `projectId` for forward-compat.
265+
f" Project: {info.get('project') or info.get('projectId', '')}",
263266
f" Version: {info.get('versionId', '')}",
264267
f" Model: {info.get('modelId', '') or '(none)'}",
265268
f" Created: {info.get('createdAt', '')}",

roboflow/core/model_eval.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ def _apply(self, info: Dict[str, Any]) -> None:
5353
if info.get("evalId"):
5454
self.id = info["evalId"]
5555
self.status: Optional[str] = info.get("status")
56-
self.project_id: Optional[str] = info.get("projectId")
56+
# `project` is the project URL slug — the same identifier the REST API
57+
# uses in URL paths. The internal Firestore doc id is intentionally
58+
# never exposed in the public API. Accept legacy `projectId` for
59+
# forward-compat with older server versions.
60+
self.project: Optional[str] = info.get("project") or info.get("projectId")
5761
self.version_id: Optional[str] = info.get("versionId")
5862
self.model_id: Optional[str] = info.get("modelId")
5963
self.created_at: Optional[str] = info.get("createdAt")
@@ -132,12 +136,11 @@ def to_dict(self) -> Dict[str, Any]:
132136
# back to attributes when only the constructor was called with no info.
133137
if self._raw:
134138
return {**self._raw, "evalId": self.id}
135-
for key in ("status", "projectId", "versionId", "modelId", "createdAt", "summary"):
139+
for key in ("status", "project", "versionId", "modelId", "createdAt", "summary"):
136140
attr = (
137141
key
138-
if key in {"status", "summary"}
142+
if key in {"status", "project", "summary"}
139143
else {
140-
"projectId": "project_id",
141144
"versionId": "version_id",
142145
"modelId": "model_id",
143146
"createdAt": "created_at",
@@ -149,7 +152,7 @@ def to_dict(self) -> Dict[str, Any]:
149152
return data
150153

151154
def __repr__(self) -> str: # pragma: no cover - debug helper
152-
return f"ModelEval(id={self.id!r}, status={self.status!r}, project={self.project_id!r})"
155+
return f"ModelEval(id={self.id!r}, status={self.status!r}, project={self.project!r})"
153156

154157

155158
__all__: List[str] = ["ModelEval"]

tests/cli/test_eval_handler.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,22 +75,35 @@ def test_list_text_calls_adapter_with_filters(self, _key, _ws, mock_list):
7575
{
7676
"id": "e1",
7777
"status": "done",
78-
"projectId": "p1",
78+
"project": "my-project-slug",
7979
"versionId": "3",
8080
"modelId": None,
8181
"createdAt": "2025-01-01",
8282
}
8383
]
8484
}
85-
args = _args(workspace=None, project="p1", version="3", model=None, status="done", limit=5)
85+
args = _args(
86+
workspace=None,
87+
project="my-project-slug",
88+
version="3",
89+
model=None,
90+
status="done",
91+
limit=5,
92+
)
8693

8794
from roboflow.cli.handlers.eval import _list_evals
8895

8996
with patch("builtins.print") as mock_print:
9097
_list_evals(args)
9198

9299
mock_list.assert_called_once_with(
93-
"key", "lee-sandbox", project="p1", version="3", model=None, status="done", limit=5
100+
"key",
101+
"lee-sandbox",
102+
project="my-project-slug",
103+
version="3",
104+
model=None,
105+
status="done",
106+
limit=5,
94107
)
95108
printed = mock_print.call_args[0][0]
96109
self.assertIn("e1", printed)
@@ -136,11 +149,10 @@ def test_get_text(self, _key, _ws, mock_get):
136149
mock_get.return_value = {
137150
"id": "e1",
138151
"status": "done",
139-
"projectId": "p1",
152+
"project": "my-project-slug",
140153
"versionId": "3",
141154
"modelId": "m1",
142155
"createdAt": "2025-01-01",
143-
"config": {"overlap": 30, "iouThreshold": 50},
144156
"summary": {"mAP": 0.91, "precision": 0.85, "recall": 0.8},
145157
}
146158
args = _args(workspace=None, eval_id="e1")

tests/test_model_eval.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def test_apply_info_populates_attributes(self):
2828
info = {
2929
"evalId": "e1",
3030
"status": "done",
31-
"projectId": "p1",
31+
"project": "my-project-slug", # URL slug — the public API only returns the slug
3232
"versionId": "3",
3333
"modelId": "m1",
3434
"createdAt": "2025-01-01",
@@ -38,7 +38,7 @@ def test_apply_info_populates_attributes(self):
3838

3939
self.assertEqual(ev.id, "e1")
4040
self.assertEqual(ev.status, "done")
41-
self.assertEqual(ev.project_id, "p1")
41+
self.assertEqual(ev.project, "my-project-slug")
4242
self.assertEqual(ev.version_id, "3")
4343
self.assertEqual(ev.model_id, "m1")
4444
self.assertEqual(ev.created_at, "2025-01-01")
@@ -174,8 +174,8 @@ def test_evals_returns_modeleval_instances(self, mock_list):
174174

175175
mock_list.return_value = {
176176
"evals": [
177-
{"evalId": "e1", "status": "done", "projectId": "p1"},
178-
{"evalId": "e2", "status": "running", "projectId": "p1"},
177+
{"evalId": "e1", "status": "done", "project": "my-project-slug"},
178+
{"evalId": "e2", "status": "running", "project": "my-project-slug"},
179179
]
180180
}
181181
ws = _make_workspace()

0 commit comments

Comments
 (0)