From ac0ebb857532a5e17bf8f5dbef76e3abe5da4538 Mon Sep 17 00:00:00 2001 From: Luke Schaefer Date: Tue, 25 Aug 2026 18:57:29 +0000 Subject: [PATCH] feat(model): add Model.model_runs() to list a model's run ids The model-scoped counterpart to Dataset.model_runs(): lists every run under a model via GET /nucleus/model/:modelId/modelRun. include_versions=True unions runs across the model's version lineage (?family=true). Results are dataset-scoped server-side. Adds a mock unit test, CHANGELOG entry, and version bump to 0.21.3. Requires the matching scaleapi route (scaleapi#158386); live calls 404 until it deploys. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 11 +++++++++ nucleus/model.py | 19 +++++++++++++++ pyproject.toml | 2 +- tests/test_model_runs_listing.py | 40 ++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 tests/test_model_runs_listing.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 021de916..555f7f07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to the [Nucleus Python Client](https://github.com/scaleapi/n The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.21.3](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.21.3) - 2026-08-25 + +### Added +- **`Model.model_runs()`.** Lists the ids of every model run for a model — the model-scoped counterpart to `Dataset.model_runs()`, which only lists a single dataset's runs. Pass `include_versions=True` to union runs across the model's version lineage (its version root and all descendants). Results are scoped server-side to runs on datasets you can read. + + ```python + run_ids = model.model_runs() + ``` + +> **Server dependency:** requires the `GET /nucleus/model/:modelId/modelRun` route in scaleapi. Unit tests pass regardless; live calls 404 until that deploys. + ## [0.21.2](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.21.2) - 2026-08-17 ### Added diff --git a/nucleus/model.py b/nucleus/model.py index 18340571..219ef5eb 100644 --- a/nucleus/model.py +++ b/nucleus/model.py @@ -229,6 +229,25 @@ def create_run( run.add_predictions(predictions) return run + def model_runs(self, include_versions: bool = False) -> List[str]: + """List the ids of every model run for this model. :: + + run_ids = model.model_runs() + + Args: + include_versions: Also include runs from other versions in this + model's lineage — its version root and all descendants. Defaults + to False, returning only runs whose ``model_id`` is this model. + + Returns: + The model run ids (``run_*``). Scoped server-side to runs on datasets + you can read, so a run on a dataset you can't access is omitted. + """ + route = f"model/{self.id}/modelRun" + if include_versions: + route += "?family=true" + return self._client.make_request({}, route, requests.get) + def evaluate(self, scenario_test_names: List[str]) -> AsyncJob: """Evaluates this on the specified Unit Tests. :: diff --git a/pyproject.toml b/pyproject.toml index 4901f914..68651aab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ ignore = ["E501", "E741", "E731", "F401"] # Easy ignore for getting it running [tool.poetry] name = "scale-nucleus" -version = "0.21.2" +version = "0.21.3" description = "The official Python client library for Nucleus, the Data Platform for AI" license = "MIT" authors = ["Scale AI Nucleus Team "] diff --git a/tests/test_model_runs_listing.py b/tests/test_model_runs_listing.py new file mode 100644 index 00000000..50cb29b7 --- /dev/null +++ b/tests/test_model_runs_listing.py @@ -0,0 +1,40 @@ +from unittest.mock import MagicMock + +import requests + +from nucleus import Model + + +def _model_with_client(): + client = MagicMock() + model = Model( + model_id="prj_123", + name="my-model", + reference_id="my-ref", + metadata=None, + client=client, + ) + return model, client + + +def test_model_runs_returns_ids_and_hits_the_route(): + model, client = _model_with_client() + client.make_request.return_value = ["run_a", "run_b"] + + result = model.model_runs() + + assert result == ["run_a", "run_b"] + payload, route, requests_command = client.make_request.call_args.args + assert payload == {} + assert route == "model/prj_123/modelRun" + assert requests_command is requests.get + + +def test_model_runs_include_versions_appends_family_query(): + model, client = _model_with_client() + client.make_request.return_value = [] + + model.model_runs(include_versions=True) + + _, route, _ = client.make_request.call_args.args + assert route == "model/prj_123/modelRun?family=true"