Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ All notable changes to the `python-domino` library will be documented in this fi
## [Unreleased]

### Added
* `model_deployment_start(model_id, model_version_id)` — start (or restart) the serving endpoint for a deployed model version. Closes #84.
* `model_deployment_stop(model_id, model_version_id)` — stop a deployed model version. Closes #84.
* `model_deployment_status(model_id, model_version_id)` — query the deployment status of a model version (returns `running`, `stopped`, etc.).
* `scripts/check_snake_case.py` — AST-based lint script that catches camelCase parameter names in new code.
* GitHub Actions CI workflow (`.github/workflows/ci.yml`) that runs lint, type-checking, and tests on every PR and push to `master`. All checks must pass before a PR can be merged.
* `pyproject.toml` with `isort` and `black` configuration (`profile = "black"`, `target-version = ["py310"]`).
Expand Down
38 changes: 38 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,44 @@ Read-only property. Returns the ID of the first app in the current project, or `
print(d.app_id) # e.g. "aabbccddeeff001122334457"
----

=== Model deployment lifecycle

Methods for starting, stopping, and checking the status of a deployed model API version. A "model" can have multiple versions; lifecycle is controlled per-version.

==== model_deployment_start(model_id, model_version_id)

Start (or restart) the serving endpoint for a specific model version.

* _model_id (string):_ The ID of the model.
* _model_version_id (string):_ The ID of the model version to start.

Returns the HTTP response from the Domino API.

==== model_deployment_stop(model_id, model_version_id)

Stop the serving endpoint for a specific model version. Useful for taking a deployed model offline (for example to save compute when the endpoint isn't in use).

* _model_id (string):_ The ID of the model.
* _model_version_id (string):_ The ID of the model version to stop.

Returns the HTTP response from the Domino API.

==== model_deployment_status(model_id, model_version_id)

Get the current deployment status of a specific model version.

* _model_id (string):_ The ID of the model.
* _model_version_id (string):_ The ID of the model version.

Returns a dict with `modelId`, `modelVersionId`, and `status` (e.g. `"running"`, `"stopped"`).

[source,python]
----
status = d.model_deployment_status("64...", "65...")
if status["status"] == "stopped":
d.model_deployment_start("64...", "65...")
----

=== Jobs

NOTE: Prefer `job_start` over `runs_start` for all new work. See the <<Executions>> section for a full comparison.
Expand Down
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,48 @@ or `None` if no app exists. Useful when you need the app ID to pass to
print(d.app_id) # e.g. "aabbccddeeff001122334457"
```

## Model deployment lifecycle

Methods for starting, stopping, and checking the status of a deployed model
API version. A "model" can have multiple versions; lifecycle is controlled
per-version.

### model_deployment_start(model_id, model_version_id)

Start (or restart) the serving endpoint for a specific model version.

- *model_id (string):* The ID of the model.
- *model_version_id (string):* The ID of the model version to start.

Returns the HTTP response from the Domino API.

### model_deployment_stop(model_id, model_version_id)

Stop the serving endpoint for a specific model version. Useful for taking
a deployed model offline (e.g. to save compute when the endpoint isn't
in use).

- *model_id (string):* The ID of the model.
- *model_version_id (string):* The ID of the model version to stop.

Returns the HTTP response from the Domino API.

### model_deployment_status(model_id, model_version_id)

Get the current deployment status of a specific model version.

- *model_id (string):* The ID of the model.
- *model_version_id (string):* The ID of the model version.

Returns a dict with `modelId`, `modelVersionId`, and `status` (e.g.
`"running"`, `"stopped"`).

```python
status = d.model_deployment_status("64...", "65...")
if status["status"] == "stopped":
d.model_deployment_start("64...", "65...")
```

## Jobs

> **Prefer `job_start` over `runs_start` for all new work.** See the [Executions](#executions) section for a full comparison.
Expand Down
55 changes: 55 additions & 0 deletions domino/domino.py
Original file line number Diff line number Diff line change
Expand Up @@ -1689,6 +1689,61 @@ def model_version_export_logs(self, model_export_id):
url = self._routes.model_version_export_logs(model_export_id)
return self._get(url)

def model_deployment_start(self, model_id: str, model_version_id: str):
"""
Start the deployment of a specific model API version.

Wraps POST /v4/models/{model_id}/{model_version_id}/startModelDeployment.
Use this to bring a stopped model version's serving endpoint back online.

:param model_id: The id of the model.
:param model_version_id: The id of the model version to start.
:return: The HTTP response from the Domino API.
"""
if not model_id:
raise ValueError("model_id is required")
if not model_version_id:
raise ValueError("model_version_id is required")
url = self._routes.model_deployment_start(model_id, model_version_id)
return self.request_manager.post(url)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: maybe return the json here, like the other methods below?


def model_deployment_stop(self, model_id: str, model_version_id: str):
"""
Stop the deployment of a specific model API version.

Wraps POST /v4/models/{model_id}/{model_version_id}/stopModelDeployment.
Useful for taking a running model offline (e.g. to save compute when
the endpoint is not in use).

:param model_id: The id of the model.
:param model_version_id: The id of the model version to stop.
:return: The HTTP response from the Domino API.
"""
if not model_id:
raise ValueError("model_id is required")
if not model_version_id:
raise ValueError("model_version_id is required")
url = self._routes.model_deployment_stop(model_id, model_version_id)
return self.request_manager.post(url)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: maybe return the json here, like the other methods below?


def model_deployment_status(self, model_id: str, model_version_id: str):
"""
Get the deployment status of a specific model API version.

Wraps GET /v4/models/{model_id}/{model_version_id}/getModelDeploymentStatus.
The response includes a `status` field (e.g. "running", "stopped").

:param model_id: The id of the model.
:param model_version_id: The id of the model version to query.
:return: Dict with model deployment status (modelId, modelVersionId, status).
"""
if not model_id:
raise ValueError("model_id is required")
if not model_version_id:
raise ValueError("model_version_id is required")
url = self._routes.model_deployment_status(model_id, model_version_id)
return self._get(url)

# Hardware Tier Functions
def hardware_tiers_list(self):
url = self._routes.hardware_tiers_list(self.project_id)
Expand Down
18 changes: 18 additions & 0 deletions domino/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,24 @@ def model_version_export_status(self, model_export_id):
def model_version_export_logs(self, model_export_id):
return self._build_models_v4_url() + "/" + model_export_id + "/getExportLogs"

def model_deployment_start(self, model_id, model_version_id):
return (
self._build_models_v4_url()
+ f"/{model_id}/{model_version_id}/startModelDeployment"
)

def model_deployment_stop(self, model_id, model_version_id):
return (
self._build_models_v4_url()
+ f"/{model_id}/{model_version_id}/stopModelDeployment"
)

def model_deployment_status(self, model_id, model_version_id):
return (
self._build_models_v4_url()
+ f"/{model_id}/{model_version_id}/getModelDeploymentStatus"
)

# Environment URLs
def _build_v4_environments_url(self) -> str:
return self.host + "/v4/environments"
Expand Down
75 changes: 75 additions & 0 deletions tests/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,81 @@ def test_model_version_export_logs_returns_dict(requests_mock, dummy_hostname):
assert result["logs"] == "export log output"


# ---------------------------------------------------------------------------
# Model deployment lifecycle (start / stop / status) — #84
# ---------------------------------------------------------------------------


@pytest.mark.usefixtures("clear_token_file_from_env", "base_mocks")
def test_model_deployment_start_posts_to_correct_url(requests_mock, dummy_hostname):
start_mock = requests_mock.post(
f"{dummy_hostname}/v4/models/{MOCK_MODEL_ID}/"
f"{MOCK_MODEL_VERSION_ID}/startModelDeployment",
status_code=200,
)
d = Domino(host=dummy_hostname, project="anyuser/anyproject", api_key="whatever")
response = d.model_deployment_start(MOCK_MODEL_ID, MOCK_MODEL_VERSION_ID)
assert start_mock.called
assert response.status_code == 200


@pytest.mark.usefixtures("clear_token_file_from_env", "base_mocks")
def test_model_deployment_stop_posts_to_correct_url(requests_mock, dummy_hostname):
stop_mock = requests_mock.post(
f"{dummy_hostname}/v4/models/{MOCK_MODEL_ID}/"
f"{MOCK_MODEL_VERSION_ID}/stopModelDeployment",
status_code=200,
)
d = Domino(host=dummy_hostname, project="anyuser/anyproject", api_key="whatever")
response = d.model_deployment_stop(MOCK_MODEL_ID, MOCK_MODEL_VERSION_ID)
assert stop_mock.called
assert response.status_code == 200


@pytest.mark.usefixtures("clear_token_file_from_env", "base_mocks")
def test_model_deployment_status_returns_dict(requests_mock, dummy_hostname):
requests_mock.get(
f"{dummy_hostname}/v4/models/{MOCK_MODEL_ID}/"
f"{MOCK_MODEL_VERSION_ID}/getModelDeploymentStatus",
json={
"modelId": MOCK_MODEL_ID,
"modelVersionId": MOCK_MODEL_VERSION_ID,
"status": "running",
},
)
d = Domino(host=dummy_hostname, project="anyuser/anyproject", api_key="whatever")
result = d.model_deployment_status(MOCK_MODEL_ID, MOCK_MODEL_VERSION_ID)
assert result["status"] == "running"
assert result["modelId"] == MOCK_MODEL_ID
assert result["modelVersionId"] == MOCK_MODEL_VERSION_ID


@pytest.mark.parametrize(
"method_name",
["model_deployment_start", "model_deployment_stop", "model_deployment_status"],
)
@pytest.mark.usefixtures("clear_token_file_from_env", "base_mocks")
def test_model_deployment_methods_require_model_id(
requests_mock, dummy_hostname, method_name
):
d = Domino(host=dummy_hostname, project="anyuser/anyproject", api_key="whatever")
with pytest.raises(ValueError, match="model_id is required"):
getattr(d, method_name)("", MOCK_MODEL_VERSION_ID)


@pytest.mark.parametrize(
"method_name",
["model_deployment_start", "model_deployment_stop", "model_deployment_status"],
)
@pytest.mark.usefixtures("clear_token_file_from_env", "base_mocks")
def test_model_deployment_methods_require_model_version_id(
requests_mock, dummy_hostname, method_name
):
d = Domino(host=dummy_hostname, project="anyuser/anyproject", api_key="whatever")
with pytest.raises(ValueError, match="model_version_id is required"):
getattr(d, method_name)(MOCK_MODEL_ID, "")


# ---------------------------------------------------------------------------
# Integration tests (require a live Domino deployment)
# ---------------------------------------------------------------------------
Expand Down
Loading